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に保存した値でも、ワークフローが明示的に出力すれば漏えいする可能性があります。
- Secretを
echoで表示せず、Secretを含む処理でset -xを使わない。 - ステージング用と本番用の認証情報を分け、各ジョブへ必要最小限の値だけ渡す。
- 本番認証情報を、信頼していないPull Requestのコードを実行するジョブへ渡さない。
- kubeconfigや平文のSecretマニフェストをリポジトリへコミットしない。
- 一時ファイルは処理の成否にかかわらず削除する。
Pull Request、依存関係、Secretsをまとめて見直す場合は、GitHub運用における三層のセキュリティ対策も確認してください。
テストを実行するタイミング
すべてのテストを毎回実行するより、失敗を早く知らせるテストと時間のかかるテストを分けるほうが運用しやすくなります。
| テスト | 確認する内容 | 実行タイミングの例 |
|---|---|---|
| Lintと型チェック | 構文、規約、型の不整合 | すべてのPull Request |
| 単体テスト | 関数やクラス単位の振る舞い | すべてのPull Request |
| APIテスト | 主要エンドポイントの入出力 | Pull Requestとmain |
| 統合テスト | DBや外部コンポーネントを含む連携 | main、または定期実行 |
どのテストをどの段階で必須にするかは、実行時間、障害時の影響、変更頻度に合わせて決めます。
ロールバックを手順として用意する
ロールバックは「以前のコードへ戻す」という曖昧な操作ではなく、正常動作を確認済みのSHAタグを指定して再デプロイする手順にします。
- 直前に正常稼働していたイメージのSHAタグを特定する。
- Deploymentが参照するイメージをそのタグへ変更する。
- 反映完了を確認し、アプリのヘルスチェックと主要な操作を検証する。
- 障害を起こした変更は、原因を修正して通常のCI/CD経路から再リリースする。
データベースの変更を伴うリリースでは、コンテナイメージを戻すだけでデータまで元に戻るとは限りません。
移行手順、後方互換性、復旧方法をアプリのリリース手順と一緒に確認します。
よくある失敗と最初の確認先
| 症状 | 確認する場所 | 見直す内容 |
|---|---|---|
| CIが長時間終わらない | 各ジョブの実行時間 | 速いテストと統合テストの分離、独立ジョブの並列化 |
| 本番だけ起動しない | 環境変数とSecretの差分 | 環境ごとの設定一覧と必須値 |
| GHCRへpushできない | ジョブのpermissions | packages: writeとPackage設定 |
| 別のコンテナまで更新された | マニフェストの置換処理 | 置換対象をAPP_IMAGEなどの固有値へ限定 |
| 失敗に気付くのが遅い | デプロイ結果の確認 | kubectl rollout statusと通知経路 |
| YAMLの修正が怖い | 一つのファイルに集まった責務 | CI、本番デプロイ、ロールバックのワークフロー分割 |
導入は小さな単位で進める
- ローカルでテストコマンドを統一する。
- Pull RequestでテストするCIを導入する。
- mainでSHAタグ付きイメージを作り、GHCRへ登録する。
- ステージングへのデプロイと完了確認を自動化する。
- 本番用Environmentに承認と専用Secretsを設定する。
- 既知のSHAタグへ戻す復旧手順を試し、記録する。
最初の実装範囲は、Pull Requestのテストまでで十分です。
CIが安定してからビルド、ステージング、本番承認、復旧の順に足すと、失敗した場所と原因を切り分けやすくなります。
公式ドキュメント
- GitHub Actions Documentation
- Workflow syntax for GitHub Actions
- GitHub Container Registryガイド
- FastAPIのDockerデプロイ
- GitHub ActionsからGKEへデプロイする方法
アクション、ランタイム、kubectlのバージョンや利用条件は変わるため、実装時には公式ドキュメントと各アクションの配布元を確認してください。
この記事に関連する株式会社greedenの取り組み
CI/CDは、ツールを導入するだけでなく、テスト、承認、復旧の流れを運用に合わせて設計してこそ定着します。株式会社greedenは、要件整理から設計、実装、テスト、リリース、保守運用まで、Webシステム開発を一貫して支援します。
