前面我们已经解决了容器间通信的问题,接下来讨论容器如何与外部世界通信。这里涉及2个方向:
(1) 容器访问外部世界
(2) 外部世界访问容器

容器访问外部世界

在我们的实现环境下,存在windows主机一台,ip为10.40.164.63,一台linux服务器,ip为10.43.39.215(环境比较特殊,应该也是被额外nat转换了,查看主机的ip address显示为172.168.100.195),在服务器上建的docker容器。

在服务器上ping windows主机,结果是可以ping通的,过程如下:

[root@EMS3 ~]# ping -c 3 10.40.164.63
PING 10.40.164.63 (10.40.164.63) 56(84) bytes of data.
64 bytes from 10.40.164.63: icmp_seq=1 ttl=251 time=2.60 ms
64 bytes from 10.40.164.63: icmp_seq=2 ttl=251 time=3.16 ms
64 bytes from 10.40.164.63: icmp_seq=3 ttl=251 time=20.3 ms

启动容器,在容器内ping windows主机,发现结果是可以ping通的,过程如下:
注意:此时创建容器并没有设置port

[root@EMS3 ~]# docker run --name kubia-container    -d docker.artnj.test.com.cn/cci/kubia:v3
930663ce15fd3111362650c77c1de3400e00919cfae3526fefe73b34a9d1566a
[root@EMS3 ~]# docker exec -it  kubia-container  sh
#  ping -c 3 10.40.164.63
PING 10.40.164.63 (10.40.164.63) 56(84) bytes of data.
64 bytes from 10.40.164.63: icmp_seq=1 ttl=250 time=6.29 ms
64 bytes from 10.40.164.63: icmp_seq=2 ttl=250 time=3.00 ms
64 bytes from 10.40.164.63: icmp_seq=3 ttl=250 time=2.08 ms

可见容器默认就能访问外网

请注意:这里的往外指的是容器网络以外的网络环境,并非特指Internet。
现象很简单,但更重要的:我们应该理解现象下的本质。

网络地址转换(NAT)

在上面的例子中,kubia-container 位于dokcer0这个私有的bridge网络中(172.17.0.0.16),kubia-container 从容器向外ping时,数据包是怎样到达windows主机的呢?

这里的关键就是NAT,我们查看下docker host上的iptables规则,如下:

[root@EMS3 ~]# iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.21.0.0/24 ! -o br-06977f67da48 -j MASQUERADE
-A POSTROUTING -s 172.18.0.0/16 ! -o br-cc45c8c6184f -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i br-06977f67da48 -j RETURN
-A DOCKER -i br-cc45c8c6184f -j RETURN
-A DOCKER -i docker0 -j RETURN

在NAT表中,有这么一条规则
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

其含义是:如果网络docker0 收到来自172.17.0.0/16 网段的外出包,把它交给MASQUERADE处理。而MASQUERADE的处理方式是将包的源地址替换成host的地址发送出去,即做了一次网络地址转换(NAT)。

验证NAT过程

下面我们通过tcpdump查看地址是如何转换的。先查看docker host的路由表,如下:

[root@EMS3 ~]# ip r
default via 172.168.100.1 dev eth0
169.254.0.0/16 dev eth0  scope link  metric 1002
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1
172.18.0.0/16 dev br-cc45c8c6184f  proto kernel  scope link  src 172.18.0.1

default via 172.168.100.1 dev eth0,默认路由通过eth0发出去,所以我们要同时监控eth0docker0上的icmp(ping)数据包。

当kubia-container ping 10.40.164.63时,tcpdump输出如下:

