K8s-kube-proxy(iptables代理模式执行流程)

基于1.25

kube-proxy 通过宿主节点上的配置iptables实现Service功能,都有syncProxyRule实现

统计State Service和Stale Endpoints

iptables代理模式第一步是针对UDP协议,统计Stale Service和Stable Endpoints

  • Endpoints是所有Service的后端地址

为什么需要针对UDP协议进行统计Stale Service和Stale Endpoints的原因?

  • 客户端访问Service负载均衡需要依赖NAT协议,而NAT机制又依赖conntrack模块
  • UDP协议不会TCP协议一样,存在断开链接,客户端和服务端之间的Conntrack记录不会马上被清除,客户端的发出的UDP数据以来按照原有的NAT发送给不存在的服务端,导致丢包,所以UDP Endpoints被销毁的时候,需要手动清除Conntrack记录
  • 如果一个Service不存在任何后端Pod,则NAT机制由于没有服务端的存在,会丢弃UDP数据包。如果此时Service有了新的后端Pod,会因为Conntrack记录继续复用,数据包也会丢弃,这种也需要手动清除除Conntrack记录

统计Stale Endpoints和Stale Service目的是及时清理过时的Conntrack记录,kube-proxy通过下面的方法判断:

  • Stale Endppoints:如果一个Service存在UDP协议的Port,有一些Endpoints曾经存在的,后面消失了,则认为这些消失的Endpoints 是Stale Endppoints

  • Stale Service:如果一个Service被判定为Stale Service,其Cluster IP、ExternalIP、LoadBalancer IP都会加入到Stale Service列表。

  • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L832

    ...
    // merge stale services gathered from updateEndpointsMap
    // an UDP service that changes from 0 to non-0 endpoints is considered stale.
    for _, svcPortName := range endpointUpdateResult.StaleServiceNames {
    if svcInfo, ok := proxier.serviceMap[svcPortName]; ok && svcInfo != nil && conntrack.IsClearConntrackNeeded(svcInfo.Protocol()) {
    klog.V(2).InfoS("Stale service", "protocol", strings.ToLower(string(svcInfo.Protocol())), "servicePortName", svcPortName, "clusterIP", svcInfo.ClusterIP())
    conntrackCleanupServiceIPs.Insert(svcInfo.ClusterIP().String())
    for _, extIP := range svcInfo.ExternalIPStrings() {
    conntrackCleanupServiceIPs.Insert(extIP)
    }
    for _, lbIP := range svcInfo.LoadBalancerIPStrings() {
    conntrackCleanupServiceIPs.Insert(lbIP)
    }
    nodePort := svcInfo.NodePort()
    if svcInfo.Protocol() == v1.ProtocolUDP && nodePort != 0 {
    klog.V(2).InfoS("Stale service", "protocol", strings.ToLower(string(svcInfo.Protocol())), "servicePortName", svcPortName, "nodePort", nodePort)
    conntrackCleanupServiceNodePorts.Insert(nodePort)
    }
    }
    }
    ...

创建基础iptables链和规则

  1. 在开始阶段,kube-proxy创建最基础的iptables链,以KUBE-开头

  2. kube-proxy创建原始iptables链(PREEOUTING、POSTROUTING链)向第一步基础的链跳转的规则

  • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L861

      
    // Create and link the kube chains.
    for _, jump := range iptablesJumpChains {
    if _, err := proxier.iptables.EnsureChain(jump.table, jump.dstChain); err != nil {
    klog.ErrorS(err, "Failed to ensure chain exists", "table", jump.table, "chain", jump.dstChain)
    return
    }
    args := append(jump.extraArgs,
    "-m", "comment", "--comment", jump.comment,
    "-j", string(jump.dstChain),
    )
    if _, err := proxier.iptables.EnsureRule(utiliptables.Prepend, jump.table, jump.srcChain, args...); err != nil {
    klog.ErrorS(err, "Failed to ensure chain jumps", "table", jump.table, "srcChain", jump.srcChain, "dstChain", jump.dstChain)
    return
    }
    }

