Kubernetes 网络的功能:
- 高度耦合的容器间通信:这个已经被 Pods 和
localhost
通信解决了。 - Pod 间通信;
- Pod 和 Service 对象间通信;
- 外部和 Service 对象间通信;
Kubernetes 本身的网络服务自带了一下功能:
- NAT: 网络地址转换
- Source NAT: 替换数据包的源 IP, 通常为节点的 IP
- Destination NAT: 替换数据包的目的 IP, 通常为 Pod 的 IP
- VIP: 一个虚拟 IP, 例如分配给每个 Kubernetes Service 的 IP
- Kube-proxy: 一个网络守护程序,在每个节点上协调 Service VIP 管理
可参考 https://kubernetes.io/zh/docs/tutorials/services/source-ip/
1. Pod IP
对于 Docker 中的 container 网络。多个容器共享一个容器的网络接口,实现多个容器共享网络、同一个 IP、同一个 hostname。
K8S 中 Pod 内多容器共享网络就是这样创建的,Pod 的 IP 是 Docker 创建和分配的容器 IP,这个 IP 是带虚拟网卡的,因此这个 IP 是可以被 ping 的,但是该 IP 只能在当前节点中被访问。
首先,创建 Pod 时,Pod 会启动一个 pause 容器,该容器创建了一个虚拟网卡,并被 Docker 分配 IP,接着 Pod 的容器会使用 container 网络模式连接到这个 pause 容器中,pause 容器的生命周期跟 Pod 的生命周期一致。
在工作节点上使用 docker ps -a | grep pause
命令查看 pause 容器:
1[root@test-60g ~]# minikube ssh
2docker@minikube:~$ docker ps -a |grep pause
367461274c207 registry.k8s.io/pause:3.9 "/pause" 25 hours ago Up 25 hours k8s_POD_nginx-7cf478bb58-h6zq9_default_4df3bc0f-7efa-4ef1-9d46-e6b300143eeb_2
4...
不过,Docker 中的容器 IP 是 172.17.0.0 地址段,而 Pod IP 的地址段一般是 10.x.x.x 网络,其中用户自定义 Pod 是 10.32.0.0 地址段。
Pod 的 IP 是 Docker 分配的,为什么其地址不是 172.17.0.0 地址段?
首先,在部署了 Docker 的机器上,都会有一个名为 docker0
的网桥。
1docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
2 inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
3 inet6 fe80::42:2aff:fe00:6577 prefixlen 64 scopeid 0x20<link>
4 ether 02:42:2a:00:65:77 txqueuelen 0 (Ethernet)
5 RX packets 4404 bytes 155753 (152.1 KiB)
6 RX errors 0 dropped 0 overruns 0 frame 0
7 TX packets 3340 bytes 158865 (155.1 KiB)
8 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
docker 的默认网桥叫 docker0
,这个网桥的 IP 是 172.17.0.1,基于这个网桥创建的容器的虚拟网卡自然是 172.17.0.0 地址段。
而如果我们使用 weava
网络插件部署集群,那么使用 ifconfig
命令,可以找到一个 weava
的自定义网桥:
1weave: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1376
2 inet 10.32.0.1 netmask 255.240.0.0 broadcast 10.47.255.255
3 inet6 fe80::ac45:ebff:fe0a:31ae prefixlen 64 scopeid 0x20<link>
4 ether ae:45:eb:0a:31:ae txqueuelen 1000 (Ethernet)
5 RX packets 2905588 bytes 391313728 (391.3 MB)
6 RX errors 0 dropped 0 overruns 0 frame 0
7 TX packets 3179102 bytes 640814125 (640.8 MB)
8 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
通过 Kubernetes 创建的自定义 Pod,会使用这个网桥创建 IP,其 IP 地址跟网络插件创建的网桥有关。
更多的 Docker 网络知识: https://docs.docker.com/network/bridge/
跨节点访问 Pod
既然 Pod 的 IP 是 Docker 创建的,而 Docker 创建的 IP 只能在本地服务器上访问,那么怎么才能在别的节点上访问这个 Pod IP?
网络插件,除了 weave ,还有很多网络插件可以使用,如 calico、flannel。Kubernetes 网络模型中有个叫 CNI
的标准接口,只要实现了这个接口,用啥网络插件都没问题,使用者不需要关心插件是怎么实现的。
CNI 的主要功能:
- 节点上的 Pod 可以不通过 NAT 和其他任何节点上的 Pod 通信(称为扁平化网络),即节点间 Pod 的互相访问;
- 节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有 Pod 通信,即系统组件访问 Pod;
2. Service
Service 是 Kubernetes 的对象,它跟网络有关, Service 不是服务提供者,也不是应用程序接口。
Service 是将运行在一组 Pods 上的应用程序,公开为网络服务的抽象方法。
如果我们使用 Deployment 、Daemon 等部署 Pod,则可为此控制器创建 Service,Service 会监控此 Deployment 上增加或移除 Pod 的操作,自动为所有 Pod 提供网络服务。当然,Service 并不是指向 Deployment、Daemon 的,而是通过 Label 指向相关的 Pod。
2.1 Service 的定义和创建
我们创建一个 Deployment 对象,包含三个 Pod 实例。
1kubectl create deployment nginx --image=nginx:latest --replicas=3
接着,为这些 Pod 创建一个 Service。
1kubectl expose deployment nginx --type=ClusterIP --port=6666 --target-port=80
查看创建的 Service:
1[root@zqf-master1 ~]# k get svc -o wide
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
3kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17m <none>
4nginx ClusterIP 10.101.6.208 <none> 6666/TCP 7s app=nginx
可以看到,Service 会生成一个随机 IP 10.101.6.208
,我们为 Pod 映射了一个新的端口为 6666,此端口映射到了 Pod 的 80 端口中,我们可以测试这个 IP 和 端口是否可用:
1[root@zqf-master1 ~]# curl 10.101.6.208:6666
2<!DOCTYPE html>
3<html>
4<head>
5<title>Welcome to nginx!</title>
6<style>
7html { color-scheme: light dark; }
8body { width: 35em; margin: 0 auto;
9font-family: Tahoma, Verdana, Arial, sans-serif; }
10</style>
11</head>
12<body>
13<h1>Welcome to nginx!</h1>
14<p>If you see this page, the nginx web server is successfully installed and
15working. Further configuration is required.</p>
16
17<p>For online documentation and support please refer to
18<a href="http://nginx.org/">nginx.org</a>.<br/>
19Commercial support is available at
20<a href="http://nginx.com/">nginx.com</a>.</p>
21
22<p><em>Thank you for using nginx.</em></p>
23</body>
24</html>
在没有安装网络插件的前提下,假如有 master、slave 两个节点,Pod 都被部署到 slave 节点上,而 master 节点没有部署此 Pod 的话,master 是访问不了此 Service的。
为了验证这样情况,我们可以消去 master 的污点,使其能够被部署用户自定义的 Pod。
1kubectl taint node zqf-master1 node-role.kubernetes.io/control-plane:NoSchedule-
然后重新部署 Deployment,但是不需要重新部署 Service。
1kubectl delete deployyment nginx
2kubectl create deployment nginx --image=nginx:latest --replicas=3
查看这些 Pod 都被部署到哪里:
1[root@zqf-master1 ~]# k get po -o wide
2NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
3nginx-56fcf95486-lg4bf 0/1 ContainerCreating 0 5s <none> zqf-slave1 <none> <none>
4nginx-56fcf95486-m5v5b 0/1 ContainerCreating 0 5s <none> zqf-master1 <none> <none>
5nginx-56fcf95486-vjk2p 0/1 ContainerCreating 0 5s <none> zqf-slave1 <none> <none>
现在,master、slave 都部署了 Pod,那么我们在 master 节点上访问此 Service,可以正常访问。
在 Deployment 对象上创建 Service,会直接关联一个 Deployment 中的所有 Pod,并监控是否有新建或移除 Pod ,无论 Pod 的数量有多少,Service 都可以代理这些 Pod。
如果我们通过 YAML 定义 Service,其模板如下:
1apiVersion: v1
2kind: Service
3metadata:
4 name: my-service
5spec:
6 selector:
7 app: MyApp
8 ports:
9 - protocol: TCP
10 port: 6666
11 targetPort: 80
12 type: ClusterIP
由于 Service 的 IP 是虚拟的,因此此 IP 是无法 Ping 通的。
2.2 Service 外部服务类型
虽然创建了 Service 后,所有的 Pod 可以被一个 IP 地址访问,但是这个 IP 只能在被部署了 Pod 的节点中访问, 不能被集群外访问,这是因为我们创建 Service 的时候,使用了 ClusterIP 类型,如果是 NodePort 类型,则可以被外界访问到。
Kubernetes Service 有个 ServiceType ,允许我们指定如何暴露服务,可以将一个 Service 暴露到集群外部,外界可以通过 IP 访问这个 Service。
Type 有四种类型,其取值说明如下:
- ClusterIP:通过集群内部 IP 暴露服务,ClusterIP 是 ServiceType 的默认值。
- NodePort:通过每个节点上的 IP 和静态端口(
NodePort
)暴露服务。由于其是节点上的 ,所以具有通过节点的公网 IP 访问这个服务。 - LoadBalancer:使用负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的
NodePort
服务和ClusterIP
服务上。需要云平台服务提供商的支持,分配公网 IP 才能使用。 - ExternalName:通过返回
CNAME
和对应值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。
需要使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用
ExternalName
类型。
ClusterIP、NodePort、LoadBalancer 三者是有关系的,前者是后者的基础。创建一个 NodePort 类型的 Service,必定带有一个 ClusterIP;创建一个 LoadBalancer,必定带有 ClusterIP、NodePort。
2.2.1 NodePort
将前面创建的 Service 修改为 NodePort 类型
1kubectl edit service nginx
然后查看 Service 列表:
1[root@zqf-master1 ~]# k get svc -o wide
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
3kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 33m <none>
4nginx NodePort 10.101.6.208 <none> 6666:31292/TCP 16m app=nginx
此时 Service 会创建一个 随机端口,这个端口映射到每个部署了 Pod 的节点上,样例中是31292,此时外界可以通过使用节点 IP 访问此 Service。
此时,可以在集群其他机器访问两台节点 IP http://10.101.5.219:31292/
和 http://10.101.5.220:31292/
可以正常返回 nginx 网页。
2.2.2 LoadBalancer
现在 Pod 负载均衡了,但不能只访问一个节点?节点的网络也需要负载均衡,而且节点 IP 这么多,用户总不能记住这么多 IP ?就算使用域名,域名也不能绑定这么多 IP 呀,此时应该使用 LoadBalancer 。
删除之前的 service ,重新创建。
1kubectl expose deployment nginx --type=LoadBalancer --port=6666 --target-port=80
如果只填写 –port ,此时映射的端口跟 Pod 端口一致
1[root@zqf-master1 ~]# k get svc
2NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 54m
4nginx LoadBalancer 10.106.1.221 <pending> 6666:31960/TCP 19s
当使用 LoadBalancer 暴露服务到集群外部网络时,云基础设施需要时间来创建负载均衡器并获取服务中的 IP 地址。如果使用的是 minikube
、kubeadm
等创建的自定义 Kubernetes 集群,没有集成 LoadBalancer ,则会一直处于 <Pending>
状态。
2.3 Service 如何选择 Pod
通过命令查看 iptables 配置:
1iptables-save
在终端控制台中查找 random
关键字:
1-A KUBE-SVC-2CMXP7HKUVJN7L6M -m comment --comment "default/nginx -> 172.16.41.201:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-KVETUJWAGOYJMI5D
2-A KUBE-SVC-2CMXP7HKUVJN7L6M -m comment --comment "default/nginx -> 172.16.41.202:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-OGCI3CVDD3KXHXYJ
3-A KUBE-SVC-2CMXP7HKUVJN7L6M -m comment --comment "default/nginx -> 172.16.41.203:80" -j KUBE-SEP-PEONUJMSH5W46AFI
有三个 default/nginx
, 第一个 pod 被访问的机会是 0.33333...
。在剩下的 2/3 的概率中,有 0.5 的概率选择第二个 Pod,剩下的 1/3 概率选择第三个 Pod。这种随机选择的模式称为 iptables
代理模式,也就是 kube-proxy 的默认模式。
2.4 kube-proxy 三种代理模式
当我们使用命令创建一个 Service 时,可看到每个 Service 都有一个 IP 地址,这是由 kube-proxy 负责为 Service 实现的一种虚拟 IP ,即 ClusterIP
。
kube-proxy 可以为多个 Pod 创建一个统一的代理,在访问 Service 时,自动选择一个 Pod 提供服务,至于如何选择 Pod,kube-proxy 有三种模式。
userspace
代理模式iptables
代理模式(默认)IPVS
代理模式(Kubernetes v1.11 ,如果要使用 IPVS,需要修改配置激活)
在这些代理模式中,客户端可以在不了解 Kubernetes 服务或 Pod 的任何信息的情况下,将 Port 代理到适当的后端。
2.4.1 userspace 代理模式
userspace 模式下, kube-proxy 通过轮转算法选择 pod。
对每个 Service,它会在本地 Node 上打开一个端口(端口号大于 30000)。 任何连接到此端口的请求,都会被代理到 Service 后端的某个 Pod
上。 使用哪个后端 Pod,是 kube-proxy 基于 YAML 的 SessionAffinity
终端来确定的。
最后,配置 iptables 规则,捕获到达该 Service 的 clusterIP
和 Port
的请求,并重定向到代理端口,代理端口再代理请求到后端 Pod。
1访问 -> clusterIP -> 代理 -> 任一 Pod
2.4.2 iptables 代理模式
kube-proxy 默认模式。iptables 代理模式的策略随机选择一个 Pod。
它会为每个 Service 配置 iptables 规则,捕获所有访问此 Service clusterIP
的请求,进而将请求重定向到 Service 的一组后端中的某个 Pod 上面。 对于每个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个后端组合。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理, 而无需在用户空间和内核空间之间切换, 这种方法也可能更可靠。
如果 kube-proxy 在 iptables 模式下运行,如果随机所选的第一个 Pod 没有响应, 则连接会失败,在这种情况下,会自动使用其他后端 Pod 重试 。
2.4.3 IPVS 代理模式
与其他代理模式相比,IPVS 模式支持更高的网络流量吞吐量。与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。
IPVS 提供了更多选项来平衡后端 Pod 的流量。 这些是:
rr
:轮替(Round-Robin)lc
:最少链接(Least Connection),即打开链接数量最少者优先dh
:目标地址哈希(Destination Hashing)sh
:源地址哈希(Source Hashing)sed
:最短预期延迟(Shortest Expected Delay)nq
:从不排队(Never Queue)
Service 暴露多端口
如果要在 Service 中暴露多个端口,则每个端口都需要设置一个名字。
1 ports:
2 - name: p1
3 port: 2323
4 protocol: TCP
5 targetPort: 81
6 - name: p2
7 port: 6666
8 protocol: TCP
9 targetPort: 82