前叙

docker是目前容器服务最常用的containerd上层构建,其docker工具链调用containerd底层实现容器调度和管理。在日常的使用环境中,docker常用四种网络模式:

1、br模式:通常dockerd在拉起时,会在linux系统中生成一个叫docker0的虚拟网卡,这个网卡的ip通常是172.19.0.x,这个网卡不被绑定到任何物理网卡上,所以也就不会进入物理冲突域。在使用br模式时,生成的容器和docker0网卡使用同段ip,以docker0作为网关,流量经过iptables后转发进入外网,或者说流量从物理网卡进入后流量经过iptables转发到docker0后转发到对应目的地址。也就是在默认情况下,出向和入向流量链路大体是对称的。

2、Host模式:容器直接复用宿主机网络,不同容器使用的是同一个ns,也就是不同的容器必须使用不通的端口,否则会冲突

3、None模式:无网络模式,这个直接就是字面意思,没有分配ip,也没有网络,用的地方比较少

4、overlay模式:跨机通信会经常用到,主要用VXLAN实现

这里还经常会用到一种特殊的网络模式:

5、MACVLAN: 可以理解为把容器虚拟成一个真实的设备,克隆一个虚拟的MAC直接暴露到冲突域内,容器的IP和宿主的IP可以是同段的(也可以不是同段的,这个主要看配置),网络直接绑定到某一个物理网络设备上

其实在生产环境中,最常用的模式是br模式,也是docker默认的网络模式,其他模式一般都是在特殊场景下使用的,这篇文章来展开讨论br模式的流量转发链路。

iptables结构

上边说道使用br模式时,docker0到物理网卡这一段流量是要通过iptables进行转发的,我们首先要研究一下iptables的结构:

在传统网络模式中,上图的网络A和网络B都是指同一个网络,也就是外网。外部流量流入网卡后,流量会先进入PREROUTING链进行路由预处理,处理完成后,传统模式中流量会被路由到INPUT链中,这条链也是最常被拿来做入站向ACL的链。随后流量会被转发到内核中,进行内核态和用户态的交互(也就是图上说的应用进程),完成后流量会被转发到OUTPUT链上,这条链是最常被用作出站向ACL的链,处理完成后直接进入POSTROUTING链转发到网络中。这就是一个完整的传统网络模式流量转发链路。

针对于docker的br网络,可以理解为流量入口和容器不在同一个网段,流向想要从宿主到容器上就需要进行NAT,所以docker的流量在从网卡进入后,会直接被NAT到指定的容器,出向流量yes被SNAT到对应的物理出口,这是一个比较复杂的链路,下面我们对docker的网络进行一下拆解。

容器联网链路

容器联网链路也就是出站向链路。在默认运行docker容器时,容器能通外网差不多是一个最基本的要求了(也有部分服务是禁止外网的,这个后边会详细说怎么实现)。在这里容器可以被理解是一个宿主上的虚拟机,出站向流量需要被SNAT出去,这个规则我们可以看到iptables中:

其实这里主要关注POSTROUTING被标注出来的这部分就好了,这条规则的意思是,把容器网络出容器网卡(即物理网卡)进行源地址转换,也就是进行了SNAT,远端回包的时候会经过PREROUTING被转发回容器中。这是br模式下最简单的网络链路。

容器端口映射

默认运行容器时,容器端口只会绑定到对应容器ip上,也就是只会绑定到对应ns上,如果用户想从外部访问容器服务时一定会使用到端口映射,把docker的容器端口映射到物理网卡上。

如我拉起一个nginx服务,把容器的80端口映射到宿主的80上:

1
docker run -itd -p 80:80 nginx

上文也说到了,容器可以近似看做宿主机上的一个虚拟机,流量想从宿主转发到对应虚拟机就需要进行DNAT转换,于是我们观察nat表就会发现以下一条规则:

流量进入到PREROUTING的nat表后,流量被DNAT转发到容器对应端口上,可以对照上面的结构图,流量被转发后,会进入路由选择:

匹配到路由后,流量会进入FORWARD链。

注意:因为路由选择匹配到了转发路由,所以流量会进入FORWARD链,而不是进入INPUT链,所以如果要给容器的映射端口添加ACL一定不是在INPUT中

观察一下FORWARD链,会发现filter的这个链真不是一般的复杂:

下面逐一解释一下这些规则到底都有什么用:

首先是第一条DOCKER-USER链,这里面主要是一些用户定义的DOCKER网络策略,一般默认情况下这条链只有一条全放通规则,后面定制转发规则时可能会用到,这里先不做过多解释。

其次是DOCKER-ISOLATION-STAGE-1这条链,可以看到这条链还引用了DOCKER-ISOLATION-STAGE-2,这是docker在使用多br卡时进行网络隔离用的,比如我添加了一个br网络后的规则是这样的:

我用br-c181e75dbe81这个网卡进行一下举例分析。当容器流量从br-c181e75dbe81发出进入非br-c181e75dbe81网卡时,进入DOCKER-ISOLATION-STAGE-2进行二阶段处理,这里知名了流量禁止跨网卡访问。

第三条是定义了从docker0网卡出来的流量可以被建立连接,这个一般不要动,保持就好。

第四条是DOCKER转发ACL,比如上图就是定义了流量从docker0网卡转发出去后允许转发到容器的80端口(所以docker的ACL一般是要放到这个链生效前生效前的,黑名单一定要在这个链之前)。

第五和第六条就是一些docker网络基础访问权限控制了,主要是放通了br网卡出向和同卡访问的权限。

基于容器的ACL

经过上面的分析,不难发现,如果要做docker容器ACL的话,要放到filter的FORWAED链中配置的,上面也说过,docker管已经专门为用户创建了一个DOCKER-USER链,所以用户可以直接把ACL放入上述链中。

从上文的梳理中可以发现,使用docker端口映射,端口即为全网段放通,所以,我们要在这里以白名单的方式添加规则。

比如,我们创建了一个映射80端口的容器,然后创建一下一些规则:

1
2
3
iptables -N T80
iptables -I DOCKER-USER -p tcp --dport 80 -j T80
iptables -I T80 -j DROP

然后用外部访问宿主机的80端口,会发现访问已经不通了:

1
2
➜ ~ curl 198.19.249.82
curl: (28) Failed to connect to 198.19.249.82 port 80 after 75006 ms: Couldn't connect to server

尝试添加白名单放通宿主同段网络:

1
iptables -I T80 --src 198.19.249.0/24 -j RETURN

再尝试访问目的地址,发现198.19.249.0/24 网段访问可以通了,其他网段仍然访问不通。

注意:上面添加的规则,一定要保证在FORWARD链的第一条,否则端口可能会被直接全部放通

特殊场景下的一些奇怪ACL

博主从事云游戏运维,目前云游戏常用方案为docker运行android容器,用户直接连入容器使用,所以就会存在内网安全问题,用户可以从容器里直接访问到到内网,这个是很危险的,所以我们要添加一条ACL禁止用户访问到IDC内网:

1
iptables -I FORWARD -i docker0 --dest 10.0.0.0/8 -j DROP

可以发现从容器内ping内网不通。这是因为容器的流量会进入docker0网卡后经过FORWARD链后进入POSTROUTING链,所以我们只需要给docker0添加一个FORWARD规则就可以直接截断流量了。

开端不规范,亲人两行泪。用docker拉起服务的时候,一定要注意管理好服务端口权限,否则数据泄露或者被注入就完蛋了。