镜像层的优化策略

在构建镜像时,很多人会忽略层的概念。每个 RUN、COPY 和 ADD 指令都会创建一个新的层。我见过一个典型的例子:

# 不推荐的写法
RUN apt-get update
RUN apt-get install -y python3
RUN pip install requests
RUN pip install flask
RUN pip install numpy

这里会创建5个独立的层。更好的做法是:

# 推荐的写法
RUN apt-get update && \
    apt-get install -y python3 && \
    pip install requests flask numpy && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

这样不仅减少层数,还清理了不必要的缓存文件。在实际项目中,我曾经通过这种方式将镜像大小从1.2GB压缩到800MB。

多阶段构建的实际应用

多阶段构建是个被低估的功能。特别是在处理编译型语言时效果显著。下面是我在处理Go项目时的实践:

# 第一阶段:构建阶段
FROM golang:1.19 as builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# 第二阶段:运行阶段
FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
CMD ["./main"]

这种方式让最终镜像只包含运行时的必要文件,去掉了编译工具链,镜像大小从350MB降到了12MB。

容器运行时的资源管理

很多人在运行容器时忽略资源限制,直到出现生产环境问题。我曾经遇到过一个容器因为内存泄漏导致整个节点崩溃的情况。现在我会这样配置:

docker run -d \
  --name my-app \
  --memory=512m \
  --memory-swap=1g \
  --cpus=1.5 \
  --pids-limit=100 \
  my-app:latest

在docker-compose中也可以设置:

services:
  app:
    image: my-app:latest
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

数据持久化的正确姿势

处理数据持久化时,很多人直接使用 bind mount,但这可能导致权限问题。我推荐使用命名卷:

# 创建命名卷
docker volume create app-data

# 使用命名卷
docker run -d \
  -v app-data:/app/data \
  my-app:latest

在开发环境中,确实需要绑定挂载时,要注意权限处理:

# 在Dockerfile中设置正确的用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

健康检查的实用配置

健康检查不应该只是简单的端口检查。我通常会设置更智能的检查:

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

或者在docker-compose中:

services:
  app:
    image: my-app:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

日志管理的经验之谈

默认的json-file驱动在处理大量日志时可能有问题。我建议根据场景选择驱动:

{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "compress": "true"
  }
}

对于生产环境,我通常配置为:

docker run -d \
  --log-driver=local \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  my-app:latest

网络配置的细微之处

创建自定义网络可以提供更好的隔离和控制:

# 创建自定义网络
docker network create --driver bridge my-network

# 运行容器时指定网络
docker run -d --network=my-network --name app1 my-app:latest
docker run -d --network=my-network --name app2 my-app:latest

这样容器间可以通过容器名直接通信,而不用关心IP地址变化。

这些实践都是我在实际工作中踩过坑后总结出来的。每个配置背后都有其设计考量,理解这些能帮助我们更好地使用这个工具。