Docker 进阶 — 网络模型、数据持久化与多阶段构建

📖 知识点简介

我们学会了 Docker 基础操作------拉镜像、跑容器、写 Dockerfile。但生产环境的容器化远不止这些:容器怎么互访?数据怎么持久保存不随容器销毁?镜像怎么构建得更小更安全?

今天从这三个维度深入,补齐 Docker 生产化部署的关键拼图。


🛠 核心知识整理

一、Docker 网络模型

Docker 提供 5 种网络模式:

网络模式 启动参数 说明 适用场景
bridge 默认 通过 docker0 网桥,容器间通过 IP 通信 单机多容器
host --network host 容器直接使用宿主机网络栈,无独立 IP 性能敏感场景(如 Nginx)
none --network none 容器无网络,完全隔离 安全敏感容器
container --network container:name 共享另一个容器的网络栈 Sidecar 模式
overlay 需 swarm/k8s 跨主机容器通信 多机集群

核心技能:用户自定义 bridge 网络

自定义 bridge 相比默认 bridge(docker0)的最大优势是:支持 DNS 自动解析(容器名解析为 IP)。

bash 复制代码
# 创建自定义网络
docker network create --driver bridge --subnet 172.20.0.0/16 app-net

# 使用自定义网络启动容器(容器名 = hostname 可互 ping)
docker run -d --name web --network app-net nginx:alpine
docker run -d --name api --network app-net node:18-alpine

# 验证:进入 web 容器 ping api
docker exec web ping api
# ✅ 解析成功!172.20.0.3 通

注:默认 bridge 只能通过 IP 互联(--link 是历史产物不推荐),自定义 bridge 才是生产推荐方案

二、数据持久化方案

Docker 容器默认是无状态的------容器删除后内部数据全部消失。三种持久化方案对比:

方案 mount 方式 特点 推荐场景
Bind Mount -v /host:/container 宿主机任意路径挂载 配置文件、开发调试
Volume -v volume_name:/container Docker 管理,隔离性好 数据库数据、生产持久化
tmpfs Mount --tmpfs /container 仅存内存,容器停即清 敏感数据(密码文件)

Volume 最佳实践:

bash 复制代码
# 创建命名卷(最推荐的生产方式)
docker volume create mysql_data

# 查看卷详情
docker volume inspect mysql_data
# 输出:{"Mountpoint": "/var/lib/docker/volumes/mysql_data/_data"}

# 使用卷启动 MySQL
docker run -d \
  --name mysql \
  -v mysql_data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  mysql:8.0

# 数据不受容器生命周期影响------即使 docker rm mysql,数据还在

# 卷备份
docker run --rm -v mysql_data:/source -v $(pwd):/backup alpine \
  tar czf /backup/mysql_data_$(date +%Y%m%d).tar.gz -C /source .

Bind Mount 常见用途:

bash 复制代码
# 将 Nginx 配置挂在宿主机,方便修改
docker run -d \
  --name nginx \
  -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro \
  -v /var/log/nginx:/var/log/nginx \
  -p 80:80 \
  nginx:alpine

三、多阶段构建

多阶段构建(Multi-stage Build)是减小镜像体积的核武器------将"编译环境"和"运行环境"分离:

dockerfile 复制代码
# ===== 阶段一:构建环境(可能 1GB+) =====
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .

# ===== 阶段二:精简运行环境(最终仅 ~20MB) =====
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app/myapp /usr/local/bin/myapp
EXPOSE 8080
CMD ["myapp"]
bash 复制代码
# 查看构建后的镜像大小
docker images | grep myapp
# myapp    latest    abc123def456   15MB   ← 对比直接用 golang:1.22 能省掉 900MB+!

多阶段构建的进阶用法:

dockerfile 复制代码
# 前端 + 后端 分离构建
FROM node:18-alpine AS frontend-builder
WORKDIR /app
COPY frontend/ ./
RUN npm ci && npm run build

FROM golang:1.22 AS backend-builder
WORKDIR /app
COPY backend/ ./
RUN go build -o server .

# 最终镜像:同时包含前端静态文件和后端二进制
FROM alpine:3.19
RUN apk add --no-cache nginx
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
COPY --from=backend-builder /app/server /usr/local/bin/server
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["/usr/local/bin/server"]

💻 实操示例

场景 1:部署 LNMP 站点的生产级 docker-compose

yaml 复制代码
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./html:/usr/share/nginx/html:ro
      - nginx_logs:/var/log/nginx
    networks:
      - frontend
    depends_on:
      - php

  php:
    build:
      context: ./php
      dockerfile: Dockerfile
    volumes:
      - ./html:/var/www/html
    environment:
      - DB_HOST=mysql
      - DB_NAME=myapp
      - DB_USER=myapp
      - DB_PASS=${DB_PASS}
    networks:
      - frontend
      - backend
    depends_on:
      mysql:
        condition: service_healthy

  mysql:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASS}
      MYSQL_DATABASE: myapp
      MYSQL_USER: myapp
      MYSQL_PASSWORD: ${DB_PASS}
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  mysql_data:
  nginx_logs:
