> 本篇文章发布于[cylon的收藏册](https://cylonchau.github.io/),转载请声明出处哦~
>
我们可以看到,*kube-proxy* 有一个 *--cluster-cidr* 的参数,我们就来解开这个参数究竟有没有用
```bash
$ 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](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)
```go
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](pkg/proxy/ipvs/proxier.go) 可以看到
```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:
// VIP:
// 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 地址 (做源地址转换)
```bash
[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),这里还是没说到两个概念 ***LocalMode*** 和 ***ProxyMode***,实际上这两个模式的区别为:
- **LocalMode**:集群 IP 伪装采用 *ClusterCIDR* 还是 *NodeCIDR*,*ClusterCIDR* 是使用集群 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 规则,如下所示
```bash
-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*
```go
} 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* 则不是被标记的请求
```go
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://cylonchau.github.io/),转载请声明出处哦~