Kubernetes上使用Ingress Nginx将服务发布到外部IP
Kubernetes的网络结构
K8s的网络相对比较复杂, 包含了如下几类IP地址:
Host Network
运行K8s集群的宿主服务器的内网IP, 其网段在配置宿主机时设置. 这些服务器可能是物理机, 也可能是ESXi或KVM虚机. 可以根据这个IP在K8s初始化时设置--apiserver-advertise-address, 配置外网访问时, 前端的负载均衡要通过这些IP访问服务.
Docker Bridge Network
运行Docker服务时启动的虚拟网卡docker0的IP, 通常为 172.17.0.1/16, 对应docker内部默认的bridge网段. 这个网络在K8s中不会用到.
Pod Network
K8s中Pod单元的IP, 其网段在K8s初始化时使用--pod-network-cidr设置, 此处使用172.16.0.0/16, 在每个宿主(node节点)上, 都会有一个对应的二级网段, 例如 172.16.1.0/24, 如果使用的是flannel, 那么在宿主机上会对应成对出现的两个虚拟网卡 cni0 和 flannel.1 (如果宿主机上还没部署pod, cni0可能不会出现)
Pod网络在集群内可以跨节点相互ping通, 在master和node主机上也可以直接访问Pod的IP
Service Network
K8s中Service单元的IP, 其网段在K8s初始化时使用--service-cidr设置, 在master和node主机上不能直接访问. 这里使用10.1.0.0/16.
Ingress的机制
要解决的问题
因为K8s中Pod是易变的, Pod IP在更新中会自动修改, 使用Service能使访问入口相对固定, 但是Service IP在集群外不能访问, 要对外提供访问, 只能把Service以NodePort, LoadBalancer这些方式Expose出去, 但是NodePort会与每一个Node主机绑定, 而LoadBalancer需要云服务商提供相应的服务(或自己安装).
原理
Ingress 启动一个独立的Pod来运行七层代理, 可以是 Nginx, Traefik 或者是 Envoy. Ingress Pod会直接代理后端提供服务的Pod, 为了能监听后端Pod的变化, 需要一个 Headless Service 通过Selector选择指定的Pod, 并收集到Pod对应的IP. 一旦后端Pod产生变化, Headless Service 会自动根据变化更改配置文件并重载.
如果使用的是 Nginx 类型的 Ingress Pod, 则每次变化后通过reload修改过的配置文件实现规则更新.
Ingress Controller
在kubernetes集群内节点上运行web七层代理所对应的Pod, 由此Pod代理集群内部的Service, Service再把流量转发给集群内部对应的Pod, 这就叫做 Ingress Controller, Ingress Controller 基于 DaemonSet 控制器实现, DaemonSet用于保证集群内部每个节点上都运行一个指定的Pod.
部署Ingress Nginx
软件版本
这里使用的环境是 K8s 1.17, Docker 18.06.3, Ingress Nginx 0.26.2
Ingress Nginx项目地址: https://github.com/kubernetes/ingress-nginx/
Ingress Nginx的安装文档: https://kubernetes.github.io/ingress-nginx/deploy/ 使用 https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/
安装前提
已经配置好K8s集群
部署公共部分
现在最新的标签是0.26.2, 使用部署模板
https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.26.2/deploy/static/mandatory.yaml
下载后在master主机上执行
kubectl apply -f mandatory.yaml
可以使用下面的命令查看部署的结果
kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx --watch kubectl get services
部署Nodeport部分
因为不使用云服务商的服务, 所以要使用Nodeport的方式, 对应的部署模板在
这一步实际上是创建一个Nodeport将ingress-nginx服务发布到Node节点的Host Network上, 原模板中未指定端口, 创建后会随机设置端口, 可以修改一下, 只能使用端口范围 3000 ~ 32767. 修改后的service-nodeport.yaml, 将80端口映射到了30080, 443映射到了30443
apiVersion: v1 kind: Service metadata: name: ingress-nginx namespace: ingress-nginx labels: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx spec: type: NodePort ports: - name: http port: 80 targetPort: 80 protocol: TCP nodePort: 30080 - name: https port: 443 targetPort: 443 protocol: TCP nodePort: 30443 selector: app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx
可以使用下面的命令查看发布的结果, 以及映射出去的实际端口
kubectl get services -n ingress-nginx
创建服务并测试
以下都是使用defalut的namespace. 都使用kubectl apply -f 模板名 命令进行发布
Deployment, 创建两个nginx的pod
# nginx-deployment.yml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 # tells deployment to run 2 pods matching the template template: metadata: labels: app: nginx spec: containers: - name: nginx image: registry.cn-shanghai.aliyuncs.com/jovi/nginx:alpine ports: - containerPort: 80
Service, 将上面创建的两个Pod发布为Servce, 将Pod的80端口映射到Service的8080端口
# nginx-service.yml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: nginx ports: - protocol: TCP port: 8080 targetPort: 80
查看service详情
$ kubectl describe service my-service Name: my-service Namespace: default Labels: <none> Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"my-service","namespace":"default"},"spec":{"ports":[{"port":8080,... Selector: app=nginx Type: ClusterIP IP: 10.1.116.99 Port: <unset> 8080/TCP TargetPort: 80/TCP Endpoints: 172.16.1.6:80,172.16.1.7:80 Session Affinity: None Events: <none>
Ingress, 将上面的Service的8080端口映射到Ingress的http
# ingress-test.yaml apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: test-ingress annotations: # use the shared ingress-nginx kubernetes.io/ingress.class: "nginx" spec: rules: - http: paths: - backend: serviceName: my-service servicePort: 8080
查看Ingress详情
$ kubectl get services -n ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx NodePort 10.1.60.152 <none> 80:30080/TCP,443:30443/TCP 69m milton@k8s00:~/backup$ kubectl describe ingress test-ingress Name: test-ingress Namespace: default Address: 10.1.60.152 Default backend: default-http-backend:80 (<none>) Rules: Host Path Backends ---- ---- -------- * my-service:8080 (172.16.1.6:80,172.16.1.7:80) Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"test-ingress","namespace":"default"},"spec":{"rules":[{"http":{"paths":[{"backend":{"serviceName":"my-service","servicePort":8080}}]}}]}} kubernetes.io/ingress.class: nginx Events: <none>
这时候, 就可以通过 http://宿主机IP:30080 访问到这两个Pod的Nginx默认页.