本文发布于Cylon的收藏册,转载请著名原文链接~


我们可以看到,kube-proxy 有一个 –cluster-cidr 的参数,我们就来解开这个参数究竟有没有用

$ kube-proxy -h|grep cidr
      --cluster-cidr string                          The CIDR range of pods in the cluster. When configured, traffic sent to a Service cluster IP from outside this range will be masqueraded and traffic sent from pods to an external LoadBalancer IP will be directed to the respective cluster IP instead

可以看到,参数说明是说,如果配置,那么从外部发往 Service Cluster IP 的流量将被伪装,从 Pod 发往外部 LB 将被直接发往对应的 cluster IP。但实际上做了什么并不知道,那么就从源码解决这个问题。

首先我们知道,参数是作为 kube-proxy server 的参数,位于 cmd/kube-proxy 下,而对应的逻辑则位于 pkg/kube-proxy 下,参数很明显,就是 clusterCIDR,那么我们就寻找这个参数的调用即可。

在 API KubeProxyConfiguration 中我们找到的对应的 ClusterCIDR ,在这里的注释又变为 ”用于桥接集群外部流量“。这里涉及到关于 kube-proxy 的两个模式 “LocalMode” 和 “ProxyMode“。

  • LocalMode:表示是来自节点本地流量的模式,包含 ClusterCIDR, NodeCIDR
  • ProxyMode:就是 kube-proxy 最常用的模式,包含 iptables, IPVS, user namespace, kernelspace

而参数 –cluster-cidr 是作为选择使用的 “本地网络检测器” (Local Network Detector),这里起到的作用就是 “将集群外部的流量伪装成 service VIP” ,从代码中我们可以看到 Detector 将决定了你使用的是什么网络,无论是 LocalMode 还是 ProxyMode

在代码 cmd/kube-proxy/app/server_others.go 中可以看到是如何选择的 LocalMode 方式,可以看出在存在三种模式:

  • 没有配置 –cluster-cidr 则会返回一个 NoOpLocalDetector
  • 在配置了 –cluster-cidr ,则将会使用 CIDR 的本地模式;
  • 如果 –cluster-cidr 没有配置,但配置了 LocalModeNodeCIDR,则会设置为 CNI 为该 Node 配置的 POD CIDR 的地址 (使用参数 –proxy-mode 指定的模式,如果为空,那么会检测对应操作系统默认 Linux 为 iptables,如果内核开启 IPVS 那么则使用 IPVS,windows 默认为 kernelspace)
func getLocalDetector(mode proxyconfigapi.LocalMode, config *proxyconfigapi.KubeProxyConfiguration, ipt utiliptables.Interface, nodeInfo *v1.Node) (proxyutiliptables.LocalTrafficDetector, error) {
	switch mode {
	case proxyconfigapi.LocalModeClusterCIDR:
		if len(strings.TrimSpace(config.ClusterCIDR)) == 0 {
			klog.Warning("detect-local-mode set to ClusterCIDR, but no cluster CIDR defined")
			break
		}
		return proxyutiliptables.NewDetectLocalByCIDR(config.ClusterCIDR, ipt)
	case proxyconfigapi.LocalModeNodeCIDR:
		if len(strings.TrimSpace(nodeInfo.Spec.PodCIDR)) == 0 {
			klog.Warning("detect-local-mode set to NodeCIDR, but no PodCIDR defined at node")
			break
		}
		return proxyutiliptables.NewDetectLocalByCIDR(nodeInfo.Spec.PodCIDR, ipt)
	}
	klog.V(0).Info("detect-local-mode: ", string(mode), " , defaulting to no-op detect-local")
	return proxyutiliptables.NewNoOpLocalDetector(), nil
}

