MonkeyCode写CI/CD流水线:从零配置GitHub Actions自动化部署

CI/CD为什么要用AI辅助

手写CI/CD配置有三大痛点:

  1. 语法复杂:YAML缩进、GitHub Actions的语法糖、Secret管理,写错一个空格流水线就挂
  2. 经验依赖:缓存策略、矩阵构建、并发控制这些经验不容易从文档学到
  3. 平台差异大:GitHub Actions、GitLab CI、Jenkins的写法完全不同

MonkeyCode的优势在于:你描述"我要做什么",它生成对应平台的配置文件。这篇文章用GitHub Actions实战。

给MonkeyCode的Prompt模板

帮我配置GitHub Actions CI/CD流水线,项目信息:
- 语言:Python 3.11 + FastAPI
- 测试框架:pytest
- 需要的功能:
  1. push和PR时自动运行测试
  2. 代码质量检查(flake8 + black + mypy)
  3. 安全扫描(bandit)
  4. 构建Docker镜像
  5. 自动部署到服务器(SSH部署)
  6. 测试报告上传到PR评论
  7. Slack通知构建结果
  8. 缓存pip依赖加速构建

环境:
- Python 3.11
- PostgreSQL测试数据库(用Docker service)
- Docker Hub自动推送镜像

MonkeyCode生成的完整流水线

# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  workflow_dispatch: # 手动触发

env:
  PYTHON_VERSION: '3.11'
  DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/myapp
  DOCKER_REGISTRY: docker.io