对应的完整的iptable命令:

todo

初始化iptables内容缓冲区

kube-porxy在iptables代理模式下,需要为每个Service都维护一套iptables链和跳转规则,所以iptables操作频繁,所以进行了优化

  • kube-proxy先把所有的创建的链和跳转规则放入缓冲区,等待所有的规则配置后,使用iptables-restore命令一次性写入宿主机

  • kube-proxy首先初始化缓冲区(filterChains\filterRules\netChains\natRules),然后把之前创建的基础KUBE链写入对应的缓冲区

  • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L888

    // Write chain lines for all the "top-level" chains we'll be filling in
    for _, chainName := range []utiliptables.Chain{kubeServicesChain, kubeExternalServicesChain, kubeForwardChain, kubeNodePortsChain, kubeProxyFirewallChain} {
    proxier.filterChains.Write(utiliptables.MakeChainLine(chainName))
    }
    for _, chainName := range []utiliptables.Chain{kubeServicesChain, kubeNodePortsChain, kubePostroutingChain, kubeMarkMasqChain} {
    proxier.natChains.Write(utiliptables.MakeChainLine(chainName))
    }

配置KUBE-POSTROUTING链跳转规则

KUBE-POSTROUTING链主要是为需要SNAT的数据包执行Masquerade操作

kube-proxy向KUBE-POSTROUTING添加的跳转规则如下:

 # 如果iptables检查到数据包没有指定MARK标记,则不继续进行后续校验
-A KUBE-POSTROUTING -m mark ! --mark {MasqueradeMark}/{MasqueradeMark} -j RETURE
// 如果数据包存在MARK 标记,首先使用“XOR”操作去掉MARK,在跳转到MASQUERADE完成SNAT操作
-A KUBE-POSTROUTING -j MARK --xor-mark {MasqueradeMark}
-A KUBE-POSTROUTING -m comment --comment "kubennetes service traffic requiring SNAT" -j MASQUERADE --random-fully

MasqueradeMark生成的方式如下:

配置KUBE-MARK-MASQ链跳转规则

KUBE-MARK-MASQ链到达该链的数据包添加MARK标记,kube-proxy为KUBE-MARK-MASQ链添加的跳转规则如下:

-A KUBE-MARK-MASQ -j MARK --or-mark {MasqueradeMark}

对于需要执行masquerade操作的数据包,iptables将其送入KUBE-MARK-MASQ链,在该链添加指定的MARK标记后,后续在KUBE-POSTROUTING链中完成masquerade操作

统计宿主机的IP地址

如果集群中的某些Service存在NodePort,则需要为Service的配置NodePort相关的iptables链和规则

  • 首先,GetNodeAddresses会读取--nodeport-addresses启动参数重的IP地址段(如果为空,则默认0.0.0.0/0与::/0)

  • 然后从networkInterface中获取节点所有网络接口上的IP地址,如果这些IP地址在--nodeport-addresses就会变成最终的nodeAddresses,如果某个IP地址已经认0.0.0.0/0或::/0,就不在需要处理

  • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L944

    nodeAddresses, err := utilproxy.GetNodeAddresses(proxier.nodePortAddresses, proxier.networkInterfacer)
    if err != nil {
    klog.ErrorS(err, "Failed to get node ip address matching nodeport cidrs, services with nodeport may not work as intended", "CIDRs", proxier.nodePortAddresses)
    }
    // nodeAddresses may contain dual-stack zero-CIDRs if proxier.nodePortAddresses is empty.
    // Ensure nodeAddresses only contains the addresses for this proxier's IP family.
    isIPv6 := proxier.iptables.IsIPv6()
    for addr := range nodeAddresses {
    if utilproxy.IsZeroCIDR(addr) && isIPv6 == netutils.IsIPv6CIDRString(addr) {
    // if any of the addresses is zero cidr of this IP family, non-zero IPs can be excluded.
    nodeAddresses = sets.NewString(addr)
    break
    }
    }