这里我们以 IPVS 为例,如果开启了 localDetector 在 这个 ipvs proxier 中做了什么? 在代码 pkg/proxy/ipvs/proxier.go 可以看到

	if !proxier.ipsetList[kubeClusterIPSet].isEmpty() {
		args = append(args[:0],
			"-A", string(kubeServicesChain),
			"-m", "comment", "--comment", proxier.ipsetList[kubeClusterIPSet].getComment(),
			"-m", "set", "--match-set", proxier.ipsetList[kubeClusterIPSet].Name,
		)
		if proxier.masqueradeAll {
			writeLine(proxier.natRules, append(args, "dst,dst", "-j", string(KubeMarkMasqChain))...)
		} else if proxier.localDetector.IsImplemented() {
			// This masquerades off-cluster traffic to a service VIP.  The idea
			// is that you can establish a static route for your Service range,
			// routing to any node, and that node will bridge into the Service
			// for you.  Since that might bounce off-node, we masquerade here.
			// If/when we support "Local" policy for VIPs, we should update this.
			writeLine(proxier.natRules, proxier.localDetector.JumpIfNotLocal(append(args, "dst,dst"), string(KubeMarkMasqChain))...)
		} else {
			// Masquerade all OUTPUT traffic coming from a service ip.
			// The kube dummy interface has all service VIPs assigned which
			// results in the service VIP being picked as the source IP to reach
			// a VIP. This leads to a connection from VIP:<random port> to
			// VIP:<service port>.
			// Always masquerading OUTPUT (node-originating) traffic with a VIP
			// source ip and service port destination fixes the outgoing connections.
			writeLine(proxier.natRules, append(args, "src,dst", "-j", string(KubeMarkMasqChain))...)
		}
	}

可以看到“不管使用了什么模式,都会更新一条 iptables 规则” 这就代表了使用了什么模式,而这个则被称之为 LocalTrafficDetector,也就是本地流量的检测,那我们看一下这个做了什么。

在使用 IPVS 的日志中,可以看到这样一条规则,这个是来自集群外部的 IP 去访问集群 CLUSTER IP (KUBE-CLUSTER-IP,即集群内所有 service IP) 时, 将非集群 IP 地址,转换为集群内的 IP 地址 (做源地址转换)

[DetectLocalByCIDR (10.244.0.0/16)] Jump Not Local: [-A KUBE-SERVICES -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst ! -s 10.244.0.0/16 -j KUBE-MARK-MASQ]

而这个步骤分布在所有模式下 (iptables&ipvs),这里还是没说到两个概念 LocalModeProxyMode,实际上这两个模式的区别为:

  • LocalMode:集群 IP 伪装采用 ClusterCIDR 还是 NodeCIDRClusterCIDR 是使用集群 Pod IP 的地址段 (IP Range),而 LocalCIDR 只仅仅使用被分配给该 kubernetes node 上的 Pod 做地址伪装
  • ProxyMode:和 LocalMode 没有任何关系,是 kube-proxy 在运行时使用什么为集群 service 做代理,例如 iptables, ipvs ,而在这些模式下将采用什么 LocalMode 为集群外部地址作伪装,大概分为三种类型:
    • 为来自集群外部地址 (cluster-off):所有非 Pod 地址的请求执行跳转 (KUBE-POSTROUTING)
    • 没有操作 :在非 iptables/ipvs 模式下,不做伪装
    • masqueradeAll:为所有访问 cluster ip 的地址做伪装

ClusterCIDR 原理

kube-proxy 为 kube node 上生成一些 NAT 规则,如下所示

-A KUBE-FIREWALL -j KUBE-MARK-DROP
-A KUBE-LOAD-BALANCER -j KUBE-MARK-MASQ
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-NODE-PORT -p tcp -m comment --comment "Kubernetes nodeport TCP port for masquerade purpose" -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ
-A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE
-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
-A KUBE-SERVICES ! -s 10.244.0.0/16 -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose" -m set --match-set KUBE-CLUSTER-IP dst,dst -j KUBE-MARK-MASQ
-A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODE-PORT
-A KUBE-SERVICES -m set --match-set KUBE-CLUSTER-IP dst,dst -j ACCEPT

