calico 学习笔记

Calico 是针对容器、虚机、物理服务器的网络解决方案

  • 提供 CNI plugin 与 k8s mesos 集成
  • 提供 Libnetwork plugin 与 docker 集成
  • 以 neutron plugin 形式与 openstack 集成
  • 支持 BGP、IPIP 两种方案

Flannel 方案中,master node 上的 flanneld 进程需要始终检测 k8s api 哪些资源发生了变化并同步 etcd,规模较大时,会造成压力。
calico 与 k8s 集成时,使用 BGP 缓解 k8s api 和 etcd 的压力;并且提供了 network policy,实现多个用户之间的网络隔离等。

k8s 集成时的基本架构

  • etcd 作为存储,可以共用 k8s,也可以使用独立的 etcd
  • master node:运行 calico-kube-controller pod 用来同步 k8s 数据和 etcd 数据
  • worker node:运行 calico-node ,配合 CNI plugin 工作来配置网络,这个容器中运行了四个进程
    • runsvdir 作为主进程,会检测并重启以下 4 个进程
    • Felix:核心组件
      1. 管理 calico 相关的网卡,处理arp、检测网卡状态等
      2. 配置 worker node 上的路由表
      3. 配置 network policy
      4. 上报节点状态
    • Brid:独立的项目,实现了 BGP 协议,运行两个进程,分别管理 ipv4 和 ipv6
    • confd:独立项目,用来从 etcd 同步配置文件,并重启相应的 bird 进程来时配置生效(自动化了 BGP 的配置)

calico 中使用的是 IBGP,部署反射器

安装

Pure BGP

以 IBGP 的方式传递 pod 的路由,可能会与物理网络地址空间冲突。

安装及配置

1
2
3
4
5
6
7
8
9
10
11
12
# 参考
https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises#install-calico-with-kubernetes-api-datastore-50-nodes-or-less
# 下载配置文件
curl https://docs.projectcalico.org/manifests/calico.yaml -O
# 修改配置文件,将 IPIP 选项改为 off,添加 pods CIDR
- name: CALICO_IPV4POOL_CIDR
value: "11.0.0.0/16"
- name: CALICO_IPV4POOL_IPIP
# value: "Always"
value: "off"
# 部署
kubectl apply -f calico.yaml

安装 calicoctl 并配置

  1. 下载 calicoctl,curl -x 可以配置代理服务器

    curl -O -L https://github.com/projectcalico/calicoctl/releases/download/v3.15.0/calicoctl

  2. 添加可执行权限(可以移动到系统变量目录中)

    1
    2
    chmod +x calicoctl
    mv ./calicoctl /usr/local/bin/
  3. 添加配置文件,让 calico 连接 k8s 的 etcd

    1
    2
    3
    4
    5
    6
    7
    8
    # more /etc/calico/calicoctl.cfg 
    apiVersion: projectcalico.org/v3
    kind: CalicoAPIConfig
    metadata:
    spec:
    datastoreType: "kubernetes"
    # 修改路径为当前 k8s 的配置路径
    kubeconfig: "/home/x/.kube/config"
  4. 获取当前 calico 节点

    1
    2
    3
    4
    x@x-vm:~$ calicoctl get nodes -o wide
    NAME ASN IPV4 IPV6
    worker (64512) 192.168.121.139/24
    x-vm (64512) 192.168.121.137/24
  5. 查看 BGP peer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    x@x-vm:~$ sudo calicoctl node status
    Calico process is running.
    IPv4 BGP status
    +-----------------+-------------------+-------+----------+-------------+
    | PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO
    |
    +-----------------+-------------------+-------+----------+-------------+
    | 192.168.121.139 | node-to-node mesh | up | 03:59:44 | Established |
    +-----------------+-------------------+-------+----------+-------------+
  6. 配置 BGP(默认已经有了,如上显示的 ASN 为 64512)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 通过 yaml 配置,vim calico_bgp.yml
    apiVersion: projectcalico.org/v3
    kind: BGPConfiguration
    metadata:
    name: default
    spec:
    logSeverityScreen: Info
    nodeToNodeMeshEnabled: true
    asNumber: 63400
    # 应用配置
    calicoctl create -f calico_bgp.yml
    # 查看 node,ASN 已经变为配置文件的
    x@x-vm:~$ calicoctl get nodes -o wide
    NAME ASN IPV4 IPV6
    worker (63400) 192.168.121.139/24
    x-vm (63400) 192.168.121.137/24

    基本原理

    veth pair 没有连接任何 Linuxbridge,所有数据转发依靠内核路由表查找;所有 pod 网络数据都直接走三层转发。

  • pod 内的网络
    pod 内只有 169.254.1.1 的默认路由,即访问任何数据都三层转发到 veth 设备。
    1
    2
    3
    4
    5
    6
    7
    / # ip route
    default via 169.254.1.1 dev eth0
    169.254.1.1 dev eth0 scope link
    / # arping 169.254.1.1
    ARPING 169.254.1.1 from 11.0.82.3 eth0
    Unicast reply from 169.254.1.1 [ee:ee:ee:ee:ee:ee] 0.015ms
    Unicast reply from 169.254.1.1 [ee:ee:ee:ee:ee:ee] 0.097ms
    如何转发?通过主机 arp proxy 来实现。
    pod 的 veth pair 一边连接 pod ,另一边挂在主机的网络命名空间,通过配置主机网络命名空间的 proxy 选项,为 pod 提供三层转发的 arp 应答,这样 pod 的数据包转发到了主机命名空间,挂在主机命名空间的网卡收到数据包后,calico 配置开启了这个网卡的 forwarding 选选项,这样就会查找主机的路由表。
    1
    2
    3
    4
    5
    6
    x@x-vm:~$ cat /proc/sys/net/ipv4/conf/cali99c376db89a/proxy_arp
    1
    x@x-vm:~$ cat /proc/sys/net/ipv4/neigh/cali99c376db89a/proxy_delay
    0
    x@x-vm:~$ cat /proc/sys/net/ipv4/conf/cali99c376db89a/forwarding
    1
    9299b099584c3923aa79801bb2991bb2.png

