睿诚科技协会

Docker最佳实践,具体技术要点有哪些?

Docker 最佳实践全景图

可以将这些实践分为以下几个核心领域:

Docker最佳实践,具体技术要点有哪些?-图1
(图片来源网络,侵删)
  1. 镜像构建最佳实践
  2. 容器运行最佳实践
  3. 安全最佳实践
  4. 多环境与编排最佳实践
  5. 监控与日志最佳实践

镜像构建最佳实践

镜像是 Docker 的基石,一个高质量的镜像是一切的基础。

a. 使用 .dockerignore 文件

为什么?.gitignore 类似,它可以防止不必要的文件(如 .git 目录、node_modules、本地 .env 文件)被发送到 Docker daemon,从而减小镜像体积、构建加速并避免信息泄露。

示例 .dockerignore

.git
.gitignore
README.md
node_modules
npm-debug.log
Dockerfile
.dockerignore
.env

b. 使用官方基础镜像

为什么? 官方镜像(如 python:3.9-slim, node:18-alpine, nginx:alpine)经过维护、安全扫描,并且通常优化得很好。slimalpine 版本体积更小,安全性更高。

Docker最佳实践,具体技术要点有哪些?-图2
(图片来源网络,侵删)

推荐:

  • 优先选择 Alpine 镜像,因为它基于 musl libc,体积极小(通常只有几 MB)。
  • 如果应用依赖 glibc,可以选择 slimbullseye 等镜像。

c. 合理使用缓存层

为什么? Docker 的构建过程是分层的,每一层都会被缓存,如果某一层没有变化,Docker 会直接使用缓存,这极大地加快了后续构建速度。

实践:

  • 将不常变化的层放在上面RUN apt-get update && apt-get install -y ... 应该合并为一层,并且放在 Dockerfile 的顶部。
  • 将经常变化的层放在下面COPY . .RUN npm install 应该放在 Dockerfile 的底部。
  • 合并指令:将多个 RUN 指令合并,以减少层数。

错误示例 (缓存效率低):

Docker最佳实践,具体技术要点有哪些?-图3
(图片来源网络,侵删)
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

requirements.txt 没变,但 目录下的代码变了,pip install 这一层会重新执行,但实际上依赖已经安装好了。

优化示例 (利用缓存):

FROM python:3.9-slim
WORKDIR /app
# 1. 先复制依赖文件,利用缓存
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 2. 再复制应用代码
COPY . .

d. 使用特定标签,避免 latest

为什么? latest 是一个动态标签,今天指向 v1.2.3,明天可能就指向 v1.3.0,这会导致构建和部署不可重现,是生产环境的大忌。

实践:

  • 在 Dockerfile 中使用明确的版本标签,如 FROM python:3.9.16-slim
  • 在 CI/CD 流水线中,使用 Git Commit ID 或版本号来构建镜像标签,如 myapp:git-abc123myapp:v1.2.3

e. 非 root 用户运行容器

为什么? 这是容器安全的核心原则,以 root 用户运行容器,一旦容器被攻破,攻击者就获得了主机的 root 权限,风险极高。

实践: 在 Dockerfile 中创建一个非 root 用户,并使用 USER 指令切换。

FROM node:18-alpine
# 创建一个没有家目录的非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001
# 切换到该用户
USER nextjs

f. 多阶段构建

为什么? 构建过程(如编译代码、安装构建工具)需要大量的依赖,但这些依赖在最终运行时是不需要的,多阶段构建允许你在一个阶段使用所有构建工具,然后在另一个阶段只复制最终产物,从而大幅减小镜像体积。

示例 (Go 应用):

# 第一阶段:构建
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# 第二阶段:运行
FROM alpine:latest
WORKDIR /root/
# 从 builder 阶段复制编译好的二进制文件
COPY --from=builder /app/myapp .
CMD ["./myapp"]

最终镜像只包含一个微小的 Alpine 系统和你的 Go 二进制文件,体积极小。

g. 清理缓存和临时文件

为什么?RUN 指令中安装软件包时,会产生缓存(如 aptvar/cache/apt),这些缓存会增加镜像大小。

实践:RUN 指令的末尾清理缓存。

示例 (APT):

