🏄‍♂️

Platform Engineering を支える GitHub Actions の小ネタ集

2024/12/09に公開

これは GLOBIS Advent Calendar 2024 9日目の記事です。

はじめに

Platform Engineering とは、開発者の認知負荷を下げて、効率的に作業できる基盤を構築・運用する取り組みのことで、SRE や DevOps の文脈で近年特に注目を集めています。

https://learn.microsoft.com/ja-jp/platform-engineering/what-is-platform-engineering

しかし、我々が運用している Terraform や Kubernetes といったツールは特にアプリケーション開発者にとって馴染みが薄く、認知負荷が大きいという課題があります。

こういった IaC 領域の開発およびデリバリープロセスは GitHub を起点として展開されているため、これらの課題に対するアプローチとして GitHub Actions を活用することが有効ではないかと考えています。

弊社の SRE チームでは GitHub Actions を様々なユースケースで活用しており、今回は、その中で得た Tips をいくつか紹介します。

GitHub Apps の活用

GITHUB_TOKEN の限界

GitHub Actions でデフォルトで提供される GITHUB_TOKEN は気軽に使えてとても便利ですが、いくつかの制約があります。特に、自動化を進めようとすると以下のような問題に直面しがちではないでしょうか。

  • 自分自身のリポジトリに対するアクセス権限に限定されているため、複数のリポジトリにまたがる操作ができないこと。
  • 再帰的なワークフローの実行に制限があるため、ワークフロー内で commit を push すると、別のワークフローが意図せずトリガーされないこと。

GitHub Apps での認証

この問題を解決するためには GitHub Apps を利用する必要があります。GitHub Apps を使うことで、より柔軟な権限管理が可能になります。

https://docs.github.com/ja/apps/using-github-apps/about-using-github-apps

具体的な手順はドキュメントに任せますが、GITHUB_TOKEN ではなく GitHub Apps を通じて発行されるトークンを使用することで、必要なリポジトリに対するアクセス権限を取得することができます。

以下の公式アクションを使うことで安全にトークンを発行できるので積極的に利用しています。

https://github.com/actions/create-github-app-token

      - name: Create a token
        id: app-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.GITHUB_APP_ID }}
          private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}

      - name: Checkout private tools
        uses: actions/checkout@v4
        with:
          repository: my-org/private-tools
          token: ${{ steps.app-token.outputs.token }}

GitHub Apps 経由で git コマンドを使う

GITHUB_TOKEN を使っている場合は、git config コマンドでユーザー名とメールアドレスを以下のように設定することで、git commit を行ったときのユーザーが適切に表示されます。

      - name: Set git user and email
        run: |
          git config --global user.name "github-actions[bot]"
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

マジックナンバーのように見える固定値 41898282 は実は GitHub Actions 自体のユーザー ID であり、noreply 用のメールアドレス形式 ID+USERNAME@users.noreply.github.com に従って設定しているだけです。

https://docs.github.com/ja/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address

GitHub Apps を利用している場合は少し特殊で、ユーザー名の値は <app_name>[bot] ですが、メールアドレスを求めるには GitHub API を使って GitHub App 自体のユーザー ID を取得する必要があります。

# gh コマンドを使ってローカルから取得する方法
❯ gh api 'users/<app_name>[bot]' --jq '.id'

# 試しに github-actions[bot] の ID を取得してみると上記の固定値と一致することがわかる
❯ gh api 'users/github-actions[bot]' --jq '.id'
41898282

あとは ID+USERNAME@users.noreply.github.com の形式に従うことで設定するべきメールアドレスがわかります。

GitHub Apps 経由で GitHub CLI を使う

さらに、GitHub Apps 経由で認証を通して GitHub CLI を使うことも可能です。特別な手順は不要で、環境変数 GH_TOKEN を設定するだけで利用できます。

      - name: Use GitHub CLI
        run: gh pr list
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}

Monorepo で変更差分を取得する

pathspaths-ignore の限界

複数のコンポーネントを1つのリポジトリで管理する monorepo 運用では、特定のディレクトリやファイルの変更をトリガーとして動的にワークフローを実行することが必要になります。弊社の場合は以下のようなケースで monorepo を採用しています。

  • 複数環境の Terraform や共通 module をディレクトリ単位で分割する
  • Argo CD Application の source としてディレクトリごとに Helm chart や Kustomize を定義する

