06 · 部署实践(Deployment Practice)

📅 预计 60 分钟 | ⭐ = 重要知识点 | 📌 中英术语见文末
🛠 本讲示例命令可直接运行,建议结合前五讲反复验证


6.0 先给直觉:从"会跑"到"跑得稳"

前面五讲解决了"怎么把容器跑起来"。真实环境里,容器能不能稳定、安全、可维护地跑,才是重点。这一讲把部署时的四个基本功串起来:

  1. 环境变量:配置和代码分离,不改代码也能换配置。
  2. 健康检查:让编排系统知道容器"真的能用"还是"只是活着"。
  3. 日志管理:出了事查得到,不把磁盘撑爆。
  4. 镜像优化:小、安全、好维护。

💡 记忆口诀:配置走环境变量,就绪靠健康检查,排错看日志,上线挑小镜像。


6.1 环境变量:配置与代码分离 ⭐

为什么要环境变量

把密码、数据库地址、端口写死在代码或镜像里,会导致两个问题:不安全(镜像是要分发的)和不灵活(换环境就得改代码重建)。

正确做法:代码里只读取环境变量,具体值由运行时注入。

# docker run 直接传单个变量
docker run -d -e DB_HOST=10.0.0.1 -e DB_PASSWORD=secret123 --name app myapp:1.0

# 从文件批量读取变量
docker run -d --env-file .env --name app myapp:1.0

.env 文件格式是简单的 KEY=value 每行一个:

DB_HOST=10.0.0.1
DB_PASSWORD=secret123
APP_PORT=3000

Compose 里的两种注入方式(第 5 讲提过):

services:
  app:
    image: myapp:1.0
    environment:            # 直接写,适合少量、非敏感
      APP_PORT: "3000"
    env_file:               # 从文件读,适合多变量
      - .env

敏感信息怎么处理 ⭐

  • 不要把密钥写进镜像:镜像会被分发到仓库,等于公开。
  • 不要写进 .env 提交到仓库.env 要进 .gitignore
  • 生产环境用 Docker 的 secret 机制、Compose 的 secrets,或平台的密钥管理服务(如云厂商的 Secret 服务)。
  • 数据库密码这类值,交付时用环境变量注入,不给默认弱密码。

ENV 与 ARG 的边界(回顾第 4 讲)

  • ARG 只在构建时有效,不会进入运行中的容器。
  • ENV 写入镜像,运行时容器里能直接读到
  • 需要运行时读取的配置,用 ENV 或运行时 -e 注入,别用 ARG

⚠️ 常见错误

  1. 把密码写死在 Dockerfile 或源码里:镜像公开 = 密码公开,这是最典型的安全事故来源。
  2. .env 被提交进 Git:忘记加 .gitignore,密钥跟着仓库泄露。
  3. 想在运行时读 ARG 的值:构建参数不进运行时,要用 ENV 或运行时注入。

6.2 健康检查:让系统知道容器"能用" ⭐

为什么需要健康检查

容器"running"不代表服务"能用"——进程还在,但数据库连接池满了、端口没监听,照样是坏的。编排系统、负载均衡器需要一种机制,知道容器真的就绪

HEALTHCHECK 指令在 Dockerfile 里声明检查方式:

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

含义:每 30 秒执行一次 curl -f http://localhost/,成功返回 0 表示健康,连续 3 次失败标记为 unhealthy

在 Compose 里也能写:

services:
  web:
    image: nginx:1.25
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s
      timeout: 5s
      retries: 3

查看健康状态:

docker inspect --format='{{.State.Health.Status}}' web
# 输出:healthy / unhealthy / starting

健康检查的实战价值

  • 编排依赖就绪depends_on + condition: service_healthy 能让 web 等数据库真正就绪再启动,弥补第 5 讲说的"depends_on 只管顺序"的短板。
  • 负载均衡摘除:反向代理(如 Nginx、负载均衡器)发现节点 unhealthy 就不再转发流量给它。
  • 自动重启:配合 restart 策略,坏掉的容器能被拉起。

Compose 里配合 depends_on 精确控制启动时机:

