这篇文章实在团队内部的分享,如下:
今天的分享不包含故事背景、docker的发展与应用…
分享源于对问题 ‘我已经在 Dockerfile 中通过 EXPOSE 指定了端口为何任然无法访问?’ 深入探索。其实今天的分享也可以视为对上一期峰哥分享的一个补充。
Docker容器的端口映射
容器的服务端口绑定到宿主机的端口上。效果就是:外部程序通过宿主机的P端口访问,就像直接访问 Docker 容器网络内部容器提供的服务一样。
今天的分享主要涉及到的 Docker run 命令,参数如下:
expose expose 参数有两种使用形式:
在 docker run
命令时指定 --expose
参数, 如 –expose=8080
在 Dockerfile 中,通过 EXPOSE
关键字
作用
EXPOSE
指令是声明运行时容器提供服务端口。
注意 :
仅仅是申明。并不是说你声明了这个端口,在运行容器的时候就会自动的暴露这个端口。使用时,还要依赖于容器的操作人员进一步指定网络规则。
本质上来说, EXPOSE
或者 --expose
只是为其他命令提供所需信息的元数据,或者只是告诉容器操作人员有哪些已知选择。
验证: 通过 docker run nginx
启动一个容器, 然后通过 `docker inspect id` 查看
1 2 3 4 5 6 7 "HostConfig" : { "PortBindings" : { "443/tcp" : null, "80/tcp" : null }, }
可以看到端口被标示成已暴露,但是没有定义任何与主机的端口映射。
-p/-P -p
与 -P
参数的完整形式:
1 2 -p, --publish list -P, --publish-all
这两个参数都是发布端口到宿主主机。但用法上存在一点区别。
-p
显式将一个或者一组端口从容器里绑定到宿主机上。-P
自动的将EXPOSE指令相关的每个端口映射到宿主机的端口上。
-p 参数常见的用法是: -p 宿主主机端口:容器端口
。 如果使用 `docker run -p 8080:80 nginx` 命令启动nginx 容器,那么容器中的 80端口会绑定到主机的8080端口。
这是,我们再通过 docker inspect id
来查看,将会看到下面的绑定关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 "HostConfig" : { "PortBindings" : { "443/tcp" : null, "80/tcp" : [ { "HostIp" : "0.0.0.0" , "HostPort" : "8080" } ] }, }
因为没有指定443端口的绑定,所以能看到仅有 80端口和宿主机存在端口映射关系。
另外,在使用-p参数是,我们可以忽略指定宿主主机端口。这是,docker会帮助我们自动的选择一个合适端口和容器端口进行绑定。这样做的好处是在启动多个容器时可以避免端口冲突。
例如上面的启动命令可以改为 docker run -p 80 nginx
, 这时如果我们本机的80端口被占用,docker就会自动的选择一个其他端口。我们可以通过 docker ps
或 docker inspect
命令来查看端口的映射关系。
-P 参数用法: docker run -P nginx
. -P 参数须配合 Dockerfile 一起使用, 能够将 Expose 声明的端口映射到宿主主机。
此时,使用 docker inspect
命令,可以看到 dockerfile 中申明的端口都已经绑定到了主机上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 "HostConfig" : { "PortBindings" : { "443/tcp" : [ { "HostIp" : "0.0.0.0" , "HostPort" : "443" } ], "80/tcp" : [ { "HostIp" : "0.0.0.0" , "HostPort" : "80" } ] }, }
-expose 和 -p 功能对比 我们可以通过如下的实验来更好的理解两参数之间的区别。
不在 Dockerfile
里 EXPOSE
,也不通过 -p
参数指定
在 Dockerfile
里 EXPOSE
,但不使用 -p
参数
在 Dockerfile
里 EXPOSE
,也使用 -p
参数
在 Dockerfile
里 EXPOSE
,也使用 -P
参数
只使用 -p
参数
结果
第一中情况: 不能在外网访问,也不能被 link 的 container 访问
第二种情况: 不能被外网访问,但是能被 link 的 container 访问
第三种情况: 能被外网访问,也能被 link 容器访问iw
第四种情况: 和第三种情况一样
第五种情况: 和第三种情况一样
Docker 端口映射原理 备注: 这一块挺乱的,我也没弄得很清楚,权当抛砖引玉了。
原本我理解端口映射是这样的一个通信过程:
Docker进程启动的时候,会在宿主主机创建路由,同时创建docker0网桥
容器启动的时候创建 vethxx 的网卡,同时链接到网桥
通过 -p 参数指定端口映射后, 创建iptables规则
当有流量通过宿主主机端口进来用,通过iptables 匹配到规则后,转换为容器对应的子网ip
主机的路由指定了 172.xx 网段的ip由 docker0 处理
docker0再将请求转发到子网中容器
后面和朋友了解了,发现还有 docker-proxy 的存在,于是,上面的理解是其实就片面的,不完善的。
到现在,关于 Docker 端口映射的实现一共有2方案.
1.7版本之前 docker-proxy + iptables DNAT 的方式 即,内网访问通过 iptables 外网访问通过 proxy
1.7版本之后的 iptables DNAT 完全由 iptables 实现
Docker 1.7版本起,Docker提供了一个配置项: -userland-proxy,以让 Docker 用户决定是否启用 docker-proxy,默认为true,即启用docker-proxy。 现在的 Docker 环境默认的是: -userland-proxy=true。iptables 和 docker-proxy 都会起作用。
-userland-proxy=true的情况下
在启用 docker-proxy 的情况下。每设置一对端口映射就会启动一个 docker-proxy 进程。
可以通过 ps 命令查看 docker-proxy 进程信息
1 2 3 4 ps -ef | grep docker-proxy root 5532 19713 0 2月20 ? 00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 443 -container-ip 172.19.0.8 -container-port 443 root 5546 19713 0 2月20 ? 00:00:01 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.19.0.8 -container-port 80
通过 sudo netstat -nltpu 查看本机的端口监听情况:
1 2 tcp6 0 0 :::80 :::* LISTEN 5546/docker-proxy tcp6 0 0 :::443 :::* LISTEN 5532/docker-proxy
通过上面的命令,会发现,我们映射到宿主机的端口被 docker-proxy 进程监听了。
别急,我们再看一下iptables,发现其中增加了对应的规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 sudo iptables-save -t nat # Generated by iptables-save v1.6.1 on Fri Feb 22 15:06:17 2019 *nat :PREROUTING ACCEPT [55751:14743102] :INPUT ACCEPT [55615:14734886] :OUTPUT ACCEPT [260072:22717364] :POSTROUTING ACCEPT [260200:22725044] :DOCKER - [0:0] -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.20.0.0/16 ! -o br-bf4c59b26d33 -j MASQUERADE -A POSTROUTING -s 172.19.0.0/16 ! -o br-e2ab1d51063d -j MASQUERADE -A POSTROUTING -s 172.18.0.0/16 ! -o br-c9af812dc067 -j MASQUERADE -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A POSTROUTING -s 172.19.0.2/32 -d 172.19.0.2/32 -p tcp -m tcp --dport 6379 -j MASQUERADE -A POSTROUTING -s 172.19.0.3/32 -d 172.19.0.3/32 -p tcp -m tcp --dport 27017 -j MASQUERADE -A POSTROUTING -s 172.19.0.5/32 -d 172.19.0.5/32 -p tcp -m tcp --dport 3306 -j MASQUERADE -A POSTROUTING -s 172.19.0.6/32 -d 172.19.0.6/32 -p tcp -m tcp --dport 9501 -j MASQUERADE -A POSTROUTING -s 172.19.0.6/32 -d 172.19.0.6/32 -p tcp sudo iptables -t filter -L Chain INPUT (policy ACCEPT) target prot opt source destination Chain FORWARD (policy DROP) target prot opt source destination DOCKER-USER all -- anywhere anywhere DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain DOCKER (4 references) target prot opt source destination ACCEPT tcp -- anywhere 172.19.0.2 tcp dpt:6379 ACCEPT tcp -- anywhere 172.19.0.3 tcp dpt:27017 ACCEPT tcp -- anywhere 172.19.0.5 tcp dpt:mysql ACCEPT tcp -- anywhere 172.19.0.6 tcp dpt:9501 ACCEPT tcp -- anywhere 172.19.0.6 tcp dpt:ssh ACCEPT tcp -- anywhere 172.19.0.8 tcp dpt:https ACCEPT tcp -- anywhere 172.19.0.8 tcp dpt:http Chain DOCKER-ISOLATION-STAGE-1 (1 references) target prot opt source destination DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere RETURN all -- anywhere anywhere Chain DOCKER-ISOLATION-STAGE-2 (4 references) target prot opt source destination DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere RETURN all -- anywhere anywhere Chain DOCKER-USER (1 references) target prot opt source destination RETURN all -- anywhere anywhere -m tcp --dport 22 -j MASQUERADE -A POSTROUTING -s 172.19.0.8/32 -d 172.19.0.8/32 -p tcp -m tcp --dport 443 -j MASQUERADE -A POSTROUTING -s 172.19.0.8/32 -d 172.19.0.8/32 -p tcp -m tcp --dport 80 -j MASQUERADE -A DOCKER -i br-bf4c59b26d33 -j RETURN -A DOCKER -i br-e2ab1d51063d -j RETURN -A DOCKER -i br-c9af812dc067 -j RETURN -A DOCKER -i docker0 -j RETURN -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 6379 -j DNAT --to-destination 172.19.0.2:6379 -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 27017 -j DNAT --to-destination 172.19.0.3:27017 -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 3306 -j DNAT --to-destination 172.19.0.5:3306 -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 9501 -j DNAT --to-destination 172.19.0.6:9501 -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 2222 -j DNAT --to-destination 172.19.0.6:22 -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 443 -j DNAT --to-destination 172.19.0.8:443 -A DOCKER ! -i br-e2ab1d51063d -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.19.0.8:80
这里的 DOCKER
对应的是由 docker 自定义的一组过滤规则,可以通过 sudo iptables -t filter -L
查看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 sudo iptables -t filter -L Chain INPUT (policy ACCEPT) target prot opt source destination Chain FORWARD (policy DROP) target prot opt source destination DOCKER-USER all -- anywhere anywhere DOCKER-ISOLATION-STAGE-1 all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED DOCKER all -- anywhere anywhere ACCEPT all -- anywhere anywhere ACCEPT all -- anywhere anywhere Chain OUTPUT (policy ACCEPT) target prot opt source destination Chain DOCKER (4 references) target prot opt source destination ACCEPT tcp -- anywhere 172.19.0.2 tcp dpt:6379 ACCEPT tcp -- anywhere 172.19.0.3 tcp dpt:27017 ACCEPT tcp -- anywhere 172.19.0.5 tcp dpt:mysql ACCEPT tcp -- anywhere 172.19.0.6 tcp dpt:9501 ACCEPT tcp -- anywhere 172.19.0.6 tcp dpt:ssh ACCEPT tcp -- anywhere 172.19.0.8 tcp dpt:https ACCEPT tcp -- anywhere 172.19.0.8 tcp dpt:http Chain DOCKER-ISOLATION-STAGE-1 (1 references) target prot opt source destination DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere DOCKER-ISOLATION-STAGE-2 all -- anywhere anywhere RETURN all -- anywhere anywhere Chain DOCKER-ISOLATION-STAGE-2 (4 references) target prot opt source destination DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere DROP all -- anywhere anywhere RETURN all -- anywhere anywhere Chain DOCKER-USER (1 references) target prot opt source destination RETURN all -- anywhere anywhere
-userland-proxy=false的情况下
待研究~
性能
docker-proxy 在网络上吐槽的比较多,因为每一对端口映射都会对一个 docker-proxy进程,如果端口较多,可能就会带来性能问题。且在单个 docker-proxy 的情况下,性能比 iptables 略差。
总结 - –link 能够访问 expose 声明的端口
-expose 仅声明端口,并不会自动映射到宿主主机
-p 指定端口映射关系
-P 将 expose 声明的端口发布到宿主主机
在处理端口映射是,iptables 规则优先,如果没有匹配到iptables规则,则由 docker-proxy处理
待深入研究的问题:
container <-> container、host <-> container、 container <-> host 各自怎么选择策略的
iptables 规则