为每个Service Port配置iptables链和规则

iptables模式中,kube-proxy会依次为每个ServicePort的ClusterIP、ExternalIP、Load BalanceIP、NodePort创建iptables链和规则,并为每个ServicePort关联的Endpoints创建iptables链和规则。

  • 链名称:前面根据不同类型,后面根据Port协议号等生成16个字符串的哈希字符串

为每个ServicePort创建的iptables:

链名称前缀 用途
KUBE-SVC- 在内部或外部流程策略为Cluster情况下,为数据包选择后端Endpoints。其中SVC可以看作Service Cluster的缩写
KUBE-SVL- 在内部或外部流程策略为Local情况下,为数据包选择后端Endpoints。其中SVL可以看作Service Local的缩写
KUBE-FW- 在LoadBalancer类型的Service配置了spec.loadBalancerSourceRanges字段情况下,用于过滤在白名单网段内的数据包。FW可以看作firewall的缩写
KUBE-EXT- 处理访问集群外部地址,如ExternalIP、LoadBalancer、NoderPort的数据包,在适当的时候为数据包添加MARK标记,并且把数据包引到KUBE-SVC-XXX或者KUBE-SVL-XXX链上。其中EXT可以看作External的缩写

为每个ServicePort关联的Endpoints创建的iptables:

链名称 介绍
KUBE-SEP- 针对每一个Endpoints(通常就是Pod和端口)创建的链,用于修改数据包的目的地址,完成请求的DNAT操作

每个Service的iptable规则配置流程:

  1. 在KUBE-SERVICE等基础上添加KUBE-SVC-XXX、KUBE-SVL-XXX、KUBE-EXT-XXX、KUBE-FW-XXX等链等跳转规则,从数据包从统一的基础KUBE链引导到各个ServicePort自己的链上
  2. 根据内外部流量策略,在KUBE-SVC-XXX、KUBE-SVL-XXX、KUBE-EXT-XXX、KUBE-FW-XXX等链等配置跳转规则和过滤操作,根据Service的配置来过滤数据包以及对数据包进行标记以后后续今昔SNAT等操作
  3. 根据Service后端Endpoints配置KUBE-SEP-XXX链,对数据包进行DNAT操作

