动态映射端口
虽然设计者们一再地强调在使用Docker时要遵循最佳实践,但很多情况下并不能完全做到最佳实践,就比如我们现在的情况,是把Docker当做虚拟机来用,每个容器中包含了很多服务,,,当然,这也带来了很多问题,就比如:要映射很多的端口到物理机。
痛苦的映射问题
在启动一个Docker容器时,可以指定-p参数来把容器内的端口映射到宿主机的IP端口上,这样可以很方便地从外界访问容器中的服务,一开始全都是用-p这种方式来做,如果容器中有10个端口需要映射到外面,那就指定10个-p选项,实际上可能更多,如下:
[root@blog ~]# docker run \
--name node1 \
-p 10.0.82.43:22:22 \
-p 10.0.82.43:8080:8080 \
-p 10.0.82.43:6066:6066 \
-p 10.0.82.43:7071:7071 \
-p 10.0.82.43:111:111 \
-p 10.0.82.43:5005:5005 \
-p 10.0.82.43:6265:6265 \
-p 10.0.82.43:7699:7699 \
...
-tid node-base
然后当docker ps的时候会看到整个屏幕被-p参数给占满了,,,好吧,这个不是问题,问题是如果在使用了很久后发现有一个端口没有映射出来!或者是你需要通过Java API远程调用容器中的程序,而又恰好没有映射那个端口,那该怎么办?
映射的实现
其实Docker run命令中的-p选项,最终是通过宿主机上的iptables来实现的(也许你早就发现了,只是没有仔细研究),在Docker的宿主机上查看一下iptables的nat表,大概会看到下面这样:
[root@blog ~]# iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 1082K packets, 173M bytes)
pkts bytes target prot opt in out source destination
637 37876 DNAT tcp -- * * 0.0.0.0/0 10.100.124.231 tcp dpt:80 to:10.100.124.231:5000
452K 27M DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 267K packets, 29M bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 144K packets, 8644K bytes)
pkts bytes target prot opt in out source destination
26032 1563K DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 144K packets, 8666K bytes)
pkts bytes target prot opt in out source destination
108K 7893K MASQUERADE all -- * !docker0 192.11.231.0/24 0.0.0.0/0
91402 6409K MASQUERADE all -- * !docker_gwbridge 192.12.231.0/24 0.0.0.0/0
0 0 MASQUERADE tcp -- * * 192.11.231.2 192.11.231.2 tcp dpt:5000
0 0 MASQUERADE tcp -- * * 192.11.231.8 192.11.231.8 tcp dpt:8080
0 0 MASQUERADE tcp -- * * 192.11.231.8 192.11.231.8 tcp dpt:5005
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
4 264 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
9 540 RETURN all -- docker_gwbridge * 0.0.0.0/0 0.0.0.0/0
26315 1579K DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.231 tcp dpt:5000 to:192.11.231.2:5000
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.115 tcp dpt:8080 to:192.11.231.8:8080
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.115 tcp dpt:5005 to:192.11.231.8:5005
最主要最下面的DOCKER链,最后那三条规则就是在启动容器时指定了-p所导致的,那个5000端口你应该很熟悉,那是Register服务的端口号,我们可以通过它在局域网中上传和下载镜像,而且我在启动它的时候也只映射了这一个端口,如下:
[root@blog ~]# docker ps -f name=repo
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
867106b006da registry:2 "/entrypoint.sh /etc/" 4 months ago Up 4 months 10.100.124.231:5000->5000/tcp repo
跟前面的提到的三条规则对比一下,是不是明白了?iptables把从10.100.124.231:5000这个端口过来的数据转发给了192.11.231.2:5000,其中192.11.231.2就是repo容器的IP。
动态映射
现在我们就用它来说明好了,假设在repo这个容器中还有一个httpd服务并且监听着8080端口,那我要怎样才能从外面访问它呢?其实很简单啊,在宿机上增加一条iptables规则就可以了,如下:
[root@blog ~]# iptables -t nat -A DOCKER -d 10.100.124.231/32 ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.11.231.2:8080
再看下iptables中的规则:
[root@blog ~]# iptables -t nat -nvL
... #前面省略掉
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
4 264 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
9 540 RETURN all -- docker_gwbridge * 0.0.0.0/0 0.0.0.0/0
26315 1579K DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.231 tcp dpt:5000 to:192.11.231.2:5000
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.115 tcp dpt:8080 to:192.11.231.8:8080
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.115 tcp dpt:5005 to:192.11.231.8:5005
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 10.100.124.231 tcp dpt:8080 to:192.11.231.2:8080
现在已经可以通过10.100.124.231:8080来访问repo容器内的httpd服务了,当然了,可以增加就可以删除啊,只要把前面那条增加命令中的-A换成-D就可以了。。
换一种映射方式
后来想,既然可以动态映射,那-p选项是不是就可以省略掉了??So,后来在启动容器时,启动命令就变成这样了:
[root@blog ~]# docker run \
--name node1 \
-p 10.0.82.43:22:22 \
-tid node-base
[root@blog ~]# con_ip=`docker inspect --format='{{.NetworkSettings.Networks.bridge.IPAddress}}' node1`
[root@blog ~]# iptables -t nat -A DOCKER -d 10.0.82.43/32 ! -i docker0 -p tcp -m tcp --dport 1025:62000 -j DNAT --to-destination $con_ip
就是在启动容器的时候只映射一个端口出来,然后通过命令得到这个容器的IP,最后在宿主机上用iptables将容器的所有端口都映射出来,干净利落,又省去了很多麻烦。
-End-