← 返回文章列表 专栏:网络与容器架构

更新于

Docker 容器化基础:镜像分层原理、多阶段构建、Bridge 网络与数据卷

解析 Docker OverlayFS 分层存储与 Namespace/Cgroups 隔离机制,编写多阶段构建 Dockerfile,并配置自定义 Bridge 网络与只读数据卷。

Docker OverlayFS 分层镜像、Namespace/Cgroups 隔离与数据卷挂载架构
Docker OverlayFS 分层镜像、Namespace/Cgroups 隔离与数据卷挂载架构

在生产服务器上直接用宿主机包管理器安装各类运行时,时间一长容易遇到依赖库版本冲突与残留配置文件难以清理的问题。通过 Docker 将服务封装为标准镜像,并配合只读数据卷挂载,可以让线上环境保持干净且易于迁移。

本文梳理 OverlayFS 镜像分层结构、多阶段 Dockerfile 编写方法、自定义 Bridge 网络通信,以及 Bind Mount 与 Named Volume 的适用场景。

容器与虚拟机的区别:三项 Linux 内核机制

Docker 容器内部不包含独立的 Linux 内核,容器进程直接运行在宿主机内核之上,其隔离与分层能力来自三项内核机制:

Docker OverlayFS 分层存储、Namespace/Cgroups v2 隔离与只读绑定挂载架构图
Docker 容器运行架构:利用多阶段构建剥离 Node.js 编译环境,通过 Namespace 与 Cgroups v2 限制资源,并以 :ro 只读方式挂载宿主机静态产物。
  1. Namespace(命名空间 —— 隔离视图):
    • PID Namespace:容器内的主进程看到自己的 PID 为 1,无法看到宿主机上的其他进程。
    • NET Namespace:为容器分配独立的虚拟网卡(eth0)、IP 地址、路由表与端口空间。
    • MNT Namespace:为容器提供独立的根目录 / 挂载视图。
  2. Cgroups v2(控制组 —— 限制资源配额):
    • 限制单个容器可使用的最大内存(如 --memory=256m)和 CPU 份额(如 --cpus=0.5)。当某个容器进程出现内存泄漏时,仅触发该容器内部的 OOM 重启,不会耗尽整台宿主机的内存。
  3. OverlayFS(联合挂载文件系统 —— 共享只读镜像层):
    • Docker 镜像由多层只读层(LowerDir)叠加而成。如果在同一台机器上运行 5 个基于 nginx:1.27-alpine 的容器,磁盘只保存一份约 18MB 的基础镜像层,每个容器仅在顶层挂载一个独立的可写层(UpperDir)。

安装 Docker Engine 与守护进程日志限制

在 Ubuntu 24.04 / Debian 12 服务器上,建议使用 Docker 官方 APT 软件源安装 Docker Engine 与 docker compose 插件,并在启动前配置日志大小上限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 1. 安装基础证书工具并导入 Docker 官方 GPG 密钥
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# 2. 写入 Docker 官方 APT 软件源并安装 Docker Engine 与 Compose 插件
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# 3. 写入 /etc/docker/daemon.json(限制容器标准输出日志体积,启用 live-restore)
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
},
"live-restore": true
}
EOF

sudo systemctl enable --now docker
sudo systemctl restart docker

配置说明:

  • log-opts 限制单个容器的 JSON 标准输出日志最多占用 20MB × 5 = 100MB,防止长时间运行后 /var/lib/docker/containers/ 占满磁盘。
  • live-restore: true 允许在升级或重启 dockerd 守护进程期间,保持正在运行的容器不中断。

编写多阶段 Dockerfile(Multi-Stage Build)

将 Astro、Next.js 或 VitePress 等静态站点打包为容器镜像时,不需要把几百兆的 node_modules 和源码带入最终运行镜像。

使用多阶段构建(Multi-Stage Build),可以在第一阶段用 node:22-alpine 编译静态网页,第二阶段仅用 nginx:1.27-alpine 托管编译产物:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ==========================================
# 阶段 1:Builder 静态编译阶段(构建完成后丢弃)
# ==========================================
FROM node:22-alpine AS builder
WORKDIR /app

# 先单独拷贝依赖锁文件,利用 Docker 缓存层加速后续构建
COPY package.json package-lock.json ./
RUN npm ci

