EmotionTechテックブログ

株式会社エモーションテックのProduct Teamのメンバーが、日々の取り組みや技術的なことを発信していくブログです。

100個超あるCloud Runマニフェストの管理方式とデプロイワークフローの紹介

はじめに

こんにちは、エモーションテック SREのおかざきです。 弊社ではGoogle Cloud Runを利用していますが、そのマニフェスト管理方式とデプロイワークフローを紹介します。

背景

弊社プロダクトでは複数のマイクロサービスをCloud RunとCloud Run Jobs上で運用しています。 各サービス・各環境ごとにCloud Runの設定ファイル(以下、マニフェスト)を管理する必要があります。

現在はCloud Run / Cloud Run Jobsを合わせて100を超えるマニフェストが存在するため、それらをうまく管理する必要があります。

全体像

方式の全体像を図で表すと以下のようになっています。

Cloud Runの設定ファイルは専用のGitHubリポジトリ(以下、マニフェストリポジトリ)で管理されています。 マイクロサービスごとにリポジトリが分かれており、Dockerイメージのビルドは各リポジトリGitHub Actionsワークフローで行っています。 git-flowにおける各ブランチをもとにDockerイメージをビルドしてArtifact Registryにpushした後、マニフェストリポジトリのデプロイワークフローを呼び出す流れになっています。

これにより、デプロイワークフローやマニフェストマニフェストリポジトリで集中管理しつつ、ビルドのようなCI処理は各マイクロサービスで独立して管理できるようにしています。

マニフェスト管理

100以上のCloud Run / Cloud Run Jobsのマニフェストを管理するため、Kustomizeを採用しています。

マニフェストリポジトリの構成

マニフェストリポジトリディレクトリ構成は以下のようになっています(一部抜粋):

<マニフェストリポジトリ名>/
  ├── manifests/
  │   └── product1/
  │       └── microservices/
  │           ├── base/
  │           │       ├── kustomization.yaml
  │           │       └── service.yaml
  │           ├── components
  │           │   └── sidecar
  │           │       └── sidecar1
  │           ├── dev/
  │           │   └── service1/
  │           │       ├── kustomization.yaml
  │           │       ├── service.yaml
  │           │       ├── service-patch.yaml
  │           │       └── env.yaml
  │           └── ...
  └── .github/
      └── workflows/
          ├── <Cloud Runへのデプロイワークフロー>.yml
          ├── <kustomize buildワークフロー>.yml
          └── ...

各サービス・各環境ごとにディレクトリを分け、Kustomizeのbase/overlay構成でパラメータ差分を管理しています。

Kustomizeのファイル構成

base/service.yaml

マニフェストの骨組みとなる構造を定義しています。以下はCloud Runの例です。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:
    run.googleapis.com/ingress: <will be replaced by kustomize>
  labels:
    app-name: <will be replaced by kustomize>
    cloud.googleapis.com/location: asia-northeast1
    env: <will be replaced by kustomize>
    service: run
  name: base
  #namespace: <will be replaced by cd-tool>
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: <will be replaced by kustomize>
        autoscaling.knative.dev/minScale: <will be replaced by kustomize>
        run.googleapis.com/client-name: githubactions
        run.googleapis.com/launch-stage: BETA
        run.googleapis.com/cpu-throttling: <will be replaced by kustomize>
    spec:
      containerConcurrency: <will be replaced by kustomize>
      containers:
        - env:
          image: will-be-replaced-by-cd-tool
          ports:
            - containerPort: <will be replaced by kustomize>
              name: http1
          resources:
            limits:
              cpu: <will be replaced by kustomize>
              memory: <will be replaced by kustomize>
      serviceAccountName: <will be replaced by kustomize>
      timeoutSeconds: <will be replaced by kustomize>

各Cloud Runの設定ファイル

Cloud Runのサービス/環境毎に以下のファイルを用意しています。これらをKustomizeでbuildしてservice.yamlを生成/更新します。デプロイの際には生成されたservice.yamlにDockerイメージタグを追記して利用します。

kustomization.yamlの例

