在 Docker 中,默认情况下容器无法直接与外部网络通信。 为了使外部网络能够访问容器内的服务,Docker 提供了端口映射功能,通过将宿主机的端口映射到容器内的端口,外部可以通过宿主机的IP和端口访问容器内的服务
以下通过动手演示, 安装一个Flask容器, 解释端口映射从外部访问容器的原理
安装一个Flask容器
文件结构
1 | . |
Dockerfile
1 | FROM rockylinux:9.3 |
app.py
1 | from flask import Flask |
构建镜像
1 | docker build -t flask-app:1.0 . |
启动容器, 进行端口映射
1 | docker run -d -p 80:5000 flask-app:1.0 |
从外部访问 (192.168.52.203是我的虚拟机IP)
1 | # curl 192.168.52.203 |
端口映射原理
当执行docker run -d -p 80:5000 flask-app:1.0
时, Docker会配置iptables规则来实现端口映射, 流程如下:
1. iptables PREROUTING 链处理, 做DNAT转换
外部请求到达宿主机时, iptables的PREROUTING链处理该请求, 根据DNAT规则将目标IP和端口(192.168.52.203:5000)替换为容器的IP和端口(172.17.0.2:5000)
1 | # iptables -t nat -L PREROUTING |
2. DNAT转换后的数据包转发到容器
经过DNAT转换后的数据包会发送到虚拟网桥docker0, 再通过veth设备转发到容器的虚拟网络接口(eth0)
1 | 宿主机的路由表 |
3. 容器接受请求并返回响应, 响应报文通过iptables POSTROUTING链处理, 做SNAT转换
容器接受请求并返回响应,响应报文先发送到 docker0, 再通过 SNAT(MASQUERADE)规则,将源IP和端口(172.17.0.2:5000)替换为宿主机的IP和端口,最后发送回客户端
1 | # iptables -t nat -L POSTROUTING -v |
附: wireshark抓包结果
- 客户端IP: 192.168.52.202
- 运行容器的宿主机IP: 192.168.52.203
- 容器IP: 172.17.0.2
抓宿主机网卡的包
1 | tcpdump -i ens160 -nn tcp and not port 22 -w ens160.pcap |
抓docker0的包
1 | tcpdump -i docker0 -nn tcp -w docker0.pcap |