CNI插件从0.3.0版本开始支持链式插件,这一一个能够解决潜在多样场景的功能,与此同时能够保证容器网络栈的统一性。本文主要分析一下链式插件的功能,并重点研究一下portmap插件的原理。
链式CNI插件
CNI插件支持依赖关系,有依赖关系的CNI插件会进行多次调用,譬如flannel插件调用bridge插件来完成网桥的创建和IP的获取等。这种CNI插件的依赖关系现实,每个插件依赖于上一次调度的插件的返回结果。
而链式插件也会执行多个插件的多次调用,每一个链式插件会依赖于都依赖于容器运行时信息,下图描述了插件的链式调用。
在进行ADD操作的过程中,在第一个插件运行后,运行时信息必须添加一个prevResult字段到接着运行的插件的配置中,prevResult内容为上一次查询运行的输出信息。
使用portmap CNI插件
portmap 是一个链式插件,它的作用是把容器的IP和端口通过iptables映射到宿主机的端口上,让外部业务可以通过访问宿主机ip和端口来访问容器内的服务。
下面我们来测试一下链式CNI的效果,首先在/etc/cni/net.d目录下准备一个配置文件:
root@herrypc:/etc/cni/net.d# cat cat /etc/cni/net.d/10-chaintest.conflist
cat: cat: 没有那个文件或目录
{
"cniVersion": "0.3.1",
"name": "mynet",
"plugins": [
{
"type": "bridge",
"isGateway": true,
"ipMasq": true,
"bridge": "br0",
"ipam": {
"type": "host-local",
"subnet": "10.10.10.0/24",
"routes": [
{ "dst": "0.0.0.0/0" }
],
"dataDir": "/run/ipam-out-net"
},
"dns": {
"nameservers": [ "8.8.8.8" ]
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true},
"snat": true
}
]
}
portmap是基于iptables来实现端口映射,所以在操作之前我们先看一下清空一下系统中的iptables规则和规则链。
root@herrypc:/# iptables -t nat -F
root@herrypc:/# iptables -t nat -X
root@herrypc:/# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 1 packets, 36 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 1 packets, 36 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
下面我们来创建一个网络命名空间,并通过cnitool给它配置想用的网络。
export CAP_ARGS='{ "portMappings": [ { "hostPort": 9090, "containerPort": 80, "protocol": "tcp" } ]}'
root@herrypc:/etc/cni/net.d# ip netns add herry
root@herrypc:/etc/cni/net.d# cnitool add mynet /var/run/netns/herry
{
"cniVersion": "0.3.1",
"interfaces": [
{
"name": "br0",
"mac": "0a:58:0a:0a:0a:01"
},
{
"name": "veth6ccf2145",
"mac": "52:09:23:72:0f:70"
},
{
"name": "eth0",
"mac": "0a:58:0a:0a:0a:07",
"sandbox": "/var/run/netns/herry"
}
],
"ips": [
{
"version": "4",
"interface": 2,
"address": "10.10.10.7/24",
"gateway": "10.10.10.1"
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"dns": {
"nameservers": [
"8.8.8.8"
]
}
}
这个命令会最终调用cni插件进行网络空间的网卡配置,我们可以通过下面命令来查看配置的网络信息:
root@herrypc:/etc/cni/net.d# ip netns exec herry ifconfig
eth0 Link encap:以太网 硬件地址 0a:58:0a:0a:0a:07
inet 地址:10.10.10.7 广播:0.0.0.0 掩码:255.255.255.0
inet6 地址: fe80::f438:6bff:fe7b:4523/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1
接收数据包:29 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:8 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:0
接收字节:4912 (4.9 KB) 发送字节:648 (648.0 B)
网络配置是成功的,对应的网络空间的ip地址为:10.10.10.7,下面我们来分析一下,前面的portmap的执行情况。
portmap的配置参数,是通过环境变量CAP_ARGS传入的,其中指定了hostPort:8080,containerPort:80,那么就是要求我们把容器中的80端口映射到宿主机的8080端口上,portmap会生成相应的iptables规则来达到这个目的:
root@herrypc:/# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 CNI-HOSTPORT-DNAT all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 10 packets, 620 bytes)
pkts bytes target prot opt in out source destination
3 186 CNI-HOSTPORT-DNAT all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 10 packets, 620 bytes)
pkts bytes target prot opt in out source destination
0 0 CNI-ce40e14b3f32baff9f7b3f44 all -- * * 10.10.10.0/24 0.0.0.0/0 /* name: "mynet" id: "cnitool-cc0eaafbb90e7e27ea49" */
Chain CNI-DN-ce40e14b3f32baff9f7b3 (1 references)
pkts bytes target prot opt in out source destination
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9090 to:10.10.10.8:80
Chain CNI-HOSTPORT-DNAT (2 references)
pkts bytes target prot opt in out source destination
0 0 CNI-DN-ce40e14b3f32baff9f7b3 tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* dnat name: "mynet" id: "cnitool-cc0eaafbb90e7e27ea49" */ multiport dports 9090
Chain CNI-ce40e14b3f32baff9f7b3f44 (1 references)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- * * 0.0.0.0/0 10.10.10.0/24 /* name: "mynet" id: "cnitool-cc0eaafbb90e7e27ea49" */
0 0 MASQUERADE all -- * * 0.0.0.0/0 !224.0.0.0/4 /* name: "mynet" id: "cnitool-cc0eaafbb90e7e27ea49" */
这些规则中,主要是实现DNAT的匹配完成端口的映射功能,并对外只会暴漏主机的ip和port信息。