kube-proxy参数ClusterCIDR做什么

> 本篇文章发布于[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: to
            // 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/),转载请声明出处哦~

你可能感兴趣的:(1024程序员节)