理解Docker单机容器网络

Posted BioMarkerInfo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解Docker单机容器网络相关的知识,希望对你有一定的参考价值。

在” 理解Docker单机容器网络 “一文中,还有一个Docker容器网络的功能尚未提及,那就是Docker容器的端口映射。即将容器的服务端口P’ 绑定到宿主机的端口P上,最终达到一种效果:外部程序通过宿主机的P端口访问,就像直接访问Docker容器网络内部容器提供的服务一样。

Docker针对端口映射前后有两种方案,一种是1.7版本之前docker-proxy+iptables DNAT 的方式;另一种则是1.7版本(及之后)提供的完全由iptables DNAT实现的端口映射。不过在目前docker 1.9.1中,前一种方式依旧是默认方式。但是从Docker 1.7版本起,Docker提供了一个配置项:–userland-proxy,以让Docker用户决定是否启用docker-proxy,默认为true,即启用docker-proxy。本文续前文,继续探讨使用端口映射时Docker容器网络的通信流程。

本文中的实验环境依旧保持与上文相同:docker 1.9.1,ubuntu 12.04宿主机,docker image基于官方ubuntu 14.04 image做的一些软件安装。

一、–userland-proxy=true(defaut)的情况下端口映射

我们首先在实验环境下采用默认的方式进行端口映射,即–userland-proxy=true。

我们来建立一个 新container – container3(172.17.0.4),实现了0.0.0.0:12580 -> container3:12580。

$docker run -it --name container3 -p 12580:12580 dockernetworking/ubuntu:14.04 /bin/bash

这个命令执行后,iptables增加了三条rules:

filter forward链:
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.4           tcp dpt:12580

nat output链:
Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 to:172.17.0.4:12580

nat postrouting链:

Chain POSTROUTING (policy ACCEPT 24 packets, 1472 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.4           172.17.0.4           tcp dpt:12580

我们可以看到了一个DNAT target,是在nat output链中,这个是一个关键点。同样是考虑到调试的方便,在这新增的rules前面,增加LOG target,新的iptables导出内容为:

iptables.portmap.stage1.rules

# Generated by iptables-save v1.4.12 on Fri Jan 15 15:31:06 2016
*raw
: PREROUTING ACCEPT [5737658:60554342802]
:OUTPUT ACCEPT [4294004:56674784720]
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
COMMIT
# Completed on Fri Jan 15 15:31:06 2016
# Generated by iptables-save v1.4.12 on Fri Jan 15 15:31:06 2016
*filter
:INPUT ACCEPT [4444190:53498587744]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4292173:56674165678]
: DOCKER - [0:0]
:FwdId0Od0 - [0:0]
:FwdId0Ond0 - [0:0]
:FwdOd0 - [0:0]
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j FwdOd0
-A FORWARD -i docker0 ! -o docker0 -j FwdId0Ond0
-A FORWARD -i docker0 -o docker0 -j FwdId0Od0
-A OUTPUT ! -s 127.0.0.1/32 -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapFowardDocker:" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j ACCEPT
-A FwdId0Od0 -i docker0 -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Od0:" --log-level 7
-A FwdId0Od0 -i docker0 -o docker0 -j ACCEPT
-A FwdId0Ond0 -i docker0 ! -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Ond0:" --log-level 7
-A FwdId0Ond0 -i docker0 ! -o docker0 -j ACCEPT
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j LOG --log-prefix "[TonyBai]-FwdOd0:" --log-level 7
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Fri Jan 15 15:31:06 2016
# Generated by iptables-save v1.4.12 on Fri Jan 15 15:31:06 2016
*nat
: PREROUTING ACCEPT [24690:5091417]
:INPUT ACCEPT [10942:2271167]
:OUTPUT ACCEPT [7756:523318]
: POSTROUTING ACCEPT [7759:523498]
: DOCKER - [0:0]
:LogNatPostRouting - [0:0]
-A PREROUTING -p icmp -j LOG --log-prefix "[TonyBai]-Enter iptables:" --log-level 7
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterNatInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j LogNatPostRouting
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapNatPostRouting:" --log-level 7
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapNatOutputDocker:" --log-level 7
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 12580 -j DNAT --to-destination 172.17.0.4:12580
-A LogNatPostRouting -s 172.17.0.0/16 ! -o docker0 -j LOG --log-prefix "[TonyBai]-NatPostRouting:" --log-level 7
-A LogNatPostRouting -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
COMMIT
# Completed on Fri Jan 15 15:31:06 2016

