上一篇 下一篇 分享链接 返回 返回顶部

Docker 上生产前,这些坑一定要提前避开

发布人:慈云数据-客服中心 发布时间:8小时前 阅读量:3

Docker 生产环境部署指南|生产环境实测

在很多团队的技术演进过程中,Docker 往往是从“开发环境统一”开始被引入的:开发者不再需要在本机反复安装不同版本的 JDK、Node.js、MySQL、Redis,只需要一条 docker compose up 就能拉起整套环境。但真正把 Docker 用到生产环境时,问题会变得复杂得多:镜像如何构建才安全?容器资源如何限制?日志如何收集?数据如何持久化?服务如何升级回滚?宿主机如何调优?安全边界在哪里?

本文结合生产环境实践,系统梳理 Docker 在生产环境部署中的关键要点,适合准备用 Docker 部署后端服务、中间件、前端应用、任务服务的团队参考。


一、生产环境使用 Docker 的核心价值

Docker 在生产环境中的价值,不只是“部署方便”,更重要的是它带来了应用交付方式的标准化。

1. 环境一致性

传统部署方式中,开发环境、测试环境、预发环境、生产环境往往存在差异:

  • JDK 版本不同;
  • 系统依赖包不同;
  • 配置文件路径不同;
  • 启动脚本不一致;
  • 运维人员手工操作导致不可追踪。

Docker 将应用和运行环境一起打包进镜像,使应用在不同环境中具备高度一致性。只要镜像相同,理论上运行结果就应保持一致。

2. 部署可重复

生产部署最怕“这次能部署成功,下次不一定”。Docker 镜像是不可变交付物,结合 CI/CD 流水线,可以做到:

  • 每次构建都有版本号;
  • 每次发布都有镜像记录;
  • 每次回滚都有明确目标;
  • 部署过程脚本化、自动化。

3. 资源隔离

Docker 通过 Linux Namespace 和 Cgroups 提供一定程度的隔离能力。虽然它不是虚拟机,安全隔离能力不能等同于 VM,但对于大多数 Web 应用、任务服务、网关服务而言,Docker 的隔离能力已经足够实用。

4. 快速扩缩容

容器启动速度通常远快于传统虚拟机。对于无状态应用,可以快速拉起多个副本,配合负载均衡实现横向扩展。


二、生产环境部署前的整体规划

在正式部署 Docker 之前,不能只考虑“容器怎么启动”,还要提前规划以下几个方面:

规划项 重点问题
镜像仓库 使用私有仓库还是公有仓库?是否需要权限控制?
网络规划 容器端口如何暴露?服务之间如何通信?
数据存储 数据库、文件、日志是否需要持久化?
配置管理 配置文件、环境变量、密钥如何管理?
资源限制 CPU、内存、磁盘 IO 是否有限制?
日志监控 日志在哪里?指标如何采集?告警如何触发?
安全策略 是否禁用特权容器?是否限制 root 权限?
升级回滚 发布失败如何快速恢复?

如果这些问题没有提前设计,Docker 很容易从“提高效率的工具”变成“隐藏风险的黑盒”。


三、生产环境宿主机准备

Docker 的稳定运行离不开宿主机。生产环境建议使用稳定的 Linux 发行版,例如:

  • Ubuntu Server LTS;
  • Debian Stable;
  • CentOS Stream / Rocky Linux / AlmaLinux;
  • 企业内部统一维护的 Linux 镜像。

1. 内核与系统要求

Docker 依赖 Linux 内核特性,生产环境建议使用较新的稳定内核。对于老旧系统,要特别关注:

  • OverlayFS 支持情况;
  • Cgroups 版本;
  • iptables/nftables 兼容性;
  • 文件句柄限制;
  • 内核参数配置。

可通过以下命令查看系统内核版本:

uname -a

查看系统版本:

cat /etc/os-release

2. 时间同步

生产环境必须配置时间同步,否则日志排查、监控指标、证书校验、分布式系统一致性都会受到影响。

推荐使用 chrony

sudo apt install -y chrony
sudo systemctl enable chrony
sudo systemctl start chrony

检查时间同步状态:

chronyc tracking

3. 文件句柄限制

高并发服务经常遇到文件句柄不足的问题。可以查看当前限制:

ulimit -n

生产环境建议根据服务规模提高限制,例如:

65535

