Google Cloud Platform(GCP)のGoogle App Engine(GAE)にGitHub Actionsを使ってLaravelをデプロイします。
前提
- Cloud SDK等諸々インストール済み
- プロジェクトなど元々ある前提
- 手順のみで詳しい解説は省きます(参考リンク参照ください)
【重要】サービスアカウントキーを使った認証は非推奨
サービスアカウントキーを使った認証は非推奨であり、今後のリリースで削除となります。
ワークロードID連携(Workload Identity Federation)が推奨されます。
2021/10/27 GitHub Actions OpenID Connect (OIDC) サポート
GitHub Actions の OpenID Connect サポートについて
https://zenn.dev/miyajan/articles/github-actions-support-openid-connect
- GitHubがGitHub Actions ワークフローにOIDCトークンを導入
2021/12 GitHub Actions からのキーなしの認証の有効化
- Workload Identity 連携を使用して GitHub Actions から Google Cloud に認証可能
- サービスアカウントキーのエクスポートが不要
新しい GitHub Action – auth
認証周りは google-github-actions/auth
ライブラリに移行されました。
ワークロードID連携とは
https://cloud.google.com/blog/ja/products/identity-security/enabling-keyless-authentication-from-github-actions
https://cloud.google.com/iam/docs/workload-identity-federation?hl=ja
従来のサービスアカウントキー
- 従来、Google Cloud の外部で実行されているアプリケーションは、サービスアカウントキーを使用して Google Cloud リソースにアクセス
- サービスアカウントキーは非常に強力な認証情報
- 正しく管理しなければセキュリティ上のリスク
ワークロードID連携のメリット
- ワークロードID連携は、Identity and Access Management(IAM)を使用
- 外部IDに対して、サービスアカウントになりすます機能を含む IAM ロールを付与
- 有効期間の短いアクセストークンを使用してリソースに直接アクセス
ワークロードプール
Workload Identity プールは、外部IDを編成および管理できます。
ワークロードプールのプロバイダ
Workload Identity プールのプロバイダは、Google Cloud と外部 ID プロバイダとの関係を表すエンティティです。
概要
- Google Cloudサービスアカウントを作成
- サービスアカウントにIAMポリシーを付与
- IAM Service Account Credentials APIを有効化
- Workload Identityプールを作成
- Workload Identityプロバイダを作成
- IAMポリシーバインディングを作成
- GitHub Actionsのワークフローを作成
- リポジトリのチェックアウト
- Google Cloud認証(ワークロードIDフェデレーション)
- Google Cloud SDKインストール
- PHPインストール
- Composerインストール
- Laravelの設定
- Google App Engineへデプロイ
- Cloud SQL Proxyインストール
- マイグレーションの実行
Cloud SDKのアップデート
$ gcloud components update
$ gcloud version
Google Cloud SDK 380.0.0
beta 2022.04.01
bq 2.0.74
core 2022.04.01
gsutil 5.8
今回はこのバージョンを使用します。
プロジェクトの一覧
$ gcloud projects list
環境変数を設定
誤入力防止のために環境変数を設定しておきます。
$ export PROJECT_ID="your-project-id"
$ export SERVICE_ACCOUNT_NAME="github-actions" # 任意のサービスアカウント名を付ける
$ export SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}"@"${PROJECT_ID}".iam.gserviceaccount.com
$ export WORKLOAD_IDENTITY_POOL_NAME="github-actions-pool" # 任意のWorkload Identityプール名を付ける
$ export WORKLOAD_IDENTITY_POOL_PROVIDER="github-actions-provider" # 任意のWorkload Identityプロバイダ名を付ける
$ export GITHUB_REPO="your-name/your-repository-name" # GitHubのユーザー名/リポジトリ名を設定する
※端末を閉じると環境変数が消えてしまうので、同じ端末で作業します。
プロジェクト切り替え
一応切り替えておきます。
$ gcloud config set project "${PROJECT_ID}"
サービスアカウントを作成
GitHub Actions上で利用するサービスアカウントを作成します。
$ gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
--project "${PROJECT_ID}" \
--display-name "${SERVICE_ACCOUNT_NAME}"
作成されたサービスアカウントを確認します。
$ gcloud iam service-accounts list --project "${PROJECT_ID}"
$ gcloud iam service-accounts describe "${SERVICE_ACCOUNT_EMAIL}" --project "${PROJECT_ID}"
サービスアカウントにIAMポリシーを付与
作成したサービスアカウントにIAMポリシーを付与します。
どんなIAMポリシーがあるかは公式ドキュメントを参照してください。
必要な権限はプロジェクトや構成によりますが、Laravelプロジェクトだと以下の権限が最低限必要でした。
- roles/iam.serviceAccountUser
- roles/appengine.deployer
- roles/appengine.serviceAdmin
- roles/storage.objectCreator
- roles/storage.admin
- roles/storage.objectAdmin
- roles/cloudbuild.builds.editor
- roles/cloudsql.client
- roles/iam.securityReviewer
必要な権限を洗い出したので割り当てていきます。
残念なことにIAMポリシーをまとめて割り当てるオプションはなかったので、一つずつコマンドを実行して権限を付与します。
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/iam.serviceAccountUser" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/appengine.deployer" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/appengine.serviceAdmin" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/storage.objectCreator" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/storage.admin" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/storage.objectAdmin" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/cloudbuild.builds.editor" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/cloudsql.client" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/iam.securityReviewer" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
IAMポリシーを付与できたか確認します。
$ gcloud projects get-iam-policy ${PROJECT_ID} \
--flatten="bindings[].members" \
--filter="bindings.members:${SERVICE_ACCOUNT_NAME}"
IAM Service Account Credentials APIを有効化
GitHub Actions上で一時的なサービスアカウントキーを生成するAPIを有効化します。
$ gcloud services enable iamcredentials.googleapis.com --project "${PROJECT_ID}"
$ gcloud services list --project "${PROJECT_ID}"
NAME TITLE
...
iam.googleapis.com Identity and Access Management (IAM) API
iam.googleapis.com
のサービスが表示されていればOK
Workload Identityプールを作成
$ gcloud iam workload-identity-pools create "${WORKLOAD_IDENTITY_POOL_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="${WORKLOAD_IDENTITY_POOL_NAME}"
Workload IdentityプールのIDを取得
$ gcloud iam workload-identity-pools describe "${WORKLOAD_IDENTITY_POOL_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--format="value(name)"
取得したプールIDを環境変数にセットします。
# 例
$ export WORKLOAD_IDENTITY_POOL_ID="projects/123456789/locations/global/workloadIdentityPools/github-actions-pool"
Workload Identityプロバイダを作成
Workload Identityプールの中にWorkload Identityプロバイダを作成します。
$ gcloud iam workload-identity-pools providers create-oidc "${WORKLOAD_IDENTITY_POOL_PROVIDER}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${WORKLOAD_IDENTITY_POOL_NAME}" \
--display-name="${WORKLOAD_IDENTITY_POOL_PROVIDER}" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
$ gcloud iam workload-identity-pools providers describe "${WORKLOAD_IDENTITY_POOL_PROVIDER}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${WORKLOAD_IDENTITY_POOL_NAME}"
attributeMapping:
attribute.actor: assertion.actor
attribute.repository: assertion.repository
google.subject: assertion.sub
displayName: github-actions-provider
name: projects/1234567890/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider
oidc:
issuerUri: https://token.actions.githubusercontent.com
state: ACTIVE
IAMポリシーバインディングを作成
IAMポリシーバインディングを作成します。
作成したサービスアカウントを使用できるGitHubリポジトリを紐づけます。
$ gcloud iam service-accounts add-iam-policy-binding "${SERVICE_ACCOUNT_EMAIL}" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${GITHUB_REPO}"
.gitignore
プロジェクトルートの .gitignore
ファイルに下記の行を追加します。
GitHub Actions上で生成されるサービスアカウントキーです。
gha-creds-*.json
GitHub Actionsのワークフローを作成
ワークフローのサンプルコードです。
name: Deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: main
- id: auth
uses: google-github-actions/auth@v0
with:
workload_identity_provider: YOUR_WORKLOAD_IDENTITY_POOL_ID
service_account: YOUR_SERVICE_ACCOUT_EMAIL
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v0
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 7.4
- name: Cache Vendor
id: cache
uses: actions/cache@v2
with:
path: ./src/vendor
key: ${{ runner.os }}-composer-${{ hashFiles('./src/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install Dependencies
working-directory: src
if: steps.cache.outputs.cache-hit != 'true'
run: composer install
- name: Laravel Settings
working-directory: src
run: |
cp .env.example .env
- name: PHP, Composer, Laravel Versions
working-directory: src
run: |
php --version
composer --version
php artisan --version
- name: Deploy to Google App Engine
env:
CI: true
PROJECT_ID: your-project-id
DB_PASSWORD: ${{ secrets.GCLOUD_DB_PASSWORD }}
working-directory: ./src
run: |
cp .env.example .env
cp secret.yaml.example secret.yaml
sed -i -e s/__DB_PASSWORD__/$DB_PASSWORD/ secret.yaml
gcloud app deploy app.yaml --quiet --no-cache --project ${PROJECT_ID}
- name: Install Cloud SQL Proxy
working-directory: src
run: |
wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
chmod +x cloud_sql_proxy
- name: Run Migration
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: YOUR_DATABASE_NAME
DB_USERNAME: YOUR_DATABASE_USER
DB_PASSWORD: ${{ secrets.GCLOUD_DB_PASSWORD }}
APP_ENV: staging
working-directory: src
run: |
./cloud_sql_proxy -instances=your-project-id:asia-northeast1:your-project-name=tcp:3306 &
php artisan config:clear
sleep 5 # cloud_sql_proxy waiting...
php artisan migrate --force
php artisan db:seed --force --class 'Database\Seeders\ProductionDatabaseSeeder'
やってることは大体下記の通りです。
- リポジトリのチェックアウト
- Google Cloud認証(ワークロードIDフェデレーション)
- Google Cloud SDKインストール
- PHPインストール
- Composerインストール
- Laravelの設定
- Google App Engineへデプロイ
- Cloud SQL Proxyインストール
- マイグレーションの実行
大事なところはGoogle Cloud認証のところです。
- id: auth
uses: google-github-actions/auth@v0
with:
workload_identity_provider: YOUR_WORKLOAD_IDENTITY_POOL_PROVIDER_ID
service_account: YOUR_SERVICE_ACCOUT_EMAIL
YOUR_WORKLOAD_IDENTITY_POOL_PROVIDER_ID
にはprojects/123456789/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider
といったプールプロバイダーIDを入れます。
補足
runtime: php74
instance_class: B1
basic_scaling:
max_instances: 2
idle_timeout: 3m
includes:
- secret.yaml
env_variables:
APP_ENV: production
APP_URL: https://example.com
APP_DEBUG: false
APP_KEY: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
APP_STORAGE: /tmp
VIEW_COMPILED_PATH: /tmp
LOG_CHANNEL: stdout
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: YOUR_DATABASE_NAME
DB_USERNAME: root
DB_SOCKET: YOUR_DATABASE_SOCKET
env_variables:
DB_PASSWORD: __DB_PASSWORD__
参考
Workload Identity連携について詳しく書かれています。
よくあるエラー集
Invalid value for "audience". This value should be the full resource name of the Identity Provider
Error: google-github-actions/setup-gcloud failed with: failed to execute command `gcloud --quiet auth login --cred-file /home/runner/work/your-repository-name/your-repository-name/gha-creds-9884ae770017c361.json`: ERROR: gcloud crashed (OAuthError): ('Error code invalid_request: Invalid value for "audience". This value should be the full resource name of the Identity Provider. See https://cloud.google.com/iam/docs/reference/sts/rest/v1/TopLevel/token for the list of possible formats.', '{"error":"invalid_request","error_description":"Invalid value for \\"audience\\". This value should be the full resource name of the Identity Provider. See https://cloud.google.com/iam/docs/reference/sts/rest/v1/TopLevel/token for the list of possible formats."}')
似た感じのIssue
https://github.com/google-github-actions/deploy-appengine/issues/247
- id: auth
uses: google-github-actions/auth@v0
with:
# workload_identity_provider: github-actions-provider # NG: プロバイダー名じゃなくプロバイダーIDを指定する
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider
workload_identity_provider
の指定ミスってると出る
ERROR: (gcloud.app.deploy) Permissions error fetching application [apps/project-id]. Please make sure that you have permission to view applications on the project and that github-actions@project-id.iam.gserviceaccount.com has the App Engine Deployer (roles/appengine.deployer) role.
Please make sure that you have permission to view applications on the project
Please make sure that you have permission to view applications on the project and that your-service-provider@your-project.iam.gserviceaccount.com has the App Engine Deployer (roles/appengine.deployer) role.
サービスアカウントにロールが付与されていないので付与する。
使用しているサービスによるのでエラーが出たら設定してあげる。
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/iam.serviceAccountUser" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"
The client is not authorized to make this request., notAuthorized
2022/04/14 08:36:48 current FDs rlimit set to 65536, wanted limit is 8500. Nothing to do here.
2022/04/14 08:36:48 using credential file for authentication; path="/home/runner/work/your-repository/your-repository/gha-creds-[26](https://github.com/your-name/your-repository/runs/6020943803?check_suite_focus=true#step:12:26)14f78da682c488.json"
Configuration cache cleared!
2022/04/14 08:36:48 errors parsing config:
googleapi: Error 403: The client is not authorized to make this request., notAuthorized
[2022-04-14 17:36:53] local.CRITICAL: SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = plus_insurance_staging and table_name = migrations and table_type = 'BASE TABLE') {"exception":"[object] (Illuminate\\Database\\QueryException(code: 2002): SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = your-database and table_name = migrations and table_type = 'BASE TABLE') at /home/runner/work/your-repository/your-repository/src/vendor/laravel/framework/src/Illuminate/Database/Connection.php:671)
[stacktrace]
#0 /home/runner/work/your-repository/your-repository/src/vendor/laravel/framework/src/Illuminate/Database/Connection.php(745): Illuminate\\Database\\Connection->runQueryCallback()
Cloud SQL Client (roles/cloudsql.client) ロールがないのが原因でした。
https://cloud.google.com/sql/docs/mysql/sql-proxy?hl=en#permissions
https://cloud.google.com/iam/docs/understanding-roles?hl=ja#cloud-sql-roles
The principal (user or service account) lacks IAM permission "cloudscheduler.locations.list" for the resource
ERROR: (gcloud.app.deploy) User [github-actions@your-project.iam.gserviceaccount.com] does not have permission to access projects instance [project-id] (or it may not exist): The principal (user or service account) lacks IAM permission "cloudscheduler.locations.list" for the resource "projects/project-id" (or the resource may not exist).
理由がわからないが、Google App Engineデプロイ時に cloudscheduler.locations.list
権限がないエラーが発生する。
セキュリティ審査担当者(roles/iam.securityReviewer)のロールを割り当てて解決。
$ gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--role="roles/iam.securityReviewer" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}"