services:
  web:
    image: myapp:1.0
    depends_on:
      db:
        condition: service_healthy   # db 健康后才启动 web
  db:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 5

这样 web 启动时,db 已经真正可连,不再有"顺序对了但连接被拒"的启动竞态。

⚠️ 常见错误

  1. 检查路径写错:探活地址必须真实反映服务状态,写一个永远返回 200 的路径,等于没检查。
  2. 只检查进程存活testCMD true 之类,容器永远"健康",失去了意义。
  3. 基础镜像没有 curlalpine 默认不带 curl,HEALTHCHECK 里用它先装好,或用 wget

6.3 日志管理:出了事查得到 ⭐

默认日志行为

容器标准输出(stdout/stderr)就是它的"日志"。默认日志驱动 json-file 会把日志按 JSON 格式存到宿主磁盘,用 docker logs 查看:

docker logs web            # 查看全部日志
docker logs -f web         # 跟随输出,实时排错
docker logs --tail 100 web # 只看最近 100 行

应用要遵守一条约定:日志写标准输出,别写文件。这样容器重启、日志驱动更换时,日志都不会丢,还能被集中收集。

限制日志大小:防止撑爆磁盘 ⭐

默认情况下 json-file 日志不设上限,一个疯狂打日志的容器几小时就能写满磁盘。部署时必须加限制:

docker run -d \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --name web nginx

意思:单个日志文件最大 10MB,保留最近 3 个文件,自动轮转。

Compose 里同样配置:

services:
  web:
    image: nginx:1.25
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

日志驱动

除默认 json-file 外,常见驱动还有 syslog(送系统日志)、journald(送 systemd)、gelf(送 Graylog 等集中日志平台)。容器本身不存日志,交给驱动的设计,是为了日志可被聚合、检索。

⚠️ 常见错误

  1. 日志写文件:应用把日志写进容器内文件,容器一删日志全丢,docker logs 也看不到。
  2. 不限制日志大小:生产事故一大类就是"磁盘被日志写满"。上线就配 max-size / max-file
  3. 排错不看日志:容器启动即退出,先 docker logs 再调配置,别靠猜。

6.4 镜像优化:小、安全、好维护

从体积入手

  • 选小基础镜像alpine(几 MB)或 distroless(无 shell,更安全)优先,对比 Ubuntu 的几百 MB,传输、启动都更快。
  • 多阶段构建(第 4 讲):只把产物拷进最终镜像,把编译器、源码留在构建阶段。
  • 合并 RUN&& 连接多条命令,减少层数。
  • .dockerignore:排除 node_modules.git、测试数据等,缩小构建上下文。

从安全入手 ⭐

  • 别用 root 跑应用:用 USER 切到普通用户,降低被入侵后的影响范围。
FROM node:18-alpine
USER node                      # 以非 root 身份运行
WORKDIR /app
COPY --chown=node:node . .
CMD ["node", "index.js"]
  • 用具体版本标签FROM node:18-alpine 好过 FROM node:latest,锁定可复现。
  • 定期更新基础镜像:基础镜像的漏洞修复要跟上,别一直用一年前的旧镜像。
  • 最小化安装:只装运行需要的东西,少一个包少一个漏洞入口。

⚠️ 常见错误

  1. 全程用 root:容器里的 root 等同宿主机的高权限入口,被攻破后果严重。
  2. latest 标签当生产基础:无法复现,哪天 latest 变了行为就变了。
  3. 忽略 .dockerignore:把密钥、.git、依赖缓存拷进镜像,又大又危险。

6.5 常见坑与排查思路

高频翻车点

现象 常见根因 对策
容器秒退 主进程崩溃,看 docker logs 先看日志再调配置
端口冲突 宿主端口已被占用 docker ps 查占用,换端口
数据丢失 没挂卷就 rm 容器 重要数据先建卷再跑
内存被杀 容器内存超限 OOM docker stats 观察,调 --memory
连不上宿主机服务 容器内 localhost 是容器自己 用宿主机 IP 或 host-gateway
时区不对 容器默认 UTC 注入 TZ=Asia/Shanghai
日志撑满磁盘 日志无上限 --log-opt max-size 限大小
启动顺序竞态 depends_on 只管顺序 健康检查 + service_healthy
卷数据丢失 忘挂卷就删容器 先建卷再跑,定期备份

