本文系统梳理 Docker 容器的核心操作、资源管控、底层实现机制以及网络模型,适合希望深入理解 Docker 内核原理的开发者与运维工程师。
文章目录
-
- 一、容器
-
- [1. 如何运行容器](#1. 如何运行容器)
-
- [示例:create + start 分步执行](#示例:create + start 分步执行)
- [示例:docker run 一步到位](#示例:docker run 一步到位)
- [2. 让容器长期运行](#2. 让容器长期运行)
- [3. 两种进入容器的方法](#3. 两种进入容器的方法)
-
- [docker attach](#docker attach)
- [docker exec](#docker exec)
- [attach 与 exec 的核心区别](#attach 与 exec 的核心区别)
- [4. 运行容器的最佳实践](#4. 运行容器的最佳实践)
- [5. 容器常用操作](#5. 容器常用操作)
- [6. 一张图搞懂容器状态机](#6. 一张图搞懂容器状态机)
- [7. 限制容器对内存的使用](#7. 限制容器对内存的使用)
- [8. 限制容器对 CPU 的使用](#8. 限制容器对 CPU 的使用)
- [9. export 和 import 容器](#9. export 和 import 容器)
-
- [docker export ------ 容器导出为 tar 包](#docker export —— 容器导出为 tar 包)
- [docker import ------ 将 tar 包导入为镜像](#docker import —— 将 tar 包导入为镜像)
- [docker save vs docker export 对比](#docker save vs docker export 对比)
- [10. 实现容器的底层技术](#10. 实现容器的底层技术)
-
- [cgroup 深入](#cgroup 深入)
- [namespace ------ 资源隔离的基石](#namespace —— 资源隔离的基石)
- 二、网络
-
- [1. Docker 网络概览](#1. Docker 网络概览)
- [2. none 网络](#2. none 网络)
- [3. host 网络](#3. host 网络)
- [4. Bridge 网络(核心重点)](#4. Bridge 网络(核心重点))
- [5. 自定义容器网络](#5. 自定义容器网络)
-
- [创建自定义 bridge 网络](#创建自定义 bridge 网络)
- 使用自定义网络
- [指定静态 IP](#指定静态 IP)
- [6. 容器之间的连通性](#6. 容器之间的连通性)
- [7. 容器通信的三种方式](#7. 容器通信的三种方式)
-
- [方式一:IP 通信](#方式一:IP 通信)
- [方式二:Docker DNS Server](#方式二:Docker DNS Server)
- [方式三:Joined 容器](#方式三:Joined 容器)
- [8. 容器如何访问外部世界](#8. 容器如何访问外部世界)
- [9. 外部世界如何访问容器](#9. 外部世界如何访问容器)
- 三、总结
一、容器
1. 如何运行容器
docker run 是启动容器的核心命令,其本质等价于 docker create + docker start 的组合。
在讨论 Dockerfile 时我们已经了解到,可以通过三种方式指定容器启动时执行的命令:
- CMD 指令
- ENTRYPOINT 指令
- 在
docker run命令行中直接指定
示例:create + start 分步执行
bash
# 使用 ubuntu 镜像创建容器
[root@docker ~]# docker create ubuntu
eb1aa0ca86b2d49250ebe64913af50e88482ad68b9c3e61ef8f8da9c24b00f7a
# create 的容器状态为 Created
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eb1aa0ca86b2 ubuntu "/bin/bash" 2 seconds ago Created quizzical_goldwasser
# 启动容器
[root@docker ~]# docker start eb1aa0ca86b2
eb1aa0ca86b2
# 查看容器状态 ------ 启动了又退出了
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
eb1aa0ca86b2 ubuntu "/bin/bash" 23 seconds ago Exited (0) 3 seconds ago quizzical_goldwasser
示例:docker run 一步到位
bash
[root@docker ~]# docker run ubuntu pwd
/
容器启动时执行 pwd,返回的 / 是容器中的当前目录。
执行 docker ps 或 docker container ls 可以查看 Docker host 中当前运行的容器:
bash
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
加 -a 参数可显示所有状态的容器(包括已退出的)。
2. 让容器长期运行
这种"一闪而过"的容器通常不是我们想要的结果。我们希望容器能够保持 running 状态,这样才能被我们使用。
核心原理:容器的生命周期依赖于启动时执行的命令,只要该命令不结束,容器也就不会退出。
理解了这个原理,我们就可以通过执行一个长期运行的命令来保持容器的运行状态:
bash
[root@docker ~]# docker run ubuntu /bin/bash -c "while true ; do sleep 1 ; echo hahaha; done"
while 语句让 bash 不会退出。不过这种方法有个缺点:它占用了一个终端 。我们可以加上 -d 参数以后台方式启动容器:
bash
[root@docker ~]# docker run -d ubuntu /bin/bash -c "while true ; do sleep 1 ; echo hahaha; done"
2a0bfa267fe146753b4fc8b23d55b08fbe3a5f9b5e093de6885133f6bbd20c56
容器启动后直接回到了 docker host 的终端。返回的长字符串即为容器的完整 ID。
容器标识方式
- 长 ID:启动时返回的完整哈希值
- 短 ID:长 ID 的前 12 个字符
- 容器名称 :可通过
--name参数显式命名,不指定则 Docker 自动分配
bash
[root@docker ~]# docker stop 081be2bc2e1d
081be2bc2e1d
运行后台服务
容器更常见的用途是运行后台服务,例如 HTTP Server:
bash
[root@docker ~]# docker run -d --name my_httpd httpd
通过 --name 指定了容器名称,容器运行命令为 httpd-foreground(由 CMD 指令指定)。
3. 两种进入容器的方法
我们经常需要进入容器内部进行工作,比如查看日志、调试、启动其他进程等。Docker 提供了两种方法:attach 和 exec。
docker attach
通过 docker attach 可以连接到容器启动命令的终端:
bash
[root@docker ~]# docker run -d ubuntu /bin/bash -c "while true ; do sleep 1 ; echo I_am_in_container ; done"
dc508b94447f83b46080267580607569a187fcc7f780433f646e9d66949731a6
[root@docker ~]# docker attach dc508b94447f
I_am_in_container
I_am_in_container
I_am_in_container
提示 :可通过
Ctrl+P然后Ctrl+Q组合键退出 attach 终端。
docker exec
通过 docker exec 进入容器:
bash
[root@docker ~]# docker exec -it dc508b94447f bash
root@dc508b94447f:/#
参数说明:
-it:以交互模式打开 pseudo-TTY,执行 bash- 进入容器后,hostname 即为容器的短 ID
- 可以像在普通 Linux 中一样执行命令
- 执行
exit退出容器,回到 docker host
docker exec -it <container> bash|sh是进入容器最常用的方式。
attach 与 exec 的核心区别
| 对比维度 | docker attach | docker exec |
|---|---|---|
| 终端类型 | 直接进入容器启动命令的终端 | 在容器中打开新终端 |
| 进程行为 | 不会启动新进程 | 可以启动新进程 |
| 适用场景 | 查看启动命令的输出 | 日常操作(推荐) |
如果只是为了查看启动命令的输出,也可以使用 docker logs 命令:
bash
[root@docker ~]# docker logs -f dc508b94447f
I_am_in_container
I_am_in_container
-f 的作用与 tail -f 类似,能够持续打印输出。
4. 运行容器的最佳实践
按用途划分,容器大致可分为两类:
服务类容器
以 daemon 形式运行,对外提供服务(如 Web Server、数据库等)。通过 -d 以后台方式启动是最佳选择。如需排查问题,可通过 exec -it 进入容器。
工具类容器
提供临时工作环境,通常以 run -it 方式运行:
bash
[root@docker ~]# docker run -it busybox
/ # wget www.baidu.com
Connecting to www.baidu.com (223.109.82.6:80)
saving to 'index.html'
index.html 100% |********************| 2381 0:00:00 ETA
'index.html' saved
/ # exit
工具类容器多使用基础镜像,例如 busybox、debian、ubuntu 等。
容器运行小结
生命周期管理:
- 当 CMD / ENTRYPOINT / docker run 指定的命令运行结束时,容器停止
- 通过
-d参数在后台启动容器 - 通过
exec -it可进入容器并执行命令
容器标识方式:
- 短 ID
- 长 ID
- 容器名称(通过
--name命名,可通过docker rename重命名)
5. 容器常用操作
stop / start / restart
通过 docker stop 可以停止运行的容器。容器在 Docker host 中实际上是一个进程,docker stop 本质上是向该进程发送 SIGTERM 信号。
如果想快速停止容器,可使用 docker kill,其作用是向容器进程发送 SIGKILL 信号。
bash
# 停止容器
[root@docker ~]# docker stop <container>
# 快速停止
[root@docker ~]# docker kill <container>
# 重新启动已停止的容器(保留首次启动的所有参数)
[root@docker ~]# docker start <container>
# 重启容器 = stop + start
[root@docker ~]# docker restart <container>
自动重启策略
容器可能因某种错误而停止运行。对于服务类容器,我们通常希望它能自动重启。启动容器时设置 --restart 即可:
bash
# 无论何种原因退出(docker stop 除外),立即重启
[root@docker ~]# docker run -d --restart=always httpd
# 进程退出代码非 0 时重启,最多 3 次
[root@docker ~]# docker run -d --restart=on-failure:3 httpd
注意 :
--restart=always在容器因docker stop或docker kill退出时不会自动重启。
pause / unpause
当需要暂时让容器暂停工作(如打快照、释放 CPU 资源)时:
bash
# 暂停容器(不占用 CPU 资源)
[root@docker ~]# docker pause <container>
# 恢复运行
[root@docker ~]# docker unpause <container>
删除容器
使用 Docker 一段时间后,host 上可能会积累大量已退出的容器,它们依然占用文件系统资源:
bash
# 删除指定容器(可一次指定多个)
[root@docker ~]# docker rm 35af7150bd17 9769bb915803
# 批量删除所有已退出的容器
[root@docker ~]# docker rm -f $(docker ps -aq -f status=exited)
# ⚠️ 慎用:删除所有状态的容器
[root@docker ~]# docker rm -f $(docker ps -aq)
docker rm删除容器,docker rmi删除镜像,注意区分。
6. 一张图搞懂容器状态机
容器的各种状态之间的转换关系可以总结为如下状态机:
docker create→ Created 状态docker start→ 以后台方式启动容器docker run=docker create+docker startdocker stop→ 发送 SIGTERM,容器进入 Exited 状态docker kill→ 发送 SIGKILL,容器快速退出docker restart→ stop + startdocker pause/docker unpause→ Paused ↔ Running
关于 --restart 的重要补充 :只有当容器的启动进程退出 时(正常退出或 OOM 等非正常退出),--restart 才生效。如果容器是因为执行 docker stop 或 docker kill 退出,则不会自动重启。
7. 限制容器对内存的使用
cgroup 简介
Docker 通过 cgroup(Control Groups)来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面。
cgroup 是 Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源的机制,被 LXC、Docker 等很多项目用于实现进程资源控制。其核心子系统包括:
| 子系统 | 功能 |
|---|---|
| blkio | 限制每个块设备的 I/O 控制(磁盘、光盘、USB 等) |
| cpu | 通过调度程序为 cgroup 任务提供 CPU 访问 |
| cpuacct | 产生 cgroup 任务的 CPU 资源报告 |
| cpuset | 为多核 CPU 的 cgroup 任务分配独立的 CPU 和内存 |
| devices | 允许或拒绝 cgroup 任务对设备的访问 |
| freezer | 暂停和恢复 cgroup 任务 |
| memory | 设置内存限制并产生内存资源报告 |
| net_cls | 标记网络包以供 cgroup 使用 |
| ns | 命名空间子系统 |
| perf_event | 监测属于特定 group 的所有线程 |
内存限额配置
与操作系统类似,容器可使用的内存包括物理内存 和 swap。Docker 通过以下参数控制:
-m或--memory:设置内存使用限额(如 100M、2G)--memory-swap:设置 内存+swap 的使用限额
bash
# 允许容器最多使用 200M 内存和 100M swap
[root@docker ~]# docker run -m 200M --memory-swap=300M ubuntu
--memory-swap 的几种情况:
| 配置 | 含义 |
|---|---|
-m 300m --memory-swap=1g |
可用 300M 物理内存 + 700M swap |
不设置 --memory-swap |
swap 默认为 -m 值的两倍 |
--memory-swap 等于 -m |
容器不能使用 swap |
--memory-swap=-1 |
内存受限,swap 不受限制 |
压力测试实验
使用 stress 工具构建测试镜像:
bash
# Dockerfile
FROM ubuntu
MAINTAINER gaoqd "6946630@qq.com"
RUN apt-get -y update && apt-get -y install stress
ENTRYPOINT ["/usr/bin/stress"]
bash
# 构建镜像
[root@docker ~]# docker build -t ubuntu-with-stress .
测试内存限额:
bash
# 分配 280M(在 300M 限额内)------ 正常工作
[root@docker ~]# docker run -it -m 200M --memory-swap=300M ubuntu-with-stress --vm 1 --vm-bytes 280M -v
# 分配超过限额 ------ stress 线程报错,容器退出
[root@docker ~]# docker run -it -m 200M --memory-swap=300M ubuntu-with-stress --vm 1 --vm-bytes 400M -v
8. 限制容器对 CPU 的使用
默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制。
Docker 通过 -c 或 --cpu-shares 设置容器使用 CPU 的权重,默认值为 1024。
核心要点 :--cpu-shares 是一个弹性加权值,不能保证获得固定的 CPU 资源。只有在 CPU 资源紧张时,权重分配才会生效。
例如,两个容器 A(1024)和 B(512),当两者都需要 CPU 资源时,A 获得的 CPU 时间片是 B 的两倍。但如果 A 空闲,B 可以独占全部可用 CPU。
bash
# 启动 container_A,cpu share 为 1024
[root@docker ~]# docker run --name "container_A" -it -c 1024 ubuntu-with-stress --cpu 4 -v
# 启动 container_B,cpu share 为 512
[root@docker ~]# docker run --name "container_B" -it -c 512 ubuntu-with-stress --cpu 4 -v
# 查看 cgroup 配置
[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/<容器长ID>/cpu.shares
1024
# 实时监控资源使用
[root@docker ~]# docker stats
经验之谈 :
--cpu设置工作线程数量应等于 host 的 CPU 核数,才能将 CPU 压满,使权重比例清晰可见。
9. export 和 import 容器
docker export ------ 容器导出为 tar 包
bash
# 创建测试容器
[root@docker ~]# docker run -d --name httpd1 httpd
e4f0a329c4df50ef0afb0bf21e22edc20e5a24c03ae64c6340aa2992d6e32525
# 导出容器(无论是否运行中)
[root@docker ~]# docker export -o myhttpd.tar httpd1
docker import ------ 将 tar 包导入为镜像
bash
[root@docker ~]# docker import myhttpd.tar myweb:v1
sha256:9fcb90561d2014130ef19f9dd8af5efd7a0a4a2154907bc818ac6b887f13755c
docker save vs docker export 对比
| 对比维度 | docker save | docker export |
|---|---|---|
| 操作对象 | 镜像 | 容器 |
| 元数据 | 保留所有元数据和历史记录 | 丢失所有元数据和历史记录 |
| 导入命令 | docker load |
docker import |
| 交叉兼容 | ❌ export 的文件不能用 load 导入 | ❌ save 的文件不能用 import 导入 |
10. 实现容器的底层技术
cgroup 和 namespace 是实现容器最关键的两种技术:cgroup 实现资源限额,namespace 实现资源隔离。
cgroup 深入
cgroup 的配置实际存储在 /sys/fs/cgroup 目录中。启动容器时,Linux 会为每个容器创建一个以容器长 ID 命名的 cgroup 目录:
bash
[root@docker ~]# docker run -it --cpu-shares 512 ubuntu-with-stress -c 1 -v
# 查看 cgroup 配置
[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/<容器长ID>/cpu.shares
512
namespace ------ 资源隔离的基石
Linux 使用了六种 namespace,分别对应六种资源:
| Namespace | 隔离的资源 |
|---|---|
| Mount | 文件系统 ------ 容器拥有独立的文件系统视图 |
| UTS | hostname ------ 容器拥有自己的主机名 |
| IPC | 共享内存和信号量 ------ 独立的进程间通信 |
| PID | 进程 ID ------ 容器拥有独立的 PID 空间 |
| Network | 网卡、IP、路由 ------ 独立的网络栈 |
| User | 用户 ------ 容器可管理自己的用户,host 不可见 |
PID namespace 示例:
bash
[root@docker ~]# docker exec -it a80f1f8b692c bash
root@a80f1f8b692c:/# ps axf
PID TTY STAT TIME COMMAND
17 pts/1 Ss 0:00 bash
25 pts/1 R+ 0:00 \_ ps axf
1 pts/0 Ss+ 0:00 /bin/bash
容器内看到的 PID 与 host 中完全不同,PID=1 的进程也不是 host 的 init 进程。
User namespace 示例:
bash
# 在容器中创建用户
root@a80f1f8b692c:/# useradd gaoqd
# 回到 host,该用户并不存在
[root@docker ~]# su - gaoqd
su: user gaoqd does not exist
二、网络
1. Docker 网络概览
Docker 网络从覆盖范围可分为:
- 单个 host 上的容器网络(本章重点)
- 跨多个 host 的网络(进阶内容)
Docker 安装时会自动在 host 上创建三个网络:
bash
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
xxx bridge bridge local
xxx host host local
xxx none null local
2. none 网络
none 网络的 driver 类型是 null,容器只有 lo 回环接口,无法与外界通信。
bash
[root@docker ~]# docker run -it --network=none busybox
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
应用场景:封闭意味着隔离,对安全性要求高且不需要联网的应用可以使用 none 网络。例如,一个仅用于生成随机密码的容器,放到 none 网络中可避免密码被窃取。
3. host 网络
连接到 host 网络的容器共享 Docker host 的网络栈,网络配置与 host 完全一样。
bash
[root@docker ~]# docker run -it --network=host busybox
/ # ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500
/ # hostname
docker
应用场景:
- 对网络传输效率有较高要求的容器
- 需要直接配置 host 网络的容器(如跨 host 网络管理工具)
缺点:牺牲灵活性,需考虑端口冲突问题。
4. Bridge 网络(核心重点)
Docker 安装时会创建一个命名为 docker0 的 Linux Bridge (网桥),可以理解为一个软件交换机。如果不指定 --network,创建的容器默认都会挂到 docker0 上。
网络架构原理:
当创建一个 Docker 容器时,会同时创建一对 veth pair 接口:
- 一端在容器内,即
eth0 - 另一端在本地,挂载到
docker0网桥,名称以veth开头
bash
# 创建容器
[root@docker ~]# docker run -itd --name busybox1 busybox
5225d246f751
# 容器内查看网卡
[root@docker ~]# docker exec -it busybox1 sh
/ # ip a
24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
# 宿主机上对应
# 25: vethddb2744@if24 ← 与容器 eth0 互为对端
veth pair 原理:一对成对出现的虚拟网卡,如同一根虚拟网线连接的两端。数据包发送到一个接口时,另一个接口也能收到相同的数据包。
Bridge 网络默认配置:
- Subnet :
172.17.0.0/16 - Gateway :
172.17.0.1(即 docker0)
bash
[root@docker ~]# ip a | grep docker0
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
5. 自定义容器网络
除了三个自动创建的网络,用户还可以根据业务需要创建 user-defined 网络。Docker 提供三种驱动:bridge、overlay(跨主机)、macvlan(跨主机)。
创建自定义 bridge 网络
bash
# 默认配置(自动分配网段)
[root@docker ~]# docker network create --driver bridge my_net
89f7bc11b602e84452ae01786113ac196a535f7296b9989ad941b3a48a5e6d04
# 指定网段和网关
[root@docker ~]# docker network create --driver bridge \
--subnet 172.22.16.0/24 --gateway 172.22.16.1 my_net2
ec761bc51778f67c2245af72fd969f00cd517ee617a10a70dc01904f9c10279d
使用自定义网络
bash
[root@docker ~]# docker run -it --network=my_net2 --name busybox2 busybox
/ # ip a
28: eth0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500
inet 172.22.16.2/24 brd 172.22.16.255 scope global eth0
指定静态 IP
注意 :只有使用
--subnet创建的网络才能指定静态 IP。
bash
[root@docker ~]# docker run -it --network=my_net2 --ip 172.22.16.100 --name busybox3 busybox
6. 容器之间的连通性
同网络容器互通:同一网络中的容器、网关之间可以直接通信。
跨网络隔离 :不同网桥上的网络默认不能通信 ,Docker 通过 iptables 的 DOCKER-ISOLATION 规则实现网络隔离。
即使 host 上已配置好路由表且启用了 ip forwarding,iptables 仍会 DROP 掉不同网桥之间双向的流量。
跨网络通信的解决方案
通过 docker network connect 为容器添加额外网卡:
bash
[root@docker ~]# docker network connect my_net2 busybox1
这会在 busybox1 中新增一块 eth1 网卡,分配 my_net2 的 IP,从而实现跨网络通信。
7. 容器通信的三种方式
方式一:IP 通信
两个容器必须属于同一个网络 ,即可通过 IP 直接交互。在容器创建时通过 --network 指定网络,或通过 docker network connect 将现有容器加入指定网络。
方式二:Docker DNS Server
从 Docker 1.10 开始,docker daemon 实现了内嵌的 DNS Server,容器可以直接通过容器名通信:
bash
[root@docker ~]# docker run -it --network my_net2 --name bbox1 busybox
[root@docker ~]# docker run -it --network my_net2 --name bbox2 busybox
# bbox2 可以直接 ping bbox1
/ # ping -c 3 bbox1
PING bbox1 (172.22.16.2): 56 data bytes
64 bytes from 172.22.16.2: seq=0 ttl=64 time=0.177 ms
限制 :Docker DNS 只能在 user-defined 网络中使用,默认的 bridge 网络不支持。
方式三:Joined 容器
Joined 容器可以使两个或多个容器共享一个网络栈 ,共享网卡和配置信息,通过 127.0.0.1 直接通信:
bash
# 创建 httpd 容器
[root@docker ~]# docker run -d -it --name web1 httpd
f7641c43eb70
# 创建 busybox 并 join 到 web1 的网络
[root@docker ~]# docker run -it --network container:web1 busybox
/ # ip a
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN>
inet 172.17.0.2/16 # 与 web1 完全相同的 IP 和 MAC
# 通过 loopback 访问 web1 的 HTTP 服务
/ # wget 127.0.0.1
saving to 'index.html'
'index.html' saved
/ # cat index.html
<html><body><h1>It works!</h1></body></html>
Joined 容器的典型场景:
- 不同容器中的程序希望通过 loopback 高效快速通信(如 Web Server 与 App Server)
- 监控其他容器的网络流量(如独立的网络监控容器)
8. 容器如何访问外部世界
容器默认就能访问外网(这里的"外网"指容器网络以外的网络环境,并非特指 Internet)。
核心机制:NAT(网络地址转换)
Docker 通过 iptables 的 NAT 规则实现容器对外网的访问:
bash
[root@docker ~]# iptables -t nat -S
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
NAT 转发流程:
- 容器发送数据包:
172.17.0.2 → 目标地址 - docker0 收到包,发现是外发包,交给 NAT 处理
- NAT 将源地址替换为 host 的 IP:
192.168.108.30 → 目标地址 - 数据包从 ens160 发出,到达目标
可通过 tcpdump 抓包验证:
bash
# 监控 docker0 接口
[root@docker ~]# tcpdump -i docker0 -n icmp
# 源地址为容器 IP:172.17.0.2
# 监控 ens160 接口
[root@docker ~]# tcpdump -i ens160 -n icmp
# 源地址已变为 host IP:192.168.108.30
9. 外部世界如何访问容器
答案:端口映射。
Docker 可将容器对外提供服务的端口映射到 host 的某个端口,外网通过该端口访问容器。
动态端口映射
bash
[root@docker ~]# docker run -d -P httpd
# 查看映射端口
[root@docker ~]# docker port <container>
80/tcp -> 0.0.0.0:32768
# 访问容器服务
[root@docker ~]# curl 192.168.108.30:32768
<html><body><h1>It works!</h1></body></html>
指定端口映射
bash
[root@docker ~]# docker run -d -p 8080:80 httpd
[root@docker ~]# curl 192.168.108.30:8080
<html><body><h1>It works!</h1></body></html>
端口映射的实现原理 :每一个映射的端口,host 都会启动一个 docker-proxy 进程来处理访问容器的流量。以 0.0.0.0:8080→80/tcp 为例:
docker-proxy监听 host 的 8080 端口- 当请求到达
host_ip:8080时,docker-proxy 转发给容器172.17.0.3:80 - 容器中的服务响应请求并返回结果
三、总结
本文从 Docker 容器的基础操作出发,系统性地梳理了容器的生命周期管理、资源限制机制、底层实现技术以及完整的网络模型。容器技术的核心依赖两大 Linux 内核机制:cgroup 负责资源限额(CPU、内存、I/O),namespace 负责资源隔离(文件系统、网络、进程、用户等),两者协同工作使得容器既轻量又安全。在网络层面,Docker 提供了 none、host、bridge 三种原生网络驱动,以及灵活的自定义网络能力;容器间通信支持 IP、DNS 和 Joined 三种方式,对外通信则分别依赖 NAT 出站和端口映射入站。掌握这些底层原理,不仅能帮助我们更高效地使用 Docker,更能为后续学习 Kubernetes 等容器编排技术打下坚实基础。建议读者结合自身环境动手实践文中的每一个实验,唯有亲手操作,方能真正内化这些知识。