乐趣区

关于kubernetes:Kubernetes学习笔记之kubeproxy-service实现原理

Overview

咱们生产 k8s 对外裸露服务有多种形式,其中一种应用 external-ips clusterip service ClusterIP Service 形式对外裸露服务,kube-proxy 应用 iptables mode。这样 external ips 能够指定固定几台 worker 节点的 IP 地址(worker 节点服务曾经被驱赶,作为流量转发节点不作为计算节点),并作为 lvs vip 下的 rs 来负载平衡。依据 vip:port 来拜访服务,并且依据 port 不同来辨别业务。相比于 NodePort Service 那样能够通过所有 worker 节点的 node_ip:port 来拜访更高效,也更容易落地生产。

然而,traffic packet 是怎么依据集群外 worker 节点的 node_ip:port 或者集群内 cluster_ip:port 拜访形式找到 pod ip 的?

并且,咱们生产 k8s 应用 calico 来作为 cni 插件,采纳 Peered with TOR (Top of Rack) routers
形式部署,每一个 worker node 和其置顶交换机建设 bgp peer 配对,置顶替换机会持续和下层外围交换机建设 bgp peer 配对,这样能够保障 pod ip 在公司内网能够间接被拜访。

然而,traffic packet 晓得了 pod ip,又是怎么跳到 pod 的呢?

以上问题能够归并为一个问题:数据包是怎么一步步跳转到 pod 的?很长时间以来,始终在思考这些问题。

原理解析

实际上答案很简略:拜访业务服务 vip:port 或者说 node_ip:port,当 packet 达到 node_ip 所在机器如 worker A 节点时,会依据 iptable rules 一步步找到 pod ip;找到了 pod ip 后,因为应用 calico bgp 部署形式,外围交换机和置顶交换机都有该 pod ip 所在的 ip 段的路由,packet 最初会跳转到某一个 worker 节点比方 worker B,而 worker B 上有 calico 早就写好的路由规定 route 和虚构网卡 virtual interface,再依据 veth pair 从而由 host network namespace 跳转到 pod network namespace,从而跳转到对应的 pod。

首先能够本地部署个 k8s 集群模仿测试下,这里应用 install minikube with calico

minikube start --network-plugin=cni --cni=calico

# 或者
minikube start --network-plugin=cni
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

而后部署个业务 pod,这里应用 nginx 为例,正本数为 2,并创立 ClusterIP Service with ExternalIPs 和 NodePort Service:


---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo-1
  labels:
    app: nginx-demo-1
spec:
  replicas: 2
  template:
    metadata:
      name: nginx-demo-1
      labels:
        app: nginx-demo-1
    spec:
      containers:
        - name: nginx-demo-1
          image: nginx:1.17.8
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              port: 80
              path: /index.html
            failureThreshold: 10
            initialDelaySeconds: 10
            periodSeconds: 10
      restartPolicy: Always
  selector:
    matchLabels:
      app: nginx-demo-1
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo-1
spec:
  selector:
    app: nginx-demo-1
  ports:
    - port: 8088
      targetPort: 80
      protocol: TCP
  type: ClusterIP
  externalIPs:
    - 192.168.64.57 # 这里 worker 节点 ip 能够通过 minikube ip 查看,这里填写你本人的 worker 节点 ip 地址
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo-2
spec:
  selector:
    app: nginx-demo-1
  ports:
    - port: 8089
      targetPort: 80
  type: NodePort
---

部署实现后,就能够通过 ExternalIP ClusterIP Service 或者 NodePort Service 两种形式拜访业务服务:

iptables 写自定义规定

当数据包通过 node_ip:port 或者 cluster_ip:port 拜访服务时,会在以后 worker 节点被内核 DNAT(Destination Network Address Translation)为 pod ip,反向 packet 又会被 SNAT(Source Network Address Translation)。这里借用 calico 官网的十分活泼的两张图阐明 About Kubernetes Services

cluster-ip service 拜访流程:

node-port service 拜访流程:

因为咱们生产 k8s 的 kube-proxy 应用 iptables mode,所以这些 snat/dnat 规定是 kube-proxy 过程通过调用 iptables 命令来实现的。iptables 应用各种 chain 来治理大量的 iptable rules,次要是五链四表,五链包含:prerouting/input/output/forward/postrouting chain,四表包含:
raw/mangle/nat/filter table,同时也能够用户自定义 chain。数据包 packet 进过内核时通过五链四表流程图如下:

而 kube-proxy 过程会在 nat table 内自定义 KUBE-SERVICES chain,并在 PREROUTING 内失效,能够通过命令查看,而后在查看 KUBE-SERVICES chain 中的规定:

sudo iptables -v -n -t nat -L PREROUTING | grep KUBE-SERVICES

sudo iptables -v -n -t nat -L KUBE-SERVICES

sudo iptables -v -n -t nat -L KUBE-NODEPORTS

