网站首页 > 教程文章 正文
我就不拐弯抹角了,你的 Docker 配置可能很糟糕。
这也不全是你的错。Docker 被宣传成一个“开箱即用”的神奇工具,把你的应用打包进容器,就像该死的便当盒一样。
但在你写了第三个 Dockerfile 和那个诅咒般需要 25 分钟才能失败的构建之间,你意识到:有地方出了大问题。
我曾和那些无法解释 CMD 和 ENTRYPOINT 区别的“DevOps 工程师”共事过。我接手过镜像 3GB、散布着五条 apt-get update 指令像撒彩纸一样的 Dockerfile。
我亲眼见过生产环境宕机,就因为有人没有固定其基础镜像的标签。
所以,如果你厌倦了脆弱的容器、失败的构建和臃肿的镜像——请继续阅读。
我们要解决所有这些问题。
错误 #1:构建庞大臃肿的镜像
问题:你的镜像大小达到 2GB+,而它们本应只有 50MB。
我曾经接手过一个 Node.js 应用,它的 Docker 镜像有 3.2GB。实际的应用程序代码?只有 12MB。其余的都是开发依赖项、缓存文件,以及一个没人需要的完整 Ubuntu 桌面环境。
发生原因:
- 使用重量级的基础镜像,如 ubuntu:latest
- 没有移除包管理器和构建工具
- 在生产镜像中遗留了开发依赖项
- 累积缓存层而不清理
修复方法:
# BAD: Heavyweight base image
FROM ubuntu:latest
RUN apt-get update && apt-get install -y nodejs npm
COPY . .
RUN npm install
CMD ["node", "app.js"]
# GOOD: Alpine-based with multi-stage build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
USER nextjs
CMD ["node", "app.js"]d
专业技巧:
- Alpine Linux 镜像通常比 Ubuntu 小 80%
- 使用 .dockerignore 排除不必要的文件
- 多阶段构建可以将最终镜像大小减少 90%
- 像 docker-slim 这样的工具可以自动优化镜像
错误 #2:以 Root 身份运行所有内容
以 root 身份运行容器,就像给你公司的每个员工都发一把能开所有门的万能钥匙。
一个被攻陷的容器就能控制你的整个宿主机系统。
现实检查:
大多数容器不需要 root 权限。那个提供静态文件的 Web 服务器?绝对不需要修改系统文件。
修复方法:
# Create a non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Set ownership
COPY --chown=appuser:appgroup . /app
# Switch to non-root user
USER appuser
这能防止什么:
- 容器逃逸攻击
- 权限提升
- 意外的系统文件修改
- 合规性违规
错误 #3:硬编码配置值
问题: 你的 Dockerfile 可能看起来像这样:
# DON'T DO THIS
ENV DATABASE_URL=postgresql://prod_user:secret@db.company.com:5432/prod_db
ENV API_KEY=sk_live_super_secret_key_here
恭喜,你刚刚把生产环境的秘密提交到了版本控制中。
正确方法:
# Use environment variables without default values
ENV DATABASE_URL=""
ENV API_KEY=""
# Or use build arguments for non-sensitive config
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
更好的方法——外部配置:
- 对敏感数据使用 Docker secrets
- 不同环境使用不同的环境文件
- 配置管理工具如 Consul 或 etcd
# Development
docker run --env-file .env.dev myapp
# Production with secrets
docker run --secret=db_password myapp
错误 #4:忽略层缓存
错误: 因为你改了一行代码,Docker 就重新构建所有东西,导致你的构建需要 20 分钟。
理解 Docker 层:
Dockerfile 中的每条指令都会创建一个新层。如果一个层发生变化,所有后续层都会被重建。把经常变化的指令放在最后。
# BAD: Changes to code rebuild everything
FROM node:18-alpine
COPY . .
RUN npm install
CMD ["npm", "start"]
# GOOD: Dependencies cached separately
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]
高级缓存策略:
# System dependencies (rarely change)
RUN apk add --no-cache git curl
# Application dependencies (change monthly)
COPY package*.json ./
RUN npm ci --only=production
# Application code (changes frequently)
COPY . .
构建时间对比:
- 糟糕的分层:每次构建 15-20 分钟
- 优化后的分层:代码变更只需 2-3 分钟
- 节省:构建时间减少 85%
错误 #5:不使用 .dockerignore
问题: 你的构建上下文包含了整个项目目录——node_modules、.git、临时文件,还有你忘记的那个 500MB 数据集。
创建一个 .dockerignore 文件:
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.env.local
.env.development.local
.env.test.local
.env.production.local
dist
*.log
.DS_Store
Thumbs.db
效果:
- 构建上下文:从 2GB 减少到 50 或 80MB
- 构建时间:从 5 分钟减少到 30 秒至 1 分钟
- 网络传输:快 97%
错误 #6:单点故障的健康检查
问题:你的容器显示为“健康”,但你的应用却给用户返回 500 错误。
基础健康检查:
# Too simple - only checks if process exists
HEALTHCHECK CMD curl -f http://localhost:3000/ || exit 1
全面的健康检查:
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
应用层健康检查端点:
// /health endpoint
app.get('/health', async (req, res) => {
try {
// Check database connection
await db.ping();
// Check external services
await redis.ping();
// Check file system
await fs.access('/tmp', fs.constants.W_OK);
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
});
错误 #7:混合构建时和运行时的依赖项
问题: 你的生产镜像包含了 TypeScript 编译器、测试框架和永远不该出现在生产环境的开发工具。
多阶段构建解决方案:
# Build stage
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER node
CMD ["npm", "start"]
大小对比:
- 单阶段:1.2GB
- 多阶段:180MB
- 减少:约 85%
错误 #8:糟糕的密钥管理
问题: 密钥放在环境变量、构建参数中,或者更糟——硬编码在镜像里。
# NEVER DO THIS
ARG API_KEY=sk_live_abc123
ENV DATABASE_PASSWORD=super_secret
正确的密钥管理:
# Use Docker secrets (Swarm mode)
COPY --from=secrets /run/secrets/api_key /etc/api_key
# Or mount secrets at runtime
# docker run -v /host/secrets:/run/secrets myapp
运行时密钥注入:
# Using environment variables from files
docker run --env-file secrets.env myapp
# Using Docker secrets
echo "my_secret_value" | docker secret create api_key -
docker service create --secret api_key myapp
最佳实践:
- 永远不要把密钥放在 Dockerfile 中
- 使用外部密钥存储(HashiCorp Vault, AWS Secrets Manager)
- 定期轮换密钥
- 使用最小权限访问原则
错误 #9:忽略日志管理
问题: 你的应用在生产环境崩溃了,但日志分散在各个容器中,有些缺失了,还有些塞满了你的磁盘。
结构化日志:
// Instead of console.log
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.json(),
transports: [
new winston.transports.Console()
]
});
logger.info('User login', {
userId: '12345',
ip: req.ip,
timestamp: new Date().toISOString()
});
日志配置:
# Limit log size and rotation
docker run --log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp
集中式日志:
# Docker Compose with logging
version: '3.8'
services:
app:
image: myapp
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "myapp"
错误 #10:没有针对 Kubernetes 进行优化
不匹配问题:你的容器用 docker run 运行正常,但在 Kubernetes 中却神秘地失败。
常见的 K8s 问题:
- 硬编码的 localhost 引用
- 假设有持久化存储
- 忽略优雅关闭信号
- 没有处理滚动更新
K8s 就绪的容器设计:
# Handle signals properly
FROM node:18-alpine
WORKDIR /app
COPY . .
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Use dumb-init as PID 1
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
优雅关闭处理:
// server.js
const server = http.createServer(app);
// Handle shutdown signals
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('SIGINT received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
process.exit(0);
});
});
Kubernetes 部署最佳实践:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
template:
spec:
containers:
- name: myapp
image: myapp:1.2.3 # Never use 'latest'
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
错误 #11:版本标签问题
问题: 在生产环境中使用 latest 标签,就像在部署中玩俄罗斯轮盘赌。
# DON'T DO THIS
FROM node:latest
FROM postgres:latest
“Latest”标签的真实含义:
- 没有版本控制
- 不可预测的更新
- 无法重现构建
- 没有警告的破坏性变更
正确的版本管理:
# Pin exact versions
FROM node:18.17.1-alpine
FROM postgres:15.4-alpine
# Or use SHA digests for ultimate reproducibility
FROM node:18-alpine@sha256:b87dc22bd9393b80eab10e2e
版本策略:
- 开发环境:使用次版本号(如 node:18-alpine)
- 预发布环境:使用补丁版本号(如 node:18.17-alpine)
- 生产环境:使用确切版本号或 SHA 摘要
错误 #12:网络安全漏洞
暴露问题: 你所有的容器都可以不受限制地相互通信、访问外部服务和互联网。
默认 Docker 网络的问题:
- 默认网桥上的所有容器都可以通信
- 没有网络分段
- 服务被不必要地暴露
# Docker Compose with custom networks
version: '3.8'
services:
web:
image: myapp
networks:
- frontend
ports:
- "80:80"
api:
image: myapi
networks:
- frontend
- backend
# No external ports exposed
database:
image: postgres:15
networks:
- backend
# Only accessible from backend network
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external access
网络安全最佳实践:
- 使用自定义网络代替默认网桥
- 按功能划分服务网络
- 为数据库启用仅内部访问的网络
- 使用服务发现代替硬编码 IP
错误 #13:跳过容器扫描
安全盲点:
你的容器正在运行带有已知漏洞的软件,这些漏洞可能在生产环境中被利用。
扫描为何重要:
- 基础镜像包含过时的软件包
- 依赖项存在安全缺陷
- 合规性要求
- 监管义务
内置 Docker 扫描:
# Scan local images
docker scan myapp:latest
# Scan during build
docker build --scan .
高级扫描工具:
# Trivy - comprehensive vulnerability scanner
trivy image myapp:latest
# Snyk - focus on application dependencies
snyk container test myapp:latest
# Clair - static analysis
clairctl analyze myapp:latest
CI/CD 集成:
# GitHub Actions example
name: Container Security
on: [push]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
扫描最佳实践:
- 使用基础镜像前先扫描
- 将扫描集成到 CI/CD 流水线中
- 设置安全策略,遇到严重漏洞时使构建失败
- 定期扫描生产环境镜像
- 保持基础镜像更新
错误 #14:低效的多平台构建
跨平台问题:
你在基于 ARM 的 MacBook 上构建的镜像,在 x86 的生产服务器上崩溃了。
平台特定问题:
- 二进制不兼容
- 特定于架构的依赖项
- 性能差异
多平台构建设置:
# Create and use buildx builder
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap
# Build for multiple platforms
docker buildx build --platform linux/amd64,linux/arm64 \
-t myapp:latest --push .
Dockerfile 优化:
# Handle different architectures
FROM --platform=$BUILDPLATFORM node:18-alpine AS builder
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "Building on $BUILDPLATFORM for $TARGETPLATFORM"
# Install platform-specific dependencies
RUN case "$TARGETPLATFORM" in \
"linux/amd64") apk add --no-cache libc6-compat ;; \
"linux/arm64") apk add --no-cache libc6-compat ;; \
esac
错误 #15:资源限制不足
问题:
一个失控的容器消耗了所有可用内存,导致你整个宿主机系统崩溃。
设置适当的限制:
# In Dockerfile (documentation only)
LABEL memory="512m"
LABEL cpu="0.5"
# At runtime (enforced)
docker run -m 512m --cpus="0.5" myapp
Docker Compose:
version: '3.8'
services:
web:
image: myapp
deploy:
resources:
limits:
memory: 512M
cpus: '0.50'
reservations:
memory: 256M
cpus: '0.25'
监控资源使用情况:
# Real-time stats
docker stats
# Historical usage
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
google/cadvisor:latest
容器革命是真实的
Docker 不会消失。容器采用率在过去几年增长了 300%,每个主要的云提供商现在都将容器服务作为其主要计算平台提供。
但问题是——大多数团队仍在犯这 15 个相同的错误。喜欢 Docker 的团队和讨厌它的团队之间的区别,通常就在于能否避免这些陷阱。
行动计划:
- 从安全开始 —— 首先修复 root 访问和密钥管理
- 为大小优化 —— 使用 Alpine、多阶段构建和正确的 .dockerignore
- 为生产环境规划 —— 添加健康检查、资源限制和监控
- 自动化一切 —— 包含扫描和测试的 CI/CD 流水线
- 持续监控 —— 跟踪资源使用情况、日志和安全警报
正确使用容器的公司部署更快、扩展更容易、晚上睡得更好。而那些没有做好的公司……嗯,他们通常正在招聘“Docker 专家”,并纳闷为什么他们的基础设施成本不断攀升。
一些常见的 Docker 问题
问:为什么我的 Docker 构建这么慢?
答:糟糕的层缓存通常是罪魁祸首。确保你在复制整个应用程序代码之前先复制依赖项文件(如 package.json)。这允许 Docker 缓存依赖项安装层。
问:如何减小 Docker 镜像大小?
答:使用 Alpine 基础镜像、多阶段构建和 .dockerignore 文件。从最终镜像中移除包管理器和开发依赖项。一个典型的 Node.js 应用通过适当优化,可以从 1GB+ 降到 100MB 以下。
问:我应该在一个容器中运行多个进程吗?
答:通常不应该。遵循“一个容器一个进程”的原则。如果你需要多个进程,使用 Docker Compose 或 Kubernetes 来编排多个容器。
问:如何在容器中处理数据库连接?
答:永远不要硬编码数据库 URL。使用环境变量、Docker secrets 或外部配置管理。实现适当的连接池和健康检查。
问:CMD 和 ENTRYPOINT 有什么区别?
答:ENTRYPOINT 定义了始终运行的可执行文件,而 CMD 提供默认参数。当你希望容器始终运行特定命令时使用 ENTRYPOINT,当你需要灵活性时使用 CMD。
作者丨Crafting-Code 编译丨Rio
来源丨网址:https://blog.stackademic.com/15-common-docker-mistakes-and-how-to-avoid-them-525b803d00f9
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
猜你喜欢
- 2025-08-06 攀登云巅 | 誉天云计算学员备考的实战心得与成长感悟
- 2025-08-06 CodeSpirit 开发环境搭建指南
- 2025-08-06 如何将Docker镜像从1.16GB瘦身到162MB?
- 2025-08-06 颜值爆表、实力超群!这款免费开源团队沟通工具千万别错过
- 2025-08-06 虚拟化与Docker容器之争
- 2025-08-06 在Ubuntu Linux上安装Minikube以便于Kubernetes开发
- 2025-08-06 M1和Docker谈了个恋爱!对于M1和Docker的爱情你们怎么看?
- 2025-08-06 Linux高级云计算教程
- 2025-08-06 Docker和Kubernetes配置安全基线检查工具vesta
- 2025-08-06 KUBERNETES超越RBAC – 通过WEBHOOK自定义授权
- 最近发表
- 标签列表
-
- location.href (44)
- document.ready (36)
- git checkout -b (34)
- 跃点数 (35)
- 阿里云镜像地址 (33)
- qt qmessagebox (36)
- mybatis plus page (35)
- vue @scroll (38)
- 堆栈区别 (33)
- 什么是容器 (33)
- sha1 md5 (33)
- navicat导出数据 (34)
- 阿里云acp考试 (33)
- 阿里云 nacos (34)
- redhat官网下载镜像 (36)
- srs服务器 (33)
- pico开发者 (33)
- https的端口号 (34)
- vscode更改主题 (35)
- 阿里云资源池 (34)
- os.path.join (33)
- redis aof rdb 区别 (33)
- 302跳转 (33)
- http method (35)
- js array splice (33)