Docker 核心技术和实现原理
Docker 镜像制作笔记
一、镜像制作的原因
当官方镜像无法满足特定需求时,需要自定义镜像,常见原因包括:
- 代码打包
将自编写的代码打包到镜像中,随镜像发布。 - 安全性考虑
第三方镜像可能存在安全漏洞,需要自制镜像。 - 特定功能需求
官方镜像无法满足功能,如数据库审计、特殊配置。 - 公司内部规范
基于公司内部操作系统或标准构建镜像。
二、镜像制作方式
1. 快照方式(偶尔制作的镜像)
- 思路:在基础镜像上启动容器,安装所需软件和配置后,创建快照。
- 命令:
docker commit - 语法:
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
- 常用参数:
-a:提交作者-c:使用 Dockerfile 指令创建镜像,可修改启动指令-m:提交说明-p:提交时暂停容器
2. Dockerfile 构建方式(经常更新的镜像)
- 思路:将安装流程写入 Dockerfile,通过
docker build构建镜像。 - 特点:
- 可重复构建
- 易于版本管理
- 自动化 CI/CD 支持
三、快照方式实战
实战一:C++ HelloWorld 镜像
- 准备工作目录:
mkdir -p /data/maxhou/commitimage
cd /data/maxhou/commitimage
- 创建 C++ 源码:
#include <stdio.h>
int main() {
printf("hello docker!\n");
return 0;
}
- 启动 CentOS 容器:
docker run -it --name mycppcommit centos:7 bash
- 替换国内软件源(CentOS 7 已停止更新):
sed -i.bak \
-e 's|^mirrorlist=|#mirrorlist=|g' \
-e 's|^#baseurl=http://mirror.centos.org/centos|baseurl=https://mirrors.ustc.edu.cn/centos-vault/centos|g' \
/etc/yum.repos.d/CentOS-Base.repo
yum makecache
- 安装编译器并创建源代码目录:
yum install -y gcc
mkdir /src/
- 拷贝源码到容器:
docker cp ./demo.c mycppcommit:/src
- 编译运行测试:
cd /src
gcc demo.c -o demo
./demo # 输出 hello docker!
- 提交为镜像:
docker commit mycppcommit mycppimg:v1.0
docker images
- 运行镜像验证:
docker run -it mycppimg:v1.0 ./src/demo
一、Dockerfile 是什么
👉 本质:
- Dockerfile = 镜像构建脚本
- 用来描述“镜像每一层怎么生成”
👉 核心思想:
镜像 = 一层一层叠加
Dockerfile = 每一层的构建指令
📌 格式
# 注释
INSTRUCTION arguments
特点:
- 指令不区分大小写(但必须大写规范写法)
- 从上到下顺序执行
- 每条指令 = 一层镜像
🧠 类比理解
👉 Dockerfile 就像:
🏠 建房子的施工图纸
- FROM → 地基
- RUN → 施工
- COPY → 搬材料
- CMD → 房子怎么用
二、为什么用 Dockerfile(比 commit 强在哪)
1️⃣ 可定制
- 自定义环境 + 代码 + 配置
2️⃣ 自动化
- 一键 build,不用手动敲命令
3️⃣ 可重复
- 同一个 Dockerfile → 构建结果一致
4️⃣ 非黑箱(重点)
- commit = 黑箱 ❌
- Dockerfile = 可读可维护 ✅
5️⃣ 镜像更小
- 可清理缓存
- 支持多阶段构建(高级重点)
三、核心指令(必须掌握)
1️⃣ FROM(基础镜像)
👉 作用:
- 指定基于哪个镜像构建
FROM ubuntu:22.04
📌 特点:
- 必须是第一条指令(除 ARG 外)
- 默认 tag = latest
- 可多次使用(多阶段构建)
2️⃣ LABEL(推荐替代 MAINTAINER)
LABEL company="com.bit" app="nginx"
👉 作用:
- 添加元数据(key-value)
3️⃣ COPY(重点)
COPY index.html /data/web/www/
👉 作用:
- 从宿主机 → 镜像
📌 特点:
- 不支持下载
- 不会自动解压
- 必须在 build 上下文内
4️⃣ ADD(增强版 COPY)
ADD nginx-1.22.1.tar.gz /usr/local/
ADD https://xxx.tar.gz /usr/local/
👉 多出来的能力:
- ✅ 自动下载 URL
- ✅ 自动解压 tar
📌 面试点:
能用 COPY 就别用 ADD(更可控)
5️⃣ WORKDIR(重点)
WORKDIR /usr/local
👉 作用:
- 设置工作目录(后续指令都在这里执行)
📌 特点:
- 默认
/ - 支持相对路径叠加
6️⃣ ENV(重点)
ENV WEB_ROOT=/data/web/www/
👉 作用:
- 设置环境变量
使用:
COPY index.html ${WEB_ROOT}
7️⃣ RUN(核心重点🔥)
RUN apt-get update && apt install -y nginx
👉 作用:
- 构建时执行命令
两种形式:
shell 形式(常用)
RUN apt-get update
exec 形式
RUN ["apt-get", "update"]
📌 面试重点:
- shell 会走
/bin/sh -c - exec 不支持变量替换
8️⃣ CMD(重点)
👉 作用:
- 容器启动默认执行命令
CMD ["nginx", "-g", "daemon off;"]
📌 特点:
- 只能有一个(后面会覆盖前面)
9️⃣ ENTRYPOINT(重点)
👉 作用:
- 程序入口(更强的 CMD)
📌 区别:
| CMD | ENTRYPOINT |
|---|---|
| 可被覆盖 | 不容易被覆盖 |
四、完整构建流程(Nginx 案例总结🔥)
🧩 整体步骤
1️⃣ 基础镜像
FROM ubuntu:22.04
2️⃣ 安装依赖(优化顺序重点)
RUN apt-get update -y && apt install -y \
build-essential \
libpcre3 libpcre3-dev \
zlib1g-dev
👉 ⚠️ 为什么放前面?
- 利用缓存(构建更快)
3️⃣ 设置变量
ENV NGINX_VERSION=nginx-1.22.1
ENV WEB_ROOT=/data/web/www/
4️⃣ 拷贝网页
COPY index.html ${WEB_ROOT}
5️⃣ 设置工作目录
WORKDIR /usr/local
6️⃣ 下载源码
ADD https://nginx.org/download/${NGINX_VERSION}.tar.gz ./src
7️⃣ 解压
RUN cd ./src && tar zxvf ${NGINX_VERSION}.tar.gz
8️⃣ 编译安装
RUN cd ./src/${NGINX_VERSION} \
&& ./configure --prefix=/usr/local/nginx \
&& make && make install
9️⃣ 拷贝配置文件
COPY nginx.conf ./nginx/conf
五、Dockerfile 优化(面试高频🔥)
1️⃣ 减少层数
RUN apt-get update && apt install -y xxx
✔ 合并命令
2️⃣ 利用缓存(非常重要)
👉 不常变的放前面:
RUN apt install ...
COPY .
3️⃣ 使用 .dockerignore
避免把无关文件打包进去
4️⃣ 多阶段构建(高级)
👉 编译和运行分离(减小镜像)
六、Dockerfile vs commit(总结)
| 对比 | Dockerfile | commit |
|---|---|---|
| 自动化 | ✅ | ❌ |
| 可维护 | ✅ | ❌ |
| 可复现 | ✅ | ❌ |
| 体积优化 | ✅ | ❌ |
| 使用场景 | 正式环境 | 临时调试 |
一、CMD vs ENTRYPOINT(最容易混)
一句话区分
|
指令 |
作用 |
是否可被 docker run 覆盖 |
|---|---|---|
|
CMD |
默认启动命令 |
✅ 会被覆盖 |
|
ENTRYPOINT |
容器入口(主程序) |
❌ 默认不覆盖 |
CMD 三种写法(重点)
CMD ["executable","param1","param2"] ✅ 推荐(exec 形式)
CMD ["param1","param2"] ✅ 给 ENTRYPOINT 传参
CMD command param1 param2 ❌ shell 形式(不推荐)
✅ 最佳实践
-
想让容器“一直运行”:
ENTRYPOINT ["nginx","-g","daemon off;"] -
想提供默认参数:
CMD ["--help"]
二、RUN vs CMD vs ENTRYPOINT 执行时机
|
指令 |
执行阶段 |
作用 |
|---|---|---|
|
RUN |
构建镜像时 |
安装软件、编译 |
|
CMD |
容器启动时 |
默认执行命令 |
|
ENTRYPOINT |
容器启动时 |
固定入口程序 |
📌 口诀
RUN 建镜像,CMD/ENTRYPOINT 跑容器
三、EXPOSE:只是“声明”,不是“映射”
核心点
EXPOSE 80/tcp
✅ 作用:
-
文档说明
-
docker ps能看到端口 -
配合
-P自动映射
❌ 不会:
-
自动开放端口
-
替代
-p
✅ 正确用法:
docker run -p 80:80 web1
📌 面试常问
EXPOSE 是给谁看的?
✅ 给运维 / 使用者看的文档
四、ARG vs ENV(构建 vs 运行)
对比总结
|
项目 |
ARG |
ENV |
|---|---|---|
|
作用阶段 |
构建阶段 |
构建 + 运行 |
|
是否可覆盖 |
✅ docker build --build-arg |
❌ 不可覆盖 |
|
是否进容器 |
❌ 默认不进 |
✅ 进容器 |
✅ 推荐写法(防空值):
ARG UBUNTU_VERSION=22.04
FROM ubuntu:${UBUNTU_VERSION}
✅ ENV 覆盖 ARG:
ARG VERSION
ENV VERSION=${VERSION:-v1.0}
五、VOLUME:防止数据丢失的“保底机制”
核心理解
VOLUME ["/data"]
✅ 特点:
-
自动创建匿名卷
-
容器删除 → 数据还在
-
用户没
-v也能用
✅ 典型场景:
-
MySQL
-
Nginx 日志
-
配置文件目录
❌ 不能:
-
指定宿主机目录
-
替代
-v
📌 一句话
VOLUME 是“防呆设计”,不是“挂载命令”
六、SHELL:切换 shell 解释器
SHELL ["/bin/bash","-cvx"]
✅ 影响:
-
后续
RUN命令 -
调试非常有用
✅ 常见用途:
-
看命令展开
-
调试 shell 脚本
七、USER:安全最佳实践
USER nginx
✅ 推荐:
-
不要一直用 root
-
服务类程序用专用用户
✅ 你例子中的结论是对的:
nginx
mysql
八、HEALTHCHECK:容器“体检”
返回值含义
|
返回码 |
含义 |
|---|---|
|
0 |
healthy |
|
1 |
unhealthy |
|
2 |
保留 |
✅ 常见写法:
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
📌 docker ps会显示:
-
(healthy) -
(unhealthy)
九、ONBUILD:镜像的“钩子”
ONBUILD RUN echo "in build" >> /tmp/build.txt
✅ 触发条件:
-
别人
FROM你的镜像
✅ 使用场景:
-
基础镜像
-
SDK / 构建模板
❌ 不适合:
-
普通业务镜像
十、STOPSIGNAL:控制容器如何退出
STOPSIGNAL 9
|
信号 |
行为 |
|---|---|
|
SIGTERM (15) |
优雅退出 |
|
SIGKILL (9) |
强制杀死 |
✅ 你实验结论非常关键:
-
有 STOPSIGNAL 9 → 直接消失
-
默认 → nginx 优雅退出
十一、整体记忆表(面试版)
|
指令 |
阶段 |
是否可覆盖 |
典型用途 |
|---|---|---|---|
|
FROM |
构建 |
❌ |
基础镜像 |
|
RUN |
构建 |
❌ |
安装/编译 |
|
CMD |
启动 |
✅ |
默认命令 |
|
ENTRYPOINT |
启动 |
❌ |
主程序 |
|
ARG |
构建 |
✅ |
构建参数 |
|
ENV |
构建+运行 |
❌ |
环境变量 |
|
EXPOSE |
文档 |
❌ |
端口声明 |
|
VOLUME |
运行 |
❌ |
数据持久 |
|
USER |
运行 |
❌ |
安全 |
|
HEALTHCHECK |
运行 |
❌ |
健康检查 |
|
ONBUILD |
构建 |
❌ |
基础镜像钩子 |
一、VOLUME到底做了什么?
Dockerfile 中的这一行:
VOLUME ["/data"]
✅ 实际效果只有一句话:
告诉 Docker:这个容器里的
/data目录,在运行时应该挂载一个 volume
⚠️ 注意:
-
构建阶段:
-
不会创建 volume
-
只是写进镜像的 metadata
-
-
运行阶段(
docker run):-
Docker 发现这个声明
-
自动创建一个匿名 volume
-
挂载到容器的
/data
-
二、谁才是“创建数据卷”的主体?
|
行为 |
是谁做的 |
|---|---|
|
创建 volume |
✅ Docker(运行时) |
|
声明挂载点 |
✅ Dockerfile 中的 |
|
管理 volume |
✅ Docker( |
|
指定宿主机目录 |
❌ |
✅ 真正创建卷的是:
docker run ...
或:
docker volume create
三、VOLUME≠ 数据卷管理
❌ VOLUME不能做的事情
-
不能指定宿主机目录
-
不能指定 volume 名字
-
不能删除 volume
-
不能设置 volume driver
✅ VOLUME能做的事情
-
防止用户忘记
-v导致数据丢失 -
保证容器删除后数据还在
-
明确“这个目录是数据目录”
📌 一句话总结
VOLUME是“声明”,不是“管理”
四、匿名卷 vs 具名卷 vs bind mount
你这个例子非常典型,我们对比一下:
1️⃣ Dockerfile 中 VOLUME
VOLUME ["/data"]
运行时:
docker run myimage
Docker 会:
/var/lib/docker/volumes/<随机ID>/_data → /data
✅ 匿名卷
-
名字随机
-
容器删了,卷还在
-
需要
docker volume prune清理
2️⃣ 具名卷(推荐生产)
docker run -v mydata:/data myimage
✅ 可控、可复用、可管理
3️⃣ bind mount(开发常用)
docker run -v /host/data:/data myimage
✅ 直接绑定宿主机目录
五、为什么 MySQL / Nginx 镜像要用 VOLUME?
以 MySQL 为例:
VOLUME /var/lib/mysql
原因只有一个:
防止用户忘记
-v,删容器把数据库全删了
📌 这是 Docker 官方镜像的“安全兜底设计”
六、你实验中的结论是完全正确的 ✅
你做的这个流程:
-
VOLUME ["/data"] -
容器启动
-
Docker 自动创建匿名卷
-
容器删除
-
volume 仍然存在
✅ 完全符合 Docker 的设计行为
一、EXPOSE 到底“给谁用”?
✅ 正确理解
EXPOSE是:
写给“人”看的说明信息
-
写给镜像使用者
-
写给运维
-
写给
docker ps/docker inspect
📌 它不会:
-
打开端口
-
允许访问
-
做网络映射
-
影响容器间通信
二、容器之间通信,根本不需要 EXPOSE ✅
这是你最关心的点
容器之间能不能互通,和 EXPOSE 完全无关
例子
docker network create app-net
docker run -d --name db --network app-net mysql
docker run -d --name web --network app-net nginx
在 web容器里:
ping db
curl db:3306
✅ 只要:
-
在同一 network
-
容器内服务真的在监听端口
❌ 不需要:
-
EXPOSE
-
-p
📌 容器间通信 = Docker 网络 + 端口监听
三、那 EXPOSE 有什么实际作用?
✅ 1️⃣ 给 docker ps看
docker ps
你会看到:
PORTS
80/tcp
这个信息来自:
EXPOSE 80
✅ 2️⃣ 给 -P用(自动映射)
docker run -P nginx
Docker 会:
-
读取 EXPOSE 的端口
-
自动映射到宿主机随机端口
📌 没有 EXPOSE:
docker run -P
👉 什么都映射不出来
✅ 3️⃣ 给镜像使用者“提示”
EXPOSE 80 443
等于在说:
“这个镜像默认会监听 80 和 443”
四、EXPOSE 在内网 / 外网中的真实角色
|
场景 |
是否需要 EXPOSE |
|---|---|
|
容器 ↔ 容器 |
❌ 不需要 |
|
容器 ↔ 宿主机 |
❌ 不需要 |
|
|
❌ 不需要 |
|
|
✅ 需要 |
|
文档说明 |
✅ 需要 |
五、一个非常重要的对比(必记)
|
项目 |
EXPOSE |
-p |
|---|---|---|
|
是否真正开放端口 |
❌ |
✅ |
|
是否影响容器通信 |
❌ |
❌ |
|
是否影响宿主机访问 |
❌ |
✅ |
|
是否只是声明 |
✅ |
❌ |
六、你为什么会觉得“EXPOSE 是给内网用的”?
这是一个非常常见的误解来源:
Docker 官方文档 + 很多教程
会说:
“EXPOSE 用于声明容器监听的端口”
然后大家就误以为:
“哦,这是给内网容器访问用的”
❌ 实际是:
“声明” ≠ “生效”
七、生产环境正确姿势 ✅
✅ 推荐写法
EXPOSE 80
✅ 推荐运行方式
docker run -p 80:80 myimage
❌ 不推荐依赖
docker run -P
八、一句话终极记忆(强烈推荐)
EXPOSE 是“说明书”,不是“开关”
容器通不通,看网络;宿主机能不能访问,看
-p
一、HEALTHCHECK 是干什么的?
检测容器内部服务是否“健康”
注意:
-
✅ 容器 进程还在 ≠ 服务正常
-
✅ 比如:
-
nginx 进程在
-
但 80 端口不响应
-
或返回 500
-
HEALTHCHECK就是干这个的。
二、Dockerfile 中的基本写法
✅ 标准格式
HEALTHCHECK [OPTIONS] CMD command
或关闭继承的健康检查:
HEALTHCHECK NONE
三、OPTIONS 参数(非常重要)
|
参数 |
默认值 |
含义 |
|---|---|---|
|
|
30s |
多久检查一次 |
|
|
30s |
单次检查超时 |
|
|
0s |
启动宽限期 |
|
|
3 |
连续失败几次算 unhealthy |
✅ 示例:
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1
四、CMD 的返回值(核心)
Docker 只看 exit code:
|
返回值 |
含义 |
|---|---|
|
|
✅ healthy |
|
|
❌ unhealthy |
|
|
⚠️ 保留(不要用) |
📌 非 0 都是不健康
五、最常见的几种写法(实战)
✅ 1️⃣ HTTP 服务(最常用)
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
或:
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
✅ 2️⃣ TCP 端口检测
HEALTHCHECK CMD nc -z localhost 80 || exit 1
✅ 3️⃣ 只检查进程是否存在(不推荐)
HEALTHCHECK CMD pgrep nginx || exit 1
⚠️ 进程存在 ≠ 服务正常
✅ 4️⃣ 禁用健康检查
HEALTHCHECK NONE
六、Dockerfile 中完整示例(nginx)
FROM nginx:1.22
RUN apt-get update && apt-get install -y curl
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -f http://localhost/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
七、构建 & 运行后怎么看?
✅ 1️⃣ docker ps
docker ps
你会看到:
STATUS
Up 10 seconds (healthy)
或:
Up 10 seconds (unhealthy)
✅ 2️⃣ docker inspect(最详细)
docker inspect <container_id>
重点字段:
"State": {
"Health": {
"Status": "healthy",
"FailingStreak": 0,
"Log": [...]
}
}
✅ 3️⃣ 查看失败原因
docker inspect --format='{{json .State.Health}}' <container>
八、HEALTHCHECK 在 docker run 中也能用
docker run \
--health-cmd="curl -f http://localhost/ || exit 1" \
--health-interval=5s \
--health-timeout=3s \
--health-retries=3 \
nginx
📌 docker run 的优先级 > Dockerfile
九、在 Docker Compose 中怎么用?
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 5s
timeout: 3s
retries: 3
Docker 镜像制作命令:docker build
功能
- 使用 Dockerfile 创建镜像。
语法
docker build [OPTIONS] PATH | URL | -
PATH:Dockerfile 所在的目录路径URL:远程 Git 仓库地址-:从标准输入读取 Dockerfile
关键参数
| 参数 | 说明 |
|---|---|
--build-arg=[] |
设置镜像创建时的变量 |
-f |
指定 Dockerfile 路径 |
--label=[] |
设置镜像元数据(标签信息) |
--no-cache |
构建镜像时不使用缓存 |
--pull |
尝试更新基础镜像到新版本 |
--quiet, -q |
安静模式,只输出镜像 ID |
--tag, -t |
指定镜像名称和标签,例如 name:tag,可为一个镜像设置多个标签 |
--network |
构建期间设置 RUN 指令的网络模式,默认 default |
示例
docker build -t mynginx:v1 .
- 在当前目录下使用 Dockerfile 构建镜像
- 镜像命名为
mynginx,标签为v1
Dockerfile 编写优秀实践
1. 善用 .dockerignore 文件
- 在执行
docker build时,忽略不必要的路径和文件 - 优点:
- 减少发送到 Docker 守护进程的数据量
- 加快镜像构建速度
2. 镜像的多阶段构建(Multi-stage Build)
- 将 编译 和 运行 分开
- 仅保留最终镜像所需环境
- 优势:
- 镜像体积更小
- 保持清晰的构建流程
- 注意:单独维护多个 Dockerfile 也可实现类似效果,但更复杂
3. 合理使用缓存
- 利用 Docker 的 缓存机制,提高构建速度
- 建议:
- 内容不变的指令尽量放在前面
- 减少 COPY 目录下的文件数量
- 将不经常改变的依赖先处理
4. 基础镜像选择
- 优先使用官方镜像
- 尽量选 小体积镜像:
- 应用镜像:
node:slim - 系统镜像:
alpine、busybox、debian
- 应用镜像:
- 避免使用过大的基础镜像(如完整 Ubuntu),减少最终镜像臃肿
5. 减少镜像层数
- 尽量合并
RUN、ADD、COPY指令 - 示例:
RUN apt-get update && apt-get install -y \
package1 package2 package3 \
&& rm -rf /var/lib/apt/lists/*
- 优点:
- 镜像层少
- 镜像体积更小
6. 精简镜像用途
- 每个镜像尽量 功能单一
- 避免构造大而复杂、多用途的镜像
- 有利于维护和升级
7. 减少外部源干扰
- 如果必须从外部下载数据:
- 指定稳定 URL
- 带版本信息
- 保证镜像可复现,其他人使用不出错
8. 减少不必要的包安装
- 只安装运行应用所需的依赖包
- 避免安装多余工具
- 直接降低镜像体积,提高安全性
Docker 镜像制作常见问题整理
1. ADD 与 COPY 的区别
| 指令 | 功能 | 使用场景 |
|---|---|---|
| ADD | 可将本地文件/目录或远程 URL 对应的资源复制到镜像 | 需要下载远程资源或解压归档文件 |
| COPY | 仅能将本地文件/目录复制到镜像 | 仅需要拷贝本地文件或压缩包到镜像 |
建议:如果只是简单拷贝本地文件,使用
COPY即可。
2. CMD 与 ENTRYPOINT 的区别
| 指令 | 功能 | 特点 |
|---|---|---|
| ENTRYPOINT | 指定容器启动后执行的命令 | 容器行为像一个可执行程序,docker run 参数会传给 ENTRYPOINT;Dockerfile 中只能有一个 ENTRYPOINT |
| CMD | 指定默认的运行命令或参数 | 可被 docker run 后的命令覆盖 |
组合使用
ENTRYPOINT:指定默认命令CMD:指定默认参数- 实现可灵活覆盖参数又保持默认命令的效果
3. 多个 FROM 指令的使用(多阶段构建)
- 每条
FROM指令都是一个构建阶段 - 最终镜像以最后一条
FROM为准 - 前置阶段可提供文件给后续阶段,常用于:
- 编译环境与运行环境分离
- 只保留运行环境的最小镜像,提高效率和安全性
4. 快照与 Dockerfile 制作镜像的区别
- 快照:手动操作生成的镜像,难以复现和管理
- Dockerfile:声明式定义镜像,可版本控制、自动化、可复现
原因:使用 Dockerfile 可以更好地管理和重建镜像
5. 空悬镜像(dangling image)
- 特征:仓库名、标签均为
<none> - 原因:
- 镜像更新后,旧镜像名被覆盖
- Dockerfile 构建时重复生成同名镜像
- 查看空悬镜像:
docker image ls -f dangling=true
- 可安全删除,因为一般已经失去存在价值
6. 中间层镜像
- 用途:加速镜像构建、重复利用资源
- 特征:
- Docker 构建每条指令产生一层 image
- 中间层镜像可能无标签,但被上层镜像依赖
- 查看中间层镜像:
docker image ls -a
- 不建议删除,否则上层镜像可能出错
- 不会占用额外存储,相同层只存一份
Docker 镜像原理笔记
一、操作系统基础
- 操作系统由以下子系统组成:
- 进程调度子系统
- 进程通信子系统
- 内存管理子系统
- 设备管理子系统
- 文件管理子系统
- 网络通信子系统
- 作业控制子系统
- Linux 文件管理子系统由 bootfs 和 rootfs 组成:
- bootfs
- 包含 bootloader 和 kernel
- 功能:引导加载内核,启动时加载,加载完成后被卸载
- Docker 镜像底层对应 bootfs
- rootfs
- 包含 Linux 标准目录
/dev,/proc,/bin,/etc等 - 对应不同 Linux 发行版(Ubuntu、CentOS 等)
- 包含 Linux 标准目录
- bootfs
二、Union FS(联合文件系统)
- 将多个目录(比如overlay2的lowerdir、upperdir)合并挂载到一个虚拟目录下(mergeddir)
- 特性:
- 支持只读和可读写目录合并
- 写时复制(Copy-on-Write,CoW)
- 写时复制原理
- 资源初次修改才创建新副本
- 未修改资源共享,节省存储和内存
- 修改操作只在新副本上发生
三、Docker 镜像概念
- 镜像 = 多层 Union FS 文件系统
- 每一层称为 layer
- 镜像特点:
- 共享宿主机内核
- Base 镜像是最小 Linux 发行版
- 同一 Docker 主机支持不同 Linux 发行版
- 分层结构,可上层引用下层,实现资源最大化共享
- 容器层可写,采用 CoW 技术,仅保存变化部分
- 容器层以下都是只读
- Docker 从上到下查找文件
四、Docker 分层存储实现原理
1. 支持的 Union FS 类型
- AUFS、overlay、overlay2、DeviceMapper、VSF
- 各发行版使用:
- CentOS: overlay2 / overlay
- Debian: aufs
- RedHat: devicemapper
- 推荐:overlay2(稳定,Docker 默认)
2. Union FS 原理
- 镜像 = 多个只读层叠加(容器就是加上一个可写层)
- 容器启动时:
- 加载镜像只读层
- 在顶部加 读写层
- 文件修改:
- Copy-up 到读写层
- 原只读层保持不变
- 文件删除:
- 在读写层创建 whiteout 文件
- 屏蔽下层文件,但下层文件不删除
3. overlay2 层级结构
| 层 | 作用 |
|---|---|
| lowerdir | 镜像只读层,包括 bootfs/rootfs 及 Dockerfile 构建的镜像层 |
| upperdir | 容器读写层,使用 CoW,存储文件修改 |
| workdir | 临时中间层,修改操作先进入 workdir,再移动到 upperdir |
| merged | 用户看到的统一目录,展示 upperdir 或 lowerdir 的文件内容 |
镜像的inspect
容器的inspect

4. 文件操作机制
- 读取:
- 文件在 upperdir → 直接读取
- 文件不在 upperdir → 从 lowerdir 读取
- 写入:
- 首次写入 → copy-up 到 upperdir
- 再次写入 → 修改 upperdir 副本
- 删除:
- 创建 whiteout 文件隐藏下层文件
- lowerdir 不受影响
注意:容器层文件删除只是“遮挡”,image 层不会改变
五、Docker 镜像加载原理
- 启动流程:
- bootfs 加载 → 内核加载完成后卸载
- rootfs 加载 → 只读检查
- 利用 union mount 将可写层挂载到 readonly rootfs 上
- 运行时:
- 所有只读镜像层共享
- 容器最顶层可读写
- 利用命名空间、控制组、rootfs 实现容器隔离与组装
六、核心概念总结
- 镜像是多层只读文件系统叠加
- 容器层为可写层,采用 CoW 技术
- 文件删除通过 whiteout 实现,原镜像不变
- overlay2 是官方推荐,稳定高效
- 镜像分层结构实现资源共享和存储节省
实战一:镜像分层存储实战
一、先用一句人话理解整个实战
👉 Docker 镜像本质就是:
一层一层“文件夹”叠起来,最后拼成一个完整的 Linux 系统
而 overlay2 做的事就是:
把多个目录“叠加”成一个目录给你看
二、先建立一个“脑子里的模型”(最重要)
想象:
第3层(你修改的) ← 可写
第2层(nginx配置)
第1层(系统文件)
第0层(Debian基础)
Docker 实际干的事:
👉 把这些层 叠起来
你看到的是:
一个完整的 Linux 系统(假的,但看起来真的)
三、磁盘上到底长什么样?(重点)
路径:
/var/lib/docker/overlay2/
结构👇
overlay2/
├── abc123/
│ ├── diff ← 这一层的真实文件
│ ├── lower ← 它的“爸爸层”
│ ├── link ← 短名字
│ └── work
├── def456/
├── ghi789/
├── l/ ← 所有层的“快捷方式”
四、逐个解释(这是核心)
1️⃣ diff —— 真正存文件的地方
👉 你可以理解为:
每一层就是一个“文件夹快照”
比如:
.../diff/usr/sbin/nginx
说明:
👉 nginx 就存在这一层里
📌 重点:
- 每一层只存“自己新增或修改的文件”
- 不重复存
2️⃣ lower —— 层之间的“族谱”
cat lower
输出:
l/xxx:l/yyy
👉 意思是:
我这一层,是基于 xxx 和 yyy 这两层的
📌 本质:
👉 层与层之间是链式关系
3️⃣ link —— 层的短名字
cat link
得到:
QJTI7BHG2F...
然后你去:
overlay2/l/QJTI7BHG2F...
会发现:
👉 它是一个软链接 → 指向 diff
📌 作用:
👉 缩短路径(不然太长)
4️⃣ l 目录 —— 所有层的“快捷方式集合”
overlay2/l/
里面全是:
短ID → 指向某个 diff
👉 类似 Windows 快捷方式
五、重点来了:容器启动前 vs 启动后
❌ 没启动容器时
只有:
diff(镜像层)
lower(关系)
👉 没有:
- upper
- merged
✅ 启动容器后
docker run -d nginx
系统帮你创建:
upperdir ← 你可以写
merged ← 给你看的“完整系统”
workdir ← 临时用
六、最关键:merged 到底是什么?
👉 merged 就是:
把所有层 + 你的修改 拼起来的“最终视图”
你进去看:
cd merged
ls
看到:
/bin
/etc
/usr
📌 这就是:
👉 你以为的 Linux 系统
七、读文件过程(超级重要)
当你访问:
/usr/sbin/nginx
Docker 怎么找?
👉 从上往下找:
1. upper(你改过?)
2. layer3
3. layer2
4. layer1
👉 找到就停
八、写文件过程(COW核心)
假设你修改 nginx:
第一次修改:
1. 从下层复制到 upper(copy_up)
2. 修改 upper 中的副本
👉 原来的不动!
第二次修改:
直接改 upper
📌 结论:
👉 所有修改都在 upper 层
九、删除文件(非常容易考)
你删除:
rm /usr/sbin/nginx
实际发生:
❌ 不会删底层文件
✅ 创建一个:
whiteout 文件(遮罩)
👉 效果:
你看不到 nginx
但其实还在
十、为什么 docker commit 会越来越大?
因为:
删除 ≠ 真删除
只是遮住
👉 所以:
- 层越来越多
- 数据越来越大
十一、把整个流程串起来(最重要总结)
镜像构建:
Dockerfile 每一行 → 一个 layer(diff)
镜像存储:
diff 存内容
lower 存关系
l 存快捷方式
容器运行:
镜像层(只读)
+ upper(可写)
= merged(你看到的系统)
十二、最简单理解版本(一定要记)
👉 Docker 做的事其实就一句话:
用 overlay2 把多个文件夹叠起来,再给你一个“假的完整系统”
overlay2联合文件系统
一、先忘掉所有术语(最关键)
你先只记一句话:
👉 overlay 就是:两个文件夹合成一个文件夹
二、用一个最简单的例子
你现在有两个文件夹:
📁 lower(旧的,只读)
A.txt 内容:hello
B.txt 内容:world
📁 upper(新的,可以改)
C.txt 内容:!!!
三、现在做一件事:合并
系统帮你生成一个新文件夹:
📁 merged(你看到的)
A.txt ← 来自 lower
B.txt ← 来自 lower
C.txt ← 来自 upper
👉 就是把两个文件夹“拼起来”
四、如果两个文件都有同名呢?(重点)
比如:
lower
A.txt = hello
upper
A.txt = 你好
merged 里会是:
A.txt = 你好
👉 结论:
upper(上层)覆盖 lower(下层)
五、现在开始最关键:修改文件
你在 merged 里改:
A.txt → 改成:HELLO!!!
实际发生的事(非常重要):
👉 系统偷偷做了两步:
第一步:复制
把 lower 里的 A.txt 复制到 upper
第二步:修改
在 upper 里改
最终变成:
lower(没变)
A.txt = hello
upper(变了)
A.txt = HELLO!!!
📌 结论:
修改 = 先复制再改(copy_up)
六、删除文件(最容易懵的点)
你在 merged 里删:
rm A.txt
实际发生:
❌ 不会删 lower
✅ 会在 upper 做一个标记
👉 相当于:
upper 说:这个文件别给用户看
结果:
lower(还在)
A.txt = hello
upper(多了个“隐藏标记”)
A.txt(隐藏标记)
merged 里:
A.txt 不见了
📌 结论:
删除 = 隐藏,不是真删
七、把你那段实验翻译成“人话”
你做的事情其实就是👇
第1步:创建三个文件夹
lower = 原始数据
upper = 修改区
merged = 给你看的
第2步:放文件
你放了:
lower: in_lower.txt
upper: in_upper.txt
两边都有: in_both.txt
第3步:合并后看到
merged:
in_lower.txt ← 来自 lower
in_upper.txt ← 来自 upper
in_both.txt ← upper 覆盖 lower
第4步:你修改 in_lower.txt
👉 系统做:
copy 到 upper → 再改
第5步:你删除 in_lower.txt
👉 系统做:
在 upper 标记“隐藏”
八、你只需要记住这3句话(够了)
1️⃣ 合并规则
上层覆盖下层
2️⃣ 修改规则
修改 = 复制到 upper 再改
3️⃣ 删除规则
删除 = upper 标记隐藏
九、再帮你一句话打通 Docker(重点)
👉 Docker 就是用这个机制:
镜像 = lower(只读)
容器 = upper(可写)
你看到的系统 = merged
Docker 卷原理
一、Docker 卷本质一句话
👉 Docker Volume 本质就是:Linux 的 mount --bind(绑定挂载)
二、整个流程(你必须搞懂的核心链路)
我给你按“时间顺序”讲清楚👇
① 容器启动前:准备 rootfs(镜像文件系统)
Docker 用的是 overlay2 联合文件系统
核心结构:
lowerdir(只读层,镜像)
upperdir(可写层,容器)
merged(合并后的根目录)
最终容器看到的是:
/var/lib/docker/overlay2/.../merged ← 容器 rootfs
👉 这个就是容器里的 /
② 容器进程创建(关键点!)
此时发生了两件事:
✔ 开启 Namespace
- Mount Namespace(挂载隔离)
- PID Namespace 等
👉 从这一步开始:
容器看到的“挂载行为”已经和宿主机隔离
③ 在 chroot 之前做一件关键操作
👉 挂载 Volume!!!
比如你:
-v /home:/test
Docker 实际干了啥?
本质操作:
mount --bind /home /var/lib/docker/.../merged/test
👉 注意这个路径:
宿主机目录 → 挂到 merged 里的某个目录
④ 然后执行 chroot
chroot merged/
容器看到的:
/test → 实际就是宿主机 /home
三、为什么容器能看到,宿主机看不到?
关键点👇
✔ 因为 Mount Namespace 已经生效
👉 挂载是在“容器的命名空间里”完成的
所以:
| 视角 | 看到的情况 |
|---|---|
| 容器 | /test 有内容 |
| 宿主机 | merged/test 是空目录 |
四、为什么 docker commit 提交不了 Volume 内容?
这个是考试/面试高频点
你看到的现象:
docker commit
👉 新镜像里:
/test 只有空目录
原因(核心!!!)
👉 Docker commit 只会提交:
overlay2 的 upperdir(可写层)
但 Volume 是:
独立挂载(bind mount)
本质区别:
| 类型 | 存储位置 |
|---|---|
| 容器修改 | overlay2/upperdir |
| Volume 数据 | /var/lib/docker/volumes/... |
👉 完全是两个地方!
更底层一句话总结:
👉 Volume 绕过了容器文件系统(overlay2)
五、用一句话彻底理解
👉 Docker Volume = 在容器 rootfs 上“插了一根管子”直接连到宿主机
所以:
- 数据不走镜像层
- 不参与 commit
- 独立生命周期
六、你这段内容的精简版总结(建议背这个)
1️⃣ rootfs 怎么来的?
👉 overlay2 = lower + upper → merged
2️⃣ Volume 怎么实现?
👉 本质是:
mount --bind 宿主机目录 容器目录
3️⃣ 为什么隔离不被破坏?
👉 因为:
- 已经进入 Mount Namespace
- 挂载只在容器可见
4️⃣ 为什么 commit 不包含 Volume?
👉 因为:
- commit 只打包 upperdir
- Volume 不在 overlay2 里
七、给你一个理解图(脑补)
宿主机
│
├── /home ← Volume源
│
├── overlay2
│ ├── lowerdir
│ ├── upperdir
│ └── merged
│ └── /test ← 挂载点(被覆盖)
│ ↑
│ mount --bind
│
容器看到:
/test → /home
八、你现在卡住的点(我帮你点出来)
你这段看不懂,本质是卡在:
👉 三个东西混在一起了:
- overlay2(镜像层)
- mount namespace(隔离)
- bind mount(卷)
Docker 网络原理
一、先给你一个“最终全景图”(先有整体)
你先看这个👇(不用完全懂,后面逐步拆)
[容器]
eth0 (172.17.0.2)
│
vethA
│
vethB
│
docker0 (172.17.0.1)
│
iptables NAT
│
宿主机网卡 eth0 (192.168.1.x)
│
外网
二、一步一步拆开(非常关键)
第一步:容器其实是“一个独立网络空间”
👉 Docker 创建容器时做了:
Network Namespace
你可以理解为:
👉 给容器开了一个“独立的小电脑”
里面有:
- 自己的网卡
- 自己的IP
- 自己的路由表
但是问题来了:
刚创建时:
容器里只有:
lo(回环)
👉 不能联网!
第二步:Docker 给你“接一根网线”(veth)
veth 是什么?
👉 一对设备:
vethA <----> vethB
👉 特点:
从 A 进去 → 直接从 B 出来
Docker 怎么用?
👉 做了这件事:
vethA → 放进容器(当 eth0)
vethB → 留在宿主机
现在变成:
容器:
eth0(其实就是 vethA)
│
vethB(在宿主机)
👉 到这里:
容器已经“连到宿主机”了
第三步:但还不够 → 需要“交换机”(bridge)
如果只有一根线:
👉 只能连一个设备
但现实是:
👉 有很多容器
Docker 创建了:
docker0(Linux Bridge)
👉 就像:
交换机
然后把所有 veth 接上去:
容器1 ─ veth ─┐
├── docker0
容器2 ─ veth ─┘
👉 现在容器之间可以互相通信了!
第四步:给容器分配 IP
Docker 会做:
容器:172.17.0.2
网关:172.17.0.1(docker0)
容器路由表:
default → 172.17.0.1
👉 意思:
不知道去哪 → 发给 docker0
第五步:访问外网(最关键)
场景:
容器执行:
curl www.baidu.com
数据路径(重点!!!)
① 容器发包
源IP: 172.17.0.2
目标IP: 百度IP
↓
② 经过 veth
eth0 → vethA → vethB
↓
③ 到 docker0
docker0 收到数据
↓
④ 走 NAT(iptables)
👉 关键操作:
172.17.0.2 → 替换成 → 宿主机IP(192.168.x.x)
👉 为什么要这样?
因为:
外网不认识 172.17.x.x
⑤ 发到真实网卡
宿主机 eth0 → 外网
⑥ 返回数据
外网 → 宿主机IP → iptables → 容器IP → 容器
三、你必须理解的3个核心组件
1️⃣ veth(网线)
👉 作用:
容器 ←→ 宿主机
2️⃣ bridge(交换机)
👉 作用:
多个容器互联
3️⃣ iptables NAT(翻译器)
👉 作用:
容器IP ↔ 外网IP
四、为什么不用 tun/tap?
你原文提到这个,其实你只要记:
tun/tap:
👉 数据路径:
内核 → 用户程序 → 再回内核
👉 缺点:
慢(走两次)
veth:
👉 数据路径:
内核 → 内核
👉 优点:
快
👉 所以:
Docker 选 veth,不选 tun/tap
五、再用一句话打通(非常重要)
👉 Docker 网络本质就是:
用 veth 把容器接出来
用 bridge 把容器连起来
用 NAT 让容器上外网
六、你现在卡住的真正原因
你刚那段资料的问题是:
👉 ❌ 把这些东西“分开讲”
但你需要的是:
👉 ✅ “一条数据怎么走”
七、我帮你做一个“极简记忆版”(建议背)
容器上网五步:
1. 容器发包
2. veth 出来
3. bridge 转发
4. NAT 改IP
5. 宿主机发出去
一、tun / tap 到底是干嘛的(核心一句话)
👉 tun/tap = 给程序用的“虚拟网卡”
但它和普通网卡有个本质区别:
普通网卡 → 连物理世界
tun/tap → 连用户程序
二、先用一个“最直观类比”理解
🧠 普通网卡
程序 → 内核协议栈 → 网卡 → 网络
🧠 tun/tap
程序A → 内核 → tun/tap → 程序B(你自己写的)
👉 数据不会直接出网,而是先被你“截胡”
三、tun vs tap(必须搞清)
1️⃣ tap(更像真实网卡)
👉 处理:
二层(以太网帧)
👉 可以干嘛:
- 模拟一台完整主机
- 可以有 MAC 地址
- 可以接入 bridge
2️⃣ tun(更常用)
👉 处理:
三层(IP包)
👉 更适合:
- VPN
- 隧道
- 代理
🔥 一句话区别
| 类型 | 类比 |
|---|---|
| tap | 一块网卡 |
| tun | 一个路由器接口 |
四、tun/tap 的真正用途(重点)
① VPN(最经典)
比如:
- OpenVPN
- WireGuard(更高级)
数据流程(必须理解)
正常情况:
你的电脑 → 直接访问网站
VPN:
你的程序 → tun设备 → VPN程序 → 加密 → 发到服务器
👉 VPN 做了什么?
- 拿到 tun 里的数据
- 加密
- 重新封装
- 发出去
核心点:
👉 tun = VPN 的入口
② 流量代理 / 抓包
你可以:
👉 拿到所有 IP 包,然后:
- 修改
- 丢弃
- 重定向
③ 自己实现网络协议
比如:
- 自己写一个“简化版 TCP/IP”
- 做实验网络栈
④ 容器网络(早期方案)
👉 早期也用过 tap,但现在基本用 veth
五、怎么用 tun/tap(重点来了)
Step 1:创建 tun 设备
ip tuntap add dev tun0 mode tun
Step 2:启用设备
ip link set tun0 up
Step 3:配置 IP
ip addr add 10.0.0.1/24 dev tun0
Step 4:写程序读取数据(核心)
Linux 里:
你其实是在读这个:
/dev/net/tun
C语言示意:
int fd = open("/dev/net/tun", O_RDWR);
// 之后 read(fd),就能拿到数据包
👉 重点来了:
read(fd) = 收到网络包
write(fd) = 发网络包
六、数据是怎么流动的(必须搞懂)
你发送一个包:
ping 10.0.0.2
内核会:
IP包 → tun0 → 交给你的程序
你的程序可以:
✔ 转发
发到真实网卡
✔ 修改
改IP / 加密
✔ 丢弃
直接不发
七、为什么 tun/tap 慢?
因为:
内核 → 用户态 → 再回内核
👉 多了一次切换
对比 veth:
内核 → 内核
👉 更快
八、总结(建议直接背)
1️⃣ tun/tap 是什么?
👉 程序控制的虚拟网卡
2️⃣ tun vs tap
tun → IP层(常用)
tap → 二层(更像真实网卡)
3️⃣ 最大用途
👉 VPN(核心应用)
4️⃣ 本质能力
拦截 + 修改 + 转发 网络数据包
一、为什么需要虚拟网络?
- 云原生时代:应用实例、容器随时扩缩,逻辑网络拓扑频繁变动
- 传统物理网络:拓扑固定,不适应动态变化
- 解决方案:在物理网络上构建一层虚拟网络 → 软件定义网络 SDN
SDN 结构
| 层级 | 名称 | 功能 |
|---|---|---|
| 下层 | Underlay | 物理网络,负责真实数据转发 |
| 上层 | Overlay | 逻辑网络,为应用提供可随意变动的 L2/L3 拓扑 |
二、VLAN:最基础的虚拟局域网
概念:
- VLAN = Virtual LAN
- 逻辑上划分广播域
- 交换机通过 VLAN ID 来隔离不同广播域
优点:
- 限制广播域,节省带宽
- 安全隔离,不同 VLAN 互不干扰
- 灵活分组,不受物理位置限制
缺点:
- VLAN ID 只有 12 位 → 最多 4096 个
- 大规模虚拟化环境下无法满足多租户需求
- MAC 表变大,影响交换机性能
三、VXLAN:大规模 Overlay 网络
解决 VLAN 局限:
- VXLAN ID = 24 bit → 支持 1600 万个虚拟网络
- 基于 L3 网络构建虚拟 L2 网络
- 以太网帧封装在 UDP 数据包中 → 可以穿越任何 IP 网络
数据流向:
容器A L2帧 → VXLAN封装成UDP → L3网络 → VXLAN解封装 → 容器B
本质:
- 创建虚拟 L2 广播域
- 节点之间通过隧道通信
- 逻辑网络与物理网络完全解耦
形象比喻:
- VXLAN 就像中国的高速路网,城市之间通过路网连接,不用管城市内部道路布局
四、MacVLAN
概念:
- 一个物理网卡可以拥有多个 MAC 地址
- 每个虚拟设备有独立 MAC → 可以直接和外部物理网络通信
特点:
- 通讯链路短 → 性能高
- 每个设备可拥有自己的 MAC
- 但硬件 MAC 地址数量有限 → 大规模部署受限
比喻:
- 一个物理人有多张身份证,每张身份证能独立登记
五、IPVLAN
概念:
- 子接口不拥有独立 MAC,所有共享父接口 MAC
- 每个子接口有独立 IP
特点:
- 减少 MAC 地址数量压力
- 适合大量容器共享同一物理网卡
- DHCP 要求使用独立 ClientID
比喻:
- 一张身份证对应多个人,但每个人有独立的电话号码
六、总结表格对比
| 技术 | 层级 | 特点 | 最大规模 | 性能 | 使用场景 |
|---|---|---|---|---|---|
| VLAN | L2 | 12-bit ID, 广播隔离 | 4096 | 高 | 小规模企业局域网 |
| VXLAN | L2 over L3 | 24-bit ID, 隧道 | 1600万 | 较高 | 云数据中心,多租户 |
| MacVLAN | L2 | 独立 MAC | 受硬件限制 | 高 | 容器直接对外通信 |
| IPVLAN | L2 | 共享 MAC, 独立 IP | 受物理网卡限制 | 高 | 大量容器共享网卡 |
Docker 网络分类与原理
Docker 的网络主要分为以下 7 种类型:
| 网络类型 | 网络驱动 | 核心原理 | 容器间通信 | 跨主机 | 适用场景 |
|---|---|---|---|---|---|
| bridge | bridge | 在宿主机创建 Linux 网桥(br-xxx),容器通过 veth pair 连接到网桥 |
同一宿主机的容器可通信 | 否 | 默认网络,单机容器通信 |
| host | host | 容器不创建独立 Network Namespace,共用宿主机网络 | 与宿主机共享 IP 和端口 | 否 | 高性能场景,需要访问宿主机端口 |
| container | container | 新容器共享已有容器的 Network Namespace | 与指定容器通信 | 否 | 多容器组合共享网络,节省资源 |
| none | none | 容器有独立 Network Namespace,但没有网卡、IP、路由 | 需手动配置 | 否 | 完全隔离网络或自定义网络方案 |
| overlay | overlay | 基于 VXLAN 隧道封装 L2 帧到 L3 网络,可跨主机 | 不同宿主机容器可通信 | 是 | Swarm、K8s 多主机集群 |
| macvlan | macvlan | 容器虚拟接口拥有独立 MAC,直接接入物理网段 | 与物理网络设备同网段 | 否 | 需要容器像物理机一样直接访问网络 |
| ipvlan | ipvlan | 容器共享父网卡 MAC,但有独立 IP | 同一网段容器可通信 | 否 | 高性能大规模容器,减少 MAC 表压力 |
核心概念解析
1️⃣ bridge 网络
- 容器启动时,Docker 在宿主机创建一个 Linux 网桥
- 每个容器通过 veth pair 接入网桥
- 默认情况下,网桥内容器互通,可通过端口映射访问宿主机外部
- 命令示例:
docker network create -d bridge bridgenet1
docker run --name c1 --network bridgenet1 -itd busybox
2️⃣ host 网络
- 容器不再虚拟化网络,直接使用宿主机的网络栈
- 容器 IP 与宿主机 IP 相同
- 网络性能最好,但容器和宿主机共享端口
- 命令示例:
docker run --name c2 -itd --network host busybox
3️⃣ container 网络
- 新容器共享已有容器的网络命名空间
- 两容器共享 IP、端口、网络接口
- 适合微服务组合使用
- 命令示例:
docker run -itd --name net2 --network container:net1 busybox
4️⃣ none 网络
- 容器独立 Network Namespace,但没有网卡、IP、路由
- 适合完全自定义网络方案
- 命令示例:
docker run -itd --name c3 --network none busybox
5️⃣ overlay 网络
- 基于 VXLAN 封装 L2 帧到 L3 网络
- 容器跨主机通信
- 常用于 Swarm 或 K8s
- 支持加密
- 命令示例:
docker network create -d overlay ovnet1
6️⃣ macvlan 网络
- 每个容器拥有独立 MAC,像物理机一样直接访问网络
- 性能高,适合高性能网络需求
- 需宿主机物理网卡支持混杂模式 (
promisc) - 命令示例:
docker network create -d macvlan \
--subnet=172.16.10.0/24 \
--gateway=172.16.10.1 \
-o parent=eth0 mac1
7️⃣ ipvlan 网络
- 容器共享父网卡 MAC,但有独立 IP
- 避免 MAC 表膨胀
- 通信在同一 L2 网段
- 命令示例:
docker network create -d ipvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o ipvlan_mode=l2 -o parent=eth0 pub_net
Docker Bridge 网络深入解析
1️⃣ 网络概念
- Docker Bridge 网络使用 bridge 驱动,底层依赖 Linux 内核的 Linux Bridge 技术。
- 本质:在宿主机上创建一个软件网桥 (
docker0),允许连接到同一网桥的容器互通,同时隔离未连接容器。 - 作用:
- 容器间通信
- 容器与宿主机通信
- 容器对外提供服务(通过 NAT/端口映射)
2️⃣ 桥接网络原理
核心技术
- veth pair
- Docker Daemon 创建两个虚拟网卡(veth0 & veth1)
- 特性:任意一端接收数据,另一端必定收到
- namespace
- veth1 被放入容器的 Network Namespace,改名为
eth0
- veth1 被放入容器的 Network Namespace,改名为
- docker0 网桥
- veth0 接入 docker0 网桥,实现宿主机与容器互通
- NAT (Network Address Translation)
- 容器对外通信通过 SNAT(源地址转换)
- 外界访问容器通过 DNAT(目标地址转换 + 端口映射)
数据流示意
| 场景 | 流程 |
|---|---|
| 容器访问外界 | 容器 eth0 → veth1 → veth0 → docker0 → 宿主机 eth0 → SNAT → 外网 |
| 外界访问容器 | 外网 → 宿主机 eth0 → DNAT 端口映射 → docker0 → veth0 → veth1 → 容器 eth0 |
iptables 与 NAT
- Docker 使用
iptables配置网络规则:- DNAT:将外部访问宿主机端口的请求映射到容器端口
- SNAT:容器访问外网时,源 IP 转换为宿主机 IP
- 示例:
# 外界访问宿主机 8088 被转发到容器 172.17.0.7:80
iptables -t nat -L -nv
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
4 204 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8088 to:172.17.0.7:80
- FORWARD 链规则保证响应报文回到容器:
iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
3️⃣ 实战操作
3.1 veth pair 对应查看
docker run --name netcontainer1 -itd busybox
docker exec -it netcontainer1 sh
# 查看容器内 eth0 对应的 iflink
cat /sys/class/net/eth0/iflink
# 宿主机查看 veth pair 对应
ip link
3.2 查看容器网络命名空间
- Docker 默认命名空间链接文件在
/var/run/docker/netns
ls /var/run/docker/netns/
- 使用
nsenter进入容器网络命名空间
# 进入容器 net4 的网络命名空间
nsenter --net=/var/run/docker/netns/00899eb26020 sh
ip a # 查看容器 eth0 IP
- 与
docker exec查看 IP 对比,结果一致
3.3 外界访问容器端口
docker run -itd --name net4 -p 81:80 busybox
# 宿主机 IP:81 → 容器 172.17.0.4:80
4️⃣ 总结
- Docker Bridge 网络是单主机容器通信的默认方案
- 核心依赖:
- veth pair:桥接容器与宿主机
- Network Namespace:隔离容器网络
- docker0 网桥:转发数据
- iptables + NAT:实现端口映射和外网访问
- 数据流:
- 容器发外:eth0 → veth → docker0 → eth0 → SNAT → 外网
- 外网访问容器:宿主机端口 → DNAT → docker0 → veth → eth0
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)