Docker常见问题
1. 生产环境的 Docker 安装过程(以 Linux 系统为例)
生产环境需优先选择官方源 + 稳定版,避免依赖第三方源导致兼容性问题,以 CentOS 7/8 或 Ubuntu 20.04+ 为例,步骤如下:
-
环境预处理
- 卸载旧版本(若存在):
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
(CentOS)或sudo apt-get remove docker docker-engine docker.io containerd runc
(Ubuntu)。 - 安装依赖工具:
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
(CentOS)或sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
(Ubuntu)。
- 卸载旧版本(若存在):
-
配置官方源
- CentOS:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
。 - Ubuntu:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
,再添加源:sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
。
- CentOS:
-
安装 Docker Engine
- 安装稳定版:
sudo yum install docker-ce docker-ce-cli containerd.io
(CentOS)或sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io
(Ubuntu)。 - 验证安装:
sudo docker --version
,输出类似Docker version 26.0.0, build 2ae903e
即成功。
- 安装稳定版:
-
生产环境基础配置
- 启动并设置开机自启:
sudo systemctl start docker && sudo systemctl enable docker
。 - 配置非 root 用户操作(避免权限风险):
sudo usermod -aG docker $USER
(需重新登录生效)。 - (可选)配置镜像加速器(解决国内拉取慢问题):编辑
/etc/docker/daemon.json
,添加阿里云 / 网易等加速器地址,如:json
重启服务:{ "registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"] }
sudo systemctl daemon-reload && sudo systemctl restart docker
。
- 启动并设置开机自启:
2. 容器运行时(Container Runtime)简略说明
容器运行时是负责容器生命周期管理的核心组件,本质是 “将容器镜像加载为可运行容器实例,并执行隔离、资源限制” 的工具,是 Docker、Kubernetes 等容器平台的 “底层引擎”。
-
核心功能:
- 镜像解析:将容器镜像(如 Docker Image)解压为文件系统层;
- 容器创建:通过 Linux Namespace/CGroups 实现进程隔离与资源限制;
- 生命周期管理:启动、停止、重启、删除容器实例;
- 日志与监控:收集容器输出日志,暴露资源使用 metrics。
-
常见类型:
- 低级运行时(如
runc
):Docker 默认运行时,仅负责 “容器进程启停”,依赖 Linux 内核特性; - 高级运行时(如
containerd
):封装runc
,增加镜像管理、网络配置、存储挂载等功能,是 Kubernetes 的主流运行时; - 其他运行时(如
CRI-O
、kata-containers
):适配 Kubernetes CRI 标准,或通过轻量虚拟机增强安全性。
- 低级运行时(如
3. 容器镜像(Container Image)及结构
定义
容器镜像是只读的、可移植的文件集合,包含运行容器所需的所有内容:代码、运行时(如 JRE/Python)、库文件、环境变量、配置文件,是 “容器实例的模板”(一个镜像可创建多个容器)。
结构(分层结构,基于 UnionFS)
镜像采用 “分层叠加” 设计,每一层对应 Dockerfile 中的一条指令(如 FROM
、RUN
、COPY
),所有层联合组成一个统一的文件系统,结构如下:
- 基础层(Base Layer):最底层,通常是官方基础镜像(如
ubuntu:22.04
、alpine:3.18
),包含操作系统核心文件(如/bin
、/etc
); - 中间层(Intermediate Layers):Dockerfile 中除最后一条外的指令生成的层(如
RUN apt install nginx
会生成一层),每一层都是只读的,且可被多个镜像共享(如多个镜像基于同一ubuntu
基础层,仅存储一份基础层,节省磁盘空间); - 可写层(Writable Layer):容器启动时在镜像顶层添加的临时可写层,容器内的文件修改(如创建、删除、修改文件)仅发生在这一层;容器销毁时,可写层也会被删除,镜像本身不变;
- 元数据(Metadata):即
image manifest
和config
文件,记录镜像的分层信息、环境变量、启动命令(CMD
/ENTRYPOINT
)、端口暴露等配置,Docker 通过元数据解析镜像如何运行。
4. 企业选择容器技术的理由
容器技术解决了传统部署 “环境不一致”“资源利用率低”“运维复杂” 三大核心痛点,企业选择的核心理由如下:
- 环境一致性(消除‘Works on my machine’):镜像包含所有依赖,从开发、测试到生产环境使用同一镜像,避免因依赖版本、配置差异导致的部署失败;
- 资源高效利用:容器共享宿主机内核,启动时仅加载必要文件(无虚拟机的操作系统开销),单台宿主机可运行数百个容器,资源利用率比虚拟机提升 30%-50%;
- 快速启停与弹性伸缩:容器启动时间通常在秒级(虚拟机需分钟级),配合 Kubernetes 等编排工具,可快速应对业务流量波动(如秒杀场景自动扩容);
- 简化运维与 CI/CD 集成:容器可通过 Dockerfile 代码化定义,与 Git、Jenkins 等工具无缝集成,实现 “代码提交→自动构建镜像→自动部署” 的 DevOps 闭环;
- 隔离性与安全性:通过 Namespace 实现进程、网络、文件系统隔离,通过 CGroups 限制资源使用,避免单个应用故障影响其他应用(隔离性弱于虚拟机,但满足绝大多数业务场景);
- 可移植性强:镜像可在任何支持容器运行时的环境(物理机、虚拟机、公有云、私有云)中运行,避免厂商锁定(如从 AWS 迁移到阿里云无需修改镜像)。
5. UnionFS 的实现原理(完整说明)
UnionFS(联合文件系统)是将多个独立的只读文件系统层(Branch)“联合挂载” 为一个统一的可读写文件系统的技术,核心目标是 “分层复用、写时复制(Copy-On-Write, CoW)”,是容器镜像分层结构的底层支撑。
1. 核心概念
- Branch(分支):UnionFS 挂载的多个原始文件系统层,分为两类:
ReadOnly Branch(只读分支)
:容器镜像的基础层、中间层,不可修改;Writeable Branch(可写分支)
:容器启动时添加的顶层,仅用于存储容器运行中的修改;
- CoW(写时复制):当需要修改只读分支中的文件时,UnionFS 先将该文件从只读分支复制到可写分支,再修改可写分支中的副本,避免直接修改只读分支(保证镜像层可复用);
- Union View(联合视图):用户 / 应用看到的统一文件系统,由所有分支的文件 “合并” 而成,同名文件以 “上层覆盖下层” 为原则(可写分支优先级最高,其次是上层只读分支)。
2. 工作流程(以容器读写文件为例)
假设容器镜像包含 3 个只读分支(Layer1→Layer2→Layer3,Layer3 为最上层只读层),容器启动时添加 1 个可写分支(Writable Layer),联合视图为 Writable Layer → Layer3 → Layer2 → Layer1
:
-
读文件操作:
- 应用请求读取
/etc/nginx/nginx.conf
,UnionFS 从最高优先级的 Writable Layer 开始查找; - 若 Writable Layer 中无该文件,依次向下查找 Layer3、Layer2、Layer1;
- 找到文件后,直接返回给应用(用户感知不到分层,仅看到 “一个文件”)。
- 应用请求读取
-
写文件操作(修改已有文件):
- 应用请求修改
/etc/nginx/nginx.conf
(该文件实际在 Layer2 中); - UnionFS 触发 CoW 机制:先将 Layer2 中的
nginx.conf
复制到 Writable Layer; - 应用仅修改 Writable Layer 中的
nginx.conf
副本; - 后续读操作会优先读取 Writable Layer 中的修改后文件(覆盖下层只读层的原文件)。
- 应用请求修改
-
创建新文件操作:
- 应用创建
/var/log/nginx/access.log
,UnionFS 直接在 Writable Layer 中创建该文件,不影响任何只读分支。
- 应用创建
-
删除文件操作:
- 应用删除
/usr/bin/ls
(该文件在 Layer1 中); - UnionFS 不实际删除 Layer1 中的
ls
(只读层不可修改),而是在 Writable Layer 中创建一个 “删除标记(Whiteout)”; - 后续读操作时,UnionFS 识别到 Whiteout 标记,会 “隐藏” 下层只读分支中的
ls
,应用感知到 “文件已删除”。
- 应用删除
3. 关键特性
- 分层复用:多个容器可共享同一套只读镜像层(如 10 个 Nginx 容器共享同一个
nginx:alpine
镜像的 3 个只读层),大幅节省磁盘空间; - 轻量写操作:仅复制被修改的文件(而非整个层),减少 IO 开销,提升容器启动速度;
- 隔离性:容器的修改仅保存在自身的可写层,多个容器基于同一镜像运行时,修改互不干扰。
4. 常见实现(Docker 支持的 UnionFS 变体)
Docker 早期支持 aufs
,目前主流使用 overlay2
(Linux 内核 3.18 + 支持),此外还有 devicemapper
、btrfs
等,其中 overlay2
因性能优、配置简单成为默认存储驱动。
6. Docker 和 Overlay2、UnionFS 的关系
三者是 “上层工具→具体实现→底层标准” 的关系,具体如下:
- UnionFS:是 “联合文件系统” 的技术标准 / 概念,定义了 “分层联合、写时复制” 的核心思想,是容器镜像分层的理论基础;
- Overlay2:是 UnionFS 的一种具体实现(Linux 内核支持的文件系统驱动),解决了早期
aufs
的兼容性问题,目前是 Docker 的默认存储驱动(Docker 18.09+ 推荐); - Docker:是容器平台,依赖 Overlay2(或其他 UnionFS 变体)实现镜像分层与容器可写层管理 ——Docker 将镜像的多个只读层作为 Overlay2 的 “lowerdir”(只读分支),容器的可写层作为 “upperdir”(可写分支),通过 Overlay2 将这些层联合挂载为容器的根文件系统,供容器内进程使用。
简单总结:UnionFS 是 “思想”,Overlay2 是 “落地工具”,Docker 是 “使用者”——Docker 通过 Overlay2 实现了 UnionFS 的分层与 CoW 特性,从而支撑镜像和容器的运行。
7. 最熟悉的构建 Docker 镜像的方法:Dockerfile + docker build
这是最主流、最可复用的构建方法,通过代码化的 Dockerfile 定义镜像分层,配合 docker build
命令生成镜像,步骤清晰且支持版本控制(可纳入 Git 仓库)。
1. 核心流程
-
编写 Dockerfile:在项目根目录创建
Dockerfile
文件,通过指令定义镜像的基础层、依赖安装、代码复制、启动命令等(示例:构建一个 Python Flask 应用镜像):dockerfile
# 1. 基础层:指定Python官方基础镜像(只读层) FROM python:3.10-slim# 2. 设置工作目录(中间层) WORKDIR /app# 3. 复制依赖文件并安装(中间层) COPY requirements.txt . # 复制本地requirements.txt到容器/app目录 RUN pip install --no-cache-dir -r requirements.txt # 安装Flask依赖# 4. 复制应用代码(中间层) COPY app.py . # 复制本地Flask应用代码# 5. 暴露端口(元数据,不实际映射) EXPOSE 5000# 6. 定义启动命令(元数据) CMD ["python", "app.py"]
(注:每一行指令对应一个镜像层,
FROM
是基础层,RUN
/COPY
等生成中间层,EXPOSE
/CMD
仅修改元数据,不生成新层)。 -
构建镜像:在 Dockerfile 所在目录执行
docker build
命令,指定镜像名称(-t
)和标签:bash
docker build -t flask-app:v1.0 . # . 表示当前目录为构建上下文(Docker会读取该目录下的文件)
构建过程中,Docker 会按 Dockerfile 指令顺序创建分层,且会缓存已生成的层(若后续构建时指令未修改,直接复用缓存层,加速构建)。
-
验证镜像:通过
docker images
查看镜像,通过docker run -p 5000:5000 flask-app:v1.0
启动容器,验证应用是否正常运行。
2. 关键优化技巧
- 合并
RUN
指令:减少中间层数量(如RUN apt update && apt install -y nginx && rm -rf /var/lib/apt/lists/*
,避免拆分多条RUN
生成多余层); - 使用
.dockerignore
文件:排除构建上下文内不需要的文件(如node_modules
、.git
),减少构建上下文大小,加速镜像传输; - 选择轻量基础镜像:如用
alpine
代替ubuntu
(alpine
镜像仅几 MB,ubuntu
约 200MB),减少镜像体积。
8. 容器隔离用到的 Linux Namespace
Linux Namespace 是Linux 内核提供的 “进程隔离” 机制,通过为进程分配独立的 “命名空间”,使其仅能看到命名空间内的资源(如进程、网络、文件系统),从而实现容器与宿主机、容器与容器之间的隔离。Docker 依赖 6 类 Namespace 实现核心隔离,如下表:
Namespace 类型 | 隔离对象 | 作用说明 |
---|---|---|
PID Namespace | 进程 ID(PID) | 容器内的进程有独立的 PID 编号(如容器内的init 进程 PID 为 1),无法看到宿主机其他进程 |
Network Namespace | 网络设备、IP、端口、路由 | 容器有独立的网络栈(如独立的网卡、IP 地址、端口范围),需通过桥接 / 端口映射与外部通信 |
Mount Namespace | 文件系统挂载点 | 容器内的挂载操作仅影响自身(如挂载 /tmp ),不修改宿主机的文件系统结构 |
UTS Namespace | 主机名(hostname)、域名 | 容器可设置独立的主机名(如 nginx-container ),与宿主机和其他容器区分 |
IPC Namespace | 进程间通信资源(信号量、消息队列) | 容器内的进程仅能与同一容器内的进程通信,无法访问其他容器的 IPC 资源 |
User Namespace | 用户 ID(UID)、组 ID(GID) | 容器内的 root 用户(UID=0)映射到宿主机的普通用户,避免容器内 root 权限逃逸 |
9. 手工创建一个类容器对象的操作(基于 Linux 命令)
手工创建类容器对象,本质是通过 unshare
/clone
命令为进程分配独立 Namespace,并通过 cgroups
限制资源,步骤如下(以创建一个隔离的 Bash 进程为例):
1. 准备环境(宿主机需为 Linux,且有 root 权限)
确保内核支持 Namespace(ls /proc/$$/ns
可查看当前进程的 Namespace)。
2. 创建独立的 Mount + PID + UTS + IPC + Network Namespace
通过 unshare
命令创建新进程,并分配 5 类 Namespace(User Namespace 需额外配置,暂不演示):
bash
# --mount:创建独立Mount Namespace
# --pid:创建独立PID Namespace(需配合--fork,否则无法启动新进程)
# --uts:创建独立UTS Namespace
# --ipc:创建独立IPC Namespace
# --net:创建独立Network Namespace
# --fork:在新Namespace中fork一个子进程
# --mount-proc:在新Mount Namespace中挂载/proc(让容器内看到自己的进程)
# bash:新Namespace中运行的进程(即容器内的“shell”)
sudo unshare --mount --pid --uts --ipc --net --fork --mount-proc bash
执行后,会进入一个 “类容器” 的 Bash 环境,此时:
hostname
可修改(如hostname container-01
),不影响宿主机;ps aux
仅能看到当前 Bash 进程(PID=1)和ps
命令进程;ip addr
仅能看到lo
回环网卡(无其他网络设备)。
3. 配置网络(让类容器能联网)
默认 Network Namespace 仅有lo
网卡,需手动创建虚拟网卡对(veth pair)连接宿主机网桥:
bash
# 1. 在宿主机执行(另开一个终端):创建veth pair(veth-host 和 veth-container)
sudo ip link add veth-host type veth peer name veth-container# 2. 将veth-host加入宿主机默认网桥(docker0,若不存在可手动创建)
sudo brctl addif docker0 veth-host
sudo ip link set veth-host up# 3. 将veth-container移入类容器的Network Namespace(需替换<container-pid>为类容器Bash的PID)
# 查看类容器PID:宿主机执行 `ps aux | grep bash`,找到unshare启动的bash进程PID
sudo ip link set veth-container netns <container-pid># 4. 回到类容器Bash,配置veth-container的IP并启动
ip link set veth-container up
ip addr add 172.17.0.10/16 dev veth-container # 与docker0同网段(默认172.17.0.0/16)
ip route add default via 172.17.0.1 # 网关指向docker0的IP(172.17.0.1)# 5. 配置DNS(让类容器能解析域名)
echo "nameserver 8.8.8.8" > /etc/resolv.conf
此时类容器可通过 ping 172.17.0.1
或 curl baidu.com
联网。
4. 挂载根文件系统(模拟容器镜像层)
类容器默认使用宿主机的根文件系统,需手动挂载一个只读目录作为 “基础层”,再挂载一个可写目录作为 “可写层”(模拟 UnionFS):
bash
# 1. 宿主机创建基础层目录(如alpine基础镜像文件)和可写层目录
sudo mkdir -p /container/base /container/writable# 2. 类容器中挂载基础层(只读)和可写层(读写),并通过overlay挂载为联合目录
sudo mount -t overlay overlay -o lowerdir=/container/base,upperdir=/container/writable,workdir=/container/work /container/root
sudo chroot /container/root # 切换根目录到联合目录,此时看到的文件系统为基础层+可写层
5. 限制资源(通过 cgroups)
bash
# 1. 宿主机创建cgroups目录(限制CPU和内存)
sudo mkdir -p /sys/fs/cgroup/cpu/container01 /sys/fs/cgroup/memory/container01# 2. 设置CPU限制(最多使用1个CPU核心的50%)
echo 50000 > /sys/fs/cgroup/cpu/container01/cpu.cfs_quota_us # 配额50ms
echo 100000 > /sys/fs/cgroup/cpu/container01/cpu.cfs_period_us # 周期100ms# 3. 设置内存限制(最多使用512MB)
echo 536870912 > /sys/fs/cgroup/memory/container01/memory.limit_in_bytes# 4. 将类容器进程加入cgroups(替换<container-pid>)
echo <container-pid> > /sys/fs/cgroup/cpu/container01/cgroup.procs
echo <container-pid> > /sys/fs/cgroup/memory/container01/cgroup.procs
6. 验证隔离效果
- 类容器内
ps aux
仅显示自身进程; - 类容器内修改文件仅影响可写层,不影响宿主机;
- 类容器 CPU / 内存使用超限时会被限制。
10. CGroups 对于容器运行的重要作用
CGroups(Control Groups,控制组)是Linux 内核提供的 “资源限制与监控” 机制,核心作用是 “为一组进程分配可量化的系统资源(CPU、内存、IO 等),并限制其最大使用量”,是容器 “资源隔离” 的核心支撑(Namespace 解决 “看到的资源不同”,CGroups 解决 “使用的资源有限”)。
其对容器运行的重要作用如下:
- 防止资源滥用:限制单个容器的 CPU、内存、磁盘 IO 等资源上限,避免某一容器因异常(如内存泄漏、无限循环)耗尽宿主机资源,导致其他容器或宿主机崩溃;
- 资源配额分配:按业务优先级为容器分配资源(如给核心服务容器分配 2 个 CPU 核心、8GB 内存,给非核心服务分配 0.5 个 CPU、2GB 内存),保证资源合理利用;
- 资源监控:实时统计容器的资源使用情况(如 CPU 使用率、内存占用、IO 吞吐量),为运维监控(如 Prometheus)和弹性伸缩提供数据支撑;
- 资源隔离强化:配合 Namespace 实现 “既看不到、也用不完” 的完整隔离 ——Namespace 隔离 “资源可见性”,CGroups 隔离 “资源可用性”;
- 支持多租户场景:在 Kubernetes 等平台中,通过 CGroups 为不同租户(团队 / 业务线)的容器分配独立资源池,实现资源隔离与成本核算。
11. CGroups 控制容器资源使用的方式(举例说明)
CGroups 通过 “子系统(Subsystem) ” 控制不同类型的资源,每个子系统对应一种资源(如 CPU 子系统控制 CPU,内存子系统控制内存),通过 “将容器进程加入对应子系统的控制组(CGroup),并配置控制组的资源参数” 实现限制。
以下以CPU 子系统和内存子系统为例,说明控制方式:
1. 核心原理
- 子系统(Subsystem):对应一种资源的控制器,如
cpu
(CPU)、memory
(内存)、blkio
(块设备 IO)、net_cls
(网络带宽)等; - 控制组(CGroup):每个子系统下的资源限制单元,可创建层级结构(如
/sys/fs/cgroup/cpu/apps/web
是apps
组下的web
子组); - 进程关联:将容器内所有进程的 PID 写入控制组的
cgroup.procs
文件,该控制组的资源限制会作用于所有关联进程。
2. 示例 1:CPU 资源控制(限制容器最多使用 1 个 CPU 核心的 50%)
假设宿主机有 4 个 CPU 核心(0-3
),通过 cpu
子系统配置:
-
创建控制组:
bash
sudo mkdir -p /sys/fs/cgroup/cpu/my-container # 在cpu子系统下创建my-container控制组
-
配置 CPU 配额(CFS Quota):
cpu.cfs_period_us
:CPU 调度周期(默认 100000 微秒 = 100ms);cpu.cfs_quota_us
:周期内允许使用的 CPU 时间(配额),若设为 50000 微秒 = 50ms,表示周期内最多使用 50% 的 CPU(1 个核心的 50%);
bash
sudo echo 100000 > /sys/fs/cgroup/cpu/my-container/cpu.cfs_period_us # 周期100ms sudo echo 50000 > /sys/fs/cgroup/cpu/my-container/cpu.cfs_quota_us # 配额50ms
-
配置 CPU 核心绑定(可选):限制容器仅使用指定 CPU 核心(如核心 0),避免 CPU 上下文切换开销:
bash
sudo echo 1 > /sys/fs/cgroup/cpu/my-container/cpu.cpuset.cpus # 仅允许使用CPU核心0(二进制1对应核心0)
-
关联容器进程:假设容器内主进程 PID 为 1234,将其加入控制组:
bash
sudo echo 1234 > /sys/fs/cgroup/cpu/my-container/cgroup.procs
-
验证:容器内运行 CPU 密集型程序(如
while true; do :; done
),通过top
或docker stats
查看,CPU 使用率会被限制在 50% 左右。
3. 示例 2:内存资源控制(限制容器最多使用 512MB 内存)
通过 memory
子系统配置:
-
创建控制组:
bash
sudo mkdir -p /sys/fs/cgroup/memory/my-container
-
配置内存上限:
memory.limit_in_bytes
:容器可使用的最大物理内存(单位:字节),512MB=51210241024=536870912 字节;memory.swappiness
:设置为 0,禁止容器使用交换分区(避免内存不足时使用 swap 导致性能下降);
bash
sudo echo 536870912 > /sys/fs/cgroup/memory/my-container/memory.limit_in_bytes # 512MB sudo echo 0 > /sys/fs/cgroup/memory/my-container/memory.swappiness # 禁用swap
-
关联容器进程:
bash
sudo echo 1234 > /sys/fs/cgroup/memory/my-container/cgroup.procs
-
验证:容器内运行内存密集型程序(如 Python 代码
a = [i for i in range(10**8)]
),当内存使用超 512MB 时,内核会触发 OOM(Out Of Memory)杀死容器进程,避免占用更多内存。
12. Docker 网络模式及适用场景
Docker 提供 5 种核心网络模式,通过 docker run --net=<模式>
指定,不同模式对应不同的网络隔离与通信能力,适用场景如下表:
网络模式 | 名称 | 核心特点 | 适用场景 |
---|---|---|---|
bridge | 桥接模式 | 容器使用独立 Network Namespace,通过虚拟网卡对(veth pair)连接宿主机docker0 网桥,默认分配 172.17.0.0/16 网段 IP,需端口映射(-p )与外部通信 | 最常用场景:多个容器在同一宿主机内通信(如 Web 容器 + 数据库容器),且需对外提供服务(如暴露 80 端口) |
host | 主机模式 | 容器不创建独立 Network Namespace,共享宿主机网络栈(使用宿主机 IP 和端口),无网络隔离 | 需高性能网络场景(如网络密集型服务),或需直接使用宿主机端口(如避免端口映射冲突) |
none | 无网络模式 | 容器创建独立 Network Namespace,但不配置任何网络(仅lo 回环网卡),完全隔离 | 无需网络的场景(如离线数据处理容器),或需手动配置复杂网络(如自定义虚拟网卡、路由) |
container:<name/id> | 容器模式 | 新容器共享指定容器的 Network Namespace(与指定容器使用同一 IP 和端口),两者网络完全互通 | 需 “父子容器” 通信场景(如 Sidecar 模式:主容器 + 日志收集容器,共享网络以便日志收集) |
overlay | 覆盖网络模式 | 跨宿主机的容器网络,通过 VXLAN 技术将多个宿主机的docker0 网桥连接为一个虚拟网络,容器可跨宿主机通信(需 Docker Swarm 或 Kubernetes 支持) | 分布式应用场景(如微服务部署在多台宿主机,容器需跨主机通信,如 K8s 集群内 Pod 通信) |
13. Bridge 模式下容器网络流量的转发过程
Bridge 模式是 Docker 默认网络模式,核心是通过宿主机的docker0
网桥实现容器与宿主机、容器与容器、容器与外部网络的通信,以下分 “容器→外部网络”“外部网络→容器”“容器→容器” 三种场景说明转发过程。
前提:Bridge 模式网络拓扑
- 宿主机有一个虚拟网桥
docker0
(默认 IP:172.17.0.1/16); - 每个容器启动时,Docker 创建一对虚拟网卡(veth pair):一端为容器内的
eth0
(IP:172.17.0.x/16,网关:172.17.0.1),另一端为宿主机的vethxxxx
(挂载到docker0
网桥); - 宿主机开启 IP 转发(
net.ipv4.ip_forward=1
),支持数据包在不同网卡间转发; - 容器对外通信依赖宿主机的 SNAT(源地址转换),外部访问容器依赖 DNAT(目的地址转换)。
1. 场景 1:容器→外部网络(如容器访问百度)
假设容器 A 的 IP 为 172.17.0.2,访问 www.baidu.com
(IP:180.101.49.12),流程如下:
- 容器内数据包生成:容器 A 的应用(如
curl
)生成 TCP 数据包,目的 IP=180.101.49.12,目的端口 = 80,源 IP=172.17.0.2,源端口 = 随机端口(如 34567); - 容器内路由:容器 A 的路由表(网关 = 172.17.0.1)判断目的 IP 不在本地网段,将数据包发送到
eth0
网卡; - veth pair 转发:数据包通过 veth pair 从容器内
eth0
发送到宿主机的vethxxxx
网卡; - docker0 网桥转发:
vethxxxx
挂载到docker0
网桥,docker0
接收数据包后,根据目的 IP(180.101.49.12)转发到宿主机的物理网卡(如eth0
,IP:192.168.1.100); - 宿主机 SNAT 转换:宿主机的
iptables
规则(Docker 自动配置)触发 SNAT,将数据包的源 IP 从 172.17.0.2 替换为宿主机物理网卡 IP(192.168.1.100),源端口保持不变; - 外部网络转发:转换后的数据包通过宿主机物理网卡发送到路由器,再转发到百度服务器;
- 响应数据包返回:百度服务器返回 TCP 响应数据包(目的 IP=192.168.1.100,目的端口 = 34567),通过路由器转发到宿主机物理网卡;
- 宿主机 DNAT 转换(反向):宿主机
iptables
根据连接跟踪(conntrack)记录,将目的 IP 从 192.168.1.100 替换为容器 A 的 IP(172.17.0.2),目的端口替换为 34567; - docker0 网桥转发到容器:数据包通过
docker0
网桥转发到宿主机的vethxxxx
网卡,再通过 veth pair 发送到容器 A 的eth0
,最终被应用接收。
2. 场景 2:外部网络→容器(如外部访问容器内 Web 服务)
假设容器 B 运行 Nginx 服务(端口 80),通过 docker run -p 8080:80
映射宿主机 8080 端口到容器 80 端口,外部主机(IP:192.168.1.200)访问 http://192.168.1.100:8080
,流程如下:
- 外部数据包生成:外部主机生成 TCP 数据包,目的 IP = 宿主机物理 IP(192.168.1.100),目的端口 = 8080,源 IP=192.168.1.200,源端口 = 随机端口(如 56789);
- 宿主机接收数据包:数据包通过宿主机物理网卡(
eth0
)接收,宿主机内核判断目的端口 = 8080,触发iptables
的 DNAT 规则; - 宿主机 DNAT 转换:Docker 预配置的
iptables
规则将数据包的目的 IP 从 192.168.1.100 替换为容器 B 的 IP(172.17.0.3),目的端口从 8080 替换为 80; - docker0 网桥转发到容器:转换后的数据包通过
docker0
网桥转发到宿主机的vethyyyy
网卡(容器 B 的 veth pair 另一端),再发送到容器 B 的eth0
; - 容器内处理:容器 B 的 Nginx 服务(监听 80 端口)接收数据包,处理后返回 TCP 响应(目的 IP=192.168.1.200,目的端口 = 56789,源 IP=172.17.0.3,源端口 = 80);
- 响应数据包返回外部:响应数据包按 “场景 1” 的反向流程,通过 veth pair→
docker0
→宿主机物理网卡,经 SNAT 转换源 IP 为 192.168.1.100 后,返回外部主机。
3. 场景 3:同一宿主机内容器→容器(如容器 A 访问容器 B 的数据库)
假设容器 A(172.17.0.2)访问容器 B(172.17.0.3,运行 MySQL 服务,端口 3306),流程如下:
- 容器 A 生成数据包:源 IP=172.17.0.2,源端口 = 随机端口,目的 IP=172.17.0.3,目的端口 = 3306;
- 容器 A 路由:目的 IP(172.17.0.3)在同一网段(172.17.0.0/16),容器 A 通过 ARP 协议获取容器 B 的
eth0
网卡 MAC 地址; - veth pair→docker0 转发:数据包通过容器 A 的
eth0
→宿主机vethxxxx
→docker0
网桥,docker0
根据 MAC 地址将数据包转发到容器 B 的vethyyyy
; - 容器 B 接收:数据包通过
vethyyyy
→容器 B 的eth0
,被 MySQL 服务接收并处理; - 响应返回:容器 B 返回的响应数据包按原路返回容器 A,无需经过宿主机物理网卡,通信效率高。
关键技术点
- veth pair:容器与宿主机的 “网络桥梁”,实现数据包双向传输;
- docker0 网桥:同一宿主机内容器的 “交换中心”,实现容器间二层通信;
- iptables NAT:实现容器与外部网络的通信(SNAT/DNAT),是 Bridge 模式对外通信的核心;
- IP 转发:宿主机内核需开启
net.ipv4.ip_forward=1
,否则数据包无法在docker0
与物理网卡间转发。