前面我们已经解决了容器间通信的问题,接下来讨论容器如何与外部世界通信。这里涉及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发出去,所以我们要同时监控eth0
和docker0
上的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-container
的ping
包,源地址为容器 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规则处理的结果,从而保证数据包能够到达外网。下面用一张图来说明这个过程,如下:
(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为例分析整个过程,如图
(1) docker-proxy监听host的32768端口
(2) 当curl访问 10.43.39.215:32768时,dokcer-proxy转发给容器172.17.0.10:8080
(3) nodejs kubia容器响应请求并返回结果。