Skip to main content

2 posts tagged with "codebuild"

View All Tags

· 10 min read
Shunsuke Suzuki

GitHub Repository の CI に CodeBuild を使う場合、 CodeBuild の Webhook integration (以下 CodeBuild GitHub integration と呼ぶことにします) を使うのが一番自然でしょう。 基本的なユースケースならこれでよいのですが、 GitHub App を活用することでより高度な CI を実現することができます。

解決したい課題

  • Batch Build の課題
    • 起動・終了が遅い
    • 全 build が成功した Batch Build を Retry できない
    • Web UI がわかりにくい
      • 余計な build が起動する
      • build 単体を Retry できない
    • build ごとに条件設定とかできない
    • buildspec を動的に生成できない
  • CodeBuild GitHub integration の課題
    • Build Project ごとに Repository Webhook が 1 つ作られる
    • Filter の条件が限られている(例えば PR label で filter とかできない)
    • 複数の build を実行できない(Batch Build も 1 つとみなした場合の話)
  • CodeBuild の課題
    • Retry した場合 webhook で起動したときの環境変数が設定されない

GitHub App

Amazon API Gateway と Lambda を使って GitHub App を構築します。 Lambda で webhook を受け取り、 AWS SDK を使って build を実行します。

codebuilder-architecture

GitHub App を作成し、 Webhook URL として API Gateway の endpoint を指定します。 internet facing な API Gateway は Repository ごとに作るのではなく、共有で 1 つ作るようにしたほうが良いかと思います。 Lambda は Repository ごとに分けたほうが権限を絞れるし、 Function がシンプルになるし、あるリポジトリのための変更が他のリポジトリに影響することがないので良いでしょう。

Batch Build を使う代わりに Build を複数並列で起動することで、 Batch Build の課題を解決できます。 実行時にパラメータを変えることもできます。 Webhook の Payload も参照できるので、 Payload から得たデータ(PR 番号、 label、PR Author 名、 etc)を環境変数として build にわたすこともできます。

従来は build 内で GitHub API を使って取得していた PR の情報を環境変数として渡せることで build の処理が簡略化されますし、 GitHub API の call 数を減らすこともできます(これは API の rate limit が問題になる場合に重要です)。

PR label による build のフィルタリングなど、 CodeBuild GitHub integration では難しいより複雑な filter も実現できます。

GitHub App であれば Repository Webhook が作られることもありませんし、 Build Project ごとに webhook の設定をする必要もありません。

Assume Role することで別の AWS Account の Build Project の build を実行することもできます。

merged event で merged commit sha で build を実行

PR がマージされたら merged commit の SHA で CI を実行したい場合、 push event を hook するのがおそらく一般的かと思います。

CodeBuild GitHub integration では merged event を hook することもできますが、 この場合 PR の head branch の SHA で build が実行されてしまいますし、 base branch の commit status が更新されません。

しかし push event では関連する PR の情報が取れない(build の環境変数として PR の情報が渡されない)という問題があります。 GitHub API でコミットと関連した PR の一覧が取れますが、複数の PR と関連づいている場合、どの PR なのか特定することができません(特定の条件付きであればできますが)。

そこで GitHub App で merged event (正確には closed event で PR がマージ済の場合) を hook しつつ、 build 起動時に source version として merged commit sha を指定することで webhook から PR の情報を取得しつつ merged commit の SHA で CI を実行できます。

GitHub の Personal Access Token の代わりに GitHub App installation access token を使う

CodeBuild の build 内で GitHub API を使いたい場合、 Personal Access Token を発行するのがシンプルですが、 Personal Access Token にはいくつか課題があります。

  • token 流出のリスク
    • rotation が難しいので有効期限が設定されてない場合が多い
  • rate limit
    • token が organization で広く共有されたりするようになると問題になりやすい
    • rate limit は account 単位なので、同じ account の token を別に作っても意味がない
    • account を増やして org に追加すると、 org のメンバーが増えるのでお金がかかる(まぁ金額的に無視してもよいかもしれませんが)

GitHub App では rotation などを考えなくても一時的な token を発行できるので、セキュリティ的にリスクが低いですし、 GitHub App を作ってもお金はかかりません。 token を発行するには

  • App ID
  • Installation ID (webhook の payload に含まれている)
  • Private key

が必要で、 build 実行時に installation id を環境変数として渡すことで Lambda だけでなく Codebuild でも installation access token が使えます。

もちろん GitHub App を使っても rate limit に引っかかることはありますが、 Personal Access Token に比べて回避しやすいかとは思います。

Lambda から Repository のコードを参照する

