K8s-kube-proxy(iptables代理模式执行流程)
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列表。
-
...
// 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链和规则
在开始阶段,kube-proxy创建最基础的iptables链,以
KUBE-
开头kube-proxy创建原始iptables链(PREEOUTING、POSTROUTING链)向第一步基础的链跳转的规则
-
// 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链写入对应的缓冲区
-
// 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标记,则不继续进行后续校验 |
MasqueradeMark生成的方式如下:
masqueradeBit对应的是
--iptables-masquerade-bit
中的启动参数-
masqueradeValue := 1 << uint(masqueradeBit)
masqueradeMark := fmt.Sprintf("%#08x", masqueradeValue)
配置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,就不在需要处理-
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规则配置流程:
- 在KUBE-SERVICE等基础上添加KUBE-SVC-XXX、KUBE-SVL-XXX、KUBE-EXT-XXX、KUBE-FW-XXX等链等跳转规则,从数据包从统一的基础KUBE链引导到各个ServicePort自己的链上
- 根据内外部流量策略,在KUBE-SVC-XXX、KUBE-SVL-XXX、KUBE-EXT-XXX、KUBE-FW-XXX等链等配置跳转规则和过滤操作,根据Service的配置来过滤数据包以及对数据包进行标记以后后续今昔SNAT等操作
- 根据Service后端Endpoints配置KUBE-SEP-XXX链,对数据包进行DNAT操作
为每个ServicePort关联的Endpoints创建的iptables配置流程:
根据内外部流量分类统计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
-
// 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)
配置ClusterIP的iptabels规则
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链
如果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
配置了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
配置了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
配置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是配置的健康检查端口号
配置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
配置KUBE-EXT链
如果Service包含NodePort、ExternalIP或LoadBalancerIP,并且在集群范围内有Service的Ready的Endpoints,则kube-proxy会配置KUBE-EXT-XXX链。进入该链的数据包根据Service的外部流程策略,会进一步跳转KUBE-SVC-XXX或KUBE-SVL-XXX链
kube-proxy向natChain缓冲区写入KUBE-EXT-XXX链
:KUBE-EXT-XXX - [0:0]
根据外部流程策略,向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
配置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被丢弃
配置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的过程如下:
-
// 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)
}
}
-
配置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 |
将iptables缓冲区内容刷新新到宿主机
kube-proxy完成了所有的iptables链和规则整理,接下来需要把这些链和规则写入宿主节点的iptables
在前面的iptables链和跳转规则都放去了filterChains、filterRules、natChains、natRules四个缓冲区。首先四个缓存区拼成一起,放入iptabesData缓冲区
-
// Sync rules.
proxier.iptablesData.Reset()
proxier.iptablesData.WriteString("*filter\n")
proxier.iptablesData.Write(proxier.filterChains.Bytes())
proxier.iptablesData.Write(proxier.filterRules.Bytes())
proxier.iptablesData.WriteString("COMMIT\n")
proxier.iptablesData.WriteString("*nat\n")
proxier.iptablesData.Write(proxier.natChains.Bytes())
proxier.iptablesData.Write(proxier.natRules.Bytes())
proxier.iptablesData.WriteString("COMMIT\n")
清理残留的UDP Conntrack记录
完成了iptabels规则更新,开始清理Stale的UDP协议的Conntrack记录
清理conntrackCleaningupServiceIPs记录的IP地址涉及到Conntrack记录。
conntrackCleaningupServiceIPs中记录的是Stale Service的ClusterIP、ExternalIP、LoadBalancerIP
conntrack -D --orig-dst {OriginIP} -p UDP
其中,OriginIP为conntrackCleaningupServiceIPs中记录的是IP地址
清理conntrackCleaningupServiceIPs中记录的NodePort涉及Conntrack记录。
ClearEntriesForPort func如下:
conntrack -D -p UDP --dport {NodePort}
其中,{NodePort}为conntrackCleanupServiceNodePorts中记录的NodePort
调用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
-
// 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)
}