容器化应用的标准目录结构
一个可复用的Docker部署项目,目录结构不能随意。约定比配置重要——团队所有人看到相同的目录就知道文件在哪。
project/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── deploy/
│ ├── nginx.conf
│ ├── supervisord.conf
│ └── entrypoint.sh
├── scripts/
│ ├── build.sh
│ └── deploy.sh
└── src/
├── app.py
└── requirements.txt
Dockerfile是容器构建的核心,写法直接决定镜像大小和构建速度:
# 多阶段构建:编译阶段
FROM python:3.11-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user -r requirements.txt --no-cache-dir
# 运行阶段:只拷贝编译产物
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY src/ .
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["gunicorn", "app:app", "-w", "4", "-b", "0.0.0.0:8000"]
多阶段构建的关键价值在于最终镜像不包含编译工具链。python:3.11-slim基础镜像约150MB,如果用完整版python:3.11则超过1GB。更小的镜像意味着更快的拉取速度和更小的攻击面。
docker-compose编排与健康检查
单容器部署的场景越来越少,至少前端加后端加数据库加缓存,四个容器起步。docker-compose是管理这种小型编排的最佳工具:
version: "3.8"
services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://app:secret@db:5432/appdb
- REDIS_URL=redis://cache:6379/0
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
interval: 5s
timeout: 3s
retries: 5
cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
web:
condition: service_healthy
volumes:
pgdata:
depends_on配合condition: service_healthy,确保数据库和缓存真正就绪后再启动web服务。没有健康检查的depends_on只保证容器启动,不保证服务可用,这是很多人的踩坑点。
GitHub Actions构建流水线
CI/CD流水线的目标:每次push到main分支,自动构建镜像、跑测试、推送到镜像仓库、触发部署。
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to Server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: deploy
key: ${{ secrets.DEPLOY_KEY }}
script: |
docker pull ghcr.io/org/app:${{ github.sha }}
cd /opt/app
export IMAGE_TAG=${{ github.sha }}
docker compose up -d --no-deps web
sleep 5
curl -f http://localhost:8000/health || (echo "Health check failed, rolling back" && docker compose up -d --no-deps --force-recreate web && exit 1)
流水线的关键设计:部署后立即做健康检查,检查失败自动回滚。这个5秒的sleep等待应用启动,然后curl验证/health端点。如果失败,回退到上一个镜像。
蓝绿部署实现零停机
滚动更新在单实例场景下必然有短暂停机。蓝绿部署的思路是同时维护两套环境,切换流量完成发布:
#!/bin/bash
# deploy_blue_green.sh
set -e
CURRENT=$(docker compose ps --format json | jq -r '.[0].Name' | grep -o 'blue\|green' || echo "none")
if [ "$CURRENT" = "blue" ]; then
TARGET="green"
SOURCE="blue"
else
TARGET="blue"
SOURCE="green"
fi
echo "当前活跃: $SOURCE, 部署目标: $TARGET"
# 构建目标环境
export COMPOSE_PROJECT_NAME="app_$TARGET"
docker compose -f docker-compose.yml -f docker-compose.$TARGET.yml up -d --build
# 健康检查
for i in $(seq 1 12); do
if curl -sf http://localhost:8000/health > /dev/null 2>&1; then
echo "目标环境 $TARGET 健康检查通过"
break
fi
if [ $i -eq 12 ]; then
echo "健康检查超时,终止部署"
docker compose -p "app_$TARGET" down
exit 1
fi
sleep 5
done
# 切换Nginx上游并reload
# 更新upstream指向新环境
nginx -s reload
# 关闭旧环境
docker compose -p "app_$SOURCE" down
echo "发布完成: $TARGET 环境已激活"
蓝绿部署的代价是需要双倍的服务器资源。在容器化环境里,多一套容器的资源开销远比停机损失的销售额低。
回滚策略与版本管理
镜像标签策略决定了回滚的难易程度。latest标签在生产环境是灾难——你不知道当前跑的是哪个版本,回滚无从下手。
正确的标签策略:
# 每次构建打两个标签
# 1. Git SHA精确版本,用于回滚
docker tag app:latest registry/app:a1b2c3d
# 2. 语义化版本,用于追踪
docker tag app:latest registry/app:v1.2.3
回滚脚本:
#!/bin/bash
# rollback.sh - 回滚到指定版本
TARGET_VERSION=$1
if [ -z "$TARGET_VERSION" ]; then
echo "用法: ./rollback.sh <version>"
echo "可用版本:"
docker images --format '{{.Repository}}:{{.Tag}}' registry/app | head -10
exit 1
fi
export IMAGE_TAG=$TARGET_VERSION
docker compose up -d --no-deps web
echo "已回滚到版本: $TARGET_VERSION"
Docker自动化部署的完整闭环:代码提交触发CI构建,构建产物推送到镜像仓库,CD流水线拉取新镜像并部署,部署后健康检查,异常自动回滚。这套机制建立后,发布从高危手工操作变成可重复的自动化流程,半夜三点出问题也能安心让系统自己回滚。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/docker-zi-dong-hua-bu-shu-shi-zhan-yong-cicd-liu-shui-xian/