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。