写点什么

Kubernetes 网络模型分析

作者:王玉川
  • 2023-04-23
    上海
  • 本文字数:5820 字

    阅读完需:约 19 分钟

Kubernetes网络模型分析

前一篇文章,介绍了Docker容器的网络模型


容器是要被 K8S 编排、管理的。而 K8S 又有自己的网络模型。我们继续学习、实验,来理解 K8S 是怎么样处理网络流量的。


实验之前,先分清楚 K8S 里面的三种 IP 地址,和三种端口。


三种 IP 地址:


  1. Cluster IP:K8S 在创建 Service 时,生成的虚拟 IP 地址。需要与 Service Port 合到一起,成为一个有效的通信端口。该 IP 地址用于集群内部的访问。

  2. Node IP:集群节点的 IP 地址。节点可能是物理机,也可能是虚拟机。该地址真实存在于物理网络。集群外部,可以通过该地址访问到集群内的节点。

  3. Pod IP:K8S 创建 Pod 时,为该 Pod 分配的 IP 地址。该地址在集群外不可见。具体的 IP 地址网段、以及 Pod 之间通信的方式,取决于集群创建时选用的 CNI 模型。本文选用了 Flannel 做为 CNI,但不涉及 CNI 的分析。


三种端口:


  1. port:是集群 Service 侦听的端口,与前面的 Cluster IP 合到一起,即 Cluster IP:port,提供了集群内部访问 Service 的入口。在 K8S 的 yaml 文件里面,port 就是 Service Port 的缩写。这是 K8S 创建 Service 时,默认的方式。

个人感觉,这个名字其实挺容易让人混淆的,还不如直接用关键字 ServicePort 来的清楚。

  1. nodePort:是在节点上侦听的端口。通过 Node IP:nodePort 的形式,提供了集群外部访问集群中 Service 的入口。

  2. targetPort:是在 Pod 上侦听的端口,比如运行 Nginx 的 Pod,会在 80 端口上监听 HTTP 请求。所有对 Service Port 和 Node Port 的访问,最后都会被转发到 Target Port 来处理。


下面,开始我们的实验。



一. 安装 K8S 集群

如果还没有集群的话,可以参考我的另一篇文章,先去把环境搭建好:基于Ubuntu安装Kubernetes集群指南

二. 创建 Deployment

随便找个目录,执行如下命令,创建一个 yaml 文件:


$ vi nginx_deployment.yaml
复制代码


输入这些内容后,按:wq 保存、退出,得到 nginx_deployment.yaml 文件:


apiVersion: apps/v1kind: Deploymentmetadata:  name: nginx-deploymentspec:  selector:    matchLabels:      app: nginx  replicas: 2  template:    metadata:      labels:        app: nginx    spec:      containers:      - name: nginx        image: nginx        ports:        - containerPort: 80
复制代码


这个 Deployment 将会生成 2 个副本的 Pod,每个 Pod 里面都运行 nginx,Pod 开放 80 端口。


然后用该 yaml 文件,去创建 K8S 资源:


$ kubectl apply -f nginx_deployment.yaml deployment.apps/nginx-deployment configured$ kubectl get pods -o wideNAME                               READY   STATUS    RESTARTS   AGE   IP           NODE                   NOMINATED NODE   READINESS GATESnginx-deployment-55f598f8d-49l2v   1/1     Running   0          87m   10.244.0.2   ycwang-ubuntu          <none>           <none>nginx-deployment-55f598f8d-pxp6x   1/1     Running   0          87m   10.244.1.4   ycwang-ubuntu-worker   <none>           <none>
复制代码


可以看到,两个 Pod 已经在运行,并且它们有各自的 IP。


此时,通过 Pod IP 即可访问 Nginx:


$ wget 10.244.0.2Connecting to 10.244.0.2:80... connected.HTTP request sent, awaiting response... 200 OKLength: 615 [text/html]Saving to: ‘index.html’
index.html 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(62.7 MB/s) - ‘index.html’ saved [615/615]
$ wget 10.244.1.4Connecting to 10.244.1.4:80... connected.HTTP request sent, awaiting response... 200 OKLength: 615 [text/html]Saving to: ‘index.html.1’
index.html.1 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(84.2 MB/s) - ‘index.html.1’ saved [615/615]
复制代码


但从集群外,是无法访问这两个 IP 地址的。

三. 创建 Service

生成一个新的 yaml 文件,用来创建 Service 资源。


$ vi nginx_svc.yaml
复制代码


输入这些内容后,按:wq 保存、退出。


apiVersion: v1kind: Servicemetadata:  name: nginx-svc  labels:    run: nginx-svcspec:  ports:  - port: 8080    targetPort: 80    protocol: TCP  selector:    app: nginx
复制代码


这个 Service 使用 app=nginx 标签选择器,来选择对应的 Pod,做为 Service 的后端。Service 的类型是默认的 Service Port,所以不必写出来。


