作为一名经历过无数次生产环境部署的老手,我必须承认Docker彻底改变了我的工作方式。还记得三年前那个噩梦般的夜晚,三台服务器因为环境不一致导致服务集体崩溃,我和团队花了整整12个小时才恢复系统。从那时起,我下定决心要掌握容器化技术。
Docker不仅仅是一个工具,它代表了一种全新的应用交付方式。通过将应用及其所有依赖项打包到一个标准化的单元中,我们终于可以告别"在我机器上能跑"的尴尬局面。但正如任何强大工具一样,Docker也有自己的学习曲线和陷阱。
在构建Docker镜像时,依赖管理是最容易出问题的环节之一。我第一次尝试构建Python应用镜像时,就遇到了依赖安装超时的问题。这主要是因为默认的PyPI源在国内访问速度极慢。
解决方案不仅仅是简单地更换镜像源,而是一套完整的优化策略:
dockerfile复制# 基础镜像选择
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 先复制requirements文件单独安装依赖
COPY requirements.txt .
# 配置pip镜像源和缓存策略
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple \
&& pip config set global.timeout 120 \
&& pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 设置容器启动命令
CMD ["python", "app.py"]
关键技巧:先单独复制requirements.txt并安装依赖,这样在修改代码时可以利用Docker的层缓存机制,避免重复安装依赖。
镜像大小直接影响部署效率和存储成本。经过多次实践,我总结出以下优化方法:
dockerfile复制# 构建阶段
FROM golang:1.16 AS builder
WORKDIR /go/src/app
COPY . .
RUN go build -o myapp
# 运行阶段
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /go/src/app/myapp .
CMD ["./myapp"]
基础镜像选择:
清理无用文件:
dockerfile复制RUN apt-get update && apt-get install -y \
build-essential \
&& make \
&& apt-get remove -y build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
新手常遇到容器启动后立即退出的问题,这通常是因为主进程退出导致容器终止。以下是几种可靠的解决方案:
方案一:使用tail保持运行
dockerfile复制CMD ["tail", "-f", "/dev/null"]
方案二:使用supervisor管理多进程
dockerfile复制# 安装supervisor
RUN apt-get update && apt-get install -y supervisor
# 配置supervisor
COPY supervisord.conf /etc/supervisor/conf.d/
# 启动命令
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"]
方案三:直接运行应用并处理信号
dockerfile复制# 使用exec形式确保应用接收信号
CMD ["python", "app.py"]
经验之谈:生产环境推荐使用方案三,因为它最符合Docker的设计理念——一个容器一个进程。
合理的日志配置可以大大简化故障排查:
bash复制docker run --log-driver=json-file --log-opt max-size=10m --log-opt max-file=3 myapp
yaml复制services:
app:
image: myapp
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
bash复制docker run -d --name myapp myapp-image
docker run -d --name db mysql:8.0
# 在myapp容器中可以通过容器名db访问MySQL
bash复制docker network create my-network
docker run -d --network my-network --name myapp myapp-image
docker run -d --network my-network --name db mysql:8.0
bash复制docker run -d --network host myapp-image
对于分布式系统,容器可能需要跨主机通信:
bash复制docker network create -d overlay my-overlay
bash复制docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
my-macvlan
生产建议:中小规模部署使用Overlay网络,需要直接暴露容器IP时使用Macvlan。
dockerfile复制VOLUME /var/lib/mysql
bash复制docker volume create mysql-data
docker run -v mysql-data:/var/lib/mysql mysql:8.0
bash复制docker run -v /host/path:/container/path myapp
bash复制docker run --tmpfs /tmp:size=100m,exec myapp
数据卷权限问题是最常见的痛点之一:
方案一:预先创建并授权目录
bash复制mkdir -p /data/app
chown -R 1000:1000 /data/app
docker run -v /data/app:/app/data -u 1000 myapp
方案二:Dockerfile中设置用户
dockerfile复制RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
方案三:使用ACL
bash复制setfacl -R -m u:1000:rwx /data/app
bash复制docker run -m 512m --memory-swap=1g myapp
bash复制docker run -m 512m --oom-kill-disable myapp
bash复制docker run --memory-swappiness=0 myapp
bash复制docker run --cpu-shares=512 myapp
bash复制docker run --cpuset-cpus="0,1" myapp
bash复制docker run --cpus=1.5 myapp
dockerfile复制RUN adduser -D appuser
USER appuser
bash复制docker run --read-only myapp
bash复制docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
bash复制docker scan myapp-image
dockerfile复制# 定期更新基础镜像
FROM python:3.9-slim@sha256:abc123...
dockerfile复制FROM golang:1.16 AS builder
...
FROM scratch
COPY --from=builder /go/bin/app /app
bash复制docker-compose up -d --scale web=3 --no-recreate
dockerfile复制HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1
bash复制# 启动新版本
docker-compose -f docker-compose-green.yml up -d
# 切换流量
docker-compose -f docker-compose-green.yml exec proxy switch
# 下线旧版本
docker-compose -f docker-compose-blue.yml down
yaml复制services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
日志收集架构:
自定义指标暴露:
python复制# Flask应用示例
@app.route('/metrics')
def metrics():
return f"app_requests_total {request_count}\n"
经过这些年的实践,我发现Docker最大的价值不在于技术本身,而在于它带来的标准化工作流程。从开发到测试再到生产,我们终于可以用同一种语言描述应用环境。虽然初期学习曲线陡峭,但一旦掌握,部署效率的提升是惊人的。
最后分享一个实用技巧:在团队中建立Dockerfile和docker-compose.yml的代码审查制度。因为基础设施即代码,它们应该和业务代码一样受到重视。每次修改都应该经过同行评审,确保最佳实践得到遵循。