[root@EMS3 ~]# tcpdump -i docker0 -n icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on docker0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:06:59.575896 IP 172.17.0.9 > 10.40.164.63: ICMP echo request, id 28, seq 1, length 64
16:06:59.578778 IP 10.40.164.63 > 172.17.0.9: ICMP echo reply, id 28, seq 1, length 64
16:07:00.577003 IP 172.17.0.9 > 10.40.164.63: ICMP echo request, id 28, seq 2, length 64
16:07:00.579742 IP 10.40.164.63 > 172.17.0.9: ICMP echo reply, id 28, seq 2, length 64
16:07:01.578440 IP 172.17.0.9 > 10.40.164.63: ICMP echo request, id 28, seq 3, length 64
16:07:01.586887 IP 10.40.164.63 > 172.17.0.9: ICMP echo reply, id 28, seq 3, length 64

docker0收到kubia-containerping包,源地址为容器 IP 172.17.0.9,这没问题,交给MASQUERADE处理。这是,我们在eth0上看到了变化,如下:

[root@EMS3 ~]# tcpdump -i eth0 -n icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:09:47.627849 IP 172.168.100.195 > 10.40.164.63: ICMP echo request, id 29, seq 1, length 64
16:09:47.629223 IP 10.40.164.63 > 172.168.100.195: ICMP echo reply, id 29, seq 1, length 64
16:09:48.629474 IP 172.168.100.195 > 10.40.164.63: ICMP echo request, id 29, seq 2, length 64
16:09:48.631634 IP 10.40.164.63 > 172.168.100.195: ICMP echo reply, id 29, seq 2, length 64
16:09:49.630987 IP 172.168.100.195 > 10.40.164.63: ICMP echo request, id 29, seq 3, length 64
16:09:49.633134 IP 10.40.164.63 > 172.168.100.195: ICMP echo reply, id 29, seq 3, length 64

ping包的源地址变成了eth0 的IP 172.168.100.195,这就是iptables NAT规则处理的结果,从而保证数据包能够到达外网。下面用一张图来说明这个过程,如下:

docker查看ping网络 docker查看容器网络_容器

(1) kubia-container发送ping包:172.17.0.9 > 10.40.164.63
(2) docker0收到ping包,发现是发送到外网的,交给NAT处理
(3)NAT将源地址换成 eth0 的IP :172.168.100.195 > 10.40.164.63
(4) ping包从eth0 出去,到达 windos主机 10.40.164.63

外部世界访问容器

下面我们来讨论另一个方向:外网如何访问到容器?
答案是:端口映射
docker 可以将容器对外提供服务的端口映射到host(主机)的某个端口,外网通过该端口访问容器。容器启动时通过-p 参数映射端口,如下:

我们建3个容器,分别指定不同的-p参数

[root@EMS3 ~]# docker run --name kubia-container    -d docker.artnj.test.com.cn/cci/kubia:v3
[root@EMS3 ~]# docker run --name kubia-container2  -p 8080   -d docker.artnj.test.com.cn/cci/kubia:v3
[root@EMS3 ~]# docker run --name kubia-container3  -p 8081:8080   -d docker.artnj.test.com.cn/cci/kubia:v3

容器名称

-p参数

docker ps 查看PORT字段

描述

kubia-container

不带参数


无端口映射

kubia-container2

8080 指定单个端口

0.0.0.0:32768->8080/tcp

自动分配一个Host上的端口32768

kubia-containe3

8081:8081

0.0.0.0:8081->8080/tcp

指定一个Host上的端口8081

每一个映射的端口,host会启动一个docker-proxy进程来处理访问容器的流量,如下:

[root@EMS3 ~]# ps -ef|grep docker-proxy
root      2837   983  0 18:53 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8081 -container-ip 172.17.0.14 -container-port 8080
root     31620   983  0 18:53 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 32768 -container-ip 172.17.0.10 -container-port 8080

容器名称

-host-port

-container-port

kubia-container2

32768

8080

172.17.0.10

kubia-container3

8081

8080

172.17.0.14

以0.0.0.0:32768->8080/tcp为例分析整个过程,如图

docker查看ping网络 docker查看容器网络_IP_02

(1) docker-proxy监听host的32768端口
(2) 当curl访问 10.43.39.215:32768时,dokcer-proxy转发给容器172.17.0.10:8080
(3) nodejs kubia容器响应请求并返回结果。