Service 监听在 8080 端口,集群内部可以通过 ClusterIP:8080 进行访问,并把流量转发到 Pod 的 80 端口进行实际的业务处理。


执行命令,用该 yaml 文件去创建 Service:


$ kubectl apply -f nginx_svc.yaml service/nginx-svc created$ kubectl get serviceNAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGEkubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    145mnginx-svc    ClusterIP   10.104.112.175   <none>        8080/TCP   3s
复制代码


可以看到,该 Service 已经创建成功。并且出现了一个新的 IP 地址:10.104.112.175。这就是我们前面介绍的第一种 IP 地址:Cluster IP。


通过该虚拟的 IP 地址,加上指定的 8080 端口,即可实现集群内部对 Service 的访问:


$ wget 10.104.112.175:8080Connecting to 10.104.112.175:8080... connected.HTTP request sent, awaiting response... 200 OKLength: 615 [text/html]Saving to: ‘index.html’
index.html 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(60.4 MB/s) - ‘index.html’ saved [615/615]
复制代码


那么,K8S 是如何访问这个不存在的虚拟 IP 地址的呢?

四. 分析 Cluster IP 访问流程

先看一下这个 Service 的信息:


$ kubectl describe service nginx-svc Name:              nginx-svcNamespace:         defaultLabels:            run=nginx-svcAnnotations:       <none>Selector:          app=nginxType:              ClusterIPIP Family Policy:  SingleStackIP Families:       IPv4IP:                10.104.112.175IPs:               10.104.112.175Port:              <unset>  8080/TCPTargetPort:        80/TCPEndpoints:         10.244.0.2:80,10.244.1.4:80Session Affinity:  NoneEvents:            <none>
复制代码


这个 Service 对应了后端的两个 Pod:10.244.0.2:80,10.244.1.4:80。


就是说,对于 10.104.112.175:8080 的访问,最后会被转发到 10.244.0.2:80 或者 10.244.1.4:80。


这要感谢 iptables 在后面默默的干活。


查看目前 iptables 的情况:


$ sudo iptables-save | grep 10.104.112.175......-A KUBE-SERVICES -d 10.104.112.175/32 -p tcp -m comment --comment "default/nginx-svc cluster IP" -m tcp --dport 8080 -j KUBE-SVC-HL5LMXD5JFHQZ6LN......
复制代码


对于 10.104.112.175 的访问,会被跳转到规则:KUBE-SVC-HL5LMXD5JFHQZ6LN。


继续查看这条规则:


$ sudo iptables-save | grep KUBE-SVC-HL5LMXD5JFHQZ6LN-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.0.2:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-T5AFCZ323NYPWW2A-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.1.4:80" -j KUBE-SEP-RQ66ZV5Y2RYOH2X3
复制代码


iptables 把对 10.104.112.175 的访问,采用轮询的负载均衡策略,依次转发给:10.244.0.2:80 和 10.244.1.4:80。


从而实现了在集群内部对 Cluster IP:port 的访问,并自带了负载均衡功能。


另外,这些 iptables 规则的增删改都是由运行在每个节点的 kube-proxy 来实现的。

五. 创建 NodePort 类型的 Service

现在,我们把 Service 的类型改成 NodePort。


$ vi nginx_svc.yaml
复制代码


输入如下内容,并按:wq 保存、退出:


apiVersion: v1kind: Servicemetadata:  name: nginx-svc  labels:    run: nginx-svcspec:  type: NodePort  ports:  - port: 8080    targetPort: 80    nodePort: 30000    protocol: TCP  selector:    app: nginx
复制代码


在这个 yaml 文件,把 Service 的类型指定为 NodePort,并在每个 Node 上,侦听 30000 端口。对 30000 端口的访问,最后会被转发到 Pod 的 80 端口。


先把之前的 Service 和 Deployment 都删掉,再用新的 yaml 文件重新创建:


$ kubectl delete -f nginx_svc.yaml service "nginx-svc" deleted$ kubectl delete -f nginx_deployment.yaml deployment.apps "nginx-deployment" deleted$ kubectl apply -f nginx_deployment.yaml deployment.apps/nginx-deployment created$ kubectl apply -f nginx_svc.yaml service/nginx-svc created
复制代码


查看 Service 和 Pod 的具体信息:


$ kubectl get servicesNAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGEkubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          3h41mnginx-svc    NodePort    10.110.65.131   <none>        8080:30000/TCP   10s$ kubectl get pods -o wideNAME                               READY   STATUS    RESTARTS   AGE   IP           NODE                   NOMINATED NODE   READINESS GATESnginx-deployment-55f598f8d-ls786   1/1     Running   0          11m   10.244.1.6   ycwang-ubuntu-worker   <none>           <none>nginx-deployment-55f598f8d-pj2xk   1/1     Running   0          11m   10.244.0.3   ycwang-ubuntu          <none>           <none>
复制代码


确认都在正常运行了。


