docker 网络之端口映射不完全探索
这篇文章实在团队内部的分享,如下:
今天的分享不包含故事背景、docker的发展与应用...
分享源于对问题 '我已经在 Dockerfile 中通过 EXPOSE 指定了端口为何任然无法访问?' 深入探索。其实今天的分享也可以视为对上一期峰哥分享的一个补充。
Docker容器的端口映射
容器的服务端口绑定到宿主机的端口上。效果就是:外部程序通过宿主机的P端口访问,就像直接访问 Docker 容器网络内部容器提供的服务一样。
今天的分享主要涉及到的 Docker run 命令,参数如下:
- -p/-P
- --expose
expose
expose 参数有两种使用形式:
- 在
docker run
命令时指定--expose
参数, 如 --expose=8080 - 在 Dockerfile 中,通过
EXPOSE
关键字
作用
EXPOSE
指令是声明运行时容器提供服务端口。
注意:
仅仅是申明。并不是说你声明了这个端口,在运行容器的时候就会自动的暴露这个端口。使用时,还要依赖于容器的操作人员进一步指定网络规则。
本质上来说, EXPOSE
或者 --expose
只是为其他命令提供所需信息的元数据,或者只是告诉容器操作人员有哪些已知选择。
验证: 通过 docker run nginx
启动一个容器, 然后通过 `docker inspect id` 查看
"HostConfig": {
"PortBindings": {
"443/tcp": null,
"80/tcp": null
},
}
可以看到端口被标示成已暴露,但是没有定义任何与主机的端口映射。
-p/-P
-p
与 -P
参数的完整形式:
-p, --publish list
-P, --publish-all
这两个参数都是发布端口到宿主主机。但用法上存在一点区别。
-p
显式将一个或者一组端口从容器里绑定到宿主机上。-P
自动的将EXPOSE指令相关的每个端口映射到宿主机的端口上。
-p 参数常见的用法是: -p 宿主主机端口:容器端口
。 如果使用 `docker run -p 8080:80 nginx` 命令启动nginx 容器,那么容器中的 80端口会绑定到主机的8080端口。
这是,我们再通过 docker inspect id
来查看,将会看到下面的绑定关系:
"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 中申明的端口都已经绑定到了主机上。
"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 进程信息
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 查看本机的端口监听情况:
tcp6 0 0 :::80 :::* LISTEN 5546/docker-proxy
tcp6 0 0 :::443 :::* LISTEN 5532/docker-proxy
通过上面的命令,会发现,我们映射到宿主机的端口被 docker-proxy 进程监听了。
别急,我们再看一下iptables,发现其中增加了对应的规则:
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
查看到:
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 规则
本作品采用《CC 协议》,转载必须注明作者和本文链接