CI/CD — GitHub Actions 自動デリバリーパイプライン
CI/CDは自動車工場の自動組み立てラインのようなもの - 部品 (コード)が入力され, 溶接 (lint), 品質検査 (テスト), 塗装 (セキュリティスキャン)を経て, 検査に合格した完成品 (Dockerイメージ)が自動的に販売店 (本番環境)に配送されます。
1. 学ぶ内容
- GitHub Actionsの基本:Workflow, Job, Step, Actionの概念
- CIパイプライン:lint → pytest → セキュリティスキャン → Dockerビルド
- CDパイプライン:イメージプッシュ → Docker Hub/GHCR → サーバーデプロイ
- 環境管理:dev / staging / productionのブランチ戦略
- Aliceのシナリオ:BobがPRを提出 → 自動テスト → Charlieがマージ → 自動デプロイ
2. Aliceの実話
(1) 悩み:手動デプロイでのミスが頻発
Bobがコードを提出した後, Aliceはサーバーから手動でコードを取得し, テストを実行し, イメージをビルドし, コンテナを再起動していました。このプロセスには毎回30分かかり, テストステップをスキップすることもよくありました - ある時, Aliceはマイグレーションの実行を忘れ, 新エンドポイントが本番で500エラーを報告しました。Charlieは3つの環境 (dev, staging, prod)でこれらのタスクを手動で実行できると言いましたが, リスクが高すぎました。
(2) GitHub Actionsによる解決策
GitHub Actionsは, プッシュやプルリクエストのたびにフルパイプライン - lint → テスト → ビルド → デプロイ - を自動実行します。すべてのステップはコードで定義されるため, 見落としはなく, 失敗すれば自動的にマージがブロックされます。
(3) 成果
デプロイ時間は30分 (手動)から5分 (自動化)に短縮され, テストの見落としは完全に排除され (テストに失敗したPRはマージできません), 3つの環境すべてでデプロイが完全に一貫性を保つようになりました。
3. GitHub Actionsの基本
(1) コア概念
flowchart LR
Trigger[Trigger: push/PR] --> Workflow[Workflow]
Workflow --> Job1[Job 1: Lint+Test]
Workflow --> Job2[Job 2: Build]
Job1 --> Step1[Step: ruff check]
Job1 --> Step2[Step: pytest]
Job2 --> Step3[Step: docker build]
Job2 --> Step4[Step: docker push]
| 概念 | 説明 | 例 |
|---|---|---|
| Workflow | 自動プロセス定義 | .github/workflows/ci.yml |
| Trigger | トリガー条件 | push, pull_request |
| Job | ステップの集合からなる実行単位 | test, build, deploy |
| Step | 単一操作 | run: pytest |
| Action | 再利用可能なアクション | actions/checkout@v4 |
| Runner | 実行環境 | ubuntu-latest |
(2) ブランチと環境のマッピング
graph TD
Feature[feature/*] -->|PR| Develop[develop]
Develop -->|Deploy| DevEnv[Dev環境]
Develop -->|PR| Main[main]
Main -->|Deploy| Staging[Staging環境]
Main -->|Tag v*| Prod[本番環境]
| ブランチ | 環境 | トリガー | デプロイ方式 |
|---|---|---|---|
feature/* |
- | PR自動テスト | デプロイなし |
develop |
Dev | 自動プッシュ | Docker Compose |
main |
Staging | 自動プッシュ | Docker Compose |
v* tag |
本番 | tag手動 | Docker Compose / K8s |
4. CIパイプライン
(1) ▶ サンプル:完全なCIワークフロー
YAML
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install UV
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install dependencies
run: uv sync --frozen
- name: Run ruff lint
run: uv run ruff check app/ tests/
- name: Run ruff format check
run: uv run ruff format --check app/ tests/
test:
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: pricetracker
POSTGRES_PASSWORD: test_password
POSTGRES_DB: pricetracker_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Install UV
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Install dependencies
run: uv sync --frozen
- name: Run pytest
env:
DATABASE_URL: postgresql+asyncpg://pricetracker:test_password@localhost:5432/pricetracker_test
REDIS_URL: redis://localhost:6379/0
SECRET_KEY: test-secret-key-for-ci
run: uv run pytest tests/ -v --cov=app --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
security:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- name: Run safety check
run: |
pip install safety
safety check --json || true
- name: Run bandit
run: |
pip install bandit
bandit -r app/ -f json || true
build:
runs-on: ubuntu-latest
needs: [test, security]
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
push: false
tags: pricetracker:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
出力:
TEXT
CI/CDパイプラインを読み込みました
パイプラインステータス:合格
テスト:12合格, 0不合格
5. CDパイプライン
(1) ▶ サンプル:本番デプロイワークフロー
YAML
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /opt/pricetracker
docker compose pull
docker compose up -d --remove-orphans
docker compose exec api alembic upgrade head
echo "Deployed version ${{ github.ref_name }}"
出力:
TEXT
CONTAINER ID IMAGE STATUS PORTS
abc123 nginx:latest Up 2 hours 0.0.0.0:80->80/tcp
(2) 完全なCI/CDパイプラインワークフロー
flowchart LR
Push[Bobがコードをプッシュ] --> Lint[Lint: ruff]
Lint --> Test[Test: pytest]
Lint --> Sec[Security: safety + bandit]
Test --> Build[Docker Build]
Sec --> Build
Build -->|On tag| Push[Push to GHCR]
Push --> Deploy[Deploy to Production]
Deploy --> Health[Health Check]
Health --> Done[✓ Live]
(2) ▶ サンプル:ブランチ保護と環境保護ルール
YAML
# .github/workflows/branch-protection.yml
name: Branch Protection Check
on:
pull_request:
types: [opened, synchronize]
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Verify PR target is main
run: |
if [ "${{ github.base_ref }}" != "main" ]; then
echo "PR must target main branch"
exit 1
fi
- name: Check environment approval
if: github.event.pull_request.merged == true
uses: octokit/request-action@v2
with:
route: POST /repos/{owner}/{repo}/deployments
environment: staging
required_reviewers: 1
出力:
TEXT
CI/CDパイプラインを読み込みました
パイプラインステータス:合格
テスト:12合格, 0不合格
❓ よくある質問
Q GitHub Actionsの無料枠は十分ですか?
A パブリックリポジトリには制限がありません。プライベートリポジトリは月2,000無料分があります。PriceTrackerのCIは1回約5分で実行され, 1日10回で約1,500分なので, 十分です。
Q CIでデータベースマイグレーションはどう処理しますか?
A CIテストでは
Base.metadata.create_all()で直接テーブルを作成し (Alembicを使わず), CDデプロイスクリプトでalembic upgrade headを実行します。Q シークレットはどのように安全に保管しますか?
A GitHubプロジェクトのSettings → Secrets and variables → Actionsで,
SERVER_SSH_KEY, DB_PASSWORDなどを保存し, YAML内で${{ secrets.XXX }}で参照します。Q mainブランチのみにデプロイするにはどうしますか?
A
on: push: tags: ['v*']を使用し, タグ作成時のみデプロイをトリガーします。開発ブランチではテストのみを実行し, デプロイは行いません。Q Dockerキャッシュはどう活用しますか?
A
cache-from: type=ghaを使用してGitHub Actionsキャッシュを活用します。初回ビルドは5分, 以降の変更は変更レイヤーのみ再ビルドされ, 1〜2分かかります。Q デプロイ失敗後のロールバックはどうしますか?
A
docker compose down + docker compose pull <previous-tag> + docker compose up -d。前バージョンのイメージタグを保持しておき, ロールバックを容易にします。📖 まとめ
- GitHub Actionsは4階層構造:Workflow → Job → Step → Action
- CIパイプライン:lint (ruff)→ テスト (pytest + PostgreSQL/Redisサービスコンテナ)→ セキュリティ → ビルド
- CDパイプライン:tagトリガー → Dockerイメージビルド → GHCRへプッシュ → SSHでサーバーにデプロイ
- ブランチ戦略:feature PRはテストのみ, developはDevデプロイ, mainはStagingデプロイ, v*タグは本番デプロイ
- Dockerレイヤーキャッシュ + GitHub Actionsキャッシュでビルドを高速化, Secretsでシークレットキーを安全に管理
📝 練習問題
- 基本問題 (難易度 ⭐):
.github/workflows/ci.ymlを作成し, プッシュやPR作成時にruff checkとpytestが自動実行されるように設定し, GitHub Actionsページで緑色の「Passed」が表示されることを確認してください。ヒント:on: push: branches: [main] - 応用問題 (難易度 ⭐⭐):テストジョブにPostgreSQLとRedisのサービスコンテナを追加し, 環境変数を設定してpytestがテストデータベースに接続できるようにし, Dockerビルドステップを追加してイメージのビルドが成功することを確認してください。ヒント:
services:+options: --health-cmd - チャレンジ (難易度 ⭐⭐⭐):CI/CDを完成させる - CIパイプラインにはlint, テスト, セキュリティチェック, ビルドを含め, CDパイプラインは
v*タグ作成時にイメージをGHCRにプッシュしSSHでサーバーにデプロイし, GitHub Secretsでキーを管理してください。ヒント:docker/login-action+appleboy/ssh-action+${{ secrets.XXX }}
---|