为每个ServicePort关联的Endpoints创建的iptables配置流程:

  1. 根据内外部流量分类统计Endpoints

    Service的流量策略为俩种:

    • InternalTraffficPolicy:内部流量策略,指集群内部Pod和节点访问Service的请求流量

      • Cluster:向集群内Service关联的所有Pod分流请求

      • Local:仅向当前宿主节点上Service关联的Pod分流请求。如果当前宿主节点没有Pod,则丢弃

    • ExternalTrafficPolicy:外部流量策略,指集群外部Pod访问Service的请求流量

      • Cluster:向集群内Service关联的所有Pod分流请求
      • Local:仅向当前宿主节点上Service关联的Pod分流请求。如果当前宿主节点没有Pod,则丢弃

    对Endpoints还需要进行统计分类

    ​ clusterEndpoints集群中Service关联的所有Ready的Endpoints

    ​ localEndpoints是当前宿主节点上Service关联的所有Ready的Endpoints。这些变量都有下面的几种情况:

    • 内部流量策略和外部流量策略均为Cluster

      • clusterEndpoints:集群中所有Ready状态的Endpoints
      • localEndpoints:空集(不需要统计)
    • 内部流量策略为Local,并且没有NodePort、External IP、LoadBalancerIP

      • clusterEndpoints:空集
      • localEndpoints:当前宿主节点上Ready的Endpoints
    • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L978

      // Figure out the endpoints for Cluster and Local traffic policy.
      // allLocallyReachableEndpoints is the set of all endpoints that can be routed to
      // from this node, given the service's traffic policies. hasEndpoints is true
      // if the service has any usable endpoints on any node, not just this one.
      allEndpoints := proxier.endpointsMap[svcName]
      clusterEndpoints, localEndpoints, allLocallyReachableEndpoints, hasEndpoints := proxy.CategorizeEndpoints(allEndpoints, svcInfo, proxier.nodeLabels)
  2. 配置ClusterIP的iptabels规则

    1. kube-proxy添加Service的ClusterIP和Port配置的iptables规则,为ClusterIP和port在natRules缓冲区添加以下规则。

      # ServicePortName 为由当前的Service Port拼接成的ServiceNamespaces/ServiceName:Port 字符串
      -A KUBE-SERVICES -m comment --comment "{ServicePortName} cluster IP" -m protocol -p protocol -d {ClusterIP} --dport {Port} -j {InternalTrafficChain}
      • 如果内部流量策略为Cluster,并且集群中有该Service的Ready的Endpoints,则InternalTrafficChain为KUBE-SVC-XXX链
      • 如果内部流量策略为Local,并且当前宿主节点上有该Service的Ready的Endpoints,则InternalTrafficChain为KUBE-SVL-XXX链
    2. 如果Service没有Ready的Endpoints,即内部流量策略为Cluster时集群范围内没有Ready的Endpoints,或者内部流量策略为Local时宿主节点没有Ready的Endpoints,则不会添加上述iptables规则,而是在filterRules缓冲区添加以下iptables规则来拒绝对ServicePort

      -A KUBE-SERVICES -m comment --comment {InternalTrafficFilterComment} -m protocol -p protocol -d {ClusterIP} --dport {Port} -j {InternalTrafficFilterTarget}
      • 如果集群中没有Service的Ready的Endpoints,则InternalTrafficFilterTarget取值为REJECT,则InternalTrafficFilterComment取值为{ServicePortName} has no endpoints
      • 如果内部流量策略为Local但宿主节点上没有任何Ready的Endpoints,则InternalTrafficFilterTarget取值为DROP,InternalTrafficFilterComment取值为{ServicePortName} has no local endpoints
  3. 配置了External IP的iptables的规则

    如果每个Service配置了Exteral IP,则kube-proxy姐下为External IP和Port配置iptables规则。

    对于每个External IP,如果集群中有Service的Ready的Endpoints,则会在natRules缓冲区添加以下iptables规则

    -A KUBE-SERVICE -m comment --comment "{ServicePortName} exterbal IP" -m protocol -p protocol -d {ExternalIP} --dport {Port} -j KUBE-EXT-XXX

    先把访问External IP数据包转发到KUBE-EXT-XXX链上,再在该链上做进一步的处理。

    如果Service没有Ready的Endpoints,即外部流量策略为Cluster时集群范围内没有Ready的Endpoints,或者外部流量策略为Local的宿主节点范围内没有Ready的Endpoints,则不会添加上述iptables规则,而是在filterRules缓冲区中添加以下的iptables规则来拒绝对ServicePort的访问请求

    -A KUBE-EXTERNAL-SERVICE -m comment --comment {ExternalTrafficFilterComment} -m protocol -p protocol -d {ExternalIP} --dport {Port} -j {ExternalTrafficFilterTarget}
    • 如果集群中没有Service的Ready的Endpoints,则ExternalTrafficFilterTarget取值为REJECT,ExternalTrafficFilterComment取值为{ServicePortNAme} has no endpoints
    • 如果外部流量策略为Local但宿主节点上没有Ready的Endpoints,则ExternalTrafficFilterTarget取值为DROP,ExternalTrafficFilterComment取值为{ServicePortNAme} has no local endpoints
  4. 配置了LoadBalancerIP的iptables规则

    对于每个LoadBalancerIP,如果该Service在集群中有Ready的Endpoints,就会在在natRules缓冲区添加以下iptables规则

    -A KUBE-SERVICES -m comment --commment "{ServicePortName} loadbalancer IP" -m protocol -p protocol -d {LoadBalancerIP} --dport {Port} -j {LoadBalancerTrafficChain}
    • 如果Service没有设置SourceRanges:LoadBalancerTrafficChain为KUBE-EXT-XXX链
    • 如果Service设置了SourceRanges:需要先校验数据包的源IP是否在白名单,因此LoadBalancerTrafficChain为KUBE-FW-XXX链

    如果Service配置了spec.loadBalancerSourceRanages字段:kube-proxy会在filterRules缓冲区添加一条iptables规则。当数据包目的IP是LoadBalancerIP且该数据包出现在KUBE-PROXY-FIREWALL链中,说明该数据包未能在nat表的KUBE-SERVICE链中被DNAT,也就说该数据包的源IP不在白名单之内,该数据包会被丢弃。该iptables规则如下:

    -A KUBE-PROXY-FIREWALL -m comment --comment "{ServicePortName} traffic not accepted by {LoadBalancerTrafficeChain}" -m protocol -p protocol -d {LoadBalancerIP} --dport {Port} -j DROP

    如果Service没有Ready的Endpoints,即外部流量策略为Cluster时集群范围没有Ready的Endpoibts,或者外部流量策略为Local宿主节点没有Ready的Endpoints,则kube-proxy不会添加上述的iptables,而是添加在filterRules缓冲区添加下的iptables,拒绝对ServicePort的访问请求

    -A KUBE-EXTERNAL-SERVICES -m comment --comment {ExternalTrafficFilterComment} -m protocol -p protocol -d {LoadBalancerIP} --dport {Port} -j {ExterbalTraffficFilterTarget}
    • 如果集群中没有Service的Ready的Endpoints,则ExterbalTraffficFilterTarget=REJECT,ExternalTrafficFilterComment={ServicePortName} has no endpoints
    • 如果外部流程策略为Local但宿主节点上没有Ready的Endpoints,则ExterbalTraffficFilterTarget=DROP,ExternalTrafficFilterComment={ServicePortName} has no local endpoints
  5. 配置NodePort的iptables规则

    kube-proxy为NodePort配置iptables规则,在natRules缓冲区添加以下规则,将会访问NodePort的请求转发到KUBE-EXT-XXX iptables链上

    -A KUBE-NODEPORTS -m comment --comment "{ServicePortName}" -m protocol -p protocol --dport {NodePort} -j KUBE-EXT-XXX 

    如果没有Service没有Ready的Endpoints,即外部流量策略为Cluster时集群范围内没有Ready的Endpoints,或外部流量策略Local宿主节点内没有Ready状态的Endpoints,则kube-proxy不会添加上述iptables规则,而是在filterRules缓冲区添加以下iptables规则拒绝对NodePort的访问请求

    -A KUBE-EXTERNAL-SERVICES -m comment --comment "{ServicePortName} health check node port" -m tcp -p tcp --dport {HealthCheckNodePort} -j ACCEPT

    HealthCheckNodePort是配置的健康检查端口号

  6. 配置Masquerade的iptables规则

    对于访问ClusterIP的数据包,有时需要为这些数据包添加SNAT标记。

    如果kube-proxy启用了--masquerade-all参数,则会所有访问Service的数据包转发给KUBE-MARK-MASQ链添加MARK标记

    -A {InternalTrafficChain} -m comment --coment "{ServicePortName} cluster IP" -m protocol -p  protocol -d {ClusertIP} --dport {Port} -j KUBE-MARK-MASQ

    如果kube-proxy不启用了--masquerade-all参数,则Masquerade所有来自非集群Pod访问ClusterIP请求

    -A {InternalTrafficChain} -m comment --coment "{ServicePortName} cluster IP" -m protocol -p  protocol -d {ClusertIP} --dport {Port} {IfNotLocal} -j KUBE-MARK-MASQ

    上述规则中,InternalTrafficChain由内部流程策略决定:

    • Cluster:InternalTrafficChain=KUBE-SVC-XXX链

    • Local:InternalTrafficChain=KUBE-SVL-XXX链

      IfNotLocal:启动参数重设置的判断请求是否来自宿主机的本地的方法,对应--detect-local-mode启动参数,如!-s 192.168.16.0/24

  7. 配置KUBE-EXT链

    如果Service包含NodePort、ExternalIP或LoadBalancerIP,并且在集群范围内有Service的Ready的Endpoints,则kube-proxy会配置KUBE-EXT-XXX链。进入该链的数据包根据Service的外部流程策略,会进一步跳转KUBE-SVC-XXX或KUBE-SVL-XXX链

    1. kube-proxy向natChain缓冲区写入KUBE-EXT-XXX链

      :KUBE-EXT-XXX - [0:0]
    2. 根据外部流程策略,向KUBE-EXT-XXX链中添加不同的iptables规则

      如果Service的外部流量策略,向KUBE-EXT-XXX链添加不同的iptables规则

      • Cluster:需要对所有访问NodePort、External IP、LoadBalancerIP的数据包进行SNAT,一遍反应数据包可以顺利回源。kubr-proxy在natRules缓冲区添加以下iptables规则

        -A KUBE-EXT-XXX -m comment --comment "masquerade traffic for {ServicePortName} external destiations" -j KUBE-MARK-MASQ
      • Local:

        • 如果请求来自于集群Pod:数据包会被转发到KUBE-SVC-XXX链,向集群访问内到Pod转发

          -A KUBE-EXT-XXX -m comment --comment "pod traffic for {ServicePortName} external destinations" {IfLocal} -j KUBE-SVC-XXX
        • 对于来自宿主节点但是非Pod的请求,依然需要SNAT,因为先跳转到KUBE-MARK-MASQ链进行标记

          -A KUBE-EXT-XXX -m comment --comment "masquerade LOCAL traffic for {ServicePortName} external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ
        • 对于宿主节点但非Pod的请求,在添加MARK标识之后,转发到KUBE-SVC-XXX链,向集群范围内的Pod转发

          -A KUBE-EXT-XXX -m comment --comment "route LOCAL traffic for {ServicePortName} external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-XXX

          数据包在KUBE-EXT-XXX链上完成千术的检查之后,跳转到KUBE-SVC-XXX或KUBE-SVL-XXX,对应的iptables规则

          -A KUBE-EXT-XXX -j {ExternalPolicyChain}

          ExternalPolicyChain的取值跟Service的外部流程策略有关:

          • Cluster:ExternalPolicyChain=KUBE-SVC-XXX
          • Local:ExternalPolicyChain=KUEBE-SVL-XXX
  8. 配置KUBE-FW-链

    当数据包跳转到KUBE-FW-XXX链时,根据spec.loadBalancerSourceRange字段中设置的源IP地址过滤数据包

    • 如果设置了字段,还需要向KUBE-FW-XXX中添加规则
    • 如果不设置,不会存在KUBE-FW-XXX

    对于Service的spec.loadBalancerSourceRange字段中的每个IP地址,向natRules缓冲区的KUBE-FW-XXX链添加以下iptables规则,对于存在源IP地址白名单中的数据包,将跳转到KUBE-EXT-XXX链。其中SourceRange为用户在Service中设置的每个白名单网段

    -A KUBE-FW-XXX -m comment --comment "{ServicePortName} loadBalancer IP" -s {SourceRange} -j KUBE-EXT-XXX

    如果未能匹配白名单中IP地址段段数据包,则不会跳转到KUBE-EXT-XXX链,也无法完成DNAT,这些导致这些数据包在后续过程中在KUBE-PROXY-FIREWALL被丢弃

  9. 配置KUBE-SVC-和KUBE-SVL-链

    数据包在KUBE-SVC-XXX链和KUBE-SVL-XXX链上完成Endpoints到随机选择。

    KUBE-SVC-XXX链用来处理外部流量策略为CLuster的情况:而KUBE-SVL-XXX用来处理内部外流量策略为Local的情况

    kube-proxy根据Service的Endpoints列表配置KUBE-SVC-XXX和KUBE-SVL-XXX的过程如下:

    • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L1344

      	// If Cluster policy is in use, create the chain and create rules jumping
      // from clusterPolicyChain to the clusterEndpoints
      if usesClusterPolicyChain {
      proxier.natChains.Write(utiliptables.MakeChainLine(clusterPolicyChain))
      proxier.writeServiceToEndpointRules(svcPortNameString, svcInfo, clusterPolicyChain, clusterEndpoints, args)
      }

      // If Local policy is in use, create the chain and create rules jumping
      // from localPolicyChain to the localEndpoints
      if usesLocalPolicyChain {
      proxier.natChains.Write(utiliptables.MakeChainLine(localPolicyChain))
      proxier.writeServiceToEndpointRules(svcPortNameString, svcInfo, localPolicyChain, localEndpoints, args)
      }

      // Generate the per-endpoint chains.
      for _, ep := range allLocallyReachableEndpoints {
      epInfo, ok := ep.(*endpointsInfo)
      if !ok {
      klog.ErrorS(err, "Failed to cast endpointsInfo", "endpointsInfo", ep)
      continue
      }

      endpointChain := epInfo.ChainName

      // Create the endpoint chain
      proxier.natChains.Write(utiliptables.MakeChainLine(endpointChain))
      activeNATChains[endpointChain] = true

      args = append(args[:0], "-A", string(endpointChain))
      args = proxier.appendServiceCommentLocked(args, svcPortNameString)
      // Handle traffic that loops back to the originator with SNAT.
      proxier.natRules.Write(
      args,
      "-s", epInfo.IP(),
      "-j", string(kubeMarkMasqChain))
      // Update client-affinity lists.
      if svcInfo.SessionAffinityType() == v1.ServiceAffinityClientIP {
      args = append(args, "-m", "recent", "--name", string(endpointChain), "--set")
      }
      // DNAT to final destination.
      args = append(args, "-m", protocol, "-p", protocol, "-j", "DNAT", "--to-destination", epInfo.Endpoint)
      proxier.natRules.Write(args)
      }
      }
  10. 配置KUBE-SEP链

    数据包在KUBE-SVC-XXX和KUBE-SVL-XXX链杀昂完成后端Endpoints后,将会在KUBE-SEP-XXX链上使用该Endpoints的IP地址和端口完成DNAT操作

    kube-proxy会为每个Service Port的每个Endpoints都配置一条KUBE-SEP-XXX链,对应一个Pod的IP和地址及其端口

    # natChains
    :KUBE-SEP-XXX - [0:0]

    # natRules
    -A KUBE-SEP-XXX -m comment --comment {ServicePortName} -s {EndpointIPPort} -j KUBE-MARK-MASQ
    -A KUBE-SEP-XXX -m comment --comment {ServicePortName} -m {Protocol} -p {Protocol} -j NAT --to-destination {EndpointIPPort}