bases:
  - ../../base/
patches:
  - path: service-patch.yaml
    target:
      group: serving.knative.dev
      version: v1
      kind: Service
      name: base
  - path: env.yaml
    target:
      group: serving.knative.dev
      version: v1
      kind: Service
      name: base

components:
  - ../../components/sidecar/datadog-agent/

base/service.yamlをもとに、同一ディレクトリ内のenv.yaml、service-patch.yamlとcomponentsに定義されているサイドカーの定義を組み合わせてマニフェストを生成します。

service-patch.yamlの例

Cloud Runのサービス名やスケーリング設定をはじめとする各種パラメータを列挙しています。 patchesを用いてreplaceオペレータでbase/service.yamlの設定項目を置換しています。

以下のようにパラメータの設計根拠となった情報(性能試験のレポートのリンクなど)を記述して、意図を残しやすくしています。

# このサービスが落ちるとxx画面が停止して重大故障になるため、余裕のある値を選定する。
# 以下にてこの値を用いて性能検証済み。
# https://<社内の性能試験レポートのURL>

- op: replace
path: /spec/template/metadata/annotations/autoscaling.knative.dev~1maxScale
value: "100"

- op: replace
path: /spec/template/metadata/annotations/autoscaling.knative.dev~1minScale
value: "5"

- op: replace
path: /metadata/annotations/run.googleapis.com~1ingress
value: internal-and-cloud-load-balancing

env.yamlの例

環境変数については数が多くなりがちであるため、見通しを良くするため専用のファイル(env.yaml)に列挙するようにしています。

- op: replace
  path: /spec/template/spec/containers/0/env
  value:
    - name: HOGE_API_KEY
      valueFrom:
        secretKeyRef:
          key: "latest"
          name: service1-HOGE_API_KEY
    - name: FUGA_API_KEY
      valueFrom:
        secretKeyRef:
          key: "latest"
          name: service1-FUGA_API_KEY

GitHub ActionsでのKustomize build

マニフェスト(service.yaml)を更新する際には、service-patch.yaml等を編集した上で、GitHub Actionsを用いてkustomize buildコマンドを実行し、service.yamlの変更PRを作成しています。

GitHub Actions ワークフローの抜粋

      - name: Kustomize Build for Cloud Run
        env:
          ENV_NAME: ${{ matrix.env_name }}
        run: |
          service_dirs=`find "manifests/product1/microservices/${ENV_NAME}" -name "kustomization.yaml" | xargs dirname`
          for dir in ${service_dirs}; do
            echo ${dir}
            kustomize build ${dir} > ${dir}/service.yaml
            cat ${dir}/service.yaml
            git add ${dir}/service.yaml
          done

... (以下、git commitやGitHub CLIでのPR作成処理) ...

変更PRをマージした後は、後述するデプロイワークフローをアプリケーションのリリースに合わせて自動実行するか、手動実行するかを選べるようにしています。 これによりマイクロサービスチームが自律的に設定を反映できるようにしています。

GitHub Actionsによるデプロイ

マイクロサービスリポジトリ側のワークフロー

例として、リリースブランチをmainブランチにマージした際のワークフローは以下のようになっています。 Dockerイメージにタグを付与し、マニフェストリポジトリのデプロイワークフローをGitHub APIで呼び出しています。

