📖 知识点简介
我们学会了 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 | 保证服务启动顺序+就绪状态 |