📘 第05课:端口映射与网络

基础 掌握Docker网络模型,理解端口映射、网络模式与容器间通信

🌐 Docker网络模型

Docker使用Linux网络命名空间(Network Namespace)为每个容器提供独立的网络栈。容器通过虚拟网桥与外部通信:

┌─────────────────────────────────────────────────┐ 宿主机 (Host) ┌──────────┐ ┌──────────┐ ┌──────────┐ 容器 A │ │ 容器 B │ │ 容器 C │ eth0 │ │ eth0 │ │ eth0 │ │172.17.0.2│ │172.17.0.3│ │172.17.0.4│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ ┌────┴──────────────┴──────────────┴────┐ docker0 (bridge) 172.17.0.1 │ └──────────────────┬───────────────────┘ │ NAT eth0 (宿主机网卡) └─────────────────────┼────────────────────────────┘外部网络

🔌 端口映射详解

-p 参数四种写法

# 1. 指定IP:主机端口:容器端口
docker run -d --name web1 -p 127.0.0.1:8080:80 nginx:alpine
# 仅本机可访问 ✅ 验证通过

# 2. 主机端口:容器端口(所有网卡)
docker run -d --name web2 -p 8081:80 nginx:alpine
# 所有网络接口可访问 ✅ 验证通过

# 3. 容器端口(随机分配主机端口)
docker run -d --name web3 -p 80 nginx:alpine
docker port web3
# 80/tcp -> 0.0.0.0:32768

# 4. 多端口映射
docker run -d --name web4 \
  -p 8082:80 \
  -p 8443:443 \
  nginx:alpine
# ✅ 验证通过:同时映射HTTP和HTTPS

# -P 大写:映射所有EXPOSE端口到随机端口
docker run -d --name web5 -P nginx:alpine
docker port web5
# 80/tcp -> 0.0.0.0:32769

端口映射原理:iptables DNAT

# 查看Docker创建的NAT规则
iptables -t nat -L DOCKER -n 2>/dev/null | head -10
# DNAT  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 to:172.17.0.2:80

# 查看端口映射详情
docker inspect web2 --format '{{json .NetworkSettings.Ports}}' | python3 -m json.tool
# {"80/tcp": [{"HostIp": "0.0.0.0", "HostPort": "8081"}]}
⚠️ 端口冲突:如果主机端口已被占用,容器启动会失败。用 ss -tlnp | grep <端口> 检查端口占用。

🏗 Docker网络模式

五大网络模式

模式参数特点适用场景
bridge--network bridge默认模式,独立网络栈大多数容器
host--network host共享宿主机网络栈性能敏感应用
none--network none无网络,只有lo接口安全隔离任务
container--network container:<id>共享另一容器网络sidecar模式
overlay--network overlay跨主机通信Swarm/集群

Bridge模式(默认)

# 创建自定义bridge网络(推荐)
docker network create --driver bridge \
  --subnet 172.20.0.0/16 \
  --gateway 172.20.0.1 \
  my-net

# 在自定义网络中运行容器
docker run -d --name app-web --network my-net nginx:alpine
docker run -d --name app-api --network my-net alpine:latest \
  sh -c "while true; do sleep 3600; done"

# 容器间通过容器名通信(自动DNS)
docker exec app-api ping -c 2 app-web
# PING app-web (172.20.0.2): 56 data bytes
# 64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.100 ms
# ✅ 验证通过:容器名DNS解析正常
💡 自定义bridge网络的容器可以通过容器名互相访问,默认bridge网络不行(只能用IP)。这是使用自定义网络的核心原因。

Host模式

# Host模式——直接使用宿主机网络
docker run -d --name host-web --network host nginx:alpine
# 容器直接监听宿主机80端口,无需-p映射

# 验证
curl -s http://localhost:80 | head -5
# ...

# 注意:host模式下 -p 参数无效
docker run -d --name host-test --network host -p 9090:80 nginx:alpine
docker logs host-test 2>&1 | head -3
# WARNING: Published ports are discarded when using host network mode

None模式