另外我们可以查看到宿主机中多了一个进程,这就是前面所说的docker-proxy,每增加一个端口映射,宿主机就会多出一个docker-proxy进程:

root      5742  2113  0 08:48 ?        00:00:00 docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12580 -container-ip 172.17.0.4 -container-port 12580

1、从10.10.126.187访问宿主机(10.10.126.101)的12580端口

10.10.126.187是与101在同一直连网路的主机,我们在其上执行telnet 10.10.126.101 12580。如果container3中有server在监听12580,则建立连接和数据通信(发送一个hello)的过程如下。

【187到101的tcp握手sync包】

101从eth0网卡收到目的地址是自己的sync数据包:

Jan 15 16:04:54 pc-baim kernel: [28410.162828] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.162862] [TonyBai]-NatPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0

由于目的地址就是自己,因此在iptables中走input chain将数据包发给user层:

Jan 15 16:04:54 pc-baim kernel: [28410.162885] [TonyBai]-FilterInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.162900] [TonyBai]-NatInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=32617 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=5840 RES=0x00 SYN URGP=0

【101回复ack sync包】

101上的用户层是docker-proxy在监听12580端口,当收到sync后,会回复ack sync。由于是user空间自产包,路由后走output链。

Jan 15 16:04:54 pc-baim kernel: [28410.162933] [TonyBai]-RawOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.162948] [TonyBai]-FilterOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=28960 RES=0x00 ACK SYN URGP=0

【187回复ack,101与187握手完成】

187回复握手过程最后的一个ack。这个过程与sync类似:

Jan 15 16:04:54 pc-baim kernel: [28410.163397] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=32618 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.163437] [TonyBai]-FilterInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=32618 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK URGP=0

重点是接下来发生的事情:101上的docker-proxy向container3上的server程序建立tcp连接!

【host向container3发送sync】

Jan 15 16:04:54 pc-baim kernel: [28410.163863] [TonyBai]-RawOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=5768 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.163901] [TonyBai]-FilterOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=5768 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0

我们看到SYN数据包源地址用的是172.17.0.1,不知是否是docker-proxy内部有意选择了网桥的ip。由于是user层发出的包,于是走iptables output链。

【container3回复ack sync】

container3回复ack sync,目的地址是172.17.0.1,host从docker0网卡收到ack sync数据,路由后发现是发给自己的包,于是走input chain.

Jan 15 16:04:54 pc-baim kernel: [28410.164000] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.164026] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=28960 RES=0x00 ACK SYN URGP=0

【host回复ack,host与container3握手完成】

host回复握手过程最后的一个ack。user空间自产数据包,于是走output chain:

Jan 15 16:04:54 pc-baim kernel: [28410.164049] [TonyBai]-RawOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=5769 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
Jan 15 16:04:54 pc-baim kernel: [28410.164058] [TonyBai]-FilterOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=5769 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0

【187 在已经建立的连接上发送”hello”】

187发送hello to host,docker-proxy收到hello数据:

Jan 15 16:04:58 pc-baim kernel: [28413.840854] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=32619 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK PSH URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.840874] [TonyBai]-FilterInput:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:19:bb:5e:0a:86:08:00 SRC=10.10.126.187 DST=10.10.126.101 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=32619 DF PROTO=TCP SPT=33250 DPT=12580 WINDOW=92 RES=0x00 ACK PSH URGP=0

