正在学习

8.5 示例:自动化完整部署工作流

阶段 2:运行时

FROM python:3.11-alpine

RUN addgroup -S app && adduser -S app -G app

USER app

WORKDIR /app

COPY --from=builder /install /usr/local

COPY app.py .

EXPOSE 8000

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]


Makefile

```makefile
APP=taskflow

REGISTRY?=ghcr.io/your-org

IMAGE=(APP)

TAG?=$(shell git rev-parse --short HEAD 2>/dev/null || echo dev)

.PHONY: help

help:

    @echo "Targets: venv, test, build, run, push, deploy, rollback, smoke"

venv:

    python -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt pytest

test: venv

    . .venv/bin/activate && pytest -q || (echo "Tests failed"; exit 1)

build:

    docker build -t (TAG) -t $(IMAGE):latest .

run:

    docker run --rm -p 8000:8000 --name (IMAGE):$(TAG)

push:

    docker push (TAG) && docker push $(IMAGE):latest

deploy:

    ./scripts/deploy.sh "(TAG)"

rollback:

    ./scripts/rollback.sh $(APP)

smoke:

    ./scripts/smoke.sh

tests/test_app.py

from fastapi.testclient import TestClient

from app import app

client = TestClient(app)

def test_health():

    r = client.get("/health")

    assert r.status_code == 200

    assert r.json()["status"] == "ok"

def test_create_and_get_task():

    r = client.post("/tasks", json={"title": "Ship"})

    assert r.status_code == 201

    tid = r.json()["id"]

    r2 = client.get(f"/tasks/{tid}")

    assert r2.status_code == 200

    assert r2.json()["title"] == "Ship"

scripts/deploy.sh

#!/usr/bin/env bash

# deploy.sh — 幂等的远程或本地部署脚本,包含健康检查

# 用法:./scripts/deploy.sh <image>

set -euo pipefail

IMAGE="${1:-}"

APP_NAME="taskflow"

PORT="${PORT:-8000}"

if [[ -z "$IMAGE" ]]; then

    echo "用法:$0 <image>"

    exit 1

fi

echo "[deploy] 正在拉取 $IMAGE"

docker pull "$IMAGE" >/dev/null 2>&1 || true

echo "[deploy] 停止旧容器(如果存在)"

docker rm -f "$APP_NAME" >/dev/null 2>&1 || true

echo "[deploy] 启动新容器"

docker run -d --name "IMAGE"

echo "[deploy] 健康检查"

for i in {1..20}; do

    if curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null; then

        echo "[deploy] 健康状态正常"

        exit 0

    fi

    sleep 1

done

echo "[deploy] 健康检查失败"

docker logs "$APP_NAME" || true

exit 1

scripts/rollback.sh

#!/usr/bin/env bash

# rollback.sh — 回滚到上一个本地镜像标签

# 用法:./scripts/rollback.sh <app_name>

set -euo pipefail

APP="${1:-taskflow}"

PREV=$(docker images --format '{{.Repository}}:{{.Tag}} {{.CreatedAt}}' \

    | grep "$APP" \

    | sort -rk2 \

    | awk 'NR==2{print $1}')

if [[ -z "$PREV" ]]; then

    echo "[rollback] 未找到上一个镜像"

    exit 1

fi

echo "[rollback] 切换到 $PREV"

docker rm -f "$APP" >/dev/null 2>&1 || true

docker run -d --name "PREV"

echo "[rollback] 完成"

scripts/smoke.sh

#!/usr/bin/env bash

# smoke.sh — 部署后简单冒烟检查

set -euo pipefail

URL="${URL:-http://127.0.0.1:8000}"

echo "[smoke] GET ${URL}/health"

curl -fsS "${URL}/health" | jq .

echo "[smoke] 创建一个任务"

curl -fsS -X POST "${URL}/tasks" -H "Content-Type: application/json" -d '{"title":"Check"}' | jq .

echo "[smoke] 列出任务"

curl -fsS "${URL}/tasks" | jq .

.github/workflows/ci.yml

name: TaskFlow 完整工作流

on:

  push:

    branches: [ "main" ]

  pull_request:

    branches: [ "main" ]

  workflow_dispatch: {}

env:

  REGISTRY: ghcr.io

  IMAGE_NAME: ${{ github.repository }}/taskflow

jobs:

  test:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5

        with:

          python-version: "3.11"

      - run: |

          python -m venv .venv

          . .venv/bin/activate

          pip install -r requirements.txt pytest

          pytest -q

  build_and_push:

    needs: test

    runs-on: ubuntu-latest

    permissions:

      contents: read

      packages: write

    steps:

      - uses: actions/checkout@v4

      - name: 登录 GHCR

        uses: docker/login-action@v3

        with:

          registry: ghcr.io

          username: ${{ github.actor }}

          password: ${{ secrets.GITHUB_TOKEN }}

      - name: 短 SHA

        id: vars

        run: echo "SHORT=GITHUB_OUTPUT

      - name: 构建并推送

        uses: docker/build-push-action@v6

        with:

          context: .

          push: true

          tags: |

            {{ env.IMAGE_NAME }}:${{ steps.vars.outputs.SHORT }}

            {{ env.IMAGE_NAME }}:latest

  deploy:

    needs: build_and_push

    runs-on: ubuntu-latest

    if: github.ref == 'refs/heads/main'

    steps:

      - name: 通过 SSH 部署

        uses: appleboy/ssh-action@v1.0.3

        with:

          host: ${{ secrets.DEPLOY_HOST }}

          username: ${{ secrets.DEPLOY_USER }}

          key: ${{ secrets.DEPLOY_KEY }}

          script: |

            set -e

            cd ${{ secrets.DEPLOY_PATH }}

            ./scripts/deploy.sh ghcr.io/{GITHUB_SHA::7}

            sleep 3

            ./scripts/smoke.sh

  rollback_on_failure:

    needs: deploy

    if: failure()

    runs-on: ubuntu-latest

    steps:

      - name: 通过 SSH 回滚

        uses: appleboy/ssh-action@v1.0.3

        with:

          host: ${{ secrets.DEPLOY_HOST }}

          username: ${{ secrets.DEPLOY_USER }}

          key: ${{ secrets.DEPLOY_KEY }}

          script: |

            set -e

            cd ${{ secrets.DEPLOY_PATH }}

            ./scripts/rollback.sh taskflow

本地使用

make venv

make test

make build TAG=$(git rev-parse --short HEAD || echo dev)

make run

# 在另一个终端中:

make smoke

CI 使用

  1. 添加仓库密钥:DEPLOY_HOST、DEPLOY_USER、DEPLOY_KEY、DEPLOY_PATH。
  2. 推送到 main 分支。工作流将自动构建、推送、部署、执行冒烟测试,并在失败时自动回滚。

澄清表

阶段 命令或文件 作用 失败行为
测试 make test, tests/test_app.py 确保 HTTP 层的契约正确性 流水线停止;Claude 可以收紧断言
打包 Dockerfile 确保可复现的运行时,使用非 root 用户 构建失败;日志指向缺失的依赖
打标签与推送 make push 或 CI 中的 build_and_push 确保镜像标签确定性(SHA、latest) 推送失败则不进行部署
部署 scripts/deploy.sh 容器重启和健康检查的幂等性 打印日志并以非零状态退出
冒烟测试 scripts/smoke.sh 部署后对核心端点的验证 在 CI 中触发回滚任务
回滚 scripts/rollback.sh 一键回退到上一个镜像 快速恢复服务

至此,你拥有了一套紧凑的端到端部署工作流:测试把关、容器化、标签驱动、健康检查、可回滚。Claude Code 的价值在于让整个系统保持一致:它可以调整 Dockerfile 以构建更小的镜像、微调 Makefile 目标、强化健康检查,或针对 CI 失败给出具体的修复步骤。基于此,你可以将该模式扩展到多个服务、环境和团队,同时保持同样的清晰度和运营安全性。

第 9 章 – 项目 3:为团队构建 Claude Code 助手

练习题

在Dockerfile运行时阶段,USER app指令的目的是什么?

A. 创建一个名为'app'的新用户
B. 将后续指令的用户上下文设置为'app'
C. 将用户'app'添加到sudoers组
D. 指定运行应用程序的默认用户

Makefile 中哪个命令负责在 Docker 容器中运行 FastAPI 应用程序?

A. make venv
B. make test
C. make run
D. make deploy

deploy.sh 脚本的目的是什么?(选择所有适用的选项)

A. 拉取 Docker 镜像
B. 如果存在旧容器则停止它
C. 使用最新镜像启动新容器
D. 对已部署的应用程序运行健康检查
E. 构建 Docker 镜像

rollback.sh 脚本可以回滚到系统上可用的任何以前的本地镜像标签。

smoke.sh 脚本中,用于发出请求的默认 URL 是 ___。

解释 Dockerfile 中 EXPOSE 8000 指令的作用。

Dockerfile中CMD指令的作用是什么?

A. 指定启动容器时运行的默认命令
B. 将文件复制到容器中
C. 设置环境变量
D. 暴露端口

以下哪些是 Makefile 中的有效目标?(选择所有适用的)

A. venv
B. test
C. build
D. deploy
E. push
F. smoke

test_app.py 文件包含测试用例,用于验证健康检查端点以及创建和检索任务的能力。

deploy.shrollback.shsmoke.sh 脚本开头的 set -euo pipefail 命令有什么意义?

Dockerfile 运行时阶段中 EXPOSE 8000 指令的目的是什么?

A. 它使容器在运行时监听 8000 端口。
B. 它将主机的 8000 端口映射到容器。
C. 它告诉 Docker 容器在运行时监听 8000 端口,但实际上并不发布该端口。
D. 它在 8000 端口上安装依赖项。

根据 TaskFlow CI 工作流,以下哪些是成功部署所必需的?(选择所有适用的)

A. test 任务必须通过后才能继续执行 build_and_push
B. deploy 任务无论在哪个分支都会运行。
C. deploy 任务仅在分支为 'main' 时运行。
D. rollback_on_failure 任务在 deploy 任务失败时运行。
E. build_and_push 任务需要手动触发。

rollback.sh 脚本可以回滚到本地 Docker 镜像列表中找到的任何先前的镜像标签。

Dockerfile 中的 CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"] 指令指定了容器启动时要运行的 ___ 。

deploy.sh 脚本中健康检查循环的目的是什么?

登录后解锁笔记、知识点解析、AI 问答

立即登录