Kubernetes 网络:Pod 和 container 的那点猫腻
1. Kubernetes 网络模型
在 Kubernetes 的网络模型中,最小的网络单位是 Pod。Pod 的网络设计原则是 IP-per-Pod,即 Pod 中 container 共享同一套网络协议栈,具有相同的网络命名空间。Pod 内的 container 通过 localhost + port 访问,类似于 Liunx 中进程访问的方式。构建 Kubernetes 的 Pod 网络模型如下:
Kubernetes 对集群网络的设计原则有:
-
所有容器都可以不用 NAT 和别的容器通信。
-
所有节点都可以不用 NAT 和所有容器通信,反之亦然。
-
容器的地址和外部看到的地址是同一个地址。
回顾前面文章,容器在 Docker 环境下访问外网是通过 NAT 的,NAT 会增加端口管理的复杂性,而且在外部看不到实际容器的 ip。这里的设计原则避开了这些点,它使得 Pod 可以被看作独立的“虚拟机”,从而较好的进行服务发现,域名解析,负载均衡等。
2. 单节点 Pod 访问
单节点构建 pod,实现 pod 内 container 的相互访问。
创建 pod:
$ cat deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: deployment spec: replicas: 1 selector: matchLabels: app: web_server template: metadata: labels: app: web_server spec: containers: - name: httpd-test image: httpd - name: bootcamp-test image: bootcamp:v1 $ kubectl apply -f deployment.yaml deployment.apps/deployment created $ kubectl get pods NAME READY STATUS RESTARTS AGE deployment-595bb7bd6c-6jxwr 2/2 Running 0 44s
pod 包括两个 container httpd 和 bootcamp。其中,httpd 监听在 80 端口,bootcamp 监听在 8080 端口:
$ netstat -ntlp | grep 80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 10204/httpd tcp6 0 0 :::8080 :::* LISTEN 10077/node $ ps aux | grep 10077 | grep -v grep root 10077 0.0 0.9 708220 23720 ? Sl 09:32 0:00 node server.js $ ps aux | grep 10204 | grep -v grep root 10204 0.0 0.1 5940 4360 ? Ss 09:32 0:00 httpd -DFOREGROUND
进入 pod ,从 pod 中访问 container:
$ kubectl exec -it deployment-595bb7bd6c-6jxwr /bin/bash Defaulting container name to bootcamp-test. Use 'kubectl describe pod/deployment-595bb7bd6c-6jxwr -n default' to see all of the containers in this pod. root@deployment-595bb7bd6c-6jxwr:/# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:05 brd ff:ff:ff:ff:ff:ff inet 172.18.0.5/24 brd 172.18.0.255 scope global eth0 valid_lft forever preferred_lft forever root@deployment-595bb7bd6c-6jxwr:/# curl 172.18.0.5:8080 Hello Kubernetes bootcamp! | Running on: deployment-595bb7bd6c-6jxwr | v=1 root@deployment-595bb7bd6c-6jxwr:/# curl 172.18.0.5:80 <html><body><h1>It works!</h1></body></html>
可以看到,进入 pod 实际上进入的是 pod 内的 container,在 continer 内通过 ip + port 实现 httpd 和 bootcamp 的访问。
同理,直接在 node 上访问 pod:
$ curl 172.18.0.5:8080 Hello Kubernetes bootcamp! | Running on: deployment-595bb7bd6c-6jxwr | v=1 $ curl 172.18.0.5:80 <html><body><h1>It works!</h1></body></html> $ curl 172.18.0.5:8081 curl: (7) Failed to connect to 172.18.0.5 port 8081: Connection refused
3. Pod 网络模型
上节实现了 pod 内 container 的相互访问,那么 container 是怎么访问到“外网”的呢?
带着这个问题我们查看和 deployment 相关的 container:
$ docker ps | grep deployment 24adb2a738f4 httpd "httpd-foreground" Up 6 minutes k8s_httpd-test_deployment-.. 1339d17223d8 8fafd8af70e9 "/bin/sh -c 'node se…" Up 7 minutes k8s_bootcamp-test_deployment-.. c86a97fff88b k8s.gcr.io/pause:3.1 "/pause" Up 7 minutes k8s_POD_deployment-...
和期望的不一样,多了一个“pause”类型的 container。查看这三个容器的网络命名空间:
$ mkdir -p /var/run/netns $ docker inspect --format '{{ .State.Pid }}' 24adb2a738f4 8349 $ docker inspect --format '{{ .State.Pid }}' 1339d17223d8 8189 $ docker inspect --format '{{ .State.Pid }}' c86a97fff88b 8100 $ ln -s /proc/8349/ns/net /var/run/netns/httpd $ ln -s /proc/8189/ns/net /var/run/netns/bootcamp $ ln -s /proc/8100/ns/net /var/run/netns/pause $ ip netns list pause (id: 3) bootcamp (id: 3) httpd (id: 3) $ ip netns exec httpd ls -la /proc/self/ns/ lrwxrwxrwx 1 root root 0 Jan 17 08:37 net -> 'net:[4026532636]' ... $ ip netns exec bootcamp ls -la /proc/self/ns/ lrwxrwxrwx 1 root root 0 Jan 17 08:37 net -> 'net:[4026532636]' ... $ ip netns exec pause ls -la /proc/self/ns/ lrwxrwxrwx 1 root root 0 Jan 17 08:37 net -> 'net:[4026532636]' ...
它们具有相同的网络命名空间,进一步的查看容器对应的网络类型:
$ docker inspect 24adb2a738f4 | grep NetworkMode "NetworkMode": "container:c86a97fff88bd73d221dfbe56e209e868f4479671bca9d01c14fd07ba1f03a4d", $ docker inspect 1339d17223d8 | grep NetworkMode "NetworkMode": "container:c86a97fff88bd73d221dfbe56e209e868f4479671bca9d01c14fd07ba1f03a4d", $ docker inspect c86a97fff88b | grep NetworkMode "NetworkMode": "default"
到这里我们大体知道了,容器 pause 的网络模式是默认(网桥)模式,容器 httpd 和 bootcamp 是 container 模式。pause 容器是作为 httpd 和 bootcamp 的“挂载点”存在的,它的存在保证了多个容器可以使用同一个 ip,同一个网络协议栈,即满足 IP-per-Pod 模型。
那么,我们查看网络命名空间的 veth 设备即能知道 Pod 的网络结构了。
$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.0242ed103e29 no vethd2c436f $ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 11: veth4132daf@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 7a:db:25:ff:27:85 brd ff:ff:ff:ff:ff:ff link-netnsid 3 inet6 fe80::78db:25ff:feff:2785/64 scope link valid_lft forever preferred_lft forever root@deployment-595bb7bd6c-6jxwr:/# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:05 brd ff:ff:ff:ff:ff:ff inet 172.18.0.5/24 brd 172.18.0.255 scope global eth0 valid_lft forever preferred_lft forever
构建网络示意图如下:
那么,如果网络容器 pause 因故障销毁了会出现什么情况呢?
首先,绑定到网络容器中的原 network namespace 还是会存在的。当网络容器销毁时 replicaSet 会触发 kubelet 对网络容器进行重建,重建的网络容器归属于新的 network namespace,而相应的原来“桥接”在网络容器上的容器也被重建,并且“桥接”到新的网络容器上。
芝兰生于空谷,不以无人而不芳。