【host返回 ack push】

Jan 15 16:04:58 pc-baim kernel: [28413.840893] [TonyBai]-RawOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22415 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=227 RES=0x00 ACK URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.840902] [TonyBai]-FilterOutput:IN= OUT=eth0 SRC=10.10.126.101 DST=10.10.126.187 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22415 DF PROTO=TCP SPT=12580 DPT=33250 WINDOW=227 RES=0x00 ACK URGP=0

接下来,docker-proxy将hello从已有连接上转发给container3。

【host转发hello到container3】

Jan 15 16:04:58 pc-baim kernel: [28413.841000] [TonyBai]-RawOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=59 TOS=0x00 PREC=0x00 TTL=64 ID=5770 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK PSH URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.841026] [TonyBai]-FilterOutput:IN= OUT=docker0 SRC=172.17.0.1 DST=172.17.0.4 LEN=59 TOS=0x00 PREC=0x00 TTL=64 ID=5770 DF PROTO=TCP SPT=43771 DPT=12580 WINDOW=229 RES=0x00 ACK PSH URGP=0

【container3回复ack 】

Jan 15 16:04:58 pc-baim kernel: [28413.841101] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=61139 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=227 RES=0x00 ACK URGP=0
Jan 15 16:04:58 pc-baim kernel: [28413.841119] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=61139 DF PROTO=TCP SPT=12580 DPT=43771 WINDOW=227 RES=0x00 ACK URGP=0

通信过程到此结束。通过这个过程,我们至少了解到两点:

1、docker-proxy将外部建立在host:12580上的连接上的数据转发到container中,反之亦然,如果container 通过与host已经建立的连接向外发送数据,docker-proxy也会将数据转发给187。2、通过iptables log输出我们可以看到:为了port map而添加的DNAT和MASQUERADE 并没有被匹配到,也就是说在这个过程中并没有用到DNAT,而是完全依靠docker-proxy做的4层代理。

2、从宿主机上访问10.10.126.101:12580

我们在宿主机本机上访问10.10.126.101:12580,看看这个通信过程与上面的是否有差异。

【与本机12580端口建立连接,发送sync包】

由于是user层发送数据包,因此走iptables output链。

Jan 15 16:40:15 pc-baim kernel: [30532.594545] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=53747 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0

在output链上,匹配到nat output上的规则:

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    1    60 LOG        tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 LOG flags 0 level 7 prefix "[TonyBai]-PortmapNatOutputDoc"
    1    60 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:12580 to:172.17.0.4:12580

于是这里将做一个DNAT,数据包的目的地址10.10.126.101被替换为172.17.0.4。

Jan 15 16:40:15 pc-baim kernel: [30532.594561] [TonyBai]-PortmapNatOutputDoc IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=53747 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0

Jan 15 16:40:15 pc-baim kernel: [30532.594572] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=53747 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0

DNAT后,将按照目的地址做一个重新路由:叫实际路由。消息实际重定向到docker0进行封包发送,sync包直接进入到container3 中。

【container3发送ack sync包】

docker0出来的ack sync 通过input chain送到user空间。这块应该由一个自动un-DNAT,将172.17.0.4自动转回10.10.126.101,但通过iptables日志无法确认这点。

Jan 15 16:40:15 pc-baim kernel: [30532.594615] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 15 16:40:15 pc-baim kernel: [30532.594624] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=28960 RES=0x00 ACK SYN URGP=0

【host发送ack,完成握手】

host回复ack。user层自产包,走output链,看rawoutput,dst依旧是126.101(telnet自然不应该知道 172.17.0.4的存在),但是filter output 前,iptables对该地址自动做了dnat,无需重新进入到nat output链,因为之前已经进过了。在filter output中,我们看到dst ip已经变成了container3的ip地址:

