OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 115

Docker 生产实践:资源、安全与恢复

围绕限制、日志、更新、凭据与备份检查容器运行条件。

所在专题Docker:从容器运行到应用编排 ↗
约 10 分钟阅读开始阅读

开发环境跑通容器只是第一步。生产环境要关心安全、资源、日志、更新和监控。


镜像安全#

用非 root 用户运行

# 错误的做法(容器内以 root 运行)
FROM node:20-alpine
COPY . /app
CMD ["node", "/app/server.js"]
# 容器内进程 = root,万一被攻破有更高权限

# 正确的做法
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --chown=appuser:appgroup . /app
CMD ["node", "/app/server.js"]

固定版本,不追 latest

# ❌ 坏:今天和明天拉到的可能是不同镜像
FROM node:latest

# ✅ 好:锁定具体版本
FROM node:20.11.1-alpine3.19

# ✅ 更好:锁定 digest(固定内容标识;仍需验证来源、支持周期和安全更新)
FROM node:<受支持版本>@sha256:<核验过的完整digest>

Docker Scout 漏洞扫描

# 扫描镜像的已知漏洞
docker scout quickview myapp:latest
docker scout cves myapp:latest

# 查看策略合规状态
docker scout policy myapp:latest

资源限制#

# Compose 方式(推荐)
services:
  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          memory: 512M       # 硬上限
          cpus: "1.0"        # 最多 1 核
        reservations:
          memory: 256M       # reservation 不等于物理内存的绝对保证,执行/调度语义按环境判断
# 命令行方式
docker run -d \
  --memory=512m \
  --memory-swap=1g \
  --cpus=1.5 \
  --cpuset-cpus=0-2 \
  --name api myapp:latest

如果不设限制会怎样?

一个容器可能吃光所有内存,导致 OOM Killer 开始杀进程(包括宿主机进程)。生产环境必须设置上限。


日志管理#

配置日志驱动

一般 Linux Engine 可通过 daemon 配置文件表达以下设置;NixOS 用 virtualisation.docker.daemon.settings 声明,不手工覆盖受管理文件。默认设置主要作用于新建容器,既有容器需按计划重建:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

不改这个,某个容器日志疯狂输出可能撑爆磁盘。

查看日志

docker logs --tail 100 myweb
docker logs --since 30m myweb
docker logs -f myweb   # 实时跟踪

集中日志收集

生产环境建议将日志发送到集中系统:

services:
  api:
    logging:
      driver: syslog
      options:
        syslog-address: "tcp://logserver:514"

健康检查与自愈#

内置健康检查

services:
  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s
# 查看健康状态
docker ps --format "table {{.Names}}\t{{.Status}}"
# api   Up 10 minutes (healthy)

自动重启不健康容器

普通 Engine 的 restart policy 根据进程退出动作,不因 unhealthy 自动重启;Swarm 服务调度和第三方监控有不同机制。示例健康命令要求镜像内实际提供 curl:

# 方案 1:Docker Swarm(内置自愈)
docker stack deploy -c compose.yaml myapp

# 方案 2:第三方 autoheal 示例(将授予 rootful daemon 管理能力,先审查镜像及配置)
docker run -d --name autoheal \
  -v /var/run/docker.sock:/var/run/docker.sock \
  willfarrell/autoheal

Updates:滚动更新与停机边界#

Rolling update(Swarm)

services:
  api:
    image: myapp:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1       # 每次更新 1 个副本
        delay: 10s           # 间隔 10 秒
        order: start-first   # 先启新再停旧
docker service update --image myapp:v2 myapp_api

Compose 单机更新

# 拉新镜像 + 重建容器
docker compose pull
docker compose up -d --remove-orphans
# 注意:单机 Compose 有短暂中断

Secrets 管理#

# Swarm secrets:先安全生成并保护来源文件,勿在命令行硬编码凭据
docker secret create db_password /path/to/protected-password-file
# swarm compose 文件
secrets:
  db_password:
    external: true

services:
  db:
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
# 非 Swarm 方式:用 .env
# .env(加入 .gitignore)
DB_PASSWORD=<从受控凭据来源提供>

绝对不要:

  • 把密码 hardcode 在 Dockerfile 里(ENV DB_PASSWORD=123)——密码进了镜像层,docker history 可见
  • 把 .env 提交到 git

备份与恢复#

卷备份

先停止写入或使用数据库原生一致性备份。恢复应在停服务、空的新卷或已备份目标上验证;tar 解压会覆盖同名文件,且不会自动清理目标多余文件。下面命令只演示文件归档,不保证数据库一致性。

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

# 恢复
docker run --rm -v mydata:/data -v "$(pwd)":/backup alpine \
  tar xzf /backup/mydata-20260620.tar.gz -C /data

定期清理#

# crontab 或 systemd timer
# 每天凌晨 3 点清理 72 小时前的未使用镜像
0 3 * * * docker image prune -af --filter "until=72h"

安全检查清单#

  • Docker daemon 是否暴露了 TCP socket?(检查 Unix socket 权限;TCP 使用认证和加密,Unix socket 也不是自动安全)
  • 容器是不是都设了 --memory 和 --cpus 限制?
  • 日志大小上限设了没?(daemon.json 的 max-size)
  • 有没有容器在挂载 /var/run/docker.sock?(极度危险)
  • .env 是否在 .gitignore 里?
  • 有没有用 :latest 标签?
  • 有没有跑 docker scout 扫过镜像?

生产验收的补充边界

start-first 与副本数不保证零停机:应用就绪、连接排空、端口冲突、迁移和集群容量都要验证。非 root 用户、rootless、capabilities/seccomp、只读挂载和最小权限分别解决不同问题,挂载 Docker socket 会扩大控制权限,即使绑定挂载标成只读也不限制 API 操作。

普通 Compose 同样支持 file secrets;它保护注入方式,但不自动加密源文件。扫描结果有时效与覆盖范围,锁定旧 digest 也不会自动获得安全更新。自动 prune 需保留回滚镜像和维护窗口,镜像创建时间过滤不等于最后使用时间。

参考与延伸

  1. Docker Engine 安全 ↗
  2. Swarm secrets ↗
  3. Compose secrets ↗
  4. 卷与备份 ↗
  5. 重启策略 ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

Docker:从容器运行到应用编排

  1. 01Docker 基础:镜像、容器与守护进程
  2. 02Docker 命令行:镜像、容器、卷与网络
  3. 03Dockerfile 与镜像构建
  4. 04Compose:定义多服务应用
  5. 05Docker 网络与存储
  6. 06Docker 生产实践:资源、安全与恢复
  7. 07NixOS 上声明 Docker 与容器
  8. 08Swarm:从服务副本到集群编排
  9. 09Docker 与 Podman:架构、兼容与共存
CONTINUE READING / 下一篇NixOS 上声明 Docker 与容器

用系统模块组织引擎设置、容器定义和凭据输入。