重学容器03: 使用CNI为Containerd容器添加网络能力
2021-04-28
在第2节中,我们部署了containerd,配置好了ctr和crictl这两个命令行工具,并且使用ctr启动了一个用于测试的redis容器,但是这个容器还不具备网络能力,只能在容器内部自己玩耍。 本节我们将尝试手工配置containerd与cni插件的集成,为容器加入基本的网络能力。
1.什么是CNI #
CNI (Container Network Interface)也是CNCF旗下的一个项目。CNI包含一些用于配置linux容器网络接口的规范、库,以及一些支持插件。CNI只关心容器创建时的网络分配,以及当容器被删除时已经分配网络资源的释放。 CNI作为容器网络的标准,使得各个容器管理平台可以通过相同的接口调用各种各样的网络插件来为容器配置网络。Kubernetes就内置了CNI并通过CNI配置网络。本小节只关注CNI的基本使用,关于CNI的具体内容先不展开学习。
2.部署安装CNI插件和工具 #
我们在前面那台安装了containerd的服务器上部署安装CNI插件,当前cni plugins项目的最新版本是0.9.1,安装下面展示的步骤进行安装:
1wget https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz
2
3mkdir -p /opt/cni/bin/
4mkdir -p /etc/cni/net.d
5
6tar -zxvf cni-plugins-linux-amd64-v0.9.1.tgz -C /opt/cni/bin/
经过上面的步骤,把cni插件这些可执行文件安装到了/opt/cni/bin
目录下:
1tree /opt/cni/bin/
2/opt/cni/bin/
3├── bandwidth
4├── bridge
5├── dhcp
6├── firewall
7├── flannel
8├── host-device
9├── host-local
10├── ipvlan
11├── loopback
12├── macvlan
13├── portmap
14├── ptp
15├── sbr
16├── static
17├── tuning
18├── vlan
19└── vrf
这些可执行文件从功能的角度可以划分为以下三类:
主插件: 用于创建网络设备
- bridge: 创建一个网桥设备,并添加宿主机和容器到该网桥
- ipvlan: 为容器添加ipvlan网络接口
- loopback: 设置lo网络接口的状态为up
- macvlan: 创建一个新的MAC地址,并将所有流量转发到容器
- ptp: 创建Veth对
- vlan: 分配一个vlan设备
- host-device: 将已存在的设备移入容器内
IPAM插件: 用于IP地址的分配
- dhcp: 在宿主机上运行dhcp守护程序,代表容器发出dhcp请求
- host-local: 维护一个分配ip的本地数据库
- static: 为容器分配一个静态IPv4/IPv6地址,主要用于调试
Meta插件: 其他插件,非单独使用插件
- flannel: flannel网络方案的CNI插件,根据flannel的配置文件创建网络接口
- tuning: 调整现有网络接口的sysctl参数
- portmap: 一个基于iptables的portmapping插件。将端口从主机的地址空间映射到容器
- bandwidth: 允许使用TBF进行限流的插件
- sbr: 一个为网络接口配置基于源路由的插件
- firewall: 过iptables给容器网络的进出流量进行一系列限制的插件
这里先对这些插件有一个基本的概念。
下面部署cnitool,由于https://github.com/containernetworking/cni
中不再提供cnitool的二进制,需要手工编译。
因测试服务器上没有按照go,这里在个人本地进行编译:
1git clone https://github.com/containernetworking/cni.git
2cd cni
3go mod tidy
4cd cnitool
5GOOS=linux GOARCH=amd64 go build .
将编译的cnitool可执行文件拷贝到服务器的/opt/cni/bin目录下。
1chmod +x /opt/cni/bin/cnitool
2ln -s /opt/cni/bin/cnitool /usr/local/bin/cnitool
3
4cnitool
5cnitool: Add, check, or remove network interfaces from a network namespace
6 cnitool add <net> <netns>
7 cnitool check <net> <netns>
8 cnitool del <net> <netns>
3.创建容器网络 #
创建containerd容器使用cni的配置文件:
1cat << EOF | tee /etc/cni/net.d/redisnet.conf
2{
3 "cniVersion": "0.4.0",
4 "name": "redisnet",
5 "type": "bridge",
6 "bridge": "cni0",
7 "isDefaultGateway": true,
8 "forceAddress": false,
9 "ipMasq": true,
10 "hairpinMode": true,
11 "ipam": {
12 "type": "host-local",
13 "subnet": "10.88.0.0/16"
14 }
15}
16EOF
创建一个名为redisnet的network namespace:
1ip netns add redisnet
2
3ip netns list
4redisnet
5
6ls /var/run/netns/
7redisnet
向这个network namespace中添加网络:
1export CNI_PATH=/opt/cni/bin
2cnitool add redisnet /var/run/netns/redisnet
3cnitool check redisnet /var/run/netns/redisnet
测试网络是否工作:
1ip -n redisnet addr
21: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1
3 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
43: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
5 link/ether be:73:a7:ae:18:7f brd ff:ff:ff:ff:ff:ff link-netnsid 0
6 inet 10.88.0.2/16 brd 10.88.255.255 scope global eth0
7 valid_lft forever preferred_lft forever
8 inet6 fe80::bc73:a7ff:feae:187f/64 scope link
9 valid_lft forever preferred_lft forever
10
11ping 10.88.0.2
4.启动带网络的容器 #
ctr run
命令在启动容器的时候可以使用--with-ns
选项让容器在启动时候加入到一个已经存在的一个linux namespace,这里加入的是起那么创建的redisnet这个网络namespace。
1ctr run --with-ns=network:/var/run/netns/redisnet -d docker.io/library/redis:alpine3.13 redis
进入到容器内部查看一下:
1ctr task exec -t --exec-id redis-sh redis sh
2/data # ip addr
31: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
43: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
5 link/ether be:73:a7:ae:18:7f brd ff:ff:ff:ff:ff:ff
6 inet 10.88.0.2/16 brd 10.88.255.255 scope global eth0
7 valid_lft forever preferred_lft forever
8 inet6 fe80::bc73:a7ff:feae:187f/64 scope link
9 valid_lft forever preferred_lft forever
这里使用的是CNI最简单的网络插件bridge,它先是在宿主机上创建一个网桥,然后通过veth pair连接该网桥到container的network namespace。
此时在宿主机上使用redis-cli
可以直接访问容器内的redis服务:
1redis-cli -h 10.88.0.2
210.88.0.2:6379>
还可以在系统的namespace中查询一下:
1lsns | grep net | grep redis
24026532171 net 1 14805 polkitd redis-server *:637
删除容器后,可以按照下面的步骤清理网络资源:
1export CNI_PATH=/opt/cni/bin
2cnitool del redisnet /var/run/netns/redisnet
3ip netns del redisnet
手动删除/var/lib/cni/networks和/var/lib/cni/results下的相关内容。