Jan 15 16:40:15 pc-baim kernel: [30532.594637] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=53748 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
Jan 15 16:40:15 pc-baim kernel: [30532.594643] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=53748 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0

【host发送hello】

这个过程同上,不赘述。

Jan 15 16:40:18 pc-baim kernel: [30535.344921] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=53749 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK PSH URGP=0
Jan 15 16:40:18 pc-baim kernel: [30535.344956] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=59 TOS=0x10 PREC=0x00 TTL=64 ID=53749 DF PROTO=TCP SPT=48039 DPT=12580 WINDOW=342 RES=0x00 ACK PSH URGP=0

【container回复ack】

不赘述。

Jan 15 16:40:18 pc-baim kernel: [30535.345027] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=43021 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=227 RES=0x00 ACK URGP=0
Jan 15 16:40:18 pc-baim kernel: [30535.345056] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=vethf0cc298 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=43021 DF PROTO=TCP SPT=12580 DPT=48039 WINDOW=227 RES=0x00 ACK URGP=0

从这个过程可以看到,在宿主机上访问container的映射端口,通信流程不走docker-proxy,而是直接通过output 的dnat将数据包被直接转给container中的server程序。

3、container to container

在container1中telnet 10.10.126.101 12580会发生什么呢?这里就不长篇大论的列log了,直接给出结论:通过docker-proxy转发,因为不满足nat output中DNAT的匹配条件。

二、在–userland-proxy=false的情况下

我们修改了一下/etc/default/docker配置,为DOCKER_OPTS增加一个option: –userland-proxy=false。

DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --userland-proxy=false"

重启docker daemon并清理iptables规则(-F),并启动做端口映射的container3。启动后,你会发现之前的docker-proxy并没有出现在启动进程列表中,iptables的规则与–userland-proxy=true时也有所不同:

$ sudo iptables -nL -v
Chain INPUT (policy ACCEPT 1645 packets, 368K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    0     0 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
    0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 263 packets, 134K bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.4           tcp dpt:12580

$ sudo iptables -t nat -nL -v
Chain PREROUTING (policy ACCEPT 209 packets, 65375 bytes)
 pkts bytes target     prot opt in     out     source               destination
   71 49357 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 98 packets, 39060 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 34 packets, 2096 bytes)
 pkts bytes target     prot opt in     out     source               destination
   21  1302 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 34 packets, 2096 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match src-type LOCAL
    0     0 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.4           172.17.0.4           tcp dpt:12580