可以看到这里做了几个链,在 KUBE-SERVICES 链中指明了非来自 ClusterCIDR 的 IP 都做一个,并且访问的目的地址是 KUBE-CLUSTER-IP (ipset 里配置的地址) 那么将跳转到 KUBE-MARK-MASQ 链做一个 --set-xmark 0x4000/0x4000 ,而在 KUBE-POSTROUTING 中对没有被标记 0x4000/0x4000 的操作不做处理

具体来说,-A KUBE-NODE-PORT -p tcp -m comment --comment "Kubernetes nodeport TCP port for masquerade purpose" -m set --match-set KUBE-NODE-PORT-TCP dst -j KUBE-MARK-MASQ 做了如下操作:

  • -A KUBE-SERVICES:将这条规则附加到名为KUBE-SERVICES的iptables链。
  • ! -s 10.244.0.0/16:排除源IP地址为10.244.0.0/16的流量(即来自Kubernetes服务集群IP的流量)。
  • -m comment --comment "Kubernetes service cluster ip + port for masquerade purpose":添加一条注释,说明这个规则的用途。
  • -m set --match-set KUBE-CLUSTER-IP dst,dst:使用IP集合KUBE-CLUSTER-IP来匹配目标IP地址和目标端口。
  • -j KUBE-MARK-MASQ:如果流量匹配了前面的条件,将流量传递到名为KUBE-MARK-MASQ的目标。

iptables -j RETURN 是用于iptables规则中的一个目标动作,它不是用于拒绝或接受数据包的动作,而是用于从当前规则链中返回(返回到调用链)的动作。

具体来说,当规则链中的数据包被标记为 RETURN 时,它们将不再受到当前链中后续规则的影响,而会立即返回到调用链,以便继续进行后续规则的处理。这通常用于某些高级设置,例如在自定义规则链中执行特定的操作后返回到主要的防火墙链。

从代码中可以看到,对应执行 jump 的操作的链就是 KUBE-MARK-MASQ

} else if proxier.localDetector.IsImplemented() {
			// This masquerades off-cluster traffic to a service VIP.  The idea
			// is that you can establish a static route for your Service range,
			// routing to any node, and that node will bridge into the Service
			// for you.  Since that might bounce off-node, we masquerade here.
			// If/when we support "Local" policy for VIPs, we should update this.
			writeLine(proxier.natRules, proxier.localDetector.JumpIfNotLocal(append(args, "dst,dst"), string(KubeMarkMasqChain))...)	

// KubeMarkMasqChain is the mark-for-masquerade chain
KubeMarkMasqChain utiliptables.Chain = "KUBE-MARK-MASQ"
    
// 具体拼接的就是 -j 链名的操作
func (d *detectLocalByCIDR) JumpIfNotLocal(args []string, toChain string) []string {
	line := append(args, "!", "-s", d.cidr, "-j", toChain)
	klog.V(4).Info("[DetectLocalByCIDR (", d.cidr, ")]", " Jump Not Local: ", line)
	return line
}

继续往下 KUBE-POSTROUTING 可以看到对应伪装是一个动态的源地址改造,而 RETURN 则不是被标记的请求

Chain KUBE-POSTROUTING (1 references)
target     prot opt source               destination         
MASQUERADE  all  --  0.0.0.0/0            0.0.0.0/0            /* Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose */ match-set KUBE-LOOP-BACK dst,dst,src
RETURN     all  --  0.0.0.0/0            0.0.0.0/0            mark match ! 0x4000/0x4000
MARK       all  --  0.0.0.0/0            0.0.0.0/0            MARK xor 0x4000
MASQUERADE  all  --  0.0.0.0/0            0.0.0.0/0            /* kubernetes service traffic requiring SNAT */

这整体就是 ClusterCIDR 在 kube-proxy 中的应用,换句话说还需要关注一个 LocalCIDR

本文发布于Cylon的收藏册,转载请著名原文链接~

链接:https://www.oomkill.com/2023/09/ch26-kube-proxy-clustercidr/

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」 许可协议进行许可。