/etc/security/limits.conf 中配置:

* soft nofile 65535
* hard nofile 65535

同时也要关注 systemd 服务限制,必要时修改 Docker 服务的限制配置。


四、Docker 安装与基础配置

1. 安装 Docker Engine

以 Ubuntu 为例,推荐使用官方仓库安装 Docker Engine,而不是使用系统默认源中的旧版本。

sudo apt update
sudo apt install -y ca-certificates curl gnupg

添加 Docker 官方 GPG key 和仓库后安装:

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

安装完成后检查版本:

docker version
docker compose version

启动并设置开机自启:

sudo systemctl enable docker
sudo systemctl start docker

2. 配置 Docker daemon

Docker 的核心配置文件通常位于:

/etc/docker/daemon.json

生产环境推荐配置示例:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  },
  "storage-driver": "overlay2",
  "exec-opts": ["native.cgroupdriver=systemd"],
  "live-restore": true
}

配置说明:

  • log-driver:默认使用 json-file,但必须限制日志大小;
  • max-size:单个日志文件最大大小;
  • max-file:日志文件保留数量;
  • storage-driver:推荐使用 overlay2
  • live-restore:Docker daemon 重启时,尽量保持容器运行;
  • cgroupdriver:建议与系统保持一致,使用 systemd。

修改后重启 Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

五、镜像构建最佳实践

镜像是 Docker 部署的核心。生产环境中,镜像构建质量直接影响安全性、稳定性和部署效率。

1. 使用精简基础镜像

不要随意使用体积过大的基础镜像。比如 Java 应用可以选择:

FROM eclipse-temurin:17-jre

Node.js 应用可以选择:

FROM node:20-alpine

但需要注意,alpine 使用 musl libc,某些依赖可能存在兼容问题。如果涉及复杂原生依赖,使用 Debian slim 镜像可能更稳妥。

2. 多阶段构建

多阶段构建可以减少最终镜像体积,避免将编译工具、源码、临时文件带入生产镜像。

以 Go 应用为例:

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 server ./cmd/server

FROM alpine:3.20

WORKDIR /app
COPY --from=builder /app/server /app/server

EXPOSE 8080
ENTRYPOINT ["/app/server"]

最终镜像只包含可执行文件和必要运行环境,体积更小,攻击面更低。

3. 不要在镜像中写死配置

错误示例:

ENV MYSQL_PASSWORD=123456

生产环境不应把密码、密钥、Token 写进镜像。镜像应保持环境无关,配置通过以下方式注入:

  • 环境变量;
  • 配置文件挂载;
  • Secret 管理工具;
  • 配置中心;
  • CI/CD 注入。

4. 固定基础镜像版本

不要在生产中长期使用:

FROM node:latest

latest 不代表稳定,只代表默认标签。基础镜像一旦更新,可能导致构建结果不可预期。

推荐写法:

FROM node:20.11.1

或者使用 digest 锁定:

FROM node@sha256:xxxx

六、容器运行参数建议

1. 设置容器名称和重启策略

生产环境建议明确容器名称,并设置重启策略:

docker run -d \
  --name app-server \
  --restart=unless-stopped \
  -p 8080:8080 \
  app-server:1.0.0

常见重启策略:

策略 说明
no 默认,不自动重启
always 无论退出原因都重启
unless-stopped 除非手动停止,否则自动重启
on-failure 非 0 退出码时重启

生产环境常用 unless-stopped

2. 限制 CPU 和内存

不限制资源是生产环境常见隐患。一个异常容器可能吃满宿主机资源,影响其他服务。

示例:

docker run -d \
  --name app-server \
  --memory=1g \
  --memory-swap=1g \
  --cpus=1.5 \
  app-server:1.0.0

参数说明:

  • --memory=1g:限制最大内存;
  • --memory-swap=1g:限制内存加 swap,总量为 1g;
  • --cpus=1.5:最多使用 1.5 个 CPU 核心。

对于 Java 应用,除了限制容器内存,还要正确设置 JVM 参数,例如:

JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0"

否则 JVM 可能误判可用内存,导致 OOM。

3. 禁止不必要的特权模式

不要随意使用:

--privileged

特权模式会赋予容器过高权限,突破容器隔离边界。除非运行特殊系统级组件,否则生产环境应避免使用。

可以使用更细粒度的能力控制:

