Github Actions + Cloud Deploy でモダンな CI/CD 実装
先日の公式ブログで Github Actions と Google Cloud Deploy の連携が可能になったことがわかりました。
GKE や Cloud Run に対する CI/CD はいくつか選択肢があると思います、Cloud Build を利用したものなど。その上で Github Actions を CI で、Cloud Deploy を CD でシームレスに利用できるということは CI 周りは Github Actions がデファクトになりつつあるって感じでしょうか。 Workload Identity によってクレデンシャルキーなしで連携できるようになったりとセキュアな仕組みもありますし。
ということで、Nginx の Cloud Run サービスに対してのビルド~デプロイまでを Github Actions + Cloud Deploy で実装してみようと思います。
=== 2024.03.18 追記 ===
Cloud Deploy についてはこちらの記事で改めて触れてみました。GA となった便利な機能や自身がハマったポイントについても書いています。Cloud Deploy について、全体像を見ながら知ってみたい という方はぜひお読みください!
Overview
Github Actions と Cloud Deploy を活用した CI/CD について検証
- GKE や Cloud 向けのコンテナ周りの CI/CD について Cloud Deploy を Github Actions からシームレスに呼び出せることができました
- Github Actions に Cloud Deploy のリリース作成がラッピングされたアクションである
google-github-actions/create-cloud-deploy-release
によって、適切な環境変数を渡すだけで連携ができました - Cloud Deploy は 継続的デリバリーに特化したサービス となっていて、複数環境にデプロイしてテストした時やテスト結果を元に次の環境に移りたい時にデリバリーパイプラインを構築しておくことで便利に利用できることがわかりました
キーワード
CI/CD の実装
Nginx の Cloud Run サービスへのビルド~デプロイまでを題材に手順をまとめていきたいと思います。
最低限の設定を手動
まずは手動で Cloud Run サービスのデプロイまでをやってみます。
Dockerfile は下記で用意します。
FROM nginx:latest
COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./index.html /var/www/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>nginx test</title>
</head>
<body>
<h1>Hello, Nginx!</h1>
</body>
</html>
server {
listen 8080 default_server;
server_name localhost;
location / {
root /var/www;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
}
ビルドして Artifact Registry に格納します。
# Artifact Registry の作成
gcloud artifacts repositories create nginx-repo --location=asia-northeast1 --repository-format=docker
# Dockerfile のビルド
docker build . -t asia-northeast1-docker.pkg.dev/{project_id}/nginx-repo/test
# Artifact Registry の認証
gcloud auth configure-docker asia-northeast1-docker.pkg.dev
# Artifact Regisry への格納
docker push asia-northeast1-docker.pkg.dev/{project_id}/nginx-repo/test
Cloud Run サービスにデプロイします。
gcloud run deploy nginx-test \
--image asia-northeast1-docker.pkg.dev/{project_id}/nginx-repo/test \
--region asia-northeast1
これでデプロイまでは最低限の手順でできました。これを Github Actions と Cloud Deploy を利用して自動化&高度化を実装していきます。
継続的デプロイメント:Cloud Deploy
Cloud Deploy について公開資料から抜粋します。
クイックスタートに沿って実装していきます。
clouddeploy.yaml の作成
デリバリーパイプラインの大枠を作成します。YAML 形式で下記のように記述します。
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
name: nginx-test
description: main application pipeline
serialPipeline:
stages:
- targetId: run-dev
profiles: [dev]
- targetId: run-prod
profiles: [prod]
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: run-dev
description: Cloud Run development service
run:
location: projects/{project_id}/locations/asia-northeast1
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: run-prod
description: Cloud Run production service
run:
location: projects/{project_id}/locations/asia-northeast1
kind: DeliveryPipeline
の serialPipeline
の stage
にデプロイしたいステップを記述します。今回は run-dev
と run-prod
を定義します。
これらのステージに kind: Target
でデプロイされるロケーションを設定しています。
skaffold.yaml と run-dev/prod.yaml の作成
Skaffold プロファイルを利用して Cloud Run サービスを定義ファイルと紐付けを行います。
apiVersion: skaffold/v3alpha1
kind: Config
metadata:
name: cloud-run-nginx
profiles:
- name: dev
manifests:
rawYaml:
- run-dev.yaml
- name: prod
manifests:
rawYaml:
- run-prod.yaml
deploy:
cloudrun: {}
clouddeploy.yaml
の profiles
と skaffold.yaml
の profiles
で紐付きます。さらに rawYaml
で設定した YAML ファイルを下記で作成します。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: nginx-dev
spec:
template:
spec:
containers:
- image: asia-northeast1-docker.pkg.dev/{project_id}/nginx-repo/test
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: nginx-prod
spec:
template:
spec:
containers:
- image: asia-northeast1-docker.pkg.dev/{project_id}/nginx-repo/test
これらによって各ステージにデプロイされるロケーションとイメージを紐づけることができました。
Cloud Deploy サービスに登録
下記のコマンドでパイプラインとターゲットを Cloud Deploy に登録します。
gcloud deploy apply \
--file=clouddeploy.yaml \
--region=asia-northeast1 \
--project={project_id}
Cloud Deply のコンソール画面に行くと nginx-test
という名でデリバリーパイプラインが作成されています。
リリースの作成
パイプラインを起動させて実際にデプロイを行います。
下記のコマンドでリリースを作成してパイプラインに流し込みます。
project, region, delivery-pipeline のパラメータを設定して実行します。
gcloud deploy releases create test-release-001 \
--project={project_id}
--region=asia-northeast1
--delivery-pipeline=nginx-test
test-relese-001
が実行されて run-dev
がグリーンになります。run-dev.yaml
で定義した Cloud Run サービス名でちゃんとデプロイされています。
Cloud Deploy の特徴でもある前段のステージでデプロイができテストが完了したら、Promote
することによって次のステージに移すことができます。同様に定義したサービス名でデプロイ完了です。
パイプラインを一度定義すればパラメータを変えて似たようなコマンドを何回も打つことがなくなるし、順々や並行に特定のステージにデプロイできるためデプロイの安定性も高められると感じました。
継続的インテグレーション:Github Actions
冒頭で紹介した公式ブログにある通り、google-github-actions/create-cloud-deploy-release@v0
がポイントみたいですね。
Cloud Deploy でデリバリーパイプラインを作成しておき、上記のアクションを用いることでリリースを流しこんでくれるイメージでしょうか。
サンプルの Github Actions のワークフローを見ていきたいと思います。
環境設定
ワークフロー起動のトリガーと環境変数設定部分です。特に特別な設定はなさそうです。
name: Build app and create a release in Cloud Deploy
on:
push:
branches:
- $default_branch
env:
PROJECT_ID: YOUR_PROJECT_ID # TODO: update Google Cloud project id
GAR_LOCATION: YOUR_GAR_LOCATION # TODO: update Artifact Registry location
REGION: YOUR_APP_REGION # TODO: update Cloud Run service region
APP: app
ビルドフェーズ
記事に詳細な解説があるので、こちらも特別付け加える点はないですね。
google-github-actions/auth@v1
で Workload Identity との連携設定がなされていてクレデンシャルキーなしで Github Actions から Google Cloud リソースへのアクセスが可能です。
あとは、手動で行った手順が記述されています。
jobs:
deploy:
# Add 'id-token' with the intended permissions for workload identity federation
permissions:
contents: 'read'
id-token: 'write'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: 'actions/checkout@v3'
- name: 'Google auth'
id: 'auth'
uses: 'google-github-actions/auth@v1'
with:
workload_identity_provider: '${{ secrets.WIF_PROVIDER }}'
service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
with:
project_id: '${{ env.PROJECT_ID }}'
- name: 'Docker auth'
run: |-
gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev
- name: 'Build and push container'
run: |-
docker build -t "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.APP }}/${{ env.APP }}:${{ github.sha }}" ./app
docker push "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.APP }}/${{ env.APP }}:${{ github.sha }}"
デプロイフェーズ
サンプルにはパイプラインの作成も工程として入っていましたが、ワークフローが起動されるたびにデリバリーパイプラインが構築されるのも違和感がありますね。
公式ブログにある通り、デリバリーパイプラインは Terraform などの IaC で管理するのが良さそうです。
このワークフロー例では、デリバリー パイプラインのサンプルを作成しましたが、実際には、アプリケーション ビルド パイプラインの外でパイプラインやターゲットを管理することをおすすめします。
そして、最後にリリースの作成を下記の記述でできるようです。
サンプルには images の設定項目がありますが、上述したリリース作成のコマンド時にはイメージの指定がなくても問題がなかったのでなくても良さそうです。
実際には skaffold.yaml
で設定した run-dev/prod.yaml
でイメージの指定していていますし。
- name: 'Create release name'
run: |-
echo "RELEASE_NAME=${{ env.APP }}-${GITHUB_SHA::7}-${GITHUB_RUN_NUMBER}" >> ${GITHUB_ENV}
- name: 'Create Cloud Deploy release'
id: 'release'
uses: 'google-github-actions/create-cloud-deploy-release@v0'
with:
delivery_pipeline: '${{ env.APP }}'
name: '${{ env.RELEASE_NAME }}'
region: '${{ env.REGION }}'
description: '${{ env.GITHUB_COMMIT_MSG }}'
skaffold_file: 'config/skaffold.yaml'
images: 'app=${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.APP }}/${{ env.APP }}:${{ github.sha }}'
- name: 'Report Cloud Deploy release'
run: |-
echo "Created release ${{ steps.release.outputs.name }} "
echo "Release link ${{ steps.release.outputs.link }} "
実際に試したワークフロー
Workload Identity の設定ができている前提で、手動での作業に合わせて下記を変更しました。
- リポジトリ名を
${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/nginx-repo/test
-
google-github-actions/create-cloud-deploy-release
のパラメータからimage
を削除
こちらで想定通りの挙動となりました。
jobs:
deploy:
permissions:
contents: 'read'
id-token: 'write'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: 'actions/checkout@v3'
- name: WorkloadIdentityAuth
id: WorkloadIdentityAuth
uses: google-github-actions/auth@v1
with:
workload_identity_provider: '${{ secrets.WIF_PROVIDER }}'
service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'
with:
project_id: '${{ env.PROJECT_ID }}'
- name: 'Docker auth'
run: |-
gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev
- name: 'Build and push container'
run: |-
docker build -t "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/nginx-repo/test:${{ github.sha }}" ./config
docker push "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/nginx-repo/test:${{ github.sha }}"
- name: 'Create release name'
run: |-
echo "RELEASE_NAME=${{ env.APP }}-${GITHUB_SHA::7}-${GITHUB_RUN_NUMBER}" >> ${GITHUB_ENV}
- name: 'Create Cloud Deploy release'
id: 'release'
uses: 'google-github-actions/create-cloud-deploy-release@v0'
with:
delivery_pipeline: '${{ env.APP }}'
name: '${{ env.RELEASE_NAME }}'
region: '${{ env.REGION }}'
description: '${{ env.GITHUB_COMMIT_MSG }}'
skaffold_file: 'config/skaffold.yaml'
- name: 'Report Cloud Deploy release'
run: |-
echo "Created release ${{ steps.release.outputs.name }} "
echo "Release link ${{ steps.release.outputs.link }} "
まとめ
GKE や Cloud Run 向けのコンテナ周りの CI/CD の実装として Github Actions + Cloud Deploy を試してみました。Cloud Deploy をあらかじめ設定できていれば、Github Actions からスムーズに呼び出せることがわかりました。
コンテナ周りの CI/CD の選択肢は色々ある中で CD として Cloud Deploy を中心にした時は CI を Github Actions を寄せやすくなりましたね。
さいごに
AWS と Google Cloud で構築したデータ基盤の開発・運用に携わっているデータエンジニアです。5 年くらい携わっていて、この業務がきっかけで Google Cloud が好きになりました。
Google Cloud 関連の情報を発信をしています。
Discussion