Chain DOCKER (2 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:12580 to:172.17.0.4:12580

可以看到nat表中prerouting链增加了target为DOCKER链的规则,并且Docker链中对dnat的匹配条件也放开了,只要是dst-type是LOCAL的,dport=12580的,都将ip映射为172.17.0.4。

由于iptables的规则有所变化,因此因此我的log target的匹配条件也该调整一下了,调整后的iptables为:

iptables.portmap.stage1.tmp.rules

# Generated by iptables-save v1.4.12 on Mon Jan 18 09:06:06 2016
*mangle
: POSTROUTING ACCEPT [0:0]
-A POSTROUTING -o docker0 -m addrtype --src-type LOCAL -j LOG --log-prefix "[TonyBai]-manglepost1" --log-level 7
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j LOG --log-prefix "[TonyBai]-manglepost2" --log-level 7
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-manglepost3" --log-level 7
COMMIT

*raw
: PREROUTING ACCEPT [1008742:377375989]
:OUTPUT ACCEPT [426678:274235692]
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawPrerouting:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-RawOutput:" --log-level 7
COMMIT
# Completed on Mon Jan 18 09:06:06 2016
# Generated by iptables-save v1.4.12 on Mon Jan 18 09:06:06 2016
*filter
:INPUT ACCEPT [187016:64478647]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [81342:51955911]
: DOCKER - [0:0]
:FwdId0Od0 - [0:0]
:FwdId0Ond0 - [0:0]
:FwdOd0 - [0:0]
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterInput:" --log-level 7
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j FwdOd0
-A FORWARD -i docker0 ! -o docker0 -j FwdId0Ond0
-A FORWARD -i docker0 -o docker0 -j FwdId0Od0
-A OUTPUT ! -s 127.0.0.1/32 -p icmp -j LOG --log-prefix "[TonyBai]-EnterFilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A OUTPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-FilterOutput:" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapFowardDocker" --log-level 7
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 12580 -j ACCEPT
-A FwdId0Od0 -i docker0 -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Od0:" --log-level 7
-A FwdId0Od0 -i docker0 -o docker0 -j ACCEPT
-A FwdId0Ond0 -i docker0 ! -o docker0 -j LOG --log-prefix "[TonyBai]-FwdId0Ond0:" --log-level 7
-A FwdId0Ond0 -i docker0 ! -o docker0 -j ACCEPT
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j LOG --log-prefix "[TonyBai]-FwdOd0:" --log-level 7
-A FwdOd0 -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Mon Jan 18 09:06:06 2016
# Generated by iptables-save v1.4.12 on Mon Jan 18 09:06:06 2016
*nat
: PREROUTING ACCEPT [34423:7014094]
:INPUT ACCEPT [9475:1880078]
:OUTPUT ACCEPT [3524:218202]
: POSTROUTING ACCEPT [3508:217098]
: DOCKER - [0:0]
:LogNatPostRouting1 - [0:0]
:LogNatPostRouting2 - [0:0]
:LogNatPostRouting3 - [0:0]
-A PREROUTING -p icmp -j LOG --log-prefix "[TonyBai]-Enter iptables:" --log-level 7
-A PREROUTING -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A PREROUTING -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatPrerouting:" --log-level 7
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A INPUT ! -i lo -p icmp -j LOG --log-prefix "[TonyBai]-EnterNatInput:" --log-level 7
-A INPUT -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A INPUT -p tcp -m tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatInput:" --log-level 7
-A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -p tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPostrouteEnter" --log-level 7
-A POSTROUTING -p tcp --sport 12580 -j LOG --log-prefix "[TonyBai]-NatPostrouteEnter" --log-level 7
-A POSTROUTING -o docker0 -m addrtype --src-type LOCAL -j LogNatPostRouting1
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j LogNatPostRouting2
-A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LogNatPostRouting3
-A DOCKER -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-PortmapNatPrerouting" --log-level 7
-A DOCKER -p tcp -m tcp --dport 12580 -j DNAT --to-destination 172.17.0.4:12580
-A LogNatPostRouting1 -o docker0 -m addrtype --src-type LOCAL -j LOG --log-prefix "[TonyBai]-NatPost1" --log-level 7
-A LogNatPostRouting1 -o docker0 -m addrtype --src-type LOCAL -j MASQUERADE
-A LogNatPostRouting2 -s 172.17.0.0/16 ! -o docker0 -j LOG --log-prefix "[TonyBai]-NatPost2" --log-level 7
-A LogNatPostRouting2 -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A LogNatPostRouting3 -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j LOG --log-prefix "[TonyBai]-NatPost3" --log-level 7
-A LogNatPostRouting3 -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 12580 -j MASQUERADE
COMMIT
# Completed on Mon Jan 18 09:06:06 2016

接下来,我们按照上面的方法再做一遍实验例子,看看通信流程有何不同。这次我们将187主机换为10.10.105.71,其他无差别。

1、 在71上telnet 10.10.126.101 12580

宿主机从eth0接口收到syn,nat prerouting中做DNAT。路由后,通过forward链转发到docker0:

Jan 18 13:35:55 pc-baim kernel: [278835.389225] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=63 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389275] [TonyBai]-NatPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=63 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389290] [TonyBai]-PortmapNatPreroutinIN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=63 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389326] [TonyBai]-PortmapFowardDockerIN=eth0 OUT=docker0 MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=62 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389339] [TonyBai]-NatPostrouteEnterIN= OUT=docker0 SRC=10.10.105.71 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=62 ID=61480 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0