RUN apt-get update && \
    apt-get install -y --no-install-recommends package1 package2 && \
    rm -rf /var/lib/apt/lists/*

示例 (YUM/DNF):

RUN yum update -y && \
    yum install -y package1 package2 && \
    yum clean all

容器运行最佳实践

a. 最小权限原则

为什么? 容器应该只拥有完成任务所必需的最小权限,这包括文件系统权限、网络访问等。

实践:

  • 使用非 root 用户运行容器。
  • 使用 --read-only 挂载一个可写的 tmpfs 卷来覆盖 /tmp 目录,防止写入容器层。
  • 使用 --cap-drop 移除不必要的 Linux Capabilities,如 --cap-drop ALL --cap-add CHOWN
  • 限制资源使用:--memory, --cpus

b. 使用 Docker Compose 进行本地开发

为什么? 对于多服务应用(如 Web App + Database + Redis),使用 Docker Compose 可以一键启动、停止和管理所有服务及其网络、卷等配置,极大简化了本地开发环境。

实践:

  • 编写一个清晰的 docker-compose.yml 文件。
  • 为不同环境(开发、测试、预发布)创建不同的 docker-compose.*.yml 文件,并通过 -f 参数指定。

c. 健康检查

为什么? Docker 健康检查能让容器声明自己是否“健康”,编排工具(如 Kubernetes)可以根据健康检查结果自动重启不健康的容器或进行流量切换。

实践: 在 Dockerfile 或 docker-compose.yml 中定义 HEALTHCHECK

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

或者:

# docker-compose.yml
services:
  web:
    image: myapp
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

安全最佳实践

a. 定期更新基础镜像

为什么? 基础镜像可能包含已知的漏洞,定期更新到最新的安全版本是防御漏洞的关键。

实践:

  • 使用 Trivy, Clair 等工具扫描镜像中的已知漏洞。
  • 在 CI/CD 流程中加入安全扫描环节,阻止构建包含高危漏洞的镜像。

b. 使用 Docker Content Trust (Docker Notary)

为什么? Docker Content Trust 通过数字签名来确保你拉取的镜像是可信的,没有被篡改。

实践:

  • 设置环境变量 export DOCKER_CONTENT_TRUST=1
  • 推送和拉取镜像时,Docker 会自动验证签名。

c. 管理敏感信息

为什么? 不要将密码、API Key、数据库连接字符串等敏感信息硬编码在镜像或命令行参数中。

实践:

  • 使用 Docker Secrets (Swarm) 或 Kubernetes Secrets。
  • 在 CI/CD 系统中使用环境变量或 secret 管理工具(如 HashiCorp Vault)。

多环境与编排最佳实践

a. 不可变基础设施

为什么? 不要尝试进入正在运行的容器中进行修改(docker exec 修改文件),如果容器需要更新,就扔掉旧的,部署一个新的、完全一样的镜像版本,这使得环境高度一致,易于回滚。

实践:

  • 所有配置和代码都通过镜像或挂载的卷来提供。
  • 使用 CI/CD 流水线自动化部署过程。

b. 使用编排工具

为什么? 对于生产环境,手动管理容器是不现实的,Kubernetes 是事实上的行业标准,提供了服务发现、负载均衡、自动扩缩容、滚动更新、自我修复等强大功能。

实践:

  • 将容器打包到 Kubernetes Pod 中。
  • 使用 Kubernetes Deployment 来管理应用的生命周期。
  • 使用 Kubernetes Service 来暴露应用。
  • 使用 Kubernetes ConfigMap 和 Secret 来管理配置。

c. 日志管理

为什么? 容器是短暂的,日志应该输出到 stdout/stderr,而不是写入容器内部的文件,这样 Docker 和编排工具可以轻松地收集、聚合和管理日志。

实践:

  • 应用应该使用日志库将日志输出到标准输出。
  • 使用日志驱动(如 json-file, syslog, journald)将日志发送到集中的日志管理系统(如 ELK Stack, Loki, Splunk)。

监控与日志最佳实践

a. 监控容器和宿主机

为什么? 你需要了解应用的性能指标(CPU、内存、网络 I/O)和运行状况。

实践:

  • 使用 cAdvisor (集成在 Docker 中) 或 Prometheus 来收集指标。
  • 使用 Grafana 来可视化这些指标。
  • 设置告警,当资源使用率过高或应用无响应时通知你。

b. 集中化日志

为什么? 在分布式系统中,一个请求可能跨越多个容器,将所有日志集中到一个地方是调试和追踪问题的唯一有效方法。

实践:

  • 如上所述,让容器输出日志到标准输出。
  • 使用日志收集器(如 Fluentd, Logstash)将日志从各个节点收集到中央存储(如 Elasticsearch)。

一份检查清单

在发布你的 Docker 应用之前,问自己以下问题:

领域 检查项
镜像构建 [ ] 是否使用了 .dockerignore
[ ] 是否使用了官方或最小化的基础镜像?
[ ] 是否使用了特定标签,避免 latest
[ ] 是否利用了 Docker 缓存?
[ ] 是否使用了多阶段构建来减小体积?
[ ] 是否创建了非 root 用户?
[ ] 是否清理了构建过程中的缓存和临时文件?
容器运行 [ ] 是否使用了非 root 用户?
[ ] 是否限制了容器的资源(CPU、内存)?
[ ] 是否定义了健康检查?
[ ] 是否为敏感信息使用了 Secrets 而非环境变量?
安全 [ ] 是否定期扫描镜像中的漏洞?
[ ] 是否启用了 Docker Content Trust?
[ ] 是否最小化了 Linux Capabilities?
编排与部署 [ ] 是否实现了不可变基础设施(通过新镜像部署)?
[ ] 是否使用了 Docker Compose(本地)或 Kubernetes(生产)?
[ ] 是否有 CI/CD 流水线来自动化构建、测试和部署?
运维 [ ] 是否有监控和告警系统?
[ ] 是否实现了集中化的日志收集?
[ ] 是否有明确的回滚策略?

遵循这些最佳实践,你将能够构建出健壮、安全、易于维护的现代化 Docker 应用。

分享:
扫描分享到社交APP
上一篇
下一篇