配置KUBE-NODEPORTS链跳转规则

KUBE-SERVICES链上配置跳转到KUBE-NODEPORTS链规则,添加到KUBE-SERVICES链的最后,用于访问NodePort的数据包引导到KUBE-NODEPORTS链上

遍历nodeAddress数组,该数组记录宿主机的IP地址。对于每条IP地址,生成跳转规则。

  • 如果IP地址0.0.0.0/0或::0,则在natRules缓冲区添加以下的规则

    -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports;NOTE: this must be the last rule in this chain" -m addrtype --dsr-type LOCAL -j KUBE-NODEPORTS
  • 如果IP地址不是0.0.0.0/0或::/0,则在natRules缓冲区添加以下规则

    -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE:this must be the last rule in this chain" -d address -j KUBE-NODEPORTS

访问”宿主节点IP地址:NodePort”的数据包会被引导KUBE-NODEPORTS。KUBE-NODEPORTS包含了各个NodePort的跳转规则,数据包会进一步跳转各个Service的KUBE-EXT-链上,完成DNAT行为

配置KUBE-FORWARD链跳转规则

向KUBE-FORWARD加入一些规则,这些规则会过滤到一些无效的数据包。

这些规则防区filterRules缓冲区,其中第一条和第三条规则根据Conntrack记录的状态丢弃或放行一些数据包