# 无网络容器
docker run -it --rm --network none alpine:latest sh -c "ip addr"
# 1: lo: <LOOPBACK,UP> mtu 65536
#     inet 127.0.0.1/8 scope host lo
# 只有回环接口,无eth0 ✅

# 无法访问外部
docker run --rm --network none alpine:latest ping -c 1 8.8.8.8
# ping: bad address '8.8.8.8' ✅ 无外部网络

Container模式(共享网络命名空间)

# 主容器
docker run -d --name main-app -p 8083:80 nginx:alpine

# Sidecar容器共享主容器网络
docker run -d --name sidecar \
  --network container:main-app \
  alpine:latest sh -c "while true; do sleep 3600; done"

# sidecar看到的是main-app的网络栈
docker exec sidecar ip addr
# 与main-app完全相同的IP地址 ✅

# sidecar可直接访问localhost:80
docker exec sidecar wget -qO- http://localhost:80 | head -3
# ... ✅ 共享网络命名空间

🔗 自定义网络进阶

# 创建带DNS别名的网络
docker network create --driver bridge \
  --subnet 172.21.0.0/16 \
  app-network

# 运行容器并指定网络别名
docker run -d --name db-server \
  --network app-network \
  --network-alias mysql \
  --network-alias database \
  mysql:8.0

# 同网络容器可通过任一别名访问
docker run --rm --network app-network alpine:latest \
  ping -c 1 mysql
# PING mysql (172.21.0.2) ✅

docker run --rm --network app-network alpine:latest \
  ping -c 1 database
# PING database (172.21.0.2) ✅

网络管理命令

# 列出所有网络
docker network ls
# NETWORK ID     NAME          DRIVER    SCOPE
# a1b2c3d4       bridge        bridge    local
# e5f6g7h8       host          host      local
# i9j0k1l2       none          null      local
# m3n4o5p6       my-net        bridge    local

# 查看网络详情
docker network inspect my-net

# 连接运行中容器到另一网络
docker network connect app-network app-web
# app-web现在同时属于两个网络 ✅

# 断开网络
docker network disconnect app-network app-web

# 删除未使用的网络
docker network prune

# 删除指定网络
docker network rm my-net

🧪 实战:多容器网络拓扑

# 创建前端和后端网络
docker network create frontend-net
docker network create backend-net

# 数据库——仅后端网络
docker run -d --name db \
  --network backend-net \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0-oracle

# API服务——同时属于两个网络
docker run -d --name api \
  --network frontend-net \
  nginx:alpine
docker network connect backend-net api

# Web前端——仅前端网络
docker run -d --name web \
  --network frontend-net \
  -p 8084:80 \
  nginx:alpine

# 验证网络隔离
docker exec web ping -c 1 api
# PING api ✅ 前端可访问API

docker exec web ping -c 1 db
# ping: bad address 'db' ✅ 前端无法访问数据库

docker exec api ping -c 1 db
# PING db ✅ API可访问数据库

# 清理
docker rm -f web api db
docker network rm frontend-net backend-net

🔧 故障排查

端口映射后外部无法访问 1. 检查容器是否运行:docker ps
2. 检查容器内服务是否监听:docker exec <c> ss -tlnp
3. 检查防火墙:iptables -L -nufw status
4. 检查绑定地址:ss -tlnp | grep <端口>,如果绑定127.0.0.1则外部不可达
容器间无法通过名称通信 默认bridge网络不支持DNS。使用自定义bridge网络:docker network create my-net,然后用 --network my-net 启动容器。
WARNING: IPv4 forwarding is disabled 启用IP转发:sysctl -w net.ipv4.ip_forward=1,并写入 /etc/sysctl.conf 持久化。

📝 练习

  1. 网络隔离实验:创建两个自定义bridge网络 net-anet-b,各放一个容器,验证跨网络不可通信
  2. Dual-homed容器:创建一个同时连接两个网络的容器,验证它可以与两个网络中的容器通信
  3. 端口映射测试:用 -p 127.0.0.1:9999:80 启动Nginx,分别从本机和外部访问,观察差异
  4. 网络性能对比:分别用 bridge 和 host 模式运行 iperf3,对比网络吞吐量

🏆 成就解锁:网络架构师