bash 复制代码
# 启动前检查
docker-compose config   # 验证 yml 语法
docker-compose config --services  # 列出服务

# 后台启动(会自动构建 php 镜像)
docker-compose up -d

# 查看各服务健康状态
docker-compose ps
docker-compose logs mysql

# 滚动升级(重新构建并只重启某个服务)
docker-compose build php
docker-compose up -d --no-deps php

场景 2:容器网络排错

bash 复制代码
# 问题:web 容器连不上 api 容器

# ① 检查两个容器是否在同一网络
docker inspect web --format '{{json .NetworkSettings.Networks}}' | jq .
docker inspect api --format '{{json .NetworkSettings.Networks}}' | jq .

# ② 如果不在同一网络,加入
docker network connect app-net web

# ③ 进入容器测试连通性
docker exec -it web -- ping api

# ④ 检查端口是否监听到 0.0.0.0(而非 127.0.0.1)
docker exec -it api -- ss -tlnp

# ⑤ 清理:所有已停止容器的网络
docker network prune

场景 3:限制容器资源(防止"坏邻居")

bash 复制代码
# 启动容器时限制 CPU 和内存
docker run -d \
  --name limited-app \
  --memory="512m" \
  --memory-reservation="256m" \
  --cpus="0.5" \
  --cpuset-cpus="0,1" \
  --pids-limit=100 \
  nginx:alpine

# 验证限制
docker stats limited-app --no-stream

⚠️ 常见坑点 / 注意事项

🚫 坑 1:--network host 下不能用 -p 映射端口

host 模式下容器直接使用宿主机网络栈,-p 参数会静默失效(无报错但不生效)。此时直接访问宿主机端口即可。

🚫 坑 2:Volume 默认权限问题

Docker Volume 首次创建时目录归 root:root,容器内非 root 进程可能无法写入。解决方法:

bash 复制代码
# 方案一:在 Dockerfile 中指定目录归属
RUN mkdir -p /data && chown -R 1000:1000 /data

# 方案二:启动时设置用户(前提镜像支持)
docker run -u 1000:1000 -v data_vol:/data myapp

🚫 坑 3:多阶段构建的缓存失效

多阶段构建中,只有 COPY 前的内容可以复用缓存 。如果 COPY . . 写在前面,源码稍有变动整个构建缓存都会失效。优化:

dockerfile 复制代码
# 把依赖下载放在前面(变化频率低)
COPY go.mod go.sum ./
RUN go mod download

# 源码放在最后(变化频率高)
COPY . .
RUN go build

🚫 坑 4:docker-compose depends_on 不等于"等就绪"

depends_on 只控制启动顺序,不保证服务已就绪(可用端口/健康)。MySQL 启动后还要等几秒才能接受连接。解决办法:

yaml 复制代码
# 方案一:用 healthcheck(推荐)
mysql:
  healthcheck:
    test: ["CMD", "mysqladmin", "ping"]
    interval: 5s
    retries: 10
# php 依赖加 condition:
# depends_on:
#   mysql:
#     condition: service_healthy

# 方案二:应用层做重试(代码层面)
# Go: 用 retry 库,每 1 秒重试,最多 30 次
# 脚本: while ! nc -z mysql 3306; do sleep 1; done

🚫 坑 5:日志文件撑爆磁盘

Day 9 提到过,再强调一次全局配置:

json 复制代码
// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}
bash 复制代码
# 立即查看当前 Docker 日志占用
du -sh /var/lib/docker/containers/*/*-json.log

# 清理所有容器的日志(不重启)
truncate -s 0 /var/lib/docker/containers/*/*-json.log

📊 今日小结

主题 核心技能 一句话总结
网络 自定义 bridge + DNS 自动解析 用自定义网络替代 --link
数据持久化 Volume vs Bind Mount 数据库数据用 volume,配置文件用 bind
多阶段构建 FROM ... AS builder + COPY --from 编译环境与运行环境分离,镜像轻至 1/50
资源限制 --memory / --cpus / --pids-limit 防止单个容器"吃掉"整台宿主机
Compose 进阶 healthcheck + networks + depends_on condition 保证服务启动顺序+就绪状态
相关推荐
用户4279254051711 小时前
《微博开放平台官方CLI开源了:70+API一行搞定,AI Agent原生支持》
后端
Csvn1 小时前
文本处理三剑客 — grep、sed、awk 实战精讲
后端
sarasuki1 小时前
JavaScript的对象、new的机制与原型包装类
javascript·后端
某鹏1 小时前
java伪共享问题的稳定解法
后端
fliter1 小时前
Rust 不是手动内存管理:它是声明式内存管理
后端
AI人工智能_电脑小能手1 小时前
【大白话说Java面试题 第125题】【并发篇】第25题:说说 Java 线程的中断机制
java·后端·面试
fliter1 小时前
Box 里到底装了什么:从 Go interface 到 Rust trait object
后端
Java内核笔记1 小时前
Spring Security 源码解析(六)无状态 JWT 实践:Session 共享与自定义过滤器
java·后端
乘云数字DATABUFF1 小时前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端