From 880351d063d913507551a6d172032dcb7ac4f43a Mon Sep 17 00:00:00 2001 From: Kanta Oikawa Date: Mon, 15 Jun 2026 22:18:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E5=8D=98=E4=B8=80=E3=82=B5=E3=83=BC?= =?UTF-8?q?=E3=83=93=E3=82=B9=E3=81=AE=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95=E3=83=AD=E3=83=BC=E3=82=92?= =?UTF-8?q?=E5=BE=A9=E5=85=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/set-env/action.yml | 56 +++++++++++ .github/workflows/cd.yml | 152 +++++++++++++---------------- 2 files changed, 126 insertions(+), 82 deletions(-) create mode 100644 .github/actions/set-env/action.yml diff --git a/.github/actions/set-env/action.yml b/.github/actions/set-env/action.yml new file mode 100644 index 0000000..939b756 --- /dev/null +++ b/.github/actions/set-env/action.yml @@ -0,0 +1,56 @@ +name: Set Environment +description: Set environment, service name, and image tag based on trigger context +inputs: + ar_hostname: + description: Artifact Registry hostname + required: true + project_id: + description: GCP project ID + required: true +outputs: + environment: + description: Target environment (e.g. stg, prod) + value: ${{ steps.set-env.outputs.environment }} + service_name: + description: Cloud Run service name + value: ${{ steps.set-env.outputs.service_name }} + image_tag: + description: Docker image tag + value: ${{ steps.set-env.outputs.image_tag }} +runs: + using: composite + steps: + - id: set-env + shell: bash + run: | + # Environment + IS_WORKFLOW_DISPATCH="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment_name != '' }}" + IS_PUSH_TO_MAIN="${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.base_ref == 'main') }}" + if [ $IS_WORKFLOW_DISPATCH = "true" ]; then + export TARGET_ENVIRONMENT="${{ github.event.inputs.environment_name }}" + elif [ $IS_PUSH_TO_MAIN = "true" ]; then + export TARGET_ENVIRONMENT="stg" + else + exit 1 + fi + + echo "Environment: $TARGET_ENVIRONMENT" + + # Service name suffix + if [ "$TARGET_ENVIRONMENT" = "prod" ]; then + export SERVICE_NAME_SUFFIX="" + else + export SERVICE_NAME_SUFFIX="-$TARGET_ENVIRONMENT" + fi + + export REPO_NAME=$(echo ${{ github.repository }} | awk -F '/' '{print $2}') + export SERVICE_NAME=$REPO_NAME$SERVICE_NAME_SUFFIX + export IMAGE_TAG=${{ inputs.ar_hostname }}/${{ inputs.project_id }}/github-actions/$REPO_NAME/$SERVICE_NAME:${{ github.sha }} + + echo "Repository name: $REPO_NAME" + echo "Service name: $SERVICE_NAME" + echo "Image tag: $IMAGE_TAG" + + echo "environment=$TARGET_ENVIRONMENT" >> "$GITHUB_OUTPUT" + echo "service_name=$SERVICE_NAME" >> "$GITHUB_OUTPUT" + echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e59be30..fd02b35 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -29,93 +29,81 @@ env: IMAGE_NAME: server jobs: - deploy: - name: Deploy (${{ github.event_name == 'workflow_dispatch' && inputs.environment_name || 'stg' }}) + set-env: runs-on: ubuntu-latest - environment: ${{ github.event_name == 'workflow_dispatch' && inputs.environment_name || 'stg' }} + outputs: + environment: ${{ steps.set-env.outputs.environment }} + service_name: ${{ steps.set-env.outputs.service_name }} + image_tag: ${{ steps.set-env.outputs.image_tag }} steps: - uses: actions/checkout@v6 - - - name: Resolve environment - id: env - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - ENVIRONMENT="${{ inputs.environment_name }}" - else - ENVIRONMENT="stg" - fi - echo "name=${ENVIRONMENT}" >> "$GITHUB_OUTPUT" - # 命名規約 (infra/locals.tf env_suffix): prod は無サフィックス、他は -。 - if [ "$ENVIRONMENT" = "prod" ]; then - echo "suffix=" >> "$GITHUB_OUTPUT" - else - echo "suffix=-${ENVIRONMENT}" >> "$GITHUB_OUTPUT" - fi - - - name: Compose image reference - id: image - run: | - echo "ref=${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPOSITORY}/${IMAGE_NAME}:${GITHUB_SHA}" >> "$GITHUB_OUTPUT" - env: - PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} - - - name: Authenticate to Google Cloud - id: auth - uses: google-github-actions/auth@v2 + - id: set-env + uses: ./.github/actions/set-env with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_DEPLOY_SERVICE_ACCOUNT }} - token_format: access_token - - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@v2 - - - name: Log in to Artifact Registry - uses: docker/login-action@v3 + ar_hostname: ${{ vars._AR_HOSTNAME }} + project_id: ${{ vars.PROJECT_ID }} + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + SERVICE_NAME: ${{ needs.set-env.outputs.service_name }} + IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} + needs: + - set-env + steps: + - uses: actions/checkout@v6 with: - registry: ${{ env.REGION }}-docker.pkg.dev - username: oauth2accesstoken - password: ${{ steps.auth.outputs.access_token }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and push image - uses: docker/build-push-action@v6 + fetch-depth: 0 + submodules: true + - name: Build + run: | + docker build \ + -t ${{ env.IMAGE_TAG }} \ + . \ + -f Dockerfile \ + --no-cache + - uses: google-github-actions/auth@v3 with: - context: . - push: true - tags: ${{ steps.image.outputs.ref }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Deploy Cloud Run services + workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@v3 + with: + project_id: ${{ vars.PROJECT_ID }} + - name: Configure Docker run: | - for svc in academic-api announcement-api user-api; do - echo "::group::update service ${svc}${SUFFIX}" - gcloud run services update "${svc}${SUFFIX}" \ - --region="$REGION" \ - --image="$IMAGE" \ - --quiet - echo "::endgroup::" - done - env: - SUFFIX: ${{ steps.env.outputs.suffix }} - IMAGE: ${{ steps.image.outputs.ref }} - - - name: Deploy Cloud Run jobs + gcloud auth \ + configure-docker \ + ${{ vars._AR_HOSTNAME }} + - name: Push run: | - for job in \ - build-class-change-notifications-job \ - dispatch-notifications-job \ - migrate-job; do - echo "::group::update job ${job}${SUFFIX}" - gcloud run jobs update "${job}${SUFFIX}" \ - --region="$REGION" \ - --image="$IMAGE" \ - --quiet - echo "::endgroup::" - done - env: - SUFFIX: ${{ steps.env.outputs.suffix }} - IMAGE: ${{ steps.image.outputs.ref }} + docker push \ + ${{ env.IMAGE_TAG }} + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + environment: ${{ needs.set-env.outputs.environment }} + env: + SERVICE_NAME: ${{ needs.set-env.outputs.service_name }} + IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} + needs: + - set-env + - build-and-push + steps: + - uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.SERVICE_ACCOUNT }} + - uses: google-github-actions/deploy-cloudrun@v3 + with: + service: ${{ env.SERVICE_NAME }} + image: ${{ env.IMAGE_TAG }} + region: ${{ vars._DEPLOY_REGION }} + env_vars: | + DB_IAM_USER=${{ vars.DB_IAM_USER }} + DB_NAME=${{ vars.DB_NAME }} + INSTANCE_CONNECTION_NAME=${{ vars.INSTANCE_CONNECTION_NAME }} + env_vars_update_strategy: overwrite + secrets_update_strategy: overwrite From 9b03dbe5e0375cf015c4cec571840e15d91f7c0e Mon Sep 17 00:00:00 2001 From: Kanta Oikawa Date: Mon, 15 Jun 2026 22:24:44 +0900 Subject: [PATCH 2/3] =?UTF-8?q?set-env=20=E3=82=A2=E3=82=AF=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=92=E5=85=B1=E6=9C=89=E3=82=A4=E3=83=A1?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=BB=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=20matrix=20=E5=AF=BE=E5=BF=9C=E3=81=AB=E6=8B=A1=E5=BC=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 単一サービス前提だった出力を見直し、cmd/* から デプロイ matrix を自動生成する set-matrix ステップを追加した。 - service_name 出力を廃止し、サービス/ジョブ名へ付与する service_suffix 出力に置き換え(prod は空) - image_tag をサービス名非依存にし、全 cmd ターゲットで 1 つのイメージを共有する形へ変更 - cmd/* を走査し、末尾 "-api" を Cloud Run Service、 それ以外を Job として分類した matrix を出力 このコミットでは set-env アクション単体の出力定義のみを変更し、 利用側ワークフローの変更は次コミットで扱う。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/set-env/action.yml | 46 ++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/.github/actions/set-env/action.yml b/.github/actions/set-env/action.yml index 939b756..2249023 100644 --- a/.github/actions/set-env/action.yml +++ b/.github/actions/set-env/action.yml @@ -1,5 +1,5 @@ name: Set Environment -description: Set environment, service name, and image tag based on trigger context +description: Set environment, shared image tag, and the deploy matrix based on trigger context inputs: ar_hostname: description: Artifact Registry hostname @@ -11,12 +11,17 @@ outputs: environment: description: Target environment (e.g. stg, prod) value: ${{ steps.set-env.outputs.environment }} - service_name: - description: Cloud Run service name - value: ${{ steps.set-env.outputs.service_name }} + service_suffix: + description: Suffix appended to each Cloud Run service/job name (empty for prod) + value: ${{ steps.set-env.outputs.service_suffix }} image_tag: - description: Docker image tag + description: Shared Docker image tag (one image for all cmd targets) value: ${{ steps.set-env.outputs.image_tag }} + matrix: + description: | + JSON deploy matrix derived from cmd/*. Shape: {"include":[{"name","type"}]}. + type is "service" for *-api targets, otherwise "job". + value: ${{ steps.set-matrix.outputs.matrix }} runs: using: composite steps: @@ -44,13 +49,36 @@ runs: fi export REPO_NAME=$(echo ${{ github.repository }} | awk -F '/' '{print $2}') - export SERVICE_NAME=$REPO_NAME$SERVICE_NAME_SUFFIX - export IMAGE_TAG=${{ inputs.ar_hostname }}/${{ inputs.project_id }}/github-actions/$REPO_NAME/$SERVICE_NAME:${{ github.sha }} + # 全 cmd ターゲットで 1 つのイメージを共有するため、タグはサービス名を含めない。 + export IMAGE_TAG=${{ inputs.ar_hostname }}/${{ inputs.project_id }}/github-actions/$REPO_NAME:${{ github.sha }} echo "Repository name: $REPO_NAME" - echo "Service name: $SERVICE_NAME" + echo "Service suffix: $SERVICE_NAME_SUFFIX" echo "Image tag: $IMAGE_TAG" echo "environment=$TARGET_ENVIRONMENT" >> "$GITHUB_OUTPUT" - echo "service_name=$SERVICE_NAME" >> "$GITHUB_OUTPUT" + echo "service_suffix=$SERVICE_NAME_SUFFIX" >> "$GITHUB_OUTPUT" echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" + - id: set-matrix + shell: bash + run: | + # cmd/* を走査してデプロイ対象を自動生成する。 + # 末尾が "-api" のターゲットを Cloud Run Service、それ以外を Cloud Run Job として分類する。 + # サービス/ジョブが増減しても cmd/ に追従するため、ワークフロー側の編集は不要。 + include="" + for dir in cmd/*/; do + name=$(basename "$dir") + [ "$name" = "*" ] && continue + case "$name" in + *-api) type="service" ;; + *) type="job" ;; + esac + include="$include{\"name\":\"$name\",\"type\":\"$type\"}," + done + if [ -z "$include" ]; then + echo "No deploy targets found under cmd/" >&2 + exit 1 + fi + matrix="{\"include\":[${include%,}]}" + echo "Matrix: $matrix" + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" From 356ba6fe7b0aa96e105b080c7c13d2f549bab675 Mon Sep 17 00:00:00 2001 From: Kanta Oikawa Date: Mon, 15 Jun 2026 22:24:59 +0900 Subject: [PATCH 3/3] =?UTF-8?q?CD=20=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=92=20matrix=20=E3=81=AB=E3=82=88?= =?UTF-8?q?=E3=82=8B=E4=B8=A6=E5=88=97=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?= =?UTF-8?q?=E3=81=B8=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 単一サービスのデプロイから、cmd/* 由来の matrix で各ターゲットを 並列デプロイする方式へ切り替えた。サービス/ジョブの増減に ワークフロー編集なしで自動追従できる。 - build-and-push を 1 回だけ実行し、共有イメージを生成・プッシュ - deploy ジョブに set-env が出力する matrix を適用し、 service_suffix と組み合わせてサービス/ジョブ名を構築 - *-api は deploy-cloudrun で Service デプロイ、それ以外は gcloud run jobs deploy で Job を作成/更新(実行はしない)。 いずれも共有イメージの起動コマンドを /bin/ に切り替え - 不要になった env(REGION/AR_REPOSITORY/IMAGE_NAME)を削除 - ポリシーに従い各 action をタグから SHA 固定に変更 (Renovate 用にタグをコメント併記) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/cd.yml | 52 +++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index fd02b35..9fd95ea 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -23,37 +23,34 @@ permissions: contents: read id-token: write -env: - REGION: asia-northeast1 - AR_REPOSITORY: server - IMAGE_NAME: server - jobs: set-env: runs-on: ubuntu-latest outputs: environment: ${{ steps.set-env.outputs.environment }} - service_name: ${{ steps.set-env.outputs.service_name }} + service_suffix: ${{ steps.set-env.outputs.service_suffix }} image_tag: ${{ steps.set-env.outputs.image_tag }} + matrix: ${{ steps.set-env.outputs.matrix }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - id: set-env uses: ./.github/actions/set-env with: ar_hostname: ${{ vars._AR_HOSTNAME }} project_id: ${{ vars.PROJECT_ID }} + + # 全 cmd ターゲットを 1 つのイメージに同梱しているため、ビルド/プッシュは 1 回だけ実行する。 build-and-push: runs-on: ubuntu-latest permissions: contents: read id-token: write env: - SERVICE_NAME: ${{ needs.set-env.outputs.service_name }} IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} needs: - set-env steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 submodules: true @@ -64,11 +61,11 @@ jobs: . \ -f Dockerfile \ --no-cache - - uses: google-github-actions/auth@v3 + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ vars.SERVICE_ACCOUNT }} - - uses: google-github-actions/setup-gcloud@v3 + - uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 with: project_id: ${{ vars.PROJECT_ID }} - name: Configure Docker @@ -80,30 +77,53 @@ jobs: run: | docker push \ ${{ env.IMAGE_TAG }} + + # cmd/* から生成した matrix で各ターゲットを並列デプロイする。サービス/ジョブが増減しても自動追従する。 deploy: runs-on: ubuntu-latest permissions: id-token: write environment: ${{ needs.set-env.outputs.environment }} - env: - SERVICE_NAME: ${{ needs.set-env.outputs.service_name }} - IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} needs: - set-env - build-and-push + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.set-env.outputs.matrix) }} + env: + SERVICE_NAME: ${{ matrix.name }}${{ needs.set-env.outputs.service_suffix }} + IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} steps: - - uses: google-github-actions/auth@v3 + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }} service_account: ${{ vars.SERVICE_ACCOUNT }} - - uses: google-github-actions/deploy-cloudrun@v3 + + # *-api: Cloud Run Service としてデプロイ。共有イメージの起動コマンドを /bin/ に切り替える。 + - if: matrix.type == 'service' + uses: google-github-actions/deploy-cloudrun@2028e2d7d30a78c6910e0632e48dd561b064884d # v3.0.1 with: service: ${{ env.SERVICE_NAME }} image: ${{ env.IMAGE_TAG }} region: ${{ vars._DEPLOY_REGION }} + flags: --command=/bin/${{ matrix.name }} env_vars: | DB_IAM_USER=${{ vars.DB_IAM_USER }} DB_NAME=${{ vars.DB_NAME }} INSTANCE_CONNECTION_NAME=${{ vars.INSTANCE_CONNECTION_NAME }} env_vars_update_strategy: overwrite secrets_update_strategy: overwrite + + # それ以外: Cloud Run Job として作成/更新する(実行はしない)。 + - if: matrix.type == 'job' + uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 + with: + project_id: ${{ vars.PROJECT_ID }} + - if: matrix.type == 'job' + name: Deploy Cloud Run Job + run: | + gcloud run jobs deploy "${{ env.SERVICE_NAME }}" \ + --image "${{ env.IMAGE_TAG }}" \ + --region "${{ vars._DEPLOY_REGION }}" \ + --command "/bin/${{ matrix.name }}" \ + --set-env-vars "DB_IAM_USER=${{ vars.DB_IAM_USER }},DB_NAME=${{ vars.DB_NAME }},INSTANCE_CONNECTION_NAME=${{ vars.INSTANCE_CONNECTION_NAME }}"