実践LESSON 04

この教材で学ぶこと テストからデプロイまでのCI/CDを構築します。

GitHub Actions — CI/CD パイプライン構築

対象読者: GitHub Actionsでインフラ・バックエンド向けのCI/CDを構築したい人。


1. 基本構造

.github/
└── workflows/
    ├── ci.yml        # テスト・ビルド(PR時)
    ├── deploy.yml    # デプロイ(main マージ時)
    └── terraform.yml # インフラ変更
# 基本的な構造
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest  # GitHub ホステッドランナー
    
    steps:
      - uses: actions/checkout@v4  # リポジトリをチェックアウト
      
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'
      
      - name: Install dependencies
        run: pip install -r requirements.txt
      
      - name: Run tests
        run: pytest --cov=src --cov-report=xml
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4

2. 実践的な Python アプリのCI

name: Python CI

on:
  pull_request:
  push:
    branches: [main]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        python-version: ['3.11', '3.12']  # 複数バージョンでテスト
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'
      
      - name: Install dependencies
        run: |
          pip install --upgrade pip
          pip install -r requirements.txt
          pip install ruff pytest pytest-cov
      
      - name: Lint(ruff)
        run: ruff check src/
      
      - name: Type check(mypy)
        run: mypy src/ --ignore-missing-imports
      
      - name: Test
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          pytest tests/ -v --cov=src --cov-report=term-missing
      
      - name: セキュリティスキャン(bandit)
        run: |
          pip install bandit
          bandit -r src/ -ll  # 中程度以上の問題を検出

  docker-build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    permissions:
      id-token: write   # OIDC認証に必要
      contents: read
    
    steps:
      - uses: actions/checkout@v4
      
      # AWS OIDC認証(キーレス!)
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions
          aws-region: ap-northeast-1
      
      - name: Login to ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2
      
      - name: Build and push
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/myapp:$IMAGE_TAG .
          docker build -t $ECR_REGISTRY/myapp:latest .
          docker push $ECR_REGISTRY/myapp:$IMAGE_TAG
          docker push $ECR_REGISTRY/myapp:latest

3. AWS OIDC 認証(キーレスで最も安全)

# Terraform で GitHub Actions 用の IAM Role を作成
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

resource "aws_iam_role" "github_actions" {
  name = "github-actions-deploy"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Federated = aws_iam_openid_connect_provider.github.arn }
      Action    = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringLike = {
          "token.actions.githubusercontent.com:sub" = 
            "repo:myorg/myrepo:ref:refs/heads/main"  # 特定ブランチのみ
        }
      }
    }]
  })
}

4. ECS デプロイパイプライン

deploy-ecs:
  runs-on: ubuntu-latest
  needs: docker-build
  environment: production  # GitHub Environments で承認フロー
  
  steps:
    - uses: actions/checkout@v4
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: ${{ vars.DEPLOY_ROLE_ARN }}
        aws-region: ap-northeast-1
    
    - name: タスク定義を更新
      id: render-task
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ecs/task-definition.json
        container-name: myapp
        image: ${{ needs.docker-build.outputs.image }}
    
    - name: ECS デプロイ
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.render-task.outputs.task-definition }}
        service: myapp-service
        cluster: production
        wait-for-service-stability: true  # 安定するまで待機
    
    - name: デプロイ通知(Slack)
      if: always()
      uses: slackapi/slack-github-action@v1
      with:
        payload: |
          {
            "text": "${{ job.status == 'success' && '✅' || '❌' }} Deploy to production: ${{ job.status }}"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

5. 便利なパターン

キャッシュで高速化

# Docker レイヤーキャッシュ
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build with cache
  uses: docker/build-push-action@v5
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max

条件付き実行

# 特定ファイルが変更された時のみ実行
on:
  push:
    paths:
      - 'src/**'
      - 'Dockerfile'
      - '!**.md'      # Markdown変更は除外

# ジョブレベルの条件
deploy:
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'

並列実行でビルドを高速化

jobs:
  test-unit:
    runs-on: ubuntu-latest
    steps: [...]   # ユニットテスト

  test-integration:
    runs-on: ubuntu-latest
    steps: [...]   # 統合テスト(並列実行)

  build:
    needs: [test-unit, test-integration]  # 両方成功後にビルド
    steps: [...]

6. Secrets 管理

# GitHub CLI でシークレット設定
gh secret set AWS_ACCESS_KEY_ID
gh secret set SLACK_WEBHOOK_URL

# 環境別シークレット(Environments 機能)
# Settings → Environments → production → Secrets

⚠️ 推奨: AWS の永続的なキーより OIDC(キーレス)認証を優先


🔗 次のステップ