jobs:
  # ============================================================
  # Job 1: 代码质量检查
  # ============================================================
  lint:
    name: Code Quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-lint-${{ hashFiles('requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-lint-

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install flake8 black isort mypy bandit
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Check formatting (black + isort)
        run: |
          black --check --diff .
          isort --check-only --diff .

      - name: Lint (flake8)
        run: |
          # 新代码用更严格规则,旧代码宽松
          flake8 . --count --statistics \
            --max-line-length=120 \
            --ignore=E501,W503 \
            --exclude=.git,__pycache__,venv,alembic

      - name: Type check (mypy)
        run: |
          mypy app/ --ignore-missing-imports --no-strict-optional
        continue-on-error: true

      - name: Security scan (bandit)
        run: |
          bandit -r app/ -f json -o bandit-report.json
        continue-on-error: true

      - name: Upload bandit report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: bandit-report
          path: bandit-report.json

  # ============================================================
  # Job 2: 测试(带PostgreSQL)
  # ============================================================
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
        ports:
          - 5432:5432
        options: >-
          --health-cmd="pg_isready -U testuser"
          --health-interval=10s
          --health-timeout=5s
          --health-retries=5

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.PYTHON_VERSION }}

      - name: Cache pip
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-test-${{ hashFiles('requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-test-

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Run database migrations
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
        run: alembic upgrade head

      - name: Run tests with coverage
        env:
          DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
          TEST_ENV: ci
        run: |
          pytest tests/ \
            --cov=app \
            --cov-report=xml:coverage.xml \
            --cov-report=html:htmlcov \
            --cov-report=term-missing \
            --junitxml=test-results.xml \
            -v --tb=short

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          file: coverage.xml
          fail_ci_if_error: false

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: |
            test-results.xml
            htmlcov/

      - name: Comment test results on PR
        if: github.event_name == 'pull_request' && always()
        uses: dorny/test-reporter@v1
        with:
          name: Test Results
          path: test-results.xml
          reporter: java-junit

  # ============================================================
  # Job 3: 构建Docker镜像
  # ============================================================
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    needs: test
    permissions:
      contents: read
      packages: write
    outputs:
      image_tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.DOCKER_IMAGE }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_DATE=${{ github.event.repository.updated_at }}
            VCS_REF=${{ github.sha }}

  # ============================================================
  # Job 4: 部署到生产环境
  # ============================================================
  deploy:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment:
      name: production
      url: https://myapp.example.com
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            # 拉取最新镜像
            docker pull ${{ env.DOCKER_IMAGE }}:${{ needs.build.outputs.image_tag }}
            
            # 健康检查
            echo "Waiting for container to be healthy..."
            
            # 滚动更新
            docker-compose -f /opt/myapp/docker-compose.yml up -d --no-deps app
            
            # 清理旧镜像
            docker image prune -f
            
            echo "Deploy complete!"

      - name: Health check
        run: |
          for i in {1..30}; do
            if curl -sf https://myapp.example.com/health; then
              echo "Health check passed!"
              exit 0
            fi
            echo "Waiting... ($i/30)"
            sleep 5
          done
          echo "Health check failed!"
          exit 1

      - name: Notify Slack on success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "✅ Deployment successful!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "✅ *Deployment to production successful*\n*Branch:* `${{ github.ref_name }}`\n*Commit:* ${{ github.sha }}\n*By:* ${{ github.actor }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow>"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

      - name: Notify Slack on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "❌ Deployment failed!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "❌ *Deployment to production FAILED*\n*Branch:* `${{ github.ref_name }}`\n*Commit:* ${{ github.sha }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View workflow>"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

配套的Dockerfile

# Dockerfile - MonkeyCode生成
FROM python:3.11-slim as builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Production stage
FROM python:3.11-slim

WORKDIR /app

# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /app /app

# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

EXPOSE 8000

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

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

MonkeyCode的CI/CD Prompt优化技巧

技巧1:分阶段描述需求

不要一次把所有需求丢给MonkeyCode,分阶段来:

第一阶段:只跑测试
帮我配置GitHub Actions,push时自动运行pytest测试,Python 3.11项目

第二阶段:加代码质量
在上面流水线基础上,增加flake8、black格式检查

第三阶段:加部署
在上面流水线基础上,增加Docker构建和SSH部署到生产服务器

每阶段确认后再加需求,避免一次生成太多导致出错。

技巧2:明确缓存策略

pip缓存使用 ~/.cache/pip,key用requirements.txt的hash
Docker层缓存使用GitHub Actions的cache-to=type=gha

MonkeyCode会生成对应缓存配置,构建时间从5分钟降到30秒。

技巧3:要求生成矩阵测试

测试job需要矩阵配置:
- Python 3.10, 3.11, 3.12
- PostgreSQL 14, 15
排除组合:Python 3.10 + PG 15

生成的矩阵:

test:
  strategy:
    matrix:
      python-version: ['3.10', '3.11', '3.12']
      postgres-version: ['14', '15']
      exclude:
        - python-version: '3.10'
          postgres-version: '15'

技巧4:要求生成docker-compose

# docker-compose.yml - MonkeyCode生成
version: '3.8'

services:
  app:
    image: ${DOCKER_IMAGE:-myapp:latest}
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://user:pass@db:5432/mydb
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '1.0'

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - app

volumes:
  pgdata:
  redisdata:

Secret管理最佳实践

GitHub Actions里用的Secret要通过GitHub仓库设置页面添加,不要硬编码:

# 需要配置的Secret列表
DOCKER_USERNAME     # Docker Hub用户名
DOCKER_PASSWORD     # Docker Hub密码或Access Token
SERVER_HOST         # 服务器IP
SERVER_USER         # SSH用户名
SSH_KEY             # SSH私钥
SLACK_WEBHOOK       # Slack Webhook URL

配置方法:GitHub仓库 → Settings → Secrets and variables → Actions → New repository secret


用MonkeyCode生成CI/CD配置后,一定要做两件事:

  1. 先在测试分支跑一遍,确认没问题再合到main
  2. 检查生成的Secret引用,确保没有硬编码密码

AI生成的YAML配置通常比手写的更完整,但Secret管理这种安全相关的东西,必须人工确认。

posted @ 2026-05-29 17:02  机房管理员  阅读(17)  评论(0)    收藏  举报