排查三板斧(回顾第 2 讲)

docker ps -a              # ① 看状态:running?exited?重启中?
docker logs -f <id>       # ② 看日志:报什么错
docker inspect <id>       # ③ 看配置:环境变量、挂载、网络对不对
docker stats              # 补充:看 CPU/内存是否异常

两个容易忽略的细节

  • 容器内访问宿主机服务:容器里的 localhost 指容器自己,不是宿主机。访问宿主机服务要用 host.docker.internal(Docker Desktop 支持)或 --network host
  • 时区:容器默认用 UTC,日志时间和本地对不上时,-e TZ=Asia/Shanghai 注入时区。

从开发到上线的完整流程

把前面所有知识点串成一条标准流程,照着走不会漏环节:

  1. 写 Dockerfile:小基础镜像、非 root 用户、多阶段构建(第 4、6 讲)。
  2. 本地构建验证docker build -t myapp:1.0 .docker run -p 3000:3000 myapp:1.0 → 本地跑通。
  3. 配置与敏感信息分离:代码读环境变量,密钥用 secret / 密钥服务(6.1)。
  4. 编排整组服务:写 compose.yaml,定义 web + db,挂卷、限日志、加健康检查(第 5、6 讲)。
  5. 上线运行docker compose up -ddocker compose ps 确认全部健康。
  6. 日常运维docker compose logs -f 看日志,docker system df 看磁盘,docker system prune 定期清理。
  7. 数据保障:数据库挂命名卷,关键卷定期用临时容器打包备份。

这条链路里每个环节都是前面某一讲的落点——Docker 不是单点工具,是一整套"构建、交付、运行"的流程。

⚠️ 常见错误

  1. 容器里用 localhost 连宿主机:连的是容器自己,多半 Connection refused,换宿主机地址。
  2. OOM 只加内存不查原因:先看 docker stats 和日志,是内存泄漏还是确实配小了。
  3. 凭感觉改配置:每改一处,就用 up -d 重建并看 docker logs 验证,别攒一堆改动再排。

📌 双语术语表(本讲)

中文 English 记忆点
环境变量 environment variable 运行时注入配置
环境变量文件 env file --env-file / env_file
健康检查 healthcheck 就绪状态检测
日志驱动 logging driver 决定日志去哪
日志轮转 log rotation max-size 限制
标准输出 stdout 容器日志的出口
多阶段构建 multi-stage build 产物瘦身
运行用户 user 避免 root
时区 timezone TZ=Asia/Shanghai
内存限制 memory limit --memory 防 OOM
就绪条件 service_healthy 等依赖真正可用
打包备份 backup archive 临时容器 tar 打包卷

⭐ 本讲考点清单

  1. 配置用环境变量注入,不写死在镜像/代码里
  2. -e KEY=value--env-file .env、Compose environment / env_file
  3. 密钥不进镜像、不进 Git;用 secret / 密钥服务
  4. ENV 运行时生效,ARG 仅构建时有效
  5. HEALTHCHECK:interval / timeout / retries,service_healthy 做就绪等待
  6. 日志写 stdout 不写文件;--log-opt max-size 防撑爆磁盘
  7. docker logs -f --tail 排错;日志驱动 json-file / syslog / gelf
  8. 镜像优化:alpine/distroless、多阶段、合并 RUN、.dockerignore
  9. 安全:USER 非 root、锁定版本标签、定期更新
  10. 排查:ps -alogsinspect;容器内 localhost 不是宿主机;TZ 时区
  11. depends_on + condition: service_healthy 解决启动顺序竞态
  12. 从开发到上线标准链路:Dockerfile → build → 本地验证 → compose 编排 → up -d → 运维清理 → 卷备份
  13. 生产事故高发点:日志无上限撑爆磁盘、忘挂卷丢数据、密钥写进镜像