Docker手动修改Iptables添加端口映射

本博客的第一篇文章,有点小激动哦,希望可以坚持下来。
lizhi

言归正传,开始整:

技术储备

一、网卡收到数据包iptables处理流程:

网卡收到一个数据包,内核决定这个数据包是发给本地的应用程序还是转发给其他主机。
如果发给本地应用程序:
nat-PREROUTING –> filter-INPUT –> 接受的应用程序

如果从本机出去:
nat-OUTPUT –> file-OUTPUT –> nat-POSTROUTING

如果收到一个数据包再转发出去:
nat-PREROUTING –> file-FORWARD –> nat-POSTROUTING

二、docker运行ipatables自动添加的规则

我们先来分析下docker添加iptables流程:

1、docker启动时自动添加规则:

nat表规则

a、数据包发往主机

1
iptables -t nat -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER

这里通过addrtype这个module来匹配数据包,如果这个数据包是发往本地的,那走DOCKER自定义链。

b、数据包发送出去

1
iptables -t nat -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER

如果数据包的目的地址是非127的LOCAL地址,则走DOCKER链。

1
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o docker0 -j MASQUERADE

这个是个SNAT规则,非docker0这个接口发送的源地址网段为172.18.0.0/16的包在出去的时候会走NAT,这个规则保证docker容器可以访问外网。

filter表规则:
1
iptables -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

-m conntrack
对下列连接通信与当前的包/连接进行匹配
–ctstate RELATED,ESTABLISHED 规则将应用到的连接状态。在本例中,ESTABLISHED 连接代表能够看到两个方向数据包的连接,而RELATED 类型代表,数据包开启一个新连接,但是与现有连接相关联。

2、docker启动容器并端口映射(-p/P)后自动添加规则:

我们这里添加2022到22端口的映射
-p 2022:22

a、公网访问2022端口,数据包进入内核后,内核走nat—PREROUTING,就匹配上了这条规则:

1
iptables -t nat -A DOCKER -d 172.18.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT

接着内核将这个包的目的地址改为172.18.0.2:22 ,然后就发给我们的容器。

b、容器访问公网的情况,按照上面所说情况,规则这样走(iptables处理流程,收到一个数据包再转发出去):
nat-PREROUTING–>file-FORWARD–>nat-POSTROUTING
此时数据包经过网桥逻辑,从docker0进入内核,然后走nat—PREROUTING,也就是走DOCKER链,但DOCKER链 !–i docker0使数据包没有被匹配,于是走剩余规则,filter—FORWARD也没有匹配,于是走nat—POSTROUTING,通过SNAT出去了。

1
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o docker0 -j MASQUERADE

手动添加iptables规则

了解了iptables处理流程及docker的iptables添加规则后,我们自己手动iptables映射端口就好实现了,下面我们分几种情况来分析下:

1、在已启动的docker容器上扩展端口,使用docker0网桥:

在以下3种情况下均生效

  • run时已添加端口映射
  • run时未添加端口映射
  • run时–net=none 后来使用pipework添加IP

扩展端口添加如下规则:

1
iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080-j DNAT --to-destination 172.18.0.2:80

保证容器可以访问外网,docker启动时已经添加如下规则(没有的话自己手动添加下):

1
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o docker0 -j MASQUERADE

2、未使用docker0,使用自定义网桥,分两种情况:

A、物理网卡桥接到bridge网桥,配置容器并连接bridge网桥

例如(使用br0网桥):

1
2
3
4
5
6
7
8
brctl addbr br0
ip addr add 10.0.0.1/24 dev br0
ip addr del 10.0.0.1/24 dev eth0
brctl addif br0 eth0
ip route del default
ip route add default gw 10.0.0.254 dev br0
docker run -itd --name test1 ubuntu /bin/bash
pipework br0 test1 10.0.0.2/24@10.0.0.254

iptables增加端口映射规则:
把docker启动后的默认添加的规则进行修改,默认docker0全部替换为新建网桥br0,包括file表和nat的规则。
实施建议:

1
2
3
4
5
6
7
iptables-save > iptables.txt
#修改iptables.txt,替换docker0为br0
iptables-restore < iptables.txt
#最后添加DNAT规则:
iptables -t nat -A DOCKER ! -i br0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 10.10.101.2:80
#一般情况此时端口映射已经添加成功了,如何还无法访问的话,继续添加如下规则,filter表的FORWARD规则:
iptables -A DOCKER -d 10.0.0.2/32 ! -i br0 -o br0-p tcp -m tcp --dport 80 -j ACCEPT

B、使用openvswitch 网桥并划分vlan

物理网卡桥接到ovs网桥,ovs网桥创建vlan,配置docker容器连接ovs网桥并划分到vlan中
例如(使用ovs1网桥):

1
2
3
4
5
6
7
8
9
10
11
ovs-vsctl add-br ovs1
add-port ovs1 eth0
ip link set dev ovs1 up
ifconfig eth0 0
ip addr add 10.0.0.1/24 dev ovs1
add-port ovs1 vlan2 tag=2 -- set interface vlan2 type=internal
docker run -d --net=none --name test1 ubuntu
pipework ovs1 test1 10.0.0.2/24@10.0.0.254 @2
#网关配置:
ip route add default via 10.0.0.254 dev ovs1
10.0.0.0/24 dev vlan2  proto kernel  scope link  src 10.0.0.2

iptables增加端口映射规则:
把docker启动后的默认添加的规则进行修改,默认docker0全部替换为新建网桥ovs1,包括file表和nat的规则
实施建议和使用br0网桥相同。
最后添加DNAT规则:(此处不同的接口不是网桥ovs1,而是vlan端口vlan2)

1
2
3
4
5
6
7
iptables -t nat -A DOCKER ! -i vlan2 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 10.0.0.2:80
#一般情况此时端口映射已经添加成功了,如何无法访问的话,继续添加如下规则,filter表的FORWARD规则:
iptables -A DOCKER -d 172.18.0.2/32 ! -i ovs1-o ovs1-p tcp -m tcp --dport 22 -j ACCEPT
#完成以上配置后,如果容器内主机还不能上网,还需要iptables添加规则(此条iptables启动时应该已经添加):
iptables -t nat -A POSTROUTING -s 10.0.0.2/24 ! -o vlan2 -j MASQUERADE
#还需file表添加规则:
iptables -A DOCKER ! -i ovs1 -o ovs1 -j ACCEPT

备注:docker自定义添加iptables端口的时候,如果都是内网环境,建议iptables的filter表可以不考虑,设置全部允许只在nat表上做相关操作。

1
2
3
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT

介绍完毕,大家有相关问题可以留言讨论。