把精简换到安全时踩过的坑

CI 流水线里最拖时间的一步是拉镜像:一个基于 Ubuntu 的全量 Python 镜像 1.2GB,冷启动要八分钟。把镜像瘦到 200MB 之后,安全扫描又报出一堆本不该进生产环境的包——优化和安全在这件事上是绑在一起的。

最初的问题

镜像太大

# 最初的 Dockerfile
FROM ubuntu:20.04

RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip \
    curl \
    vim \
    git

COPY . /app
WORKDIR /app

RUN pip3 install -r requirements.txt

CMD ["python3", "app.py"]

问题

  • 镜像太大(500MB+)
  • 包含不需要的软件
  • 安全隐患多

基础优化

选择合适的基础镜像

# 使用官方镜像
FROM python:3.9-slim

# 或者使用 Alpine
FROM python:3.9-alpine
基础镜像大小优势劣势
ubuntu:20.04~70MB软件丰富镜像大
debian:bullseye-slim~80MB稳定性好镜像中等
alpine:3.14~5MB镜像小兼容性问题
scratch0B最小需要静态编译

多阶段构建

# 构建阶段
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# 运行阶段
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
CMD ["node", "dist/index.js"]

结果:镜像从 500MB 降到 100MB

深度优化

合并 RUN 指令

# 错误:多个 RUN
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# 正确:合并 RUN
RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/*

删除不需要的文件

# 使用 .dockerignore
node_modules
npm-debug.log
.git
.github
.vscode
*.md
test
coverage

使用缓存

# 优化 Dockerfile 利用缓存
FROM python:3.9-slim

# 先复制依赖文件
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 再复制代码
COPY . .

CMD ["python", "app.py"]

安全优化

使用非 root 用户

FROM python:3.9-slim

# 创建非 root 用户
RUN useradd -m appuser

# 切换到非 root 用户
USER appuser

WORKDIR /home/appuser

COPY --chown=appuser:appuser . .

CMD ["python", "app.py"]

扫描漏洞

# 使用 Trivy 扫描
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    aquasec/trivy image myapp:latest

# 使用 Clair 扫描
clairctl analyze myapp:latest

定期更新基础镜像

# 使用明确的版本号
FROM python:3.9.13-slim

# 不要使用 latest
# FROM python:latest  # 不要这样做

最小化攻击面

FROM python:3.9-slim

# 只安装必要的依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# 不安装不必要的软件
# RUN apt-get install -y vim git curl  # 不要这样做

最佳实践

层级顺序

FROM python:3.9-slim

# 1. 系统依赖(变化少,放前面)
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# 2. 应用依赖(变化中等,放中间)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 3. 应用代码(变化多,放后面)
COPY . .

# 4. 配置(可能变化,放最后)
ENV FLASK_ENV=production

健康检查

FROM python:3.9-slim

COPY . .
RUN pip install -r requirements.txt

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

CMD ["python", "app.py"]

信号处理

# app.py
import signal
import sys

def handle_shutdown(signum, frame):
    print('Received shutdown signal, cleaning up...')
    # 清理资源
    sys.exit(0)

signal.signal(signal.SIGTERM, handle_shutdown)
signal.signal(signal.SIGINT, handle_shutdown)

# 主循环
while True:
    # 应用逻辑
    pass

镜像构建工具

BuildKit

# 启用 BuildKit
export DOCKER_BUILDKIT=1

# 使用 BuildKit 构建
docker build -t myapp:latest .

Buildx 多架构构建

# 构建多架构镜像
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .

Kaniko(在 Kubernetes 中构建)

apiVersion: v1
kind: Pod
metadata:
  name: kaniko
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:latest
    args:
    - "--dockerfile=Dockerfile"
    - "--context=s3://myapp-bucket/"
    - "--destination=myapp:latest"

踩过的坑

坑一:Alpine 兼容性问题

用了 Alpine,结果有些依赖安装失败。

解决

  • 使用 Debian-slim 替代
  • 或者使用官方维护的 Alpine 变体
# Debian-slim 更稳定
FROM python:3.9-slim  # 推荐

# 或者使用 Node.js 官方的 Alpine 镜像
FROM node:16-alpine   # 官方维护,兼容性好

坑二:时区问题

容器时区不对,导致日志时间混乱。

解决

FROM python:3.9-slim

# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone

ENV TZ=Asia/Shanghai

坑三:文件权限问题

容器内的文件权限不正确。

解决

FROM python:3.9-slim

RUN useradd -m appuser

COPY --chown=appuser:appuser . /app

USER appuser

WORKDIR /app

镜像检查清单

构建前

  • 选择合适的基础镜像
  • 使用多阶段构建
  • 配置 .dockerignore
  • 合并 RUN 指令
  • 利用缓存机制

构建后

  • 扫描安全漏洞
  • 检查镜像大小
  • 验证镜像功能
  • 测试镜像启动

部署前

  • 测试非 root 用户
  • 测试健康检查
  • 测试信号处理
  • 设置资源限制

写在最后

Docker 镜像优化这东西,不只是为了小,更是为了安全和稳定。

优化了

  • 镜像大小
  • 构建速度
  • 启动速度
  • 安全性

带来了

  • 维护成本
  • 测试成本
  • 复杂度增加

优化之前先评估:

  • 镜像使用场景
  • 安全要求
  • 构建频率
  • 团队能力

不是所有场景都需要极致优化,有时候一个合适的镜像就够了。


这次 Docker 镜像优化花了两周,从基础优化到深度优化。优化完成后,镜像从 500MB 降到 100MB,构建时间从 5 分钟降到 2 分钟,部署速度提升明显。

版权声明: 本文首发于 指尖魔法屋-把精简换到安全时踩过的坑https://blog.thinkmoon.cn/post/69-docker-image-optimization-security-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!