适合ingress-nginx接入外部负载均衡的部署


ingress-nginx是K8S中众多Ingress controller的其中一个,使用NGINX作为反向代理和负载均衡器,可将外部请求转发到K8S集群内部,以实现七层代理。

在生产环境中,一般使用外部的Nginx作为公网的统一访问入口,通过与ingress-nginx对接可以将请求转发到K8S集群。在不使用公有云的情况下,我们可以选择以下两种方式ingress-nginx部署方式。

Over a NodePort Service

一、部署

默认使用Deployment+nodePort部署

1. 先决条件通用部署命令

# yaml文件
 wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
# 部署
kubectl apply -f mandatory.yaml

# 查看pod
# kubectl get pod -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-5bb8fb4bb6-kdvbg   1/1     Running   0          2m10s
     
# 此时还没有svc
# kubectl get svc -n ingress-nginx
No resources found in ingress-nginx namespace.

2.创建servcie,接收集群外部流量

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
kubectl apply -f service-nodeport.yaml

# 查看svc
kubectl get svc  -n ingress-nginx
NAME            TYPE       CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.1.9.57    <none>        80:31596/TCP,443:30524/TCP   109s

3. 新建ingress规则

与其关联的service、deployment暂且忽略。

# vim ingress.yaml
# 注意使用的是自定义namespace:test
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: helloworld
  namespace: test
spec:
  rules:
    - host: hello.test.cn 
      http:
        paths:
          - path: /
            backend:
              serviceName: helloworld
              servicePort: 8080
# 应用
kubectl apply -f ingress.yaml

# kubectl get ing -n test
NAME         CLASS    HOSTS           ADDRESS     PORTS   AGE
helloworld   <none>   hello.test.cn   10.1.9.57   80      13s

4.测试

# 通过svc访问
# curl -x 10.1.9.57:80 hello.test.cn
Hello,world!
# 通过nodePort访问
# curl -x 192.168.3.218:31596 hello.test.cn
Hello,world!

二、源地址转换

默认情况下,NodePort类型的服务执行源地址转换。 这意味着HTTP请求的源IP始终是从nginx接收到该请求的Kubernetes节点的IP地址。

通过查看service日志,我们发现访问来源地址的确都是集群内部IP,而不是实际的客户端IP。

# kubectl logs -f svc/ingress-nginx -n ingress-nginx
10.244.0.0 - - [23/Jul/2020:07:48:33 +0000] "GET HTTP://hello.test.cn/ HTTP/1.1" 200 12 "-" "curl/7.29.0" 127 0.072 [test-helloworld-8080] [] 10.244.2.96:8080 12 0.072 200 1d2b39f8b24caace07ef56d7c22e13e7
10.244.1.0 - - [23/Jul/2020:07:48:59 +0000] "GET HTTP://hello.test.cn/ HTTP/1.1" 200 12 "-" "curl/7.29.0" 127 0.003 [test-helloworld-8080] [] 10.244.2.96:8080 12 0.004 200 9f912e99f7de3443deb62ba57ef0ff25
10.244.1.0 - - [24/Jul/2020:00:09:30 +0000] "GET http://hello.test.cn/ HTTP/1.1" 200 12 "-" "curl/7.58.0" 127 0.025 [test-helloworld-8080] [] 10.244.2.96:8080 12 0.025 200 da0a3886f5257769035003a96967e61e

此时我们可以在service上设置externalTrafficPolicy: Local解决。

vim service-nodeport.yaml
# 在spec中添加如下一行
externalTrafficPolicy: Local
# 应用修改
kubectl replace -f service-nodeport.yaml --force
# 查看日志
kubectl logs -f svc/ingress-nginx -n ingress-nginx
# 此时真正的源IP已能够正常记录
192.168.3.133 - - [24/Jul/2020:00:29:18 +0000] "GET HTTP://hello.test.cn/ HTTP/1.1" 200 12 "-" "curl/7.29.0" 127 0.002 [test-helloworld-8080] [] 10.244.2.96:8080 12 0.001 200 71518b244339f7ff945e1b5e3fd82793
10.11.2.102 - - [24/Jul/2020:00:29:54 +0000] "GET http://hello.test.cn/ HTTP/1.1" 200 12 "-" "curl/7.58.0" 127 0.002 [test-helloworld-8080] [] 10.244.2.96:8080 12 0.002 200 4dffef85c5765a5c8776bbd7965c9ea8

后遗症:

在未设置externalTrafficPolicy: Local前,nodePort允许我们访问任何node节点都可访问到service,但是在设置externalTrafficPolicy: Local后,我们只能通过访问实际运行ingress-nginx pod所在节点的node IP 。

# 未设置前
curl -x 192.168.3.217:31596 hello.test.cn
Hello,world!
curl -x 192.168.3.218:31596 hello.test.cn
Hello,world!
curl -x 192.168.3.219:31596 hello.test.cn
Hello,world!

# 设置后,由于replace重启了service,因此端口会更换
#  curl -x 192.168.3.217:30643 hello.test.cn
curl: (7) Failed connect to 192.168.3.217:30643; Connection refused
#  curl -x 192.168.3.218:30643 hello.test.cn
curl: (7) Failed connect to 192.168.3.218:30643; Connection refused
#  curl -x 192.168.3.219:30643 hello.test.cn
Hello,world!

# kubectl get pod -n ingress-nginx -o wide
NAME                                        READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
nginx-ingress-controller-5bb8fb4bb6-kdvbg   1/1     Running   0          17h   10.244.2.97   node-3-219   <none>           <none>

