Calico IP地址管理
Kubernetes 使用 IPAM 插件管理 POD 的 IP 地址信息,默认情况下 containernetworking/plugins 项目中提供的 host-local 作为 IPAM 工具,该工具存储 POD IP 地址在每个节点的 /var/lib/cni/networks/$NETWORK_NAME 并且支持配置多个子网、路由、resolvConf 等内容。
Calico 支持和 host-local 一起工作,但提供了专用的 calico-ipam 插件,从配置 /etc/cni/net.d/10-calico.conflist 中可以看出默认情况下 calico 使用的是 calico-ipam 。
1. IP Pool
calico-ipam 从一个指定 IPPool 中为 POD 分配 IP 地址,默认情况下 Cailco 会自动创建 ip 地址池 ippools/default-ipv4-ippool ,用户没有特殊配置时所有 Pod 的 ip 地址从该池中分配:
1
2
3
4
5
6
7
8
9
10
11
|
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: default-ipv4-ippool
spec:
blockSize: 28
cidr: 172.31.253.0/24
ipipMode: Never
natOutgoing: true
nodeSelector: all()
vxlanMode: CrossSubnet
|
通过 Mainifest 进行安装时,我们可以通过注入环境变量指定默认 IPPOOL 的相关配置(参考):
环境变量 |
默认值 |
说明 |
CALICO_IPV4POOL_CIDR |
192.168.0.0/16, 172.16.0.0/16, .., 172.31.0.0/16 |
默认地址池的CIDR,当定义了环境变量NO_DEFAULT_POOLS时,该配置无效 |
CALICO_IPV4POOL_BLOCK_SIZE |
26 |
每个节点在 ippool 中分配的默认地址块 |
CALICO_IPV4POOL_IPIP |
Always |
该 ippool 使用 ipip 模式,Always, CrossSubnet, Never(Off) |
CALICO_IPV4POOL_VXLAN |
Never |
该 ippool 使用 vxlan 模式,Always, CrossSubnet, Never(Off) |
CALICO_IPV4POOL_NAT_OUTGOING |
true |
设置为 false 时,pod 访问集群之外的服务不进行 nat,即禁止集群外的流量 |
CALICO_IPV4POOL_NODE_SELECTOR |
all() |
设置 ippool 作用的容器范围,node-selector 配置参考 |
CALICO_IPV6POOL_BLOCK_SIZE |
<a randomly chosen /48 ULA> |
同 ipv4 同名配置 |
CALICO_IPV6POOL_BLOCK_SIZE |
122 |
同 ipv4 同名配置 |
CALICO_IPV6POOL_VXLAN |
Never |
同 ipv4 同名配置 |
CALICO_IPV6POOL_NAT_OUTGOING |
false |
同 ipv4 同名配置 |
CALICO_IPV6POOL_NODE_SELECTOR |
all() |
同 ipv4 同名配置 |
NO_DEFAULT_POOLS |
false |
禁止 calico 创建默认 ippool |
calico-ipam 相比 host-local 的最大优点包括:
- 支持动态的定义多个 ippool
- 支持将 ippool 作用范围和 Node、Namespace、Pod 进行关联
- ippool 中 spec.nodeSelector 可以关联不同 Node 到不同 ippool,此时运行在不同 Node 上的 pod 将从不同的 ippool 中获取 ip 地址(assign-ip-addresses-topology)
- 通过在 Namespace 中添加注解,此时该命名空间中的 Pod 都将从指定 IPPool 中获取 ip,如:
"cni.projectcalico.org/ipv4pools": "[\"poolname\"]"
- 通过在 Pod 中添加注解,可以实现从指定 ippool 中分配 IP
Calico 支持使用以下注解进行 IP 地址的管理,参考:
- cni.projectcalico.org/ipv4pools:指定 ipv4 地址的 ippool,可以指定多个
- cni.projectcalico.org/ipv6pools:指定 ipv6 地址的 ippool,可以指定多个
- cni.projectcalico.org/ipAddrs:指定 ip 地址,但是指定的 IP 地址需要属于某个 ippool,且没有被分配?
- cni.projectcalico.org/ipAddrsNoIpam: 指定 ip 地址,并且绕过 ipam 插件的检查(可以认为是配置静态 ip)
- cni.projectcalico.org/floatingIPs:指定浮动 ip
2. IP BlockSize
calico-ipam 和 host-local 类似,同样将 ippool 中的 cidr 划分成多个 ipamblocks ,默认情况下每个 ipamblocks 的掩码长度是 26 ,每个 Node 会和一个或者多个 ipamblocks 关联。
当 pod 调度到指定 Node 时,calico-ipam 会尽可能从该节点关联的 ipamblocks 中分配 ip 地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
apiVersion: crd.projectcalico.org/v1
kind: IPAMBlock
metadata:
name: 172-31-253-128-27
spec:
affinity: host:node1 # 关联的主机名称
allocations: [0,1,null,null...] # IP 地址槽,已经分配的地址通过数字表示、未分配的通过 null 表示
unallocated: [2,3...] # 未分配的地址槽]
attributes: # 分配的 IP 地址详情
- handle_id: vxlan-tunnel-addr-node1 # 第一个 IP 通常分配给 ipip/vxlan 等网关设备
secondary:
node: node1
type: vxlanTunnelAddress
- handle_id: k8s-pod-network.b3250f3b6a6ab9bfd318ad67fc11ccacb02e2eb0e74d86ffdd220f32736242bb
secondary:
namespace: kube-system
node: node1
pod: coredns-5798446d7b-wghzp
timestamp: 2022-07-25 08:05:16.226345746 +0000 UTC
cidr: 172.31.253.128/27
deleted: false
strictAffinity: false
|
calico-ipam 划分 ipamblocks 的目的是:将在同一个 Node 上运行的 Pod 分配同一个子网中,以此减小主机上 route 表的数量。
- 某个节点的 ipamblocks 的资源耗尽,calico 会自动为该节点关联一个新的 ipamblocks
- 某个节点的 ipamblocks 的资源耗尽,并且没有未分配 ipamblocks 时,calico 可以从其他节点的 ipamblocks 中分配地址
为了验证上述特性,我们进行以下实验:
- 在 2 节点 kubernetes 环境中,创建第二个子网 cidr 为 10.10.10.0/29 ,blockSize 为 30
1
2
3
4
5
6
7
8
9
10
11
12
|
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
generation: 1
name: yoaz
spec:
blockSize: 30
cidr: 10.10.10.0/29
ipipMode: Never
natOutgoing: true
nodeSelector: all()
vxlanMode: Always
|
- 创建在每个节点上创建一个 Deployment()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: yoaz-<nodename>
spec:
replicas: 1
selector:
matchLabels:
app: yoaz
template:
metadata:
annotations:
"cni.projectcalico.org/ipv4pools": "[\"yoaz\"]"
labels:
app: yoaz
spec:
nodeSelector:
kubernetes.io/hostname: <nodename>
containers:
- command: ["/bin/sh", "-c", "trap : TERM INT; sleep infinity & wait"]
image: rcos-dockerimage_centos:v2.0.4
imagePullPolicy: IfNotPresent
name: centos
|
- 执行 calicoctl ipam show –show-blocks 查看 ip 分配情况,可以看到 10.10.10.0/29 被分成了 2 个 ipamblocks ,并且关联到不同的节点,此时 ippool 中已经没有多余 ipamblocks
1
2
3
4
5
6
7
8
9
10
|
+----------+-------------------+-----------+------------+-----------+
| GROUPING | CIDR | IPS TOTAL | IPS IN USE | IPS FREE |
+----------+-------------------+-----------+------------+-----------+
| IP Pool | 172.31.253.0/24 | 256 | 6 (2%) | 250 (98%) |
| Block | 172.31.253.128/27 | 32 | 5 (16%) | 27 (84%) |
| Block | 172.31.253.160/27 | 32 | 1 (3%) | 31 (97%) |
| IP Pool | 10.10.10.0/29 | 8 | 1 (12%) | 7 (88%) |
| Block | 10.10.10.0/30 | 4 | 1 (25%) | 3 (75%) | # 关联到节点 node1
| Block | 10.10.10.4/30 | 4 | 1 (0%) | 4 (100%) | # 关联到节点 node2
+----------+-------------------+-----------+------------+-----------+
|
- 扩容其中一个节点的 deploy 的副本书为 5 ,使 ipamblocks 耗尽。此时所有新增加的 pod 都能正常获取 ip ,yoaz-node2-77c466f757-7grbm 运行在 node2 节点,但是其 ip 地址为 10.10.10.2,该 IP 从 10.10.10.0/30 分配,而这个 ipamblocks 实际上和 node1 关联。
1
2
3
4
5
6
7
8
9
10
|
kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE
yoaz-node1-56677c6496-c662m 1/1 Running 0 10m 10.10.10.1 node1
yoaz-node2-77c466f757-56dk2 1/1 Running 0 4s 10.10.10.4 node2
yoaz-node2-77c466f757-5l4nr 1/1 Running 0 4s 10.10.10.6 node2
yoaz-node2-77c466f757-7grbm 1/1 Running 0 4s 10.10.10.2 node2 # 特殊 POD
yoaz-node2-77c466f757-d6lfh 1/1 Running 0 4s 10.10.10.7 node2
yoaz-node2-77c466f757-n4fmp 1/1 Running 0 10m 10.10.10.5 node2
|
- 查看两个节点的路由信息: node1 的路由表中,专门为 10.10.10.2/32 增加了独立路由,并通过 vxlan.calico 直接转发到 node2 ,node2 中同样为 10.10.10.2/32 增加了独立路由并转发到本地容器对应的 veth 。需要注意的是:node1 中对路由表的顺序有一定要求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
节点 node1
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.28.112.1 0.0.0.0 UG 800 0 0 rcosmgmt
10.10.10.1 0.0.0.0 255.255.255.255 UH 0 0 0 cali9a29d83d8a0
10.10.10.2 172.31.253.160 255.255.255.255 UGH 0 0 0 vxlan.calico # 独立 Route 信息
10.10.10.4 172.31.253.160 255.255.255.252 UG 0 0 0 vxlan.calico
节点 node2
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.28.112.1 0.0.0.0 UG 800 0 0 rcosmgmt
10.10.10.0 172.31.253.128 255.255.255.252 UG 0 0 0 vxlan.calico
10.10.10.2 0.0.0.0 255.255.255.255 UH 0 0 0 cali459ad9c24e2 # 独立 Route 信息
10.10.10.4 0.0.0.0 255.255.255.255 UH 0 0 0 califad72e995d4
10.10.10.5 0.0.0.0 255.255.255.255 UH 0 0 0 calic07dc2fb526
10.10.10.6 0.0.0.0 255.255.255.255 UH 0 0 0 cali511a020a7d4
10.10.10.7 0.0.0.0 255.255.255.255 UH 0 0 0 calibdd00a1c9e8
|
当创建多个 ippool 时,如果使用的 tunnel 方案(ipip or vxlan),那么是可以共用的 tunnel 设备的。换句话说,上述实验中创建的 ippools/yoaz 并没有分配一个独立 ip 作为 tunnel 设备的地址,而是复用了 ippools/default-ipv4-ippool 中创建的 vxlanTunnelAddress ,其路由地址如下:
1
2
3
4
5
|
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.28.112.1 0.0.0.0 UG 800 0 0 rcosmgmt
10.10.10.0 172.31.253.128 255.255.255.252 UG 0 0 0 vxlan.calico # 172.31.253.128是对端vxlan.calico设备的ip地址
10.10.10.5 0.0.0.0 255.255.255.255 UH 0 0 0 calic07dc2fb526
|
需要注意 ippool 不能通过直接修改 blockSize 改变 ipamblocks 的长度,如果 calico 已经部署完成,需要遵循官方给出的步骤变更。
https://projectcalico.docs.tigera.io/networking/change-block-size
3. Floating IP
Floating IP 是 IaaS 架构中提出的概念,只在工作负载不知情的情况的下为其提供 external IP,使集群之外的服务可以通过 external IP 访问工作负载。目前绝大多数公有云虚拟机的公网 IP 都是通过 Floating IP 方式实现的。 Calico 支持在 Kubernetes/OpenStack 中为 Workload 设置 Floating IP。
在 Kubernetes 场景中,Calico Floating IP 和 Kubernetes Service 相似,基于 DNAT/SNAT 工作。用户可以定义特定的 IPPOOL ,并通过注解将其中的 IP 关联到 POD 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
name: floating-ippool
spec:
blockSize: 30
cidr: 10.10.10.0/29
natOutgoing: true
nodeSelector: "!all()" # 通过 nodeSelector 预留整个 IPPOOL
---
apiVersion: v1
kind: Pod
metadata:
name: centos-floating-ip
annotations:
cni.projectcalico.org/floatingIPs: "[\"10.10.10.2\"]"
spec:
containers:
- command:
- /bin/sh
- -c
- 'trap : TERM INT; sleep infinity & wait'
image: rcos-dockerimage_centos:v2.0.4
imagePullPolicy: IfNotPresent
name: centos
|
calico 在运行 Pod 的物理机上,为 Floating IP 添加了额外的 DNAT/SNAT 规则
1
2
3
4
5
6
7
|
# cali-fip-dnat 安装在 cali-OUTPUT 和 cali-PREROUTING
-A cali-fip-dnat -d 10.10.10.2/32 -m comment --comment "cali:utJYcbVCl62obave" -j DNAT --to-destination 172.31.253.162
# cali-fip-snat 安装在 cali-POSTROUTING
-A cali-fip-snat -s 172.31.253.162/32 -d 172.31.253.162/32 -m comment --comment "cali:-JAgnyKh1gAKBkh7" -j SNAT --to-source 10.10.10.2 --random-fully
PS:由于容器内和物理 iptable 命令行工具存在版本差异,物理上查看的 rule 可能不完整。
|
cali-fip-dnat 规则保证外部 Client 能以 FloatingIP 访问目标容器,由于这里往返流量都经过物理机,所以无需进行 SNAT(类似 Kubernetes Service 的 externalTrafficPolicy = Local 模式)。
cali-fip-snat 的存在是为了在配置了 Floating IP 的容器中,可以正常访问 Floating IP 。
最后,Floating IP 只在 BGP 模式下使用,VXLAN 模式下 Floating IP 无法正常工作 【参考 issues/519】 。
4. IP 地址预留
通过 IPReservation 可以预留 IPPool 的指定 ip 不被自动分配给容器(参考)。
1
2
3
4
5
6
7
8
9
|
apiVersion: projectcalico.org/v3
kind: IPReservation
metadata:
name: my-ipreservation-1
spec:
reservedCIDRs:
- 192.168.2.3
- 10.0.2.3/32
- cafe:f00d::/123
|
通过注解可以强制分配预留的 IP。