しかし、GitHub Actions の pathspaths-ignore オプションだけでは柔軟に制御できない場合があります。
例えば、変更があったディレクトリの分だけ動的に matrix を定義することはできませんし、個別にワークフローを定義しようとするとファイル数が増えて認知負荷に繋がる恐れがあります。

tj-actions/changed-files を使う

この問題を解決するために、tj-actions/changed-files アクションを利用しています。このアクションを使うことで、より柔軟に変更差分を検知し、必要十分なコンポーネントにのみ後続の処理を実行することができます。

https://github.com/tj-actions/changed-files

アクションの共通化

Reusable Workflow と Composite Action の使い分け

GitHub Actions では Reusable Workflow や Composite Action を使って処理を共通化することができます。

https://docs.github.com/ja/actions/sharing-automations/reusing-workflows

https://docs.github.com/ja/actions/sharing-automations/creating-actions/creating-a-composite-action

機能的にはどちらでもほぼ同じことが実現できますが、Platform Engineering 観点では複数のプロダクト(≒リポジトリ)から呼び出されることを考慮して、以下の基準で使い分けています。

Reusable Workflow

  • プロダクトごとの固有の文脈をプロダクト内で共通化したいとき
  • ex. デプロイ用のワークフローを複数環境に展開する
    • トリガーやデプロイ先の情報しか変わらないので処理をワークフローごと共通化できる

Composite Action

  • プロダクト横断で抽象化できる処理をステップ単位で共通化したいとき
  • ex. Kubernetes マニフェストや Terraform コードの静的解析を実行する
    • プロダクトのディレクトリ構成によらない形で切り出せる
    • 呼び出し側にプロダクト固有のロジックを寄せられるので責務を分離できる

カスタムアクションを利用する

シェルや actions/github-script アクションを用いたスクリプトでは記述しにくいほど処理が複雑な場合は、独自の TypeScript カスタムアクションを作成して運用しています。

TypeScript や Node.js のエコシステムに関する知識が必要になりますが、使いこなせると非常に強力なツールとなるので学習するメリットは大きいと考えています。

公式から以下のテンプレートリポジトリが公開されているので、最初は clone するだけで使い始めることができます。

https://github.com/actions/typescript-action

また、SRE チームでは複数のカスタムアクションや Composite Action を運用しており、それぞれ独立したリポジトリを作ると認知負荷につながってしまうため、npm workspaces を使って1つのリポジトリに集約しています。

sre-actions
├── codeowners-validator
├── manifest-analyzer
├── terraform-lockfile-checker
├── ...
├── package.json
├── tsconfig.json
└── ...

事例紹介

最後にこれらの Tips を活用した弊社 SRE チームの事例をいくつか紹介します。

terraform-lockfile-checker

Terraform の lockfile (.terraform.lock.hcl)をコミットしているかチェックするためのアクションです。変更があったディレクトリを tj-actions/changed-files アクションで取得し、その中に lockfile が含まれていない場合はコメントで警告を出しています。

codeowners-validator

CODEOWNERS に指定できるのはユーザーまたはチームであり、GitHub App を Bot ユーザーとして指定することはできません。

また、Renovate による automerge 機能は renovate-approve という GitHub App で自動的にレビューを通すことで実現されています。

これらの仕様により、GitHub 側で CODEOWNERS によるレビューを必須にすると、Renovate の automerge が機能しなくなるという課題があります。

https://github.com/apps/renovate-approve

このアクションは上記の課題を解決するために、CODEOWNERS に GitHub App も指定できるようにして renovate-approve がレビューを行ったかどうか検証します。これを必須のステータスチェックとすることで Renovate の automerge が機能するようにしています。

実装としては、CODEOWNERS ファイルの解析や判定ロジックを記述するために TypeScript でカスタムアクションを定義しています。

manifest-analyzer

こちらは Kubernetes マニフェスト用のアクションです。Kustomize を展開して差分を可視化しつつ、各種静的解析を実行しています。

詳細については以下の記事にまとめていますが、複数のリポジトリから呼び出されることを前提に Composite Action として作られています。

https://zenn.dev/yukin01/articles/kubernetes-manifests-pr-check

まとめ

今後は GitHub Actions や GitHub Apps をさらに活用して、開発者にゴールデンパスを提供する Bot を開発したり、静的解析をさらに充実させたりしていきたいと考えています。
ここまで GitHub Actions を活用した取り組みを紹介しましたが、少しでも参考になることがあれば幸いです。

GLOBIS Tech

Discussion