• kube-proxy 实现原理(iptables方式)


    Service是k8s中的一个概念,是对一组pod的服务抽象,主要负责将请求分发给对应的pod,完成反向代理和负载均衡(负载均衡一般采用Round Robin算法)。

    kube-proxy来具体实现Service。

    kube-proxy实现转发的方式有两种方式:Userspace,iptables。k8s1.2版本后默认用iptables的方式,实现一系列的包过滤、转发、nat操作。(1.8版本后增加了IPVS方式)

    kube-proxy监听 Kubernetes Master 增加和删除 Service 以及 Endpoint 的消息。对于每一个 Service,Kube Proxy 创建相应的 IPtables 规则,发送到 Service Cluster IP 的流量转发到 Service 后端提供服务的 Pod 的相应端口上。

    微信截图_20210328152515.png

    在创建Service对象时,会为其分配一个虚拟IP(VIP),称为Cluster IP,Cluster IP在外部无法访问,主要作用是供内部的pod之间通信使用。确切来说,Cluster IP 只是 IPtables 中的规则,并不对应到一个任何网络设备。个人认为Cluster IP只是为了实现负载均衡。

    [root@master ~]# kubectl get svc
    NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
    kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   2d2h
    [root@master ~]# ping 10.96.0.1
    PING 10.96.0.1 (10.96.0.1) 56(84) bytes of data.
    From 10.0.30.10 icmp_seq=1 Time to live exceeded
    From 10.0.30.10 icmp_seq=2 Time to live exceeded
    From 10.0.30.10 icmp_seq=3 Time to live exceeded
    From 10.0.30.10 icmp_seq=5 Time to live exceeded
    From 10.0.30.10 icmp_seq=9 Time to live exceeded
    ^C
    --- 10.96.0.1 ping statistics ---
    10 packets transmitted, 0 received, +5 errors, 100% packet loss, time 9011ms
    

    20170724161410421.png

    具体来说,对Service的访问主要分为:

    1. 内部从pod到service,进而转发到具体的pod;
    2. 外部从node port到service,进而转发到具体的pod。

    测试环境

    web服务。

    首先定义一个提供web服务的RC,由2个tomcat容器副本组成,每个容器通过containerPort设置提供服务的端口号为8080:

    [root@master ~]# vi webapp-RC.yaml
    
    apiVersion: v1
    kind: ReplicationController
    metadata:
      name: webapp
    spec:
      replicas: 2
      template:
        metadata:
          name: webapp
          labels:
            app: webapp
        spec:
          containers:
          - name: webapp
            image: tomcat
            ports:
            - containerPort: 8080
    
    [root@master ~]# kubectl create -f webapp-RC.yaml
    replicationcontroller "webapp" created
    

    然后创建一个service,绑定这两个pod:

    [root@master ~]# vi webapp-svc.yaml
    
    apiVersion: v1
    kind: Service
    metadata:
      name: webapp
    spec:
      ports:
      - port: 8081
        targetPort: 8080
      selector:
        app: webapp
    
    [root@master ~]# kubectl create -f webapp-svc.yaml
    service "webapp" created
    

    内部访问service

    [root@master ~]# kubectl get svc
    NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
    ...
    webapp       ClusterIP   10.107.237.254   <none>        8081/TCP   34m
    
    [root@master ~]# kubectl get pod -o wide
    NAME                    READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
    webapp-74bxv            1/1     Running   0          46m     10.244.2.7   node1   <none>           <none>
    webapp-ln5nh            1/1     Running   0          46m     10.244.1.6   node2   <none>           <none>
    

    webapp的Cluster IP为10.107.237.254,pod的ip为10.244.1.610.224.2.7

    node节点(node ip: 10.0.0.201)访问service(cluster ip: 10.107.237.254)。

    OUTPUT链

    首先流量会到达OUTPUT链:

    [root@master ~]# iptables-save -t nat | grep -- '-A OUTPUT'
    -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
    

    然后该链跳转到KUBE-SERVICES子链里:

    [root@master ~]# iptables-save -t nat | grep -- '-A KUBE-SERVICES'
    ...
    -A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.107.237.254/32 -p tcp -m comment --comment "default/webapp cluster IP" -m tcp --dport 8081 -j KUBE-MARK-MASQ
    -A KUBE-SERVICES -d 10.107.237.254/32 -p tcp -m comment --comment "default/webapp cluster IP" -m tcp --dport 8081 -j KUBE-SVC-2IRACUALRELARSND
    

    有两条规则:

    1. 第一条负责打标记MARK0x4000/0x4000,后面会用到这个标记;
    2. 第二条规则跳到KUBE-SVC-2IRACUALRELARSND子链。

    KUBE-SVC-2IRACUALRELARSND子链的规则:

    [root@master ~]# iptables-save -t nat | grep -- '-A KUBE-SVC-2IRACUALRELARSND'
    -A KUBE-SVC-2IRACUALRELARSND -m comment --comment "default/webapp" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-EA2C3KXU4TFQDFBN
    -A KUBE-SVC-2IRACUALRELARSND -m comment --comment "default/webapp" -j KUBE-SEP-ELK6VM6EZTBKIB4X
    

    发现有两条子链:

    • 1/2的概率跳转到子链KUBE-SEP-EA2C3KXU4TFQDFBN
    • 剩下1/2概率跳转到子链KUBE-SEP-ELK6VM6EZTBKIB4X

    首先看KUBE-SEP-EA2C3KXU4TFQDFBN子链:

    [root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-SEP-EA2C3KXU4TFQDFBN'
    -A KUBE-SEP-EA2C3KXU4TFQDFBN -s 10.244.1.6/32 -m comment --comment "default/webapp" -j KUBE-MARK-MASQ
    -A KUBE-SEP-EA2C3KXU4TFQDFBN -p tcp -m comment --comment "default/webapp" -m tcp -j DNAT --to-destination 10.244.1.6:8080
    

    可见这条规则的目的是做了一次DNAT,DNAT目标为其中一个Endpoint,即Pod服务。

    而另一个子链KUBE-SEP-ELK6VM6EZTBKIB4X

    [root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-SEP-ELK6VM6EZTBKIB4X'
    -A KUBE-SEP-ELK6VM6EZTBKIB4X -s 10.244.2.7/32 -m comment --comment "default/webapp" -j KUBE-MARK-MASQ
    -A KUBE-SEP-ELK6VM6EZTBKIB4X -p tcp -m comment --comment "default/webapp" -m tcp -j DNAT --to-destination 10.244.2.7:8080
    

    可见,KUBE-SVC-2IRACUALRELARSND的功能就是按照等概率的原则DNAT到其中的一个endpoint。

    即:

    10.0.0.201:xxxx(node) --> 10.107.237.254:8081
    ↓ DNAT
    10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)

    完成DNAT后,接着到POSTROUTING链。

    POSTROUTING链

    [root@node1 ~]# iptables-save -t nat | grep -- '-A POSTROUTING'
    -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
    

    KUBE-POSTROUTING

    [root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-POSTROUTING'
    -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
    -A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
    -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE
    

    这两条规则只做一件事就是只要标记了0x4000/0x4000的包就一律做MASQUERADE(SNAT),由于10.244.1.2默认是从flannel.1转发出去的,因此会把源IP改为flannel.1的IP10.244.0.0

    10.0.0.201:xxxx(node) --> 10.107.237.254:8081
    ↓ DNAT
    10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)
    ↓ SAT
    10.244.0.0:xxxx --> 10.244.1.6:8080(例如pod1)

    外部访问service

    重新创建NodePort类型的service:

    apiVersion: v1
    kind: Service
    metadata:
      name: webapp
    spec:
      ports:
      - port: 8081 # Cluster IP虚拟端口
        targetPort: 8080 # 服务端口
        nodePort: 30080 # NodePort端口
      type: NodePort
      selector:
        app: webapp
    

    外部访问node节点的30080端口,转发到Cluster IP的8080端口,进一步转发到容器的8081端口。

    [root@master ~]# kubectl get svc
    NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
    webapp       NodePort    10.96.187.75   <none>        8081:30080/TCP   13m
    

    假设10.0.0.1访问10.0.0.201:30080。

    PREROUTING链

    首先到达PREROUTING链:

    [root@node1 ~]# iptables-save -t nat | grep -- '-A PREROUTING'
    -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
    

    KUBE-SERVICES

    [root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-SERVICES'
    -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
    

    PREROUTING的规则非常简单,凡是发给自己的包,则交给子链KUBE-NODEPORTS处理。注意前面省略了判断ClusterIP的部分规则。

    [root@node1 ~]# iptables-save -t nat | grep -- '-A KUBE-NODEPORTS'
    -A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp" -m tcp --dport 30080 -j KUBE-MARK-MASQ
    -A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp" -m tcp --dport 30080 -j KUBE-SVC-2IRACUALRELARSND
    

    这个规则首先给包打上标记0x4000/0x4000,然后交给子链KUBE-SVC-2IRACUALRELARSND处理,KUBE-SVC-2IRACUALRELARSND就是按照概率均等的原则DNAT到其中一个Endpoint IP,即Pod IP,假设为10.244.1.6。

    10.0.0.1:xxxx(node) --> 10.0.0.201:30080
    ↓ DNAT
    10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)

    此时发现10.244.1.6不是自己的IP,于是经过路由判断目标为10.244.1.6需要从flannel.1发出去。

    FORWARD链

    接着到了FORWARD链:

    [root@node1 ~]# iptables-save -t filter | grep -- '-A FORWARD'
    -A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
    

    KUBE-FORWARD

    [root@node1 ~]# iptables-save -t filter | grep -- '-A KUBE-FORWARD'
    -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
    -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
    

    FORWARD表在这里只是判断下,只允许打了标记0x4000/0x4000的包才允许转发。

    最后来到POSTROUTING链,这里和ClusterIP就完全一样了,在KUBE-POSTROUTING中做一次MASQUERADE(SNAT),最后结果:

    10.0.0.1:xxxx(node) --> 10.0.0.201:30080
    ↓ DNAT
    10.0.0.201:xxxx(node) --> 10.244.1.6:8080(例如pod1)
    ↓ SNAT
    10.244.0.0:xxxx --> 10.244.1.6:8080(例如pod1)

    补充

    service的三种端口

    • port:service暴露在cluster ip上的端口,是提供给集群内部客户访问service的入口。
    • nodePort:nodePort是k8s提供给集群外部客户访问service入口的一种方式,nodePort 是提供给集群外部客户访问service的入口。
    • targetPort:targetPort是pod上的端口,从port和nodePort上到来的数据最终经过kube-proxy流入到后端pod的targetPort上进入容器。

    具体k8s的flannel网络参考:https://www.jianshu.com/p/2f91907b2aba
    具体iptables链参考:
    iptables.png


    当我们创建pod时,仅仅是创建了pod,要为其创建rc(ReplicationController),他才会有固定的副本,然后为其创建service,集群内部才能访问该pod,使用 NodePort 或者 LoadBalancer 类型的 Service,外部网络也可以访问该pod;每个 service 会创建出来一个虚拟 ip,通过访问 vip:port 就能获取服务的内容(内部访问,因为这是一个vip,外部无法访问的)

    参考

    https://www.xiexianbin.cn/kubernetes/2016-07-25-kubernetes-proxy/index.html
    https://zhuanlan.zhihu.com/p/94418251?from_voters_page=true
    http://www.voidcn.com/article/p-fzkcdsqq-bqu.html
    https://blog.csdn.net/liyingke112/article/details/76022267
    https://www.jianshu.com/p/f28534fe507a
    https://www.jianshu.com/p/2f91907b2aba
    https://www.jianshu.com/p/3beb4336e251

  • 相关阅读:
    【转载】SG定理
    P3235 [HNOI2014]江南乐(Multi-Nim)
    斐波那契数列
    WC2021游记
    线性基性质证明和应用
    「NOI2018」屠龙勇士
    XiaoMi Interview Log
    打靶训练
    八皇后问题
    Docker的一些常用
  • 原文地址:https://www.cnblogs.com/LMIx/p/14588522.html
Copyright © 2020-2023  润新知