# 拷贝源码与文章并执行生产构建
COPY . .
RUN npm run build

# ==========================================
# 阶段 2:Production 运行阶段(不含 Node.js 与源码)
# ==========================================
FROM nginx:1.27-alpine AS production

# 拷贝自定义 Nginx 站点配置
COPY deploy/nginx/blog.conf /etc/nginx/conf.d/default.conf

# 仅从 builder 阶段复制编译生成的 public/ 目录
COPY --from=builder /app/public /usr/share/nginx/html

# 声明监听端口与健康检查
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1:80/ >/dev/null || exit 1

这样构建出的运行镜像体积不到 25MB,且镜像内不包含构建工具链与源代码。


Docker 网络模型与数据持久化(Bind Mount vs. Volume)

1. 自定义 Bridge 网络与容器名解析

Docker 默认的 bridge(docker0 网桥)不支持通过容器名称进行 DNS 解析,容器之间只能按内网 IP 通信。创建自定义网桥(User-Defined Bridge)后,Docker 会为该网桥提供内嵌 DNS 解析(127.0.0.11):

1
2
3
4
# 创建名为 web-net 的自定义桥接网络
docker network create web-net

# 接入同一自定义网络的容器,在 Nginx 配置中可直接按容器名转发:proxy_pass http://umami:3000;

2. 端口映射与宿主机防火墙的关系

执行 docker run -p 8080:80 ... 时,Docker 会在内核 iptables 的 PREROUTING / DOCKER 链中直接添加转发规则,其匹配位置在宿主机 ufw 防火墙规则之前。因此,即使 ufw 未放行 8080 端口,外网依然可以直接访问 http://<服务器IP>:8080。

对于仅需由宿主机或前置 Nginx 反向代理访问的内部容器,应将监听地址显式绑定到本机回环 127.0.0.1:

1
2
# 仅允许本机回环访问 8080 端口,不向公网网卡暴露
docker run -d --name internal-app -p 127.0.0.1:8080:80 nginx:1.27-alpine

3. Bind Mount 与 Named Volume 的选择

特性维度 绑定挂载(Bind Mount) 命名数据卷(Named Volume)
挂载语法 -v /var/www/blog/current:/usr/share/nginx/html:ro -v postgres-data:/var/lib/postgresql/data
宿主机路径 指定宿主机上的任意绝对路径 由 Docker 统一存放在 /var/lib/docker/volumes/<name>/_data
适用场景 静态网页产物目录、Nginx 配置文件(nginx.conf)、SSL 证书目录(便于宿主机脚本直接更新) 数据库存储目录(PostgreSQL / MySQL / SQLite)、Redis 数据文件(由容器自动初始化目录属主与权限)

使用 Docker Compose 编排服务栈

当服务器上同时运行静态博客网关与统计等辅助容器时,可用 /opt/blog-stack/compose.yaml 统一管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
name: blog-production

services:
blog-web:
image: nginx:1.27-alpine
container_name: blog-nginx
restart: unless-stopped
read_only: true
ports:
- "80:80"
- "443:443"
tmpfs:
- /var/cache/nginx:uid=101,gid=101,mode=0755
- /var/run:uid=101,gid=101,mode=0755
- /tmp:mode=1777
volumes:
- /var/www/blog/current:/usr/share/nginx/html:ro
- /opt/blog-stack/nginx/conf.d:/etc/nginx/conf.d:ro
- /etc/nginx/ssl:/etc/nginx/ssl:ro
- /var/log/nginx-blog:/var/log/nginx:rw
deploy:
resources:
limits:
cpus: "0.50"
memory: 256M
networks:
- web-net

networks:
web-net:
driver: bridge

日常维护常用的四条命令:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 后台启动或应用更新后的配置
docker compose -f /opt/blog-stack/compose.yaml up -d --remove-orphans

# 2. 查看各容器运行状态、端口映射与实时资源占用
docker compose -f /opt/blog-stack/compose.yaml ps
docker stats --no-stream

# 3. 在容器内测试 Nginx 配置语法
docker exec -it blog-nginx nginx -t

# 4. 清理未使用的悬空镜像与构建缓存
docker system prune -f