接下来从docker0网卡收到container3的ack syn应答,在从eth0转发出去前自动un-DNAT, src ip从172.17.0.4变为101.0126.101,但这个在日志中看不出来。

Jan 18 13:35:55 pc-baim kernel: [278835.389496] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.105.71 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=41502 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389519] [TonyBai]-FwdId0Ond0:IN=docker0 OUT=eth0 PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.105.71 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=12580 DPT=41502 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.389528] [TonyBai]-manglepost2IN= OUT=eth0 PHYSIN=veth0d66af2 SRC=172.17.0.4 DST=10.10.105.71 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=12580 DPT=41502 WINDOW=28960 RES=0x00 ACK SYN URGP=0

回送ack,这回无需再匹配natprerouting链,前面进过链一次,后续自动进行DNAT:

Jan 18 13:35:55 pc-baim kernel: [278835.390079] [TonyBai]-RawPrerouting:IN=eth0 OUT= MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=63 ID=61481 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
Jan 18 13:35:55 pc-baim kernel: [278835.390149] [TonyBai]-PortmapFowardDockerIN=eth0 OUT=docker0 MAC=2c:59:e5:01:98:28:00:23:89:7d:b6:b1:08:00 SRC=10.10.105.71 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=62 ID=61481 DF PROTO=TCP SPT=41502 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0

这次我们看到,在这种方式下,外部流量也是通过DNAT方式导入到container中的。

2、在宿主机上 telnet 10.10.126.101 12580

telnet发起tcp握手,syn包进入output链,匹配到nat output规则,做DNAT。目的ip转换为172.17.0.4。注意继续向下,我们看iptables匹配到了NatPost1,也就是规则:

-A LogNatPostRouting1 -o docker0 -m addrtype --src-type LOCAL -j MASQUERADE

即将源地址伪装为出口网卡docker0的当前地址:172.0.0.1。于是实际上进入到container3的syn数据包的源地址为172.0.0.1,目的地址:172.0.0.4。

Jan 18 13:49:43 pc-baim kernel: [279663.426497] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426526] [TonyBai]-PortmapNatPreroutinIN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426545] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426553] [TonyBai]-manglepost1IN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426561] [TonyBai]-NatPostrouteEnterIN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426567] [TonyBai]-NatPost1IN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=40854 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=43690 RES=0x00 SYN URGP=0

container3返回ack,从宿主机角度来看,相当于从docker0网卡收到ack。我们看到进来的原始数据:dst = 172.17.0.1,这是上面MASQUERADE的作用。在进入input链前,做自动un-SNAT,目的地址由172.17.0.1转换为10.10.126.101。在真正送到user层之前(output链等同的左边同纬度位置),做自动un-DNAT(但在下面日志中看不出来),src由172.17.0.4变为10.10.126.101。数据包的变换总体次序依次为:即DNAT -> SNAT -> (应答包)un-SNAT -> un-DNAT。

Jan 18 13:49:43 pc-baim kernel: [279663.426646] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.1 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=52736 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426665] [TonyBai]-FilterInput:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=10.10.126.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=52736 WINDOW=28960 RES=0x00 ACK SYN URGP=0

宿主机回复ack,握手完成。由于之前走过nat output和post链,因此这里不会再匹配,而是自动DNAT和SNAT:

Jan 18 13:49:43 pc-baim kernel: [279663.426690] [TonyBai]-RawOutput:IN= OUT=lo SRC=10.10.126.101 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=40855 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426707] [TonyBai]-FilterOutput:IN= OUT=lo SRC=10.10.126.101 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=40855 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0
Jan 18 13:49:43 pc-baim kernel: [279663.426719] [TonyBai]-manglepost1IN= OUT=docker0 SRC=10.10.126.101 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=40855 DF PROTO=TCP SPT=52736 DPT=12580 WINDOW=342 RES=0x00 ACK URGP=0

