所有文章

Docker - 动态映射端口

虽然设计者们一再地强调在使用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-


编写日期:2017-05-31