--cap-drop=ALL
--cap-add=NET_BIND_SERVICE

4. 使用只读文件系统

对于无状态应用,可以考虑启用只读根文件系统:

docker run -d \
  --read-only \
  --tmpfs /tmp \
  app-server:1.0.0

这样即使应用被入侵,也能降低攻击者写入恶意文件的风险。


七、Docker Compose 生产部署示例

虽然大规模集群通常会使用 Kubernetes,但对于中小型项目、单机多服务部署、内部系统,Docker Compose 仍然非常实用。

下面是一个生产环境 Compose 示例:

services:
  app:
    image: registry.example.com/app-server:1.0.0
    container_name: app-server
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      TZ: Asia/Shanghai
      SPRING_PROFILES_ACTIVE: prod
      JAVA_OPTS: "-XX:MaxRAMPercentage=75.0"
    volumes:
      - ./config/application-prod.yml:/app/config/application-prod.yml:ro
      - ./logs/app:/app/logs
    depends_on:
      - redis
    networks:
      - app-net
    deploy:
      resources:
        limits:
          cpus: "1.5"
          memory: 1024M

  redis:
    image: redis:7.2
    container_name: redis
    restart: unless-stopped
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
    volumes:
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
      - ./redis/data:/data
    networks:
      - app-net

networks:
  app-net:
    driver: bridge

需要注意的是,deploy.resources 在普通 docker compose 模式下部分配置可能不生效,资源限制更推荐使用 Compose 的兼容参数,或在 Swarm/Kubernetes 中管理。

启动服务:

docker compose up -d

查看服务:

docker compose ps

查看日志:

docker compose logs -f app

停止服务:

docker compose down

八、日志管理:生产环境必须限制日志大小

Docker 默认使用 json-file 保存容器标准输出日志。如果应用持续输出大量日志,而没有设置日志轮转,磁盘很容易被打满。

查看容器日志路径:

docker inspect --format='{{.LogPath}}' app-server

建议在 /etc/docker/daemon.json 中统一配置日志限制:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  }
}

也可以在 Compose 中单独配置:

logging:
  driver: json-file
  options:
    max-size: "100m"
    max-file: "5"

生产环境日志管理建议:

  1. 应用日志优先输出到标准输出;
  2. 由 Docker 或日志采集器统一收集;
  3. 配置日志轮转,防止磁盘打满;
  4. 重要日志进入 ELK、Loki、Kafka 或云日志平台;
  5. 日志中不要打印密码、Token、身份证号等敏感信息。

九、数据持久化:不要把重要数据只放在容器内

容器本身是易变的,删除容器后,容器内写入层数据也会丢失。因此数据库、上传文件、缓存持久化数据必须挂载到宿主机目录或 Docker Volume。

1. 使用宿主机目录挂载

docker run -d \
  --name mysql \
  -v /data/mysql:/var/lib/mysql \
  mysql:8.0

优点是路径直观,方便备份和排查。缺点是需要自己管理权限、目录结构。

2. 使用 Docker Volume

docker volume create mysql-data

docker run -d \
  --name mysql \
  -v mysql-data:/var/lib/mysql \
  mysql:8.0

Docker Volume 由 Docker 管理,适合标准化部署。

3. 数据目录权限

挂载目录时,经常出现容器内进程无权限写入的问题。需要确认容器内进程 UID/GID,并调整宿主机目录权限:

chown -R 1000:1000 /data/app

不要为了省事直接:

chmod -R 777 /data/app

这在生产环境中非常危险。


十、网络与反向代理

生产环境不建议所有容器都直接暴露端口。更常见的做法是:

  • 内部服务只在 Docker 网络内通信;
  • 对外统一通过 Nginx、Traefik、Caddy 或云负载均衡暴露;
  • HTTPS 证书由网关层统一管理。

Nginx 反向代理示例

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app-server:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

如果 Nginx 和应用容器在同一个 Docker 网络中,可以直接使用服务名访问。


十一、健康检查与自动恢复

容器“运行中”不代表服务“可用”。应用可能进程存在,但接口已无法响应。因此生产环境建议配置健康检查。

Dockerfile 示例:

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

Compose 示例:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 5s
  retries: 3

应用侧应提供轻量级健康检查接口,例如:

GET /health