能够看到,如果在集群内通过 cluster_ip:port 即 10.196.52.1:8088,或者在集群外通过 external_ip:port 即 192.168.64.57:8088 形式拜访服务,都会在内核里匹配到 KUBE-SVC-JKOCBQALQGD3X3RT chain 的规定,这个对应 nginx-demo-1 service;如果是在集群内通过 cluster_ip:port 即 10.196.89.31:8089,或者集群外通过 nodeport_ip:port 即 192.168.64.57:31755 形式拜访服务,会匹配到 KUBE-SVC-6JCCLZMUQSW27LLD chain 的规定,这个对应 nginx-demo-2 service:

而后持续查找 KUBE-SVC-JKOCBQALQGD3X3RT chain 和 KUBE-SVC-6JCCLZMUQSW27LLD chain 的规定,发现每一个 KUBE-SVC-xxx 都会跳转到 KUBE-SEP-xxx chain 上,并且因为 pod 正本数是 2,这里就会有两个 KUBE-SEP-xxx chain,并且以 50% 概率跳转到任何一个 KUBE-SEP-xxx chain,即 rr(round robin)负载平衡算法,这里 kube-proxy 应用 iptables statistic module 来设置的,最初就会跳转到 pod ip 10.217.120.72:80(这里假如拜访这个 pod)。总之,通过 kube-proxy 调用 iptables 命令,依据 service/endpoint 设置对应的 chain,最终一步步跳转到 pod ip,从而数据包 packet 下一跳是该 pod ip:

sudo iptables -v -n -t nat -L KUBE-SVC-JKOCBQALQGD3X3RT
sudo iptables -v -n -t nat -L KUBE-SEP-CRT5ID3374EWFAWN

sudo iptables -v -n -t nat -L KUBE-SVC-6JCCLZMUQSW27LLD
sudo iptables -v -n -t nat -L KUBE-SEP-SRE6BJUIAABTZ4UR

总之,不论是通过 cluster_ip:port、external_ip:port 还是 node_ip:port 形式拜访业务服务,packet 通过 kube-proxy 过程自定义的各种 chain 找到了下一跳 pod ip 地址。

然而,packet 如何晓得这个 pod ip 在哪个节点呢?

calico 写自定义 routes 和 virtual interface

上文曾经说过,咱们部署 calico 形式能够保障 pod ip 在集群外是能够被路由的,这是因为交换机上会有 node level 的路由规定,在交换机上执行 dis bgp routing-table 会有相似如下路由规定。示意如果拜访 10.20.30.40/26 pod 网段下一跳是 worker B 的 IP 地址。这些路由规定是部署在每一个 worker 节点的 bird 过程 (bgp client) 散发的,交换机通过 BGP 学习来的:

# 这里是随机假造的地址
Network                 NextHop         ...
10.20.30.40/26          10.203.30.40    ...

所以,packet 在晓得了 pod ip 10.217.120.72:80 后(这里假如拜访了 pod nginx-demo-1-7f67f8bdd8-fxptt),很容易找到了 worker B 节点,本文章示例即是 minikube 节点。查看该节点的路由表和网卡,找到了在 host network namespace 这一侧是网卡 cali1087c975dd9,编号是 13,这个编号很重要,能够通过编号晓得这个 veth pair 的另一端在哪个 pod network namespace。发现 pod nginx-demo-1-7f67f8bdd8-fxptt 的网卡 eth0 就是 veth pair 的另一端,并且编号也是 13,:

# 因为该 nginx 容器没有 ifconfig 命令和 ip 命令,能够创立 nicolaka/netshoot:latest 容器并退出到该 nginx container 的 namespace 中
docker ps -a | grep nginx
export CONTAINER_ID=f2ece695e8b9 # 这里是 nginx container 的 container id
# nicolaka/netshoot:latest 镜像地址 github.com/nicolaka/netshoot
docker run -it --network=container:$CONTAINER_ID --pid=container:$CONTAINER_ID --ipc=container:$CONTAINER_ID nicolaka/netshoot:latest ip -c addr
ip -c addr

以上路由表规定和虚构网卡是 calico cni 的 calico network plugin 创立的,而 pod ip 以及每一个 node 的 pod ip cidr 网段都是由 calico ipam plugin 创立治理的,并且这些数据会写入 calico datastore 内。
至于 calico network plugin 和 calico ipam plugin 具体是如何做的,后续有工夫再记录学习。

总结

不论集群内 cluster_ip:port,还是集群外 external_ip:port 或 node_ip:port 形式拜访服务,都是会通过 kube-proxy 过程设置的各种 iptables rules 后跳转到对应的 pod ip,而后借助于 calico bgp 部署形式跳转到指标 pod 所在 worker 节点,并通过该节点的路由表和虚构网卡,找到对应的那个 pod,packet 由 host network namespace 再跳转到 pod network namespace。始终以来的无关 service 和 calico 疑难也算是搞明确了。

参考文献

About Kubernetes Services

Kube-Proxy 的设计与实现

Kube-Proxy Iptables 的设计与实现

Kube-Proxy IPVS 模式的原理与实现

退出移动版