GitHub Actions ワークフローの抜粋

      - name: Add prod tag to the release image
        id: add-prod-tag
        run: |
          # production tagを付与
          release_tag=`echo ${{ github.event.pull_request.head.ref }} | sed -e 's/\//-/'`
          prod_tag="prod-${release_tag}"
          gcloud artifacts docker tags add "${{ env.GCP_ARTIFACT_REGISTRY }}/${{ env.GCP_PROJECT }}/${{ env.SERVICE_CODE_NAME }}-${{ env.SYSTEM_CODE_NAME }}/${{ env.SERVICE_CODE_NAME }}:${release_tag}" \
          "${{ env.GCP_ARTIFACT_REGISTRY }}/${{ env.GCP_PROJECT }}/${{ env.SERVICE_CODE_NAME }}-${{ env.SYSTEM_CODE_NAME }}/${{ env.SERVICE_CODE_NAME }}:${prod_tag}"
          echo "prod_tag=$prod_tag" >> $GITHUB_OUTPUT

      - name: Generate github token
        id: generate_token
        uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
        with:
          app-id: ${{ secrets.BOT_APP_ID }}
          private-key: ${{ secrets.BOT_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}

      - name: Trigger Cloud Run YAML Update for API
        env:
          GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
        run: |
          curl --location --request POST 'https://api.github.com/repos/<organiztion>/<repository>/dispatches' \
          --header 'Content-Type: application/json' \
          --header 'Authorization: token ${{ env.GITHUB_TOKEN }}' \
          --header 'Accept: application/vnd.github.everest-preview+json' \
          --data-raw '{
              "event_type": "cloudrun_image_tag_update",
              "client_payload": {
                  "system_code_name": "${{ env.SYSTEM_CODE_NAME }}",
                  "service_code_name": "${{ env.API_CLUSTER_SERVICE_CODE_NAME }}",
                  "image_tag": "${{ steps.add-prod-tag.outputs.prod_tag }}",
                  "env_name": "${{ env.ENV_NAME }}"
              }
          }'

マニフェストリポジトリのデプロイワークフロー

マイクロサービスリポジトリ側から受け取ったイメージタグをはじめとする情報を用いてデプロイを行います。 service.yamlのイメージタグを書き換えて、google-github-actions/deploy-cloudrunを用いてCloud Runのデプロイを実行します。

      - name: Modify config image tag
        continue-on-error: true
        run: |
          TARGET_FILE=${{ inputs.cloud_run_yaml_path }}
          sed -i "s/image:[ ]*\(.*\):'$/image: \1:${{ inputs.image_tag }}'/g" ${TARGET_FILE}

      - name: "Deploy to Cloud Run"
        uses: google-github-actions/deploy-cloudrun@1ec29da1351112c7904fb60454a55e3e1021a51c # v2.7.2
        with:
          metadata: ${{ inputs.cloud_run_yaml_path }}
          region: "${{ env.GCP_REGION }}"
          project_id: ${{ env.GCP_PJ }}
          no_traffic: false

3年ほど運用してみての所感

良かった点

以下の点を気に入っています。

  • Cloud Runのマニフェストを一元管理することで、他マイクロサービスとの設定差分を確認しやすい点
  • Kustomizeを利用する事でservice-patch.yamlにコメントで設計意図を残したり、サイドカーといった共通的な要素の追加がしやすい点
  • Cloud Runのデプロイ処理のワークフローは一つのリポジトリで集中管理しつつも各チームが自律的にデプロイをしたり、CI処理をメンテナンス可能である点

また、この記事では紹介しませんでしたが、一部AWS Fargateを利用している部分もあります。Fargateのタスク定義ファイルとデプロイワークフローも同様の構成をしており、GitHub Actionsを利用することで異なるインフラを同じような構成で扱うことができたのも良かった点です。 (Google CloudだけならCloud BuildやCloud Deployのような製品を使うという手もあるかと思います。)

改善の余地があると考える点

各メンバーにマイクロサービスリポジトリのワークフローの記述改善等を分担してもらっていますが、サービス数が増加傾向であるのでそのようなメンテナンス作業が大変ではあります。 今後はマイクロサービス側のワークフローの変更時にPRを一括で作成する仕組みのようなものを考えて、運用改善していきたいと考えます。

おわりに

いかがでしたでしょうか。 改めて数えてみるとマニフェストの数がプロダクトの初期構築時から5倍程度になっていますが、KustomizeやGitHub Actionsを活用することでなんとか運用できているのではと考えています。 引き続き運用の改善はしていきたいと思います。 エモーションテックでは、顧客体験・従業員体験の改善を支えるプロダクト開発を一緒に進めてくれる仲間を募集しています。ご興味のある方はぜひ採用ページからご応募ください!

careers.emotion-tech.co.jp