健康检查不应依赖过多外部服务,否则可能造成误判。可以区分:

  • 存活检查:进程是否正常;
  • 就绪检查:是否可以接收流量;
  • 深度检查:数据库、Redis、MQ 是否可用。

十二、发布、升级与回滚策略

生产部署不能只考虑“上线”,更要考虑“失败后怎么回退”。

1. 镜像版本命名

建议镜像标签包含:

  • 应用名;
  • Git commit;
  • 构建时间;
  • 语义化版本号。

例如:

registry.example.com/app-server:1.3.2-8f3a9c1

不建议生产直接部署:

app-server:latest

因为无法明确当前运行的是哪个版本。

2. 标准发布流程

一个可靠的 Docker 发布流程通常包括:

  1. 开发提交代码;
  2. CI 执行单元测试;
  3. 构建 Docker 镜像;
  4. 扫描镜像漏洞;
  5. 推送到私有镜像仓库;
  6. 部署到测试环境;
  7. 部署到预发环境;
  8. 人工或自动审批;
  9. 部署到生产环境;
  10. 监控观察;
  11. 异常时回滚。

3. 回滚方式

Docker 回滚通常很简单:重新部署上一版本镜像。

docker stop app-server
docker rm app-server

docker run -d \
  --name app-server \
  --restart=unless-stopped \
  -p 8080:8080 \
  registry.example.com/app-server:1.3.1

如果使用 Compose,可以修改镜像标签后执行:

docker compose pull
docker compose up -d

需要特别注意:应用回滚不等于数据库回滚。如果新版本执行了不可逆数据库变更,单纯回滚镜像可能仍然失败。因此数据库变更必须遵循兼容性原则。


十三、安全加固建议

Docker 不是安全沙箱。生产环境中必须进行必要安全加固。

1. 不使用 root 用户运行应用

Dockerfile 中建议创建普通用户:

FROM eclipse-temurin:17-jre

RUN useradd -r -u 10001 appuser

WORKDIR /app
COPY app.jar /app/app.jar

USER appuser

ENTRYPOINT ["java", "-jar", "/app/app.jar"]

这样即使应用被入侵,攻击者获得的也是普通用户权限。

2. 镜像漏洞扫描

可以使用:

  • Trivy;
  • Grype;
  • Clair;
  • 云厂商镜像扫描服务。

示例:

trivy image registry.example.com/app-server:1.0.0

发现高危漏洞后,应及时升级基础镜像或依赖包。

3. 控制 Docker Socket

/var/run/docker.sock 权限非常敏感。挂载 Docker Socket 到容器内等同于赋予容器控制宿主机 Docker 的能力。

危险示例:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock

除非非常清楚风险,否则不要这样做。

4. 最小权限原则

生产容器建议:

  • 禁用特权模式;
  • 限制 Linux capabilities;
  • 使用只读文件系统;
  • 不挂载敏感宿主机目录;
  • 不在镜像和环境变量中暴露敏感密钥;
  • 定期更新基础镜像;
  • 只开放必要端口。

十四、监控与告警

生产环境必须建立监控体系。Docker 相关监控通常包含:

1. 宿主机指标

  • CPU 使用率;
  • 内存使用率;
  • 磁盘使用率;
  • 磁盘 IO;
  • 网络流量;
  • 系统负载;
  • 文件句柄数量。

2. 容器指标

  • 容器 CPU;
  • 容器内存;
  • 容器重启次数;
  • 容器网络流量;
  • 容器磁盘读写;
  • 容器健康状态。

可以使用:

  • Prometheus + Grafana;
  • cAdvisor;
  • Node Exporter;
  • Loki;
  • ELK;
  • 云监控。

查看容器资源使用:

docker stats

docker stats 只适合临时排查,不能替代长期监控系统。

3. 告警建议

至少应配置以下告警:

  • 宿主机磁盘使用率超过 80%;
  • 容器异常退出或频繁重启;
  • 应用接口错误率升高;
  • CPU 长时间超过阈值;
  • 内存使用接近限制;
  • 日志中出现大量异常;
  • 健康检查失败;
  • 数据库连接池耗尽。

十五、生产环境常见问题与实测经验

1. 磁盘被 Docker 日志打满

这是生产中最常见的问题之一。表现为:

  • 应用突然无法写入文件;
  • 数据库异常;
  • 容器无法启动;
  • df -h 显示根分区 100%。