ingress-nginx pod运行在192.168.3.219上,只能通过192.168.3.219:30643访问服务。

问题思考:

如果通过nodePort 方式接入外部负载均衡,由于设置`externalTrafficPolicy: Local`导致访问的node节点无法固定,再加上一旦service重启端口变化,势必会影响外部负载均衡无法正确转发流量。

下面我们通过解决pod单点故障问题,使pod在所有node节点运行,那么IP就可以固定了。

三、单点故障

基于Deployment+nodePort部署我们发现ingress-nginx 只有一个pod,由于`mandatory.yaml`中定义副本数为1,因此存在单点故障

虽然我们可以将副本数改为2或多个解决单点问题,但是由于pod的端口固定为80//443,当pod调度分配到同一个节点时,会出现端口分配问题,这也正是Deployment类型的副本数默认为1的原因

$ kubectl -n ingress-nginx describe pod <unschedulable-ingress-nginx-controller-pod>
...
Events:
  Type     Reason            From               Message
  ----     ------            ----               -------
  Warning  FailedScheduling  default-scheduler  0/3 nodes are available: 3 node(s) didn't have free ports for the requested pod ports.

这种情况我们可以通过将pod设置为DaemonSet解决,利用DaemonSet特性,保证每个节点运行pod。

注意:DaemonSet会在node、master点都运行,但因为master节点默认有污点NoSchedule,表示k8s将不会将pod调度到具有该污点的master节点上。

# vim mandatory.yaml 
# 1.将Deployment更改为DaemonSet
apiVersion: apps/v1
kind: DaemonSet
# 2.删除replicas: 1

# 3.应用
kubectl apply -f  mandatory.yaml 
# 注意:通过replace重载配置,并不会在节点上新建daemon类型pod;但是通过重新部署(delete后重新apply)才可以在node节点上创建DaemonSet类型的pod的。

# 4.查看pod,应分布在集群的两个node节点上了。
# kubectl get pod -n ingress-nginx -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
nginx-ingress-controller-gnx8l   1/1     Running   0          66m   10.244.2.98   uvmsvr-3-219   <none>           <none>
nginx-ingress-controller-sdn86   1/1     Running   0          66m   10.244.1.59   uvmsvr-3-218   <none>           <none>

# 5.访问service
#  curl -x 192.168.3.218:31516 hello.test.cn
Hello,world!
#  curl -x 192.168.3.219:31516 hello.test.cn
Hello,world!

设置externalTrafficPolicy: Local并使用DaemonSet后,我们就可以在所有的node节点访问service了,但是由于nodeport暴露的端口是随机端口,即使在前面再搭建一套负载均衡器来转发请求,也需维护因service重启导致的端口变化问题

NodePort方式暴露ingress虽然简单方便,但是NodePort多了一层NAT,在请求量级很大时可能对性能会有一定影响。下面我们来介绍Via the host network

Via the host network

hostNetwork方式允许使用主机网络,即可以将pod的端口80和443直接绑定到Kubernetes宿主机网络上,直接通过宿主机IP+端口80/443访问服务,而无需NodePort Services施加额外的网络转换。

注意:由于此时外部请求直接访问pod所在节点的IP+port,并没有通过service访问,因此service可忽略,官方建议删除。

结合前面使用DaemonSet+hostNetwork部署。

# vim mandatory.yaml
# 1.更改DaemonSet
apiVersion: apps/v1
kind: DaemonSet
# 2.更改hostNetwork
template:
  spec:
    hostNetwork: true

# 应用,可不必应用service-nodeport.yaml
kubectl apply -f deploy.yaml

# 查看pod
# kubectl get pod -n ingress-nginx -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
nginx-ingress-controller-7frnd   1/1     Running   0          10s   192.168.3.218   uvmsvr-3-218   <none>           <none>
nginx-ingress-controller-gqvrz   1/1     Running   0          10s   192.168.3.219   uvmsvr-3-219   <none>           <none>
```
此时两个pod的IP不再是10.244.0.0段集群内部IP,而是node节点IP。我们可直接使用pod的80或443端口直接访问。
```bash
# 直接访问pod
# curl -x 192.168.3.219:80  hello.test.cn
Hello,world! 
# curl -x 192.168.3.218:80  hello.test.cn
Hello,world!

通过HostNetwork直接把该pod与宿主机node的网络打通,直接使用宿主机的80/433端口就能访问服务。由于IP和端口固定,我们可以将pod直接暴露给外部的负载均衡使用

这种方式没有经过nodePort的NAT,而是直接利用宿主机节点的网络和端口,性能会更高些。比较适合大并发的生产环境使用。

使用DaemonSet,当集群新加入新的node节点时会自动创建新的pod,由于hostNetwork下没有使用service,也就无法使用便签选择器selector自动添加新增的pod。如果接入外部负载均衡时,需手动添加新节点,这是此种部署的一个缺点。

总结

1. ingress-nginx通过调用k8s api 动态感知集群中pod的变化,动态更新配置文件nginx并重载nginx。配合外部负载均衡,可以很好的实现nginx配置的自动管理。

2. ingress-nginx接入外部负载均衡的要点是IP和端口尽量固定,否则外部负载均衡就无法定位到ingress-nginx。

通过以上两种部署方式,学习到了ingress-nginx的一些细节,帮助我们更好的了解并使用K8S。

参考:https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/baremetal.md

举报
评论 0