-A KUBE-FORWARD -m conntrck --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment ---cimment "kubernetes forwarding rules" -m mark --mark {MasqueradeMark}/{MasqueradeMark} -j ACCEPT
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

将iptables缓冲区内容刷新新到宿主机

kube-proxy完成了所有的iptables链和规则整理,接下来需要把这些链和规则写入宿主节点的iptables

在前面的iptables链和跳转规则都放去了filterChains、filterRules、natChains、natRules四个缓冲区。首先四个缓存区拼成一起,放入iptabesData缓冲区

清理残留的UDP Conntrack记录

完成了iptabels规则更新,开始清理Stale的UDP协议的Conntrack记录

  1. 清理conntrackCleaningupServiceIPs记录的IP地址涉及到Conntrack记录。

    conntrackCleaningupServiceIPs中记录的是Stale Service的ClusterIP、ExternalIP、LoadBalancerIP

    conntrack -D --orig-dst {OriginIP} -p UDP
  2. 其中,OriginIP为conntrackCleaningupServiceIPs中记录的是IP地址

  3. 清理conntrackCleaningupServiceIPs中记录的NodePort涉及Conntrack记录。

    ClearEntriesForPort func如下:

    conntrack -D -p UDP --dport {NodePort}
  4. 其中,{NodePort}为conntrackCleanupServiceNodePorts中记录的NodePort

  5. 调用deleteEndpointConnectionsfunc 清理各个Stale Endpoints涉及到Conntrack记录,包括从NodePort到PodIP的记录,以及从ClusterIP、ExternalIP、LoadBalancerIP到PodIP。deleteEndpointConnections func使用conntrack

    conntrack -D -p UDP --dport {NodePort} --dst-nat {EndpointsIP}
    conntrack -D --orig-dst {OriginIP} --dst-nat {EndpointsIP} -p UDP
  • Ref:https://github.com/kubernetes/kubernetes/blob/88e994f6bf8fc88114c5b733e09afea339bea66d/pkg/proxy/iptables/proxier.go#L1539

    	// Finish housekeeping.
    // Clear stale conntrack entries for UDP Services, this has to be done AFTER the iptables rules are programmed.
    // TODO: these could be made more consistent.
    klog.V(4).InfoS("Deleting conntrack stale entries for services", "IPs", conntrackCleanupServiceIPs.UnsortedList())
    for _, svcIP := range conntrackCleanupServiceIPs.UnsortedList() {
    if err := conntrack.ClearEntriesForIP(proxier.exec, svcIP, v1.ProtocolUDP); err != nil {
    klog.ErrorS(err, "Failed to delete stale service connections", "IP", svcIP)
    }
    }
    klog.V(4).InfoS("Deleting conntrack stale entries for services", "nodePorts", conntrackCleanupServiceNodePorts.UnsortedList())
    for _, nodePort := range conntrackCleanupServiceNodePorts.UnsortedList() {
    err := conntrack.ClearEntriesForPort(proxier.exec, nodePort, isIPv6, v1.ProtocolUDP)
    if err != nil {
    klog.ErrorS(err, "Failed to clear udp conntrack", "nodePort", nodePort)
    }
    }
    klog.V(4).InfoS("Deleting stale endpoint connections", "endpoints", endpointUpdateResult.StaleEndpoints)
    proxier.deleteEndpointConnections(endpointUpdateResult.StaleEndpoints)
    }