Docker 镜像制作笔记

一、镜像制作的原因

当官方镜像无法满足特定需求时,需要自定义镜像,常见原因包括:

  1. 代码打包
    将自编写的代码打包到镜像中,随镜像发布。
  2. 安全性考虑
    第三方镜像可能存在安全漏洞,需要自制镜像。
  3. 特定功能需求
    官方镜像无法满足功能,如数据库审计、特殊配置。
  4. 公司内部规范
    基于公司内部操作系统或标准构建镜像。

二、镜像制作方式

1. 快照方式(偶尔制作的镜像)

  • 思路:在基础镜像上启动容器,安装所需软件和配置后,创建快照。
  • 命令docker commit
  • 语法

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

  • 常用参数
    • -a:提交作者
    • -c:使用 Dockerfile 指令创建镜像,可修改启动指令
    • -m:提交说明
    • -p:提交时暂停容器

2. Dockerfile 构建方式(经常更新的镜像)

  • 思路:将安装流程写入 Dockerfile,通过 docker build 构建镜像。
  • 特点
    • 可重复构建
    • 易于版本管理
    • 自动化 CI/CD 支持

三、快照方式实战

实战一:C++ HelloWorld 镜像

  1. 准备工作目录

mkdir -p /data/maxhou/commitimage
cd /data/maxhou/commitimage

  1. 创建 C++ 源码

#include <stdio.h>
int main() {
printf("hello docker!\n");
return 0;
}

  1. 启动 CentOS 容器