实操

分别在两个node上各创建一个临时的 pod

1
2
kubectl run -it --rm --restart=Never test1 --image busybox --overrides='{"apiVersion": "v1","spec": {"nodeSelector": {"kubernetes.io/hostname": "x-vm"}}}' sh 
kubectl run -it --rm --restart=Never test2 --image busybox --overrides='{"apiVersion": "v1","spec": {"nodeSelector": {"kubernetes.io/hostname": "worker"}}}' sh

在主机上 brctl show ,只有一个 docker0 网桥,未挂载任何网卡
81e0d93c767f93cfe28017f53230c565.png
在 pod 内发送 arp 请求,在 node 上抓包
e1dbf464487e10784a39f0020538cbc8.png
b39270e48c3b0f504d1b65d9f6c0f751.png
查看 master node 的路由表
170c6be1d931b2ab64b57a5107bdde6f.png
查看 worker node 的路由表
3885e04cc52b51af9d9bcde2688d0dfd.png
在 pod 上 traceroute 查看数据转发路径(跨 node )
8a374dbb35e40ab2eb25d2fa78605c47.png
在 pod 上 traceroute 查看转发路径(同 node)
d8e1cb9ffea64e07e4bba730f1594fe7.png

IPIP

Pure BGP 实现的时候,同一个 node 网卡出来的数据包会有多个源 IP,而且 pod 网络可能会与物理网络冲突。
Calico 使用 IP in IP 封装协议来实现,外层 IP Header 协议号是 4,也是一种隧道技术,但是封装简单,无法实现多租户(GRE 可以)。
实现方式与 flannel 类似,会新增一个 ipip 类型的 netdev 设备,路由表中将远端 node 的路由指向 ipip 设备,进行封装。
同 work node pod 通信,与 pure BGP 一样,直接通过主机明细通信;跨 work node 通信,多了一层 ipip 的封装。
ac657457ee5f50ef8d7efad6de5ddc08.png

安装配置

修改 calico 配置文件,打开 ipip 选项,可以不用重新部署,直接在之前 pure bgp 的基础之上,导出相关的配置文件进行修改。
将当前的 ipPool 配置导出,修改 ipip 选项为 always

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看当前的 ipPool 配置
x@x-vm:~$ calicoctl get ipPool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ipv4-ippool 11.0.0.0/16 true Never Never false all()
# 导出配置
calicoctl get ipPool -o yaml > calico_ipip.yaml
# 修改 ipip 选项
# vim calico_ipip.yaml
cidr: 11.0.0.0/16
ipipMode: Always
natOutgoing: true
# 应用配置
x@x-vm:~$ calicoctl apply -f calico_ipip.yaml
Successfully applied 1 'IPPool' resource(s)
# 再次查看 ipPool 选项,看到 ipip 已经打开
x@x-vm:~$ calicoctl get ipPool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ipv4-ippool 11.0.0.0/16 true Always Never false all()

查看网卡详细信息,新增了 tunnel 0,占用了一个 pod 地址,类型为 ipip,remote 为 any,即可以向任意远端发送数据包。

1
2
3
4
5
6
7
ip -d addr
...
10: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1440 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0
ipip any remote any local any ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 11.0.82.5/32 brd 11.0.82.5 scope global tunl0
valid_lft forever preferred_lft forever

实操

分别在两个node上各创建一个临时的 pod

1
2
kubectl run -it --rm --restart=Never test1 --image busybox --overrides='{"apiVersion": "v1","spec": {"nodeSelector": {"kubernetes.io/hostname": "x-vm"}}}' sh 
kubectl run -it --rm --restart=Never test2 --image busybox --overrides='{"apiVersion": "v1","spec": {"nodeSelector": {"kubernetes.io/hostname": "worker"}}}' sh

查看 pod 信息,分布在不同的 node 上
706bfc0afa48e5d3a053084326dd28b7.png
pod test1 去 ping test2,在物理网卡上抓包,指定 ip proto 4,可以看到封装的报文
34f2405eb443b6ed28c855034802c719.png
在 tunl0 上抓包,可以看到两个 pod 的原始数据包
093350cd412cb02140b7528b7ff30d93.png
pod test1 上 traceroute test2,可以看到路径,第一跳到了当前 node ,第二跳到了远端 node 的 tunl0,最终到达 test2
60c7f601cf9f8eef1e183f2cf8ee24aa.png