サイトアイコン IT & ライフハックブログ|学びと実践のためのアイデア集

FastAPI×GitHub ActionsのCI/CD入門:テスト、Dockerビルド、Kubernetesデプロイ

green snake

Photo by Pixabay on Pexels.com

FastAPIアプリのテスト、Dockerイメージの作成、Kubernetesへの反映を手作業で続けると、実行漏れや手順のばらつきが起きやすくなります。

CI(継続的インテグレーション)は変更のたびにテストや静的解析を実行する仕組みで、CD(継続的デリバリーまたは継続的デプロイ)は検証済みの成果物を環境へ届ける仕組みです。

この記事では、Pull Requestでテストし、mainブランチへの反映後にコンテナを作り、ステージングへ配布する流れを組み立てます。

本番への反映には承認を残し、異常時にはコミットSHAで識別した以前のイメージへ戻せる構成を目指します。

CI/CDパイプラインの全体像

最初に、イベント、処理、成果物の対応を決めます。

きっかけ 実行する処理 得られる結果
Pull Request Lint、型チェック、テスト mainへ統合できる状態かを確認
mainへのpush テスト、イメージ作成、GHCRへの登録 コミットSHAで識別できるコンテナイメージ
ビルド成功後 ステージングへ自動デプロイ 本番前に動作を確認できる環境
手動起動と承認後 本番へデプロイ 承認履歴を伴うリリース

needsでジョブの依存関係を結ぶと、テストに失敗した変更はビルドへ進まず、ビルドに失敗した変更はデプロイへ進みません。

ステージングと本番では同じSHAタグのイメージを使い、環境ごとの差は設定と認証情報に限定すると、どの成果物を検証して公開したかを追いやすくなります。

実装前にリポジトリの前提を揃える

ワークフローを書く前に、ローカルで再現できるテストコマンド、Dockerfile、Kubernetesマニフェストを用意します。

fastapi-app/
├─ app/
│  ├─ main.py
│  └─ routers/
├─ tests/
├─ k8s/
│  ├─ deployment.yaml
│  ├─ service.yaml
│  └─ ingress.yaml
├─ Dockerfile
├─ requirements.txt
├─ requirements-dev.txt
└─ .github/
   └─ workflows/
      └─ ci-cd.yaml

アプリが大きくなり、どこへ設定やテストを置くか迷う場合は、FastAPIのプロジェクト構成を保守しやすくする考え方も参考になります。

ローカルとCIで同じコマンドを使う

CI専用の手順を増やすと、失敗を手元で再現しにくくなります。

次のように、開発者の端末でもCIでも同じコマンドを実行できる状態にします。

ruff check .
mypy app
pytest -q

プロジェクトで使わないツールまで導入する必要はありません。

まずは既存のテストを確実に実行し、必要になった時点で静的解析や統合テストを足します。

Pull RequestでCIを実行する

GitHub Actionsのワークフローは、リポジトリの.github/workflowsディレクトリにYAMLファイルとして置きます。

次の例は、mainへのPull Requestとpushで同じテストを実行する最小構成です。

name: CI/CD for FastAPI

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

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

      - name: Lint and test
        run: |
          ruff check .
          mypy app
          pytest -q

usesに指定するアクションのメジャーバージョンとPythonのバージョンは例です。

導入時には、各アクションの公式リリース情報とアプリの対応バージョンを確認して固定します。

テスト後にDockerイメージをGHCRへ登録する

コンテナレジストリは、作成したコンテナイメージを保存し、デプロイ先から取得できるようにする保管場所です。

ここではGitHub Container Registry(GHCR)を使い、latestとコミットSHAの二つのタグを付けます。

latestは現在の候補を見つける用途には便利ですが、復旧先を一意に指定できないため、デプロイにはSHAタグを使います。

Dockerfileの設計や実行プロセスを先に確認したい場合は、FastAPIをDockerで本番運用する際の構成を参照してください。

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    # 前節のtestジョブ

  build-and-push:
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

REGISTRYとIMAGE_NAMEをワークフロー全体のenvに置くのは、後続のデプロイジョブでも同じ値を使うためです。

GITHUB_TOKENでGHCRへ登録する場合も、packages: writeの権限と、リポジトリやOrganization側のPackage設定を確認します。

Kubernetesへステージングデプロイする

ここでは、GitHub Actionsからクラスタへ接続してkubectlを実行するpush型のデプロイを使います。

Kubernetesマニフェストのimage:にはAPP_IMAGEという置換用の値を置き、対象の行だけをSHAタグ付きイメージへ差し替えます。

すべてのimage:行を一括置換すると、サイドカーなど別のコンテナまで変えるおそれがあるためです。

  deploy-staging:
    needs: build-and-push
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://stg.example.com

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up kubectl
        uses: azure/setup-kubectl@v4
        with:
          version: ${{ vars.KUBECTL_VERSION }}

      - name: Configure kubeconfig
        env:
          KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG_STAGING }}
        run: |
          printf '%s' "$KUBECONFIG_CONTENT" > kubeconfig
          chmod 600 kubeconfig
          echo "KUBECONFIG=$PWD/kubeconfig" >> "$GITHUB_ENV"

      - name: Render and apply manifests
        run: |
          IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
          sed -i "s|APP_IMAGE|${IMAGE}|g" k8s/deployment.yaml
          kubectl apply -f k8s/configmap.yaml
          kubectl apply -f k8s/deployment.yaml
          kubectl apply -f k8s/service.yaml
          kubectl apply -f k8s/ingress.yaml
          kubectl rollout status deployment/fastapi-app

      - name: Remove temporary credentials
        if: always()
        run: rm -f kubeconfig

