说说 Kubernetes 是怎么实现服务发现的( 三 )


这组规则其实是用于负载均衡的 , 我们看到了--probability 依次是 1/3、1/2、1 , 由于 iptables 规则是自上而下匹配的 , 所以设置这些值能保证每条链匹配到的几率一样 。处理完负载均衡的逻辑后 , 又分别将请求转发到了另外三条规则 , 我们来看一下:
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 172.28.21.66/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 172.28.21.66:9376-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 172.28.29.52/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 172.28.29.52:9376-A KUBE-SEP-X3P2623AGDH6CDF3 -s 172.28.70.13/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 172.28.70.13:9376
可以看到 KUBE-SEP 链就是三条 DNAT 规则 , 并在 DNAT 之前设置了一个 0x00004000 的标志 。DNAT 规则就是在 PREROUTING , 即路由作用之前 , 将请求的目的地址和端口改为 --to-destination 指定的 podIP 和端口 。这样一来 , 我们起先访问 10.212.8.127 这个 CluserIP 的请求 , 就会被负载均衡到各个 Pod 上 。
那么 Pod 重启了 , podIP 变了怎么办?自然是 kube-proxy 负责监听 Pod 变化以及更新维护 iptables 规则了 。
而对于 Headless service 来说 , 我们直接通过固定的 A 记录访问到了 Pod , 自然不需要这些 iptables 规则了 。
iptables 理解起来比较简单 , 但实际上性能并不好 。可以想象 , 当我们的 Pod 非常多时 , 成千上万的 iptables 规则将被创建出来 , 并不断刷新 , 会占用宿主机大量的 CPU 资源 。一个行之有效的方案是基于 IPVS 模式的 Service , IPVS 不需要为每个 Pod 都设置 iptables 规则 , 而是将这些规则都放到了内核态 , 极大降低了维护这些规则的成本 。
集群间通信 外界访问 Service
以上我们讲了请求怎么在 Kubernetes 集群内互通 , 主要基于 kube-dns 生成的 DNS 记录以及 kube-proxy 维护的 iptables 规则 。而这些信息都是作用在集群内的 , 那么自然我们从集群外访问不到一个具体的 Service 或者 Pod 了 。
Service 除了默认的 CluserIP 模式外 , 还提供了很多其他的模式 , 比如 nodePort 模式 , 就是用于解决该问题的 。
apiVersion: v1 kind: Service metadata: name: hostnames spec: selector: app: hostnames type: NodePort ports: - nodePort: 8477protocol: TCPport: 80targetPort: 9376
我们编写了一个 NodePort 模式的 Service , 并且设置 NodePort 为 8477 , 那么意味着我们可以通过任意一台宿主机的 8477 端口访问到 hostnames 这个 Service 。
sh-4.2# curl 10.1.6.25:8477 hostnames-8548b869d7-j5lj9 sh-4.2# curl 10.1.6.25:8477 hostnames-8548b869d7-66vnv sh-4.2# curl 10.1.6.25:8477 hostnames-8548b869d7-szz4f
我们随便找了一台 Node 地址去访问 , 得到了相同的返回配方 。
那么这个时候它的 iptables 规则是怎么作用的呢:
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/hostnames: nodePort" -m tcp --dport 8477 -j KUBE-SVC-67RL4FN6JRUPOJYM
kube-proxy 在每台宿主机上都生成了如上的 iptables 规则 , 通过 --dport 指定了端口 , 访问该端口的请求都会跳转到 KUBE-SVC 链上 , KUBE-SVC 链和之前 CluserIP Service 的配方一样 , 接下来就和访问 CluserIP Service 没什么区别了 。
不过还需要注意的是 , 在请求离开当前宿主机发往其他 Node 时会对其做一次 SNAT 操作:
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE