链式CNI插件与portmap端口映射

CNI插件从0.3.0版本开始支持链式插件,这一一个能够解决潜在多样场景的功能,与此同时能够保证容器网络栈的统一性。本文主要分析一下链式插件的功能,并重点研究一下portmap插件的原理。

链式CNI插件

CNI插件支持依赖关系,有依赖关系的CNI插件会进行多次调用,譬如flannel插件调用bridge插件来完成网桥的创建和IP的获取等。这种CNI插件的依赖关系现实,每个插件依赖于上一次调度的插件的返回结果。

而链式插件也会执行多个插件的多次调用,每一个链式插件会依赖于都依赖于容器运行时信息,下图描述了插件的链式调用。

image.png

在进行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信息。

推荐阅读更多精彩内容