在第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,安装下面展示的步骤进行安装:

1
2
3
4
5
6
wget https://github.com/containernetworking/plugins/releases/download/v0.9.1/cni-plugins-linux-amd64-v0.9.1.tgz

mkdir -p /opt/cni/bin/
mkdir -p /etc/cni/net.d

tar -zxvf cni-plugins-linux-amd64-v0.9.1.tgz -C /opt/cni/bin/

经过上面的步骤,把cni插件这些可执行文件安装到了/opt/cni/bin目录下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
tree /opt/cni/bin/
/opt/cni/bin/
├── bandwidth
├── bridge
├── dhcp
├── firewall
├── flannel
├── host-device
├── host-local
├── ipvlan
├── loopback
├── macvlan
├── portmap
├── ptp
├── sbr
├── static
├── tuning
├── vlan
└── 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,这里在个人本地进行编译:

1
2
3
4
5
git clone https://github.com/containernetworking/cni.git
cd cni
go mod tidy
cd cnitool
GOOS=linux GOARCH=amd64 go build .

将编译的cnitool可执行文件拷贝到服务器的/opt/cni/bin目录下。

1
2
3
4
5
6
7
8
chmod +x /opt/cni/bin/cnitool
ln -s /opt/cni/bin/cnitool /usr/local/bin/cnitool

cnitool
cnitool: Add, check, or remove network interfaces from a network namespace
  cnitool add   <net> <netns>
  cnitool check <net> <netns>
  cnitool del   <net> <netns>

3.创建容器网络

创建containerd容器使用cni的配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
cat << EOF | tee /etc/cni/net.d/redisnet.conf
{
    "cniVersion": "0.4.0",
    "name": "redisnet",
    "type": "bridge",
    "bridge": "cni0",
    "isDefaultGateway": true,
    "forceAddress": false,
    "ipMasq": true,
    "hairpinMode": true,
    "ipam": {
        "type": "host-local",
        "subnet": "10.88.0.0/16"
    }
}
EOF

创建一个名为redisnet的network namespace:

1
2
3
4
5
6
7
ip netns add redisnet

ip netns list
redisnet

ls /var/run/netns/
redisnet

向这个network namespace中添加网络:

1
2
3
export CNI_PATH=/opt/cni/bin
cnitool add redisnet /var/run/netns/redisnet
cnitool check redisnet /var/run/netns/redisnet

测试网络是否工作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ip -n redisnet addr
1: 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
3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether be:73:a7:ae:18:7f brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.88.0.2/16 brd 10.88.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::bc73:a7ff:feae:187f/64 scope link
       valid_lft forever preferred_lft forever

ping 10.88.0.2

4.启动带网络的容器

ctr run命令在启动容器的时候可以使用--with-ns选项让容器在启动时候加入到一个已经存在的一个linux namespace,这里加入的是起那么创建的redisnet这个网络namespace。

1
ctr run --with-ns=network:/var/run/netns/redisnet -d docker.io/library/redis:alpine3.13 redis

进入到容器内部查看一下:

1
2
3
4
5
6
7
8
9
ctr task exec -t --exec-id redis-sh redis sh
/data # ip addr
1: 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
3: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether be:73:a7:ae:18:7f brd ff:ff:ff:ff:ff:ff
    inet 10.88.0.2/16 brd 10.88.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::bc73:a7ff:feae:187f/64 scope link
       valid_lft forever preferred_lft forever

这里使用的是CNI最简单的网络插件bridge,它先是在宿主机上创建一个网桥,然后通过veth pair连接该网桥到container的network namespace。 此时在宿主机上使用redis-cli可以直接访问容器内的redis服务:

1
2
redis-cli -h 10.88.0.2
10.88.0.2:6379>

还可以在系统的namespace中查询一下:

1
2
lsns | grep net | grep redis
4026532171 net        1 14805 polkitd redis-server *:637

删除容器后,可以按照下面的步骤清理网络资源:

1
2
3
export CNI_PATH=/opt/cni/bin
cnitool del redisnet /var/run/netns/redisnet
ip netns del redisnet

手动删除/var/lib/cni/networks和/var/lib/cni/results下的相关内容。

参考