docker run -it --name mycppcommit centos:7 bash

  1. 替换国内软件源(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

  1. 安装编译器并创建源代码目录

yum install -y gcc
mkdir /src/

  1. 拷贝源码到容器

docker cp ./demo.c mycppcommit:/src

  1. 编译运行测试

cd /src
gcc demo.c -o demo
./demo # 输出 hello docker!

  1. 提交为镜像

docker commit mycppcommit mycppimg:v1.0
docker images

  1. 运行镜像验证

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

管理 volume

✅ Docker(docker volume命令)

指定宿主机目录

VOLUME做不到

✅ 真正创建卷的是:



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 官方镜像的“安全兜底设计”


六、你实验中的结论是完全正确的 ✅

你做的这个流程:

  1. VOLUME ["/data"]

  2. 容器启动

  3. Docker 自动创建匿名卷

  4. 容器删除

  5. 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

容器 ↔ 容器

❌ 不需要

容器 ↔ 宿主机

❌ 不需要

docker run -p

❌ 不需要

docker run -P

✅ 需要

文档说明

✅ 需要


五、一个非常重要的对比(必记)

项目

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 参数(非常重要)

参数

默认值

含义

--interval=DURATION

30s

多久检查一次

--timeout=DURATION

30s

单次检查超时

--start-period=DURATION

0s

启动宽限期

--retries=N

3

连续失败几次算 unhealthy

✅ 示例:



HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

四、CMD 的返回值(核心)

Docker 只看 exit code

返回值

含义

0

✅ healthy

1

❌ unhealthy

2

⚠️ 保留(不要用)

📌 非 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
    • 系统镜像:alpinebusyboxdebian
  • 避免使用过大的基础镜像(如完整 Ubuntu),减少最终镜像臃肿

5. 减少镜像层数

  • 尽量合并 RUNADDCOPY 指令
  • 示例:

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>
  • 原因:
    1. 镜像更新后,旧镜像名被覆盖
    2. Dockerfile 构建时重复生成同名镜像
  • 查看空悬镜像:

docker image ls -f dangling=true

  • 可安全删除,因为一般已经失去存在价值

6. 中间层镜像

  • 用途:加速镜像构建、重复利用资源
  • 特征:
    • Docker 构建每条指令产生一层 image
    • 中间层镜像可能无标签,但被上层镜像依赖
  • 查看中间层镜像:

docker image ls -a

  • 不建议删除,否则上层镜像可能出错
  • 不会占用额外存储,相同层只存一份

Docker 镜像原理笔记

一、操作系统基础

  • 操作系统由以下子系统组成:
    • 进程调度子系统
    • 进程通信子系统
    • 内存管理子系统
    • 设备管理子系统
    • 文件管理子系统
    • 网络通信子系统
    • 作业控制子系统
  • Linux 文件管理子系统由 bootfsrootfs 组成:
    1. bootfs
      • 包含 bootloader 和 kernel
      • 功能:引导加载内核,启动时加载,加载完成后被卸载
      • Docker 镜像底层对应 bootfs
    2. rootfs
      • 包含 Linux 标准目录 /dev, /proc, /bin, /etc
      • 对应不同 Linux 发行版(Ubuntu、CentOS 等)

二、Union FS(联合文件系统)

  • 将多个目录(比如overlay2的lowerdir、upperdir)合并挂载到一个虚拟目录下(mergeddir)
  • 特性:
    • 支持只读和可读写目录合并
    • 写时复制(Copy-on-Write,CoW)
  • 写时复制原理
    • 资源初次修改才创建新副本
    • 未修改资源共享,节省存储和内存
    • 修改操作只在新副本上发生

三、Docker 镜像概念

  • 镜像 = 多层 Union FS 文件系统
  • 每一层称为 layer
  • 镜像特点:
    1. 共享宿主机内核
    2. Base 镜像是最小 Linux 发行版
    3. 同一 Docker 主机支持不同 Linux 发行版
    4. 分层结构,可上层引用下层,实现资源最大化共享
    5. 容器层可写,采用 CoW 技术,仅保存变化部分
    6. 容器层以下都是只读
    7. Docker 从上到下查找文件

四、Docker 分层存储实现原理

1. 支持的 Union FS 类型

  • AUFS、overlay、overlay2、DeviceMapper、VSF
  • 各发行版使用:
    • CentOS: overlay2 / overlay
    • Debian: aufs
    • RedHat: devicemapper
  • 推荐:overlay2(稳定,Docker 默认)

2. Union FS 原理

  • 镜像 = 多个只读层叠加(容器就是加上一个可写层)
  • 容器启动时:
    1. 加载镜像只读层
    2. 在顶部加 读写层
    3. 文件修改:
      • Copy-up 到读写层
      • 原只读层保持不变
    4. 文件删除:
      • 在读写层创建 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 镜像加载原理

  • 启动流程:
    1. bootfs 加载 → 内核加载完成后卸载
    2. rootfs 加载 → 只读检查
    3. 利用 union mount 将可写层挂载到 readonly rootfs 上
  • 运行时:
    • 所有只读镜像层共享
    • 容器最顶层可读写
    • 利用命名空间、控制组、rootfs 实现容器隔离与组装

六、核心概念总结

  1. 镜像是多层只读文件系统叠加
  2. 容器层为可写层,采用 CoW 技术
  3. 文件删除通过 whiteout 实现,原镜像不变
  4. overlay2 是官方推荐,稳定高效
  5. 镜像分层结构实现资源共享和存储节省

实战一:镜像分层存储实战

一、先用一句人话理解整个实战

👉 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


八、你现在卡住的点(我帮你点出来)

你这段看不懂,本质是卡在:

👉 三个东西混在一起了:

  1. overlay2(镜像层)
  2. mount namespace(隔离)
  3. 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 做了什么?

  1. 拿到 tun 里的数据
  2. 加密
  3. 重新封装
  4. 发出去

核心点:

👉 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 来隔离不同广播域

优点:

  1. 限制广播域,节省带宽
  2. 安全隔离,不同 VLAN 互不干扰
  3. 灵活分组,不受物理位置限制

缺点:

  • 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️⃣ 桥接网络原理

核心技术

  1. veth pair
    • Docker Daemon 创建两个虚拟网卡(veth0 & veth1)
    • 特性:任意一端接收数据,另一端必定收到
  2. namespace
    • veth1 被放入容器的 Network Namespace,改名为 eth0
  3. docker0 网桥
    • veth0 接入 docker0 网桥,实现宿主机与容器互通
  4. 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
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