GitHub Actions & Google CloudでつくるかんたんなCI/CD
こんにちは
先日ハッカソンで作成したものをガチでサービス化するためにインフラ経験ゼロの僕が,クラウドサービスを使ってCI/CDを整えてみました.GCPこれから触るよって人にぜひ読んでもらえたらと思います.
そもそもなんでやるの?
これに関しては,現在開発している成果物を「みんなに使ってもらいたい」,「安定した基盤の上で動かしたい」という理由があって今回Google CloudとActionsを使ってCI/CDを組んでみました.
まとめると以下のとおりです.
- サービスを「公開・リリース」して「定期的に更新」したい
- GitHubのmainブランチにマージしたら自動でアプリがアップデートされる
- Gemini APIなど、有料API利用もあるので 構成は最小限かつ柔軟に
- 開発フェーズが終わってから本格運用に備える形でもOK!
※ これを活用した制作物はこれ↓(宣伝)
Google Cloudを選んだ理由
比較項目 | AWS | Google Cloud |
---|---|---|
利用企業 | 多い | 少なめ |
難易度 | 高(ツールが多い) | 低(シンプル) |
無料枠 | 少ない | 多い(Cloud Run など) |
ドキュメント | 多い(日本語も豊富) | 少なめ |
自由度 | 高(設計の幅が広い) | 低〜中 |
コスト感 | 上がりやすい | 抑えやすい |
結論:Google Cloudにして良かった。
今はプロトタイプ開発フェーズなので、リリース・運用の経験値獲得が最優先!
今回作る全体構成図・技術構成
開発者 → GitHub Push(PR作成)
↓
[GitHub Actions - test.yml]
└── pytest(tests/unit)
↓
✅ 通過 → Merge可
❌ 失敗 → Merge 不可
mainブランチにマージ
↓
[GitHub Actions - deploy.yml]
├── Docker build
├── integrationテスト
└── Cloud Build → Cloud Runへ Deploy
↓
[Cloud Run] FastAPI API
⇄ [Cloud Firestore]
今回は,mainブランチにマージしたら早速デプロイするようにしておりますが,僕らの環境では dev
ブランチとmain
ブランチを用意して,それぞれにマージしたらstg
環境,prod
環境にデプロイするように作成しました.
また,しれっと「Pytest」「FastAPI」などと書いてありますが今回は以下の技術スタックでローカル環境を構築 → デプロイ をやってみます.
技術 | 備考 |
---|---|
Python | 書きやすい!簡単! |
FastAPI | PythonのWebフレームワーク |
Firestore-emulator | Firestoreをローカルで動かせるやつ |
やっていく
今回は,実際にAPIを作ってそれをデプロイするといった具合でやってみます.
今回作るAPIは,人材紹介サービスのユーザープロフィール情報管理APIとしてみましょう.
できることとしては
- ユーザー情報のCRUD処理
- 必須カラム(名前,年齢,性別,職業,技術経験,自己紹介文)
- 非必須カラム(趣味タグ,資格,希望職種,ポートフォリオ)
環境構築
Google Cloud関連を用意
Google Cloudの環境が必要なので,以下からアカウント登録&プロジェクトを作りましょう!
作成したプロジェクトを選択してから,gcloud-cli
を入れます.これはローカルでGoogleCloudのプロダクトを操作するために必要なCLIツールです.以下からセットアップできます.
そして
gcloud emulators firestore start
で動作が確認できたらOKです.
GitHub
完成版をGitHubに公開してあります!
ローカル環境で構築
前提条件
- Python 3.10がローカルに入っていること
これだけです.ではまず,作業ディレクトリを作ってPythonの仮想環境を作っちゃいましょう!
mkdir (任意)
python3 -m venv venv
source venv/bin/activate
次に,必要なライブラリを入れていきます.
touch requirements.txt
fastapi
uvicorn
pydantic
google-cloud-firestore
dotenv
pip install -r requirements.txt
動作確認
これで整ったので,確認していきます.
まず,Firestore emulatorを動かします.
gcloud emulators firestore start --host-port=localhost:8090
次に以下のPythonファイルを作成,実行します.
import os
from dotenv import load_dotenv
from google.cloud import firestore
# .env の読み込み
load_dotenv()
# google cloudに設定したproject名とローカルホスト先
project_id = os.getenv("FIRESTORE_PROJECT_ID")
emulator_host = os.getenv("FIRESTORE_EMULATOR_HOST")
if not emulator_host:
raise RuntimeError("FIRESTORE_EMULATOR_HOST is not set in .env")
print(f"Connecting to Firestore Emulator at {emulator_host}, project={project_id}")
db = firestore.Client(project=project_id)
# --- 書き込みテスト ---
doc_ref = db.collection("test_collection").document("test_doc")
doc_ref.set({
"message": "Hello Firestore!",
"status": "ok"
})
print("✅ Document written to Firestore Emulator.")
# --- 読み込みテスト ---
doc = doc_ref.get()
if doc.exists:
print("✅ Document read from Firestore Emulator:")
print(doc.to_dict())
else:
print("❌ Failed to read document from Firestore Emulator.")
実行結果
(venv) ╭─kei@Keis-MacBook-Pro ~/Documents/sample_fastapi_cicd
╰─$ /Users/kei/Documents/sample_fastapi_cicd/venv/bin/python /Users/kei/Documents/sample_fastapi_cicd/test.py 1 ↵
Connecting to Firestore Emulator at localhost:8090, project=sample-cicd
✅ Document written to Firestore Emulator.
✅ Document read from Firestore Emulator:
{'status': 'ok', 'message': 'Hello Firestore!'}
ここまでできたらOKです.
API構築
さて,ここからAPIを構築していきます.
今回は,人材紹介サービスのユーザープロフィール情報管理APIを作ってみます.
pytestやCI/CDを組みこむので少し本格的なフォルダ構成でやってみます.
ディレクトリ&ソースコード
├── app
│ ├── api
│ │ └── routers
│ │ └── users.py
│ ├── domain
│ │ └── schema.py
│ ├── infra
│ │ ├── firestore_client.py
│ │ └── repository.py
│ ├── main.py
│ └── usecase
│ └── user_usecase.py
├── requirements.txt
├── test.py
└── tests
├── integration
│ └── test_users_api.py
└── unit
└── test_usecases.py
ソースコードはGitHubを貼っておくのでここのやつを使ってください.
実際にlocalhost:8000/docs
にアクセスしてSwaggerが出たらOKです.
いくつかのAPIにリクエストを送って反応を確認してください.
テストを実行する
今回は,CIの段階でPytest
を稼働させています.つまり,PullRequestを出すごとにActions側で/tests
のテストが全て実行されるようにします.
用意したテストは
- unitテスト(全ユースケース)
- integrationテスト(API丸ごとテスト)
に簡単に用意しておきました.これをルートディレクトリで
PYTHONPATH=. pytest -q
とやって通ることを確認してください.
今回はちょっとしっかりめなAPIを構築したため,それぞれのユースケースに応じて動作確認することをいちいち手動で確認してられないので,テストを用いてしっかりと確認してから本番環境にあげられるようにするためです!
CI構築
それでは,CIを構築していきます.ActionsにPRがあるたびにテストしてほしいので,以下のようなファイルを作成・レポジトリに上げます.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
CI:
name: check-CI
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
PYTHONPATH=. pytest -q --disable-warnings --maxfail=1
そうすると勝手にレポジトリでこのように動きます.
そして,絶対にGitHubのブランチルールを以下の記事などを参考にテストが通らないとマージできないようにしてください.
試しに,ブランチを切ってテストが落ちるようなことをしてみると...
PRを出してもマージできないですね.これでOKです.
CD構築
長いですね....お待たせしました本番です.
ここでやりたいことは
- 自動で本番環境にデプロイする仕組みを作る
- いつアクセスしても動くようにする
この2つになります.
まず,GCP上で構築したい仕組みの図を示します.
フローとしては
- mainブランチにpushされた内容を検知
- Cloud BuildでDockerイメージを構築
- Artifact Registryに完成したイメージを保存
- Cloud Run・Firestoreで完成したイメージを元に動かす.
といった具合です.
CloudRun構築まで
こちらを参考に行っていきます.
以下3つのAPIを有効化してください.- Cloud Build API
- Compute Engine API
- Artifact Registry API
次に,サービスアカウントに権限を与えていきます.このアカウントは,僕らがGoogle Cloudにアクセスしているアカウントではなく,デフォルトで設定されているGoogle Cloudの処理を実行してくれるアカウントです.
# 各自PCのCLI or gcloud cliで実行
gcloud config set project sample-cicd
# 権限付与
# storage.objectUserに追加
gcloud projects add-iam-policy-binding sample-cicd-468503 \
--member=serviceAccount:$(gcloud projects describe sample-cicd-468503 \
--format="value(projectNumber)")-compute@developer.gserviceaccount.com \
--role="roles/storage.objectUser"
# Artifact Registryに書き込み権限
gcloud projects add-iam-policy-binding sample-cicd-468503 \
--member=serviceAccount:$(gcloud projects describe sample-cicd-468503 \
--format="value(projectNumber)")-compute@developer.gserviceaccount.com \
--role="roles/artifactregistry.writer"
# loggingに書き込み権限
gcloud projects add-iam-policy-binding sample-cicd-468503 \
--member=serviceAccount:$(gcloud projects describe sample-cicd-468503 \
--format="value(projectNumber)")-compute@developer.gserviceaccount.com \
--role="roles/logging.logWriter"
gcloud iam service-accounts add-iam-policy-binding $(gcloud projects describe sample-cicd-468503 \
--format="value(projectNumber)")-compute@developer.gserviceaccount.com \
--member=serviceAccount:$(gcloud projects describe sample-cicd-468503 \
--format="value(projectNumber)")-compute@developer.gserviceaccount.com \
--role="roles/iam.serviceAccountUser" \
--project=sample-cicd-468503
# Firestoreの有効化とDB作成
gcloud services enable firestore.googleapis.com && \
gcloud firestore databases create \
--region=asia-northeast1 \
--type=firestore-native
そして,Cloud BuildはDockerfileからイメージを構築し,Cloud Runで動かすという流れになるため,Dockerfileをローカルで作成します.(配布レポジトリではすでに作成済)
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
次に,Dockerイメージを保存・Cloud Runに渡すためにArtifact Registryをセットアップします.(うまくいかないときはGUIで試してもOKです!)
gcloud artifacts repositories create sample-fastapi-cicd-docker --repository-format=docker \
--location=asia-northeast1 --description="Docker repository for fastapi"
そして最後に,CloudBuildのためにcloudbuild.yml
を作成します.
steps:
# Install dependencies
- name: python
entrypoint: pip
args: ["install", "-r", "requirements.txt", "--user"]
# Docker Build
- name: "gcr.io/cloud-builders/docker"
args:
[
"build",
"-t",
"asia-northeast1-docker.pkg.dev/${PROJECT_ID}/sample-fastapi-cicd-docker/backend:${SHORT_SHA}",
".",
]
# Docker push to Google Artifact Registry
- name: "gcr.io/cloud-builders/docker"
args:
[
"push",
"asia-northeast1-docker.pkg.dev/${PROJECT_ID}/sample-fastapi-cicd-docker/backend:${SHORT_SHA}",
]
# Deploy to Cloud Run
- name: google/cloud-sdk
args:
[
"gcloud",
"run",
"deploy",
"sample-cicd",
"--image=asia-northeast1-docker.pkg.dev/${PROJECT_ID}/sample-fastapi-cicd-docker/backend:${SHORT_SHA}",
"--region",
"asia-northeast1",
"--platform",
"managed",
"--allow-unauthenticated",
]
options:
logging: CLOUD_LOGGING_ONLY
これで完成しました!
そしたら「Cloud Build」にて以下を行ってください.
まず,サービスアカウントの権限を与えましょう.
こうしないとデプロイできなくなります!
次に,mainにマージしたらデプロイされるように構成します.
これで,mainブランチにマージされたらデプロイが実行されます.
CloudBuildの履歴が
こんな感じに成功になっていればOKです.
次に,CloudRunにアクセスして,デプロイしたコンテナ -> セキュリティ -> 認証 から「公開アクセスを許可する」を有効にしたらURLからアクセスできます!
/health
にアクセスして
完璧です!
さいごに
ここまで読んでくださりありがとうございました!
初めてマネージドサービスを複数用いて構築しましたが,かなり苦労したのでこの記事を読んで困らない人が増えれば嬉しいです!
Discussion