解决方法:

docker system df

查看 Docker 占用空间。对于日志,需要提前配置日志轮转,而不是等磁盘满了再处理。

2. 容器内时间不一致

如果容器时区不正确,日志时间会混乱。可以在 Compose 中设置:

environment:
  TZ: Asia/Shanghai

或者挂载宿主机时区文件:

volumes:
  - /etc/localtime:/etc/localtime:ro

3. 容器内存限制后 Java 应用 OOM

Java 应用在容器中运行时,要确认 JVM 是否正确识别容器内存限制。建议使用较新的 JDK,并设置:

-XX:MaxRAMPercentage=75.0

避免堆内存占满容器,给 Metaspace、线程栈、直接内存留出空间。

4. 使用 latest 导致版本不可追踪

很多团队为了方便使用 latest,结果发布后无法确认生产环境实际运行版本。生产必须使用明确版本标签。

5. 容器重启后数据丢失

如果数据库、上传目录没有挂载卷,删除容器就会导致数据丢失。生产环境中,所有重要数据都必须外置持久化,并定期备份。


十六、生产部署检查清单

上线前建议逐项检查:

  • [ ] 镜像是否固定版本,而不是 latest
  • [ ] Dockerfile 是否使用多阶段构建;
  • [ ] 镜像中是否没有密钥、密码、Token;
  • [ ] 容器是否设置重启策略;
  • [ ] 容器是否限制 CPU 和内存;
  • [ ] 日志是否配置大小限制;
  • [ ] 数据目录是否持久化;
  • [ ] 配置文件是否只读挂载;
  • [ ] 是否避免使用 --privileged
  • [ ] 是否使用普通用户运行应用;
  • [ ] 是否配置健康检查;
  • [ ] 是否接入监控和告警;
  • [ ] 是否有回滚方案;
  • [ ] 是否完成数据备份;
  • [ ] 是否验证过预发环境;
  • [ ] 是否记录当前发布版本。

十七、推荐目录结构

对于单机 Docker Compose 部署,可以采用如下目录结构:

/opt/app
├── docker-compose.yml
├── .env
├── config
│   └── application-prod.yml
├── logs
│   └── app
├── redis
│   ├── redis.conf
│   └── data
├── nginx
│   ├── nginx.conf
│   └── conf.d
└── scripts
    ├── deploy.sh
    ├── rollback.sh
    └── backup.sh

这种结构清晰直观,便于备份、迁移和排查问题。


十八、一个简单的部署脚本示例

#!/bin/bash
set -e

APP_DIR="/opt/app"
IMAGE="registry.example.com/app-server"
VERSION="$1"

if [ -z "$VERSION" ]; then
  echo "Usage: ./deploy.sh "
  exit 1
fi

cd $APP_DIR

echo "Deploying version: $VERSION"

sed -i "s#image: ${IMAGE}:.*#image: ${IMAGE}:${VERSION}#g" docker-compose.yml

docker compose pull
docker compose up -d

docker compose ps

echo "Deploy finished."

实际生产中,部署脚本还应增加:

  • 发布前备份;
  • 健康检查;
  • 发布失败自动回滚;
  • 操作日志记录;
  • 通知机制。

结语

Docker 在生产环境中不是简单地把应用“装进容器”就完成了。真正可靠的 Docker 生产部署,需要从宿主机、镜像、运行参数、网络、存储、日志、监控、安全、发布回滚等多个方面进行系统设计。

从生产实测经验来看,Docker 本身足够稳定,大多数事故并不是 Docker 引起的,而是因为部署规范缺失:日志没有限制、资源没有隔离、数据没有持久化、版本不可追踪、配置和密钥管理混乱、监控告警缺位。

因此,生产环境使用 Docker 的关键不是“会不会启动容器”,而是能否建立一套稳定、可审计、可回滚、可监控、可持续演进的容器化部署体系。

如果你的团队刚开始在生产环境使用 Docker,建议先从单机 Compose 标准化部署做起,逐步完善镜像构建、日志收集、监控告警、安全扫描和自动化发布。当服务规模继续扩大、发布频率提高、机器数量增多时,再进一步引入 Kubernetes、服务网格、GitOps 等更复杂的云原生体系。这样既能控制复杂度,也能让 Docker 真正成为提升交付效率和系统稳定性的基础设施。

目录结构
全文