此时,可以在集群内,通过 Node IP:NodePort 进行访问,此节点的 IP 是 192.168.111.128:


$ wget 192.168.111.128:30000Connecting to 192.168.111.128:30000... connected.HTTP request sent, awaiting response... 200 OKLength: 615 [text/html]Saving to: ‘index.html’
index.html 100%[===============================================================================================================================>] 615 --.-KB/s in 0s
(87.4 MB/s) - ‘index.html’ saved [615/615]
复制代码


也可以在集群外的 Windows 机器,通过 Node IP:NodePort 进行访问:


$ curl 192.168.111.128:30000  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                 Dload  Upload   Total   Spent    Left  Speed100   615  100   615    0     0   207k      0 --:--:-- --:--:-- --:--:--  300k<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>html { color-scheme: light dark; }body { width: 35em; margin: 0 auto;font-family: Tahoma, Verdana, Arial, sans-serif; }</style></head><body><h1>Welcome to nginx!</h1><p>If you see this page, the nginx web server is successfully installed andworking. Further configuration is required.</p>
<p>For online documentation and support please refer to<a href="http://nginx.org/">nginx.org</a>.<br/>Commercial support is available at<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p></body></html>
复制代码

六. 分析 Node IP:NodePort 访问流程

我们继续探究,访问 Node IP:NodePort 是怎么被转到 Pod 上面去的?


答案依然是 iptables:


$ sudo iptables-save | grep 30000-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-svc" -m tcp --dport 30000 -j KUBE-EXT-HL5LMXD5JFHQZ6LN
复制代码


对 30000 端口的访问,会被跳转到规则:KUBE-EXT-HL5LMXD5JFHQZ6LN。


而 KUBE-EXT-HL5LMXD5JFHQZ6LN,又被跳转到:KUBE-SVC-HL5LMXD5JFHQZ6LN


$ sudo iptables-save | grep KUBE-EXT-HL5LMXD5JFHQZ6LN-A KUBE-EXT-HL5LMXD5JFHQZ6LN -j KUBE-SVC-HL5LMXD5JFHQZ6LN
复制代码


KUBE-SVC-HL5LMXD5JFHQZ6LN 这条规则的具体内容:


$ sudo iptables-save | grep KUBE-SVC-HL5LMXD5JFHQZ6LN-A KUBE-SERVICES -d 10.110.65.131/32 -p tcp -m comment --comment "default/nginx-svc cluster IP" -m tcp --dport 8080 -j KUBE-SVC-HL5LMXD5JFHQZ6LN-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.0.3:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-PU7AOSZG6OVFMASF-A KUBE-SVC-HL5LMXD5JFHQZ6LN -m comment --comment "default/nginx-svc -> 10.244.1.6:80" -j KUBE-SEP-OZ4KTOWKCOJKYUPL
复制代码


跟 Cluster IP 的做法一样,iptables 把对 Node IP:NodePort 的访问,采用轮询的负载均衡策略,依次转发给:10.244.0.3:80 和 10.244.1.6:80 这两个 Endpoints。


K8S 里面的网络访问流程差不多就这样了。它采用了一个很巧妙的设计,去中心化、让每个节点都承担了负载均衡的功能。




补充点题外话,在 Node IP:NodePort 这种模式下,直接访问节点还是会有点问题的。


因为客户需要指定某个 Node 进行访问。这样会带来单点问题;而且,客户按理不应该知道、也不需要知道具体的 Node 和它的 IP。


所以,在实际应用中,可以在 K8S 集群外部,搭建一个负载均衡器。客户访问此负载均衡器,再由该负载均衡器把流量分发到各个 Node 上。很多云厂商也已经带了这样的功能。


但是,既然外部有了支持各种负载均衡算法的职业选手,把流量分发到各个 Node 上。如果 Node 收到后,再次用 iptables 进行负载均衡,就没有什么意义了。不清楚 Google 为什么要这么设计?


是不是可以考虑在 K8S 里面内置一个负载均衡的模块,专门运行在某个 Node 上。在 NodePort 模式下,可以选择启用该模块,由它来专门提供客户访问的入口并做负载均衡,然后此刻各个 Node 上的 iptables 负载均衡可以禁用了?期待各路高人高见……


BTW 一下,既然都说到了负载均衡,捆绑推销一下我的另一篇文章吧:负载均衡算法的实现


用户头像

王玉川

关注

https://yuchuanwang.github.io/ 2018-11-13 加入

https://www.linkedin.com/in/yuchuan-wang/

评论 (2 条评论)

发布
用户头像
话说那个内置负载均衡模块的想法,各厂有出过ingress controller,感觉有些相似,但是不确定是否仍然使用了iptable,有待深究
2023-04-24 16:58 · 上海
回复
谢谢评论
2023-04-25 00:13 · 上海
回复
没有更多了
Kubernetes网络模型分析_Kubernetes_王玉川_InfoQ写作社区