切分 Pod 网段并设计插件链
目标
同时从节点对象和插件配置两方面确认 Pod 地址从哪里来,并亲手计算如何切分出互不重叠的网段。
为什么重要
新建集群时,最先要定下、也最难回头的就是地址网段。Pod、Service、节点这三种地址分别由不同的分配者负责,它们彼此并不商量,因此确保互不重叠是设计者的责任。此外,hostPort 或带宽限制这类功能,清单里写了,但没有插件就会被悄悄忽略;如果不先分清由谁负责什么,就会长时间困在“配置明明是对的,却不生效”的问题里。
步骤
- 把集群 Pod 网段
10.244.0.0/16切分成/24,从前往后依次固定给lab-node-0、lab-node-1、lab-node-2(spec.podCIDR和spec.podCIDRs两个字段都要设置)。然后在/root/k8nd-cni/01-podcidr.txt中按节点名称顺序写入三行노드이름 대역(占位符依次为节点名称与网段)。 - 在
/root/k8nd-cni/02-capacity.txt中写入五行——subnets=(把10.244.0.0/16切成/24时得到的分片数)、addresses_per_subnet=(每个分片的地址总数)、usable_per_subnet=(去掉网络地址和网关后的数量)、node_pod_limit=(节点对象中status.allocatable.pods的值)、binding_limit=(两者中先达到上限的一方,填写subnet或node)。 - 创建
/root/k8nd-cni/10-k8nd.conflist——cniVersion为1.0.0,name为k8nd-pod-network,plugins恰好三个,依次为bridge、portmap、bandwidth。bridge的ipam为host-local,subnet为lab-node-0的 Pod 网段,portmap的capabilities.portMappings必须为 true,bandwidth的capabilities.bandwidth必须为 true。 - 在
/root/k8nd-cni/shaped.yaml中编写 Pod 清单并应用——命名空间为k8nd-cni,名称为shaped,注解为kubernetes.io/ingress-bandwidth: 1M和kubernetes.io/egress-bandwidth: 1M,容器为web(nginx:1.27-alpine),containerPort和hostPort均为 8080。然后在/root/k8nd-cni/04-chain.txt中写入hostport_plugin=、bandwidth_plugin=、ingress_annotation=、egress_annotation=四行(插件填写第 3 步 conflist 中负责该功能的插件的type,注解填写实际添加的值)。 - 在
/root/k8nd-cni/edge-node.yaml中编写节点k8nd-cni-edge并应用——把spec.unschedulable设为 true,并在spec.taints中添加一个自定义的键k8nd.example.com/cni-missing(effect 为NoSchedule)。应用之后,在/root/k8nd-cni/05-notready.txt中写入node=、custom_taint=(你添加的键)、controller_taint=(控制器额外添加的内置污点键)、unschedulable=四行。 - 创建
/root/k8nd-cni/06-node1.conflist和/root/k8nd-cni/06-node2.conflist。它们的ipam.subnet分别使用固定给lab-node-1和lab-node-2的网段,ipam.gateway为该网段的第一个可用地址,ipam.routes中必须同时包含0.0.0.0/0和集群 Pod 网段10.244.0.0/16。ipam.type为host-local。 - 判断四种网段规划——p1 的 Pod 为
10.244.0.0/16、Service 为10.96.0.0/16、节点为192.168.10.0/24;p2 的 Pod 为10.96.0.0/12、Service 为10.96.0.0/16、节点为192.168.10.0/24;p3 的 Pod 为10.244.0.0/16、Service 为10.245.0.0/16、节点为10.244.3.0/24;p4 的 Pod 为172.16.0.0/16、Service 为10.96.0.0/16、节点为192.168.10.0/24。在/root/k8nd-cni/07-overlap.txt中写入从p1=到p4=的四行(值为ok或overlap),再加上rule=pod,service,node一行,共五行。 - 在
/root/k8nd-cni/08-report.md中写入cluster_pod_cidr=、per_node_prefix=、nodes_addressable=(该前缀能容纳的节点数)、notready_node=(第 5 步创建的节点名称)、chained_plugins=(第 3 步 conflist 中接在 bridge 之后的插件,用逗号分隔)五行,并在下面写至少四行以-开头的行。
参考
- 本环境中没有真正的容器运行时,因此 CNI 插件实际上不会被调用。Pod IP 也是模拟的,所以无法直接查看数据包或网络接口——评分改为检查你设计的内容能否被 API 接受,以及计算是否正确。
- 使用 Kubernetes 命令之前,请先执行一次
export KUBECONFIG=/root/.kube/config。 - 刚创建新命名空间时,默认 ServiceAccount 还不存在。请像
for i in $(seq 1 30); do kubectl -n <ns> get sa default >/dev/null 2>&1 && break; sleep 1; done这样等待之后,再创建 Pod。 - 节点的
spec.podCIDR只有在为空时才能写入值。一旦写错,该节点上就无法恢复,所以请先算完再写入。 - 常见错误——以为前缀越长,网段越大。其实
/12比/16更大。
为每个节点切分 Pod 网段
把集群 Pod 网段 10.244.0.0/16 切分成 /24,从前往后依次固定给 lab-node-0、lab-node-1、lab-node-2(spec.podCIDR 和 spec.podCIDRs 两个字段都要设置)。然后在 /root/k8nd-cni/01-podcidr.txt 中按节点名称顺序写入三行 노드이름 대역(占位符依次为节点名称与网段)。
Pod 地址不是由 API 服务器分配的。每个节点领取属于自己的网段,网络插件的 IPAM 再从中为 Pod 逐个分出地址。因此即使节点增加,地址管理也只在节点内部完成。用 kubectl patch node <이름> --type=merge -p '{"spec":{"podCIDR":"...","podCIDRs":["..."]}}'(占位符为节点名称)固定网段,再用 kubectl get node <이름> -o jsonpath='{.spec.podCIDR}'(占位符为节点名称)读回确认。值一旦写入,就不能改成别的值。
这个网段能容纳多少个 Pod
在 /root/k8nd-cni/02-capacity.txt 中写入五行——subnets=(把 10.244.0.0/16 切成 /24 时得到的分片数)、addresses_per_subnet=(每个分片的地址总数)、usable_per_subnet=(去掉网络地址和网关后的数量)、node_pod_limit=(节点对象中 status.allocatable.pods 的值)、binding_limit=(两者中先达到上限的一方,填写 subnet 或 node)。
统计地址和统计 Pod 是两回事。一个 /24 分片含有 256 个地址,但网络地址和网关各占掉一个。而节点接收的 Pod 数量由 kubelet 一侧的上限单独决定,通常先达到上限的是它,而不是地址。节点上限用 kubectl get node lab-node-0 -o jsonpath='{.status.allocatable.pods}' 读取。
串联插件配置
创建 /root/k8nd-cni/10-k8nd.conflist——cniVersion 为 1.0.0,name 为 k8nd-pod-network,plugins 恰好三个,依次为 bridge、portmap、bandwidth。bridge 的 ipam 为 host-local,subnet 为 lab-node-0 的 Pod 网段,portmap 的 capabilities.portMappings 必须为 true,bandwidth 的 capabilities.bandwidth 必须为 true。
在一个配置文件中写入多个插件的格式叫 conflist。插件从前往后依次被调用,后面的插件接收并调整前面插件产生的结果。因此,分配地址、开放 hostPort、限制带宽这三件事,由不同的插件分别负责。默认配置目录是 /etc/cni/net.d,二进制目录是 /opt/cni/bin。写完之后,用 jq . <파일>(占位符为文件名)检查语法。
hostPort 和带宽限制由谁负责
在 /root/k8nd-cni/shaped.yaml 中编写 Pod 清单并应用——命名空间为 k8nd-cni,名称为 shaped,注解为 kubernetes.io/ingress-bandwidth: 1M 和 kubernetes.io/egress-bandwidth: 1M,容器为 web(nginx:1.27-alpine),containerPort 和 hostPort 均为 8080。然后在 /root/k8nd-cni/04-chain.txt 中写入 hostport_plugin=、bandwidth_plugin=、ingress_annotation=、egress_annotation= 四行(插件填写第 3 步 conflist 中负责该功能的插件的 type,注解填写实际添加的值)。
Pod 规范中的 hostPort 和带宽注解都不是 API 服务器负责的事情,二者都由接在插件链上的插件读取并执行。所以,如果节点上没有安装该插件,清单虽然能通过,却什么都不会发生——这是一类被悄悄忽略的故障。用 kubectl apply -f 创建 Pod,再用 kubectl -n <ns> get pod shaped -o jsonpath='{.metadata.annotations}'(占位符为命名空间)读回注解。
没有插件,节点就会被整体挡住
在 /root/k8nd-cni/edge-node.yaml 中编写节点 k8nd-cni-edge 并应用——把 spec.unschedulable 设为 true,并在 spec.taints 中添加一个自定义的键 k8nd.example.com/cni-missing(effect 为 NoSchedule)。应用之后,在 /root/k8nd-cni/05-notready.txt 中写入 node=、custom_taint=(你添加的键)、controller_taint=(控制器额外添加的内置污点键)、unschedulable= 四行。
没有配置网络插件的节点无法接收任何 Pod。真正造成这种隔离的是污点,官方文档的内置污点列表中并列有 node.kubernetes.io/network-unavailable(网络不可用)和 node.kubernetes.io/unschedulable。这里有一个重要区别——由状况派生的内置污点由控制器管理。即使手动添加,只要状况不是那个状态,它就会立刻被清除;反过来,把 spec.unschedulable 设为 true 后,控制器会自动添加一个污点。所以人可以添加的只有使用自己键名的污点。不要假定列表中的第一项就是你写的那一个,请用 kubectl get node <이름> -o json | jq -r '.spec.taints[].key'(占位符为节点名称)全部确认。
为每个节点创建不同的 IPAM 配置
创建 /root/k8nd-cni/06-node1.conflist 和 /root/k8nd-cni/06-node2.conflist。它们的 ipam.subnet 分别使用固定给 lab-node-1 和 lab-node-2 的网段,ipam.gateway 为该网段的第一个可用地址,ipam.routes 中必须同时包含 0.0.0.0/0 和集群 Pod 网段 10.244.0.0/16。ipam.type 为 host-local。
host-local 正如其名,只在该节点内部管理地址,不会询问各节点互相分出去了什么——所以网段重叠时,两个节点会若无其事地分出相同的地址。切分出互不重叠的网段,是人或控制平面的责任。之所以要单独写出通往集群网段的路由,是因为走默认路由的话,Pod 之间的流量会被送出节点,而这里需要在节点内部处理。
重叠时,去向就无法确定
判断四种网段规划——p1 的 Pod 为 10.244.0.0/16、Service 为 10.96.0.0/16、节点为 192.168.10.0/24;p2 的 Pod 为 10.96.0.0/12、Service 为 10.96.0.0/16、节点为 192.168.10.0/24;p3 的 Pod 为 10.244.0.0/16、Service 为 10.245.0.0/16、节点为 10.244.3.0/24;p4 的 Pod 为 172.16.0.0/16、Service 为 10.96.0.0/16、节点为 192.168.10.0/24。在 /root/k8nd-cni/07-overlap.txt 中写入从 p1= 到 p4= 的四行(值为 ok 或 overlap),再加上 rule=pod,service,node 一行,共五行。
Kubernetes 官方文档明确规定,Pod、Service、节点这三种地址不能互相重叠。一旦重叠,先命中哪条规则取决于具体实现,这种差异会表现为“只有某些节点不行”。不要靠肉眼判断,请计算——一行 python3 -c "import ipaddress; print(ipaddress.ip_network('10.96.0.0/12').overlaps(ipaddress.ip_network('10.96.0.0/16')))" 就够了。前缀越短,网段越大,这一点很容易被忽略。
留下 Pod 网络设计备忘
在 /root/k8nd-cni/08-report.md 中写入 cluster_pod_cidr=、per_node_prefix=、nodes_addressable=(该前缀能容纳的节点数)、notready_node=(第 5 步创建的节点名称)、chained_plugins=(第 3 步 conflist 中接在 bridge 之后的插件,用逗号分隔)五行,并在下面写至少四行以 - 开头的行。
值要取自前面步骤创建的文件,而不是凭记忆填写。说明行中要写下下次设计集群时真正用得上的句子——比如“分配地址的是谁”“什么会被悄悄忽略”。