LoginSignup
4
3

More than 1 year has passed since last update.

GCP GAEにGitHub ActionsでLaravelをデプロイする

Last updated at Posted at 2022-04-14

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 に認証可能
  • サービスアカウントキーのエクスポートが不要

2_GitHub_Actions.max-1100x1100.jpg

新しい 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を入れます。

補足

app.yaml
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
secret.yaml.example
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}"
4
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3