以前就好奇docker run时的port公开方式-p 8080:8080和--expose(或者Dockerfile里的EXPOSE)的区别,

当时相信了stackoverflow上高手的回答Difference between "expose" and "publish" in docker - Stack Overflow,提到

如果什么都不指定,那么除了该容器自己,谁都不能访问那个port。

**后来有一天细看Docker的网络的时候,觉得不应该是这样。**从Docker主机之外的确不能访问,这点没有疑问,可是从Docker主机之内或者其他容器,其实能够访问,而且很合理。

Docker在主机上做了个docker0网卡172.17.0.1。又为每个容器创建了一个vethXxxx网卡。

网卡是有两头的,一头接外边,一头接容器,就像管道。

这个veth网卡,一头给了容器做eth0 172.17.0.N,另一头被默认地被加入到docker0网卡上,相当把所有的容器的网卡都插到一个hub上了,这就是互通的基础。

照这么说,在其中一个容器里侦听eth0的端口,那么主机和其他容器都应该能够访问到才对。


实验环境: Mac OS X EI Capitan 10.11.5

$ docker-machine -v
docker-machine version 0.8.0-rc1, build fffa6c9
$ docker -v
Docker version 1.12.0-rc3, build 91e29e8, experimental

启动一个容器,什么-p -P --expose都不指定。 就用busybox吧,很小。起了以后就是一个bash等在那儿。

$ docker run -it busybox

得到他的ip,一般第一个启动的就是172.17.0.2。

/ # ifconfig eth0 |grep 'inet addr:'
          inet addr:172.17.0.2  ...

在容器里执行一个netcat侦听端口7777。 一旦得到连接就发送date命令的结果给对方。

/ # nc -ll -p 7777 -e date

在别的容器里连接这个端口

$ docker run busybox nc 172.17.0.2 7777
Fri Jul  8 03:50:34 UTC 2016

能通!

在docker主机里连接这个端口

$ docker-machine ssh default nc 172.17.0.2 7777
Fri Jul  8 03:49:40 UTC 2016

自然也能通。

因为不是在Linux主机上,所以这里的docker主机实际值docker-machine这个boot2docker虚拟机。

这个现象应该不是因为实验版本导致的问题,就是这么设计的。

  • 如果什么都不指定,那么从Docker主机以及其他容器,都能访问那个port。
  • 当有时候忘了加-p端口映射选项就启动了容器做了一堆事儿之后,如果立刻想从Docker主机外访问的,可是却舍不得退出容器,那就还有补救机会。

就在Docker主机里用iptables加个端口转发规则,(VirtualBox里也可以加一个端口映射让本地访问):

$ docker ssh default
docker@default:~$ sudo iptables -A PREROUTING -t nat -p tcp --dport 7777 -j DNAT --to 172.17.0.2:7777

或者在真正的Mac OS X上加个路由,允许经Docker主机(一般为192.168.99.100)访问到172.17.0.N

sudo route -n add 172.17.0.0/24 192.168.99.100

  • 而对于-p 7777:7777这样的用法,这是没有疑问的,的确就在Docker主机上被加了个端口转接的rule了,所以和从Docker主机之外能够访问这个端口了。
  • 剩下一个--expose 7777(或者Dockerfile里的EXPOSE 7777),实质没有什么用,也许是给发现服务用的。并没有对iptables做任何改变。
  • 直到-P(--publish-all)时才会被用到--expose里的端口信息。
  • 就是说, 只有既指定--expose 7777(或者Docerfile里的EXPOSE 7777)又指定-P(--publish-all)时,才会有实际动作。但这是把Docker主机上的随机端口映射给容器里的7777,所以一般不用。

例如,先做一个EXPOSE 7777端口的Docker image。

$ cat Dockerfile
FROM busybox
EXPOSE 7777
$ docker build -t busybox_expose_7777 - < Dockerfile
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM busybox
 ---> 2b8fd9751c4c
Step 2 : EXPOSE 7777
 ---> Running in bddf30c42dcd
 ---> 2c92defacdd4
Removing intermediate container bddf30c42dcd
Successfully built 2c92defacdd4

然后运行时加上 --publish-all,

docker run -it --publish-all busybox_expose_7777
/ # nc -ll -p 7777 -e date

这时再看看到底Docker主机的哪个端口被映射到容器里的7777了。

docker ps
...
fca504f3bc49        busybox_expose_7777   "sh"  ... 0.0.0.0:32769->7777/tcp   nostalgic_galileo

就是这个32769端口了。那访问Docker主机的32769端口看看。

(Docker主机的ip一般是192.168.99.100(可以从DOCKER_HOST环境变量里看出来))

$ nc 192.168.99.100 32769
Fri Jul  8 11:14:08 UTC 2016

的确如此。

真真是聊胜于无的东西,难以置信谁会用这个。却搞得不好理解,文档也说的不明不白。


对于新型的,不用VirtualBoxDocker for Mac或者Docker for Windows,会怎么样呢?

结果:在其他容器里也是可以访问别的容器的端口的。在docker主机里就不知道了,因为进不去,不过应该是行的。

这两种都直接利用Mac/Windows自带的虚拟设备API(例如Hypervisor.framework)直接生成一个linux虚拟机作为docker主机,

而且它刻意淡化这个虚拟机的存在,我暂时找不到命令进入这个虚拟机,所以没做完这个实验,不过无所谓了,原理一样,应该是一样的结果。

顺便,这种新型工具,以xhyve虚拟机为基础,非常轻巧好用,连VirtualBox都不用装了,一切和以前一样的用法,除了docker-machine命令不起作用。