KUBECTL_VERSIONには、利用するクラスタと互換性を確認したkubectlのバージョンを設定します。

この例にSecretマニフェストの適用を含めていないのは、平文の認証情報をリポジトリへ置かないためです。

Secretはクラスタと組織の運用に合う方法で事前に用意し、ワークフローのログへ値を出力しないようにします。

Deployment、Service、Ingress、HPAの役割を確認する場合は、FastAPIをKubernetesへデプロイする手順で補えます。

ステージングと本番を分ける

GitHub Environmentsを使うと、stagingとproductionに別々のSecrets、変数、保護ルールを割り当てられます。

項目 ステージング 本番
起動条件 mainのビルド成功後 workflow_dispatchなどの手動起動
認証情報 ステージング専用 本番専用
承認 チームの方針に応じて設定 利用可能ならRequired reviewersを設定
デプロイ対象 SHAタグ付きイメージ ステージングで確認した同じSHAタグ

Required reviewersなど一部の保護機能は、GitHubのプランやリポジトリの公開範囲によって利用条件が異なります。

本番フローを決める前に、対象リポジトリで選べる保護ルールを確認してください。

Secretsを扱うときの確認事項

Secretsに保存した値でも、ワークフローが明示的に出力すれば漏えいする可能性があります。

Pull Request、依存関係、Secretsをまとめて見直す場合は、GitHub運用における三層のセキュリティ対策も確認してください。

テストを実行するタイミング

すべてのテストを毎回実行するより、失敗を早く知らせるテストと時間のかかるテストを分けるほうが運用しやすくなります。

テスト 確認する内容 実行タイミングの例
Lintと型チェック 構文、規約、型の不整合 すべてのPull Request
単体テスト 関数やクラス単位の振る舞い すべてのPull Request
APIテスト 主要エンドポイントの入出力 Pull Requestとmain
統合テスト DBや外部コンポーネントを含む連携 main、または定期実行

どのテストをどの段階で必須にするかは、実行時間、障害時の影響、変更頻度に合わせて決めます。

ロールバックを手順として用意する

ロールバックは「以前のコードへ戻す」という曖昧な操作ではなく、正常動作を確認済みのSHAタグを指定して再デプロイする手順にします。

  1. 直前に正常稼働していたイメージのSHAタグを特定する。
  2. Deploymentが参照するイメージをそのタグへ変更する。
  3. 反映完了を確認し、アプリのヘルスチェックと主要な操作を検証する。
  4. 障害を起こした変更は、原因を修正して通常のCI/CD経路から再リリースする。

データベースの変更を伴うリリースでは、コンテナイメージを戻すだけでデータまで元に戻るとは限りません。

移行手順、後方互換性、復旧方法をアプリのリリース手順と一緒に確認します。

よくある失敗と最初の確認先

症状 確認する場所 見直す内容
CIが長時間終わらない 各ジョブの実行時間 速いテストと統合テストの分離、独立ジョブの並列化
本番だけ起動しない 環境変数とSecretの差分 環境ごとの設定一覧と必須値
GHCRへpushできない ジョブのpermissions packages: writeとPackage設定
別のコンテナまで更新された マニフェストの置換処理 置換対象をAPP_IMAGEなどの固有値へ限定
失敗に気付くのが遅い デプロイ結果の確認 kubectl rollout statusと通知経路
YAMLの修正が怖い 一つのファイルに集まった責務 CI、本番デプロイ、ロールバックのワークフロー分割

導入は小さな単位で進める

  1. ローカルでテストコマンドを統一する。
  2. Pull RequestでテストするCIを導入する。
  3. mainでSHAタグ付きイメージを作り、GHCRへ登録する。
  4. ステージングへのデプロイと完了確認を自動化する。
  5. 本番用Environmentに承認と専用Secretsを設定する。
  6. 既知のSHAタグへ戻す復旧手順を試し、記録する。

最初の実装範囲は、Pull Requestのテストまでで十分です。

CIが安定してからビルド、ステージング、本番承認、復旧の順に足すと、失敗した場所と原因を切り分けやすくなります。

公式ドキュメント

アクション、ランタイム、kubectlのバージョンや利用条件は変わるため、実装時には公式ドキュメントと各アクションの配布元を確認してください。

この記事に関連する株式会社greedenの取り組み

CI/CDは、ツールを導入するだけでなく、テスト、承認、復旧の流れを運用に合わせて設計してこそ定着します。株式会社greedenは、要件整理から設計、実装、テスト、リリース、保守運用まで、Webシステム開発を一貫して支援します。

モバイルバージョンを終了