3、从container1 telnet 10.10.126.101 12580

container1向服务发起tcp连接,宿主机从docker0网卡收到sync包。

Jan 18 13:51:10 pc-baim kernel: [279750.806496] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806519] [TonyBai]-NatPrerouting:IN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806531] [TonyBai]-PortmapNatPreroutinIN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0

做DNAT后,再次路由到docker0,于是走forward链,但是没有匹配上nat postrouting,也就没有做SNAT:

Jan 18 13:51:10 pc-baim kernel: [279750.806581] [TonyBai]-FwdId0Od0:IN=docker0 OUT=docker0 PHYSIN=veth44a97d7 PHYSOUT=veth0d66af2 MAC=02:42:ac:11:00:04:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806608] [TonyBai]-NatPostrouteEnterIN= OUT=docker0 PHYSIN=veth44a97d7 PHYSOUT=veth0d66af2 SRC=172.17.0.2 DST=172.17.0.4 LEN=60 TOS=0x10 PREC=0x00 TTL=64 ID=31888 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=29200 RES=0x00 SYN URGP=0

container3回复ack sync。宿主机从docker0收到ack sync包,目的地址172.17.0.2,再次路由到docker0。

Jan 18 13:51:10 pc-baim kernel: [279750.806719] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth0d66af2 MAC=02:42:ac:11:00:02:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=54408 WINDOW=28960 RES=0x00 ACK SYN URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806746] [TonyBai]-FwdOd0:IN=docker0 OUT=docker0 PHYSIN=veth0d66af2 PHYSOUT=veth44a97d7 MAC=02:42:ac:11:00:02:02:42:ac:11:00:04:08:00 SRC=172.17.0.4 DST=172.17.0.2 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=12580 DPT=54408 WINDOW=28960 RES=0x00 ACK SYN URGP=0

由于之前docker0上做过DNAT,因此从docker0回到172.17.0.2时,src地址会自动un-DNAT,从172.17.0.4改为10.10.126.101,不过在上面日志中看不出这一点。

172.17.0.2回复ack,握手完成,DNAT自动进行:

Jan 18 13:51:10 pc-baim kernel: [279750.806823] [TonyBai]-RawPrerouting:IN=docker0 OUT= PHYSIN=veth44a97d7 MAC=02:42:23:39:fd:f5:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=10.10.126.101 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=31889 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0
Jan 18 13:51:10 pc-baim kernel: [279750.806852] [TonyBai]-FwdOd0:IN=docker0 OUT=docker0 PHYSIN=veth44a97d7 PHYSOUT=veth0d66af2 MAC=02:42:ac:11:00:04:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=172.17.0.4 LEN=52 TOS=0x10 PREC=0x00 TTL=64 ID=31889 DF PROTO=TCP SPT=54408 DPT=12580 WINDOW=229 RES=0x00 ACK URGP=0

三、网络性能考量

docker-proxy常被docker使用者诟病,一是因为每个映射端口都要启动一个docker-proxy进程,映射端口多了,大量进程被创建、被调度势必消耗大量系统资源;二来,在高负载场合,docker-proxy的转发性能也力不从心。理论上,docker-proxy代理转发流量的方式在性能方面要比单纯iptables DNAT要弱上一些。不过我在单机上通过 sparkyfish 测试的结果倒是二者相差不大,估计是因为我仅仅启动了一个docker-proxy,系统负荷并不大的缘故。

以上是关于理解Docker单机容器网络的主要内容,如果未能解决你的问题,请参考以下文章

理解Docker:若干企业生产环境中的容器网络方案

Docker的单主机容器网络

Docker-04-docker单机网络详解

Docker底层网络经典文章分享

49-Docker-网络管理及Compose单机多容器编排

Kafka学习篇1:Docker安装Kafka(单机默认参数版,依赖于Zookeeper)