https://blog.studysapuri.jp/entry/2020/12/03/080000 では CodeBuild でリポジトリのコードを checkout し、 build の中で動的に buildspec を生成して batch build を実行しています。

同じようなことを Lambda でやろうとした場合、色々制約があります。 Lambda ではリポジトリを checkout してくる代わりに、 build を実行するのに必要な情報を静的に生成してリポジトリにコミットしておきそれを GitHub API で取得するというやり方があります。 その場合、ファイルを生成するコマンドをリポジトリに用意しておき、 ちゃんとファイルが最新になっているか CI の中でチェックし、 なっていなければ CI を fail させるか自動で更新してコミットするというのをやるのが良さそうです。 Git の pre-push hook などで check するのもありかもしれません。

GitHub App の開発をいかに楽にするか

ようは webhook を受け取って build を実行する Lambda Function を実装すればいいだけなのですが、 毎回 0 からコードを書くのはちょっとした手間なので、 Go の簡単な library を作っています。

https://github.com/suzuki-shunsuke/go-github-app-for-aws-codebuild

このライブラリ自体は大したものではないので、皆さんのユースケースや言語に合わせて独自に作っても良いでしょう。

サンプル

簡単なサンプルも書いています。

https://github.com/suzuki-shunsuke/example-github-app-for-aws-codebuild

· 6 min read
Shunsuke Suzuki

AWS CodeBuild を検証しているんですが、結構良いですね。 現状 Jenkins や CircleCI で実行しているジョブや CI/CD を一部移行したいなと思いました。

一部と言っているのは、単純に全部いきなり移行するのは難しいから共存する前提で考えるくらいの意味です。

なお、これを書いている時点ではまだ軽く検証しているだけなので、CodeBuild の理解は浅いです。

特徴としては

  • Managed
  • AWS 以外のサービスに AWS の credential を登録しなくて良い
    • Secret を AWS Secrets Manager で管理できる
    • Secret を至るところに設定するのではなく、 AWS Secrets Manager か何かで一元管理するのが理想
    • Role 作って Build Project の Service Role として指定するだけなら credential を扱う必要がなくて楽で安心
  • VPC 内で実行できる
  • GitHub 連携も簡単
    • Webhook の設定で PR の細かなイベント(merge とか reopen とか)に対応しているのも良い
    • PR を merge したときも PR で変更されたファイルによって Webhook をフィルタできるのが良い
  • 変更されたファイルによって実行するBuild Project を変更するようなロジックを実現できる(monorepo で特に有効)

といった点が挙げられます。

変更検知について

上記の特徴についてはだいたい「まぁそうだね」というふうに思ってもらえるのではないかと思いますが、変更検知の部分について補足します。 monorepo をやっているとサービス A 関連のジョブはサービス A 関連のコードが変更された場合のみ実行したいというのがあります。 なのですが、 CircleCI だと自分の知る限りそれはできません。

https://circleci.com/docs/2.0/configuration-reference/

なので現状 Job を実行してからサービス A 関連のコードが変更されているかチェックし、変更されていなかったら skip ないし circleci step halt で Job を終了しています。 ただ、差分検知の部分を自分で実装しないといけないし、 circleci step halt で終了するにしても Job の起動自体にそこそこ時間がかかったりするし(これだけでも金銭的コストがかかっているはず)、 Job の実行時間や error rate のモニタリングでもノイズとして混じってきてしまいます。

CodeBuild の場合、1つのリポジトリに複数の Build Project を設定でき、 Build Project ごとに buildspec を指定できます。 Build Project の Webhook の FILE_PATH の設定で特定のファイルが変更された場合のみビルドを実行することもできます。 なのでサービス A 用の build spec 及び Build Project を作成し、サービス A のコードが変更されたときのみその Build Project が実行されるように Webhook を設定すれば良さそうです。

気になる点

  • FILE_PATH で 1 個の正規表現しか設定できないので、場合によっては複雑な正規表現を書かないといけない
  • Terraform で管理するのが結構面倒くさい
    • いい感じにモジュール化とかテンプレート化しないとだめかも
  • Pull Request のラベルがついていたらこのジョブを実行する、みたいな複雑なことは一工夫必要かも
    • Webhook の設定だと無理なので、 build は実行しつつラベルが設定されていなかったら skip とかしないといけない

おまけ: CodeBuild 関連のニュース

以前検証したときより良くなってる気がするなと思って更新をチェックしたらやっぱ色々更新があるみたいですね。

https://aws.amazon.com/jp/new/?whats-new-content-all.sort-by=item.additionalFields.postDateTime&whats-new-content-all.sort-order=desc&awsf.whats-new-developer-tools=general-products%23aws-codebuild