はじめに
こんにちは、エモーションテック 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を活用することでなんとか運用できているのではと考えています。 引き続き運用の改善はしていきたいと思います。 エモーションテックでは、顧客体験・従業員体験の改善を支えるプロダクト開発を一緒に進めてくれる仲間を募集しています。ご興味のある方はぜひ採用ページからご応募ください!