Jenkins Agentの運用が辛いのでAWS CodeBuildを使う
Jenkins Agentの運用のつらみ
ピーク時に数十人が同時に使うような大きめなプロジェクトでJenkinsをMaster / Agent構成で利用していると以下のようなつらみがあります。
- 開発が活発な時はビルドキューが溜まってJenkinsのビルドの待ち時間が発生する
- 開発が落ち着いている時はAgentが遊んでいてサーバの稼働費用が無駄
- 長期間運用しているとAgentサーバのツールバージョンアップ等のメンテがつらい
PushトリガーによるCI/CD環境を構築すると開発が盛り上がってるときには1日に数十回Jenkins上でビルドが走ります。Pull Requestを投げた後にビルド中にトイレに行って返ってきたら、他の人が先にマージされてて自分のPRにConflictが発生してると「またビルドを待つのか...😇」という残念気持ちになりますよね。
Agentサーバを増やすという解決策もありますがビルドサーバ分のお金がかかりますし、開発の盛り上がりに合わせて手動で上げたり下げたり都度Jenkinsの設定するのも面倒です。
GitHub + CircleCI使えばいいって?
大人の事情で使えない人もいるんですよ!!!
本記事ではこんな課題を持っている人のために、JenkinsからAWS CodeBuildを利用する方法を紹介します。
※注意事項
本記事で紹介する内容は2018年9月7日時点での内容をもとに記載しており、今後のAWSのアップデートによっては最適な解決策でなくなる場合があります。
AWS CodeBuildとは
AWS CodeBuildはAWSが提供するマネージドのコンテナビルドサービスです。詳細は公式のページに記載されていますが、要点を抜粋すると以下のような特徴があります。
- 指定したランタイムのコンテナイメージによるコンテナビルドを実行してくれる
- AWS ECRに登録した自作のコンテナイメージも指定できる
- 最大20並列でコンテナビルドできる
- かかる費用はビルド時間単位の従量課金
CodeBuildはマネージドサービスのため運用コストの削減が期待できます。
また費用はビルド時間単位の従量課金制のため、Jenkins AgentとしてEC2を常時複数台起動させておくよりもコスト削減が望めます。
何より、最大20並列で実行できるためビルドの待ち時間をほぼなくすことができます!!!
ビルドで利用するコンテナイメージはAWSでデフォルトで用意されているものがありますが、ランタイムのバージョンが実環境のものと違っていたりモジュールが不足してたりするため、CI/CDで実行する内容に合わせて独自のイメージを作ってECRに登録して使うとよいでしょう。
GitLabとJenkinsとAWS CodeBuildの連携
私の環境ではレポジトリとしてGitLabを使っているので、以下のような構成で連携しています。
Jenkinsのビルドの定義をJenkinsfileに、AWS CodeBuildのビルド定義をbuildspec.ymlに記載してソースレポジトリと一緒に管理しています。
GitLabへのPushやMerge RequestをトリガーにJenkinsが実行され、Jenkinsで実行されるビルドの中でAWS CodeBuildを実行します。
JenkinsからAWS CodeBuildを呼び出すのはJenkinsのAWS CodeBuild Pluginを使います。これを使うことでAWS CodeBuildのログもJenkins側のログから見ることができます。
AWS CodeBuildのかゆいところ
次に上記の構成で実際に使ってみてハマった点やTipsを紹介します。
AWS CodeBuildのArtifactはブランチに対応してない
AWS CodeBuildではビルドの成果物をArtifactとして定義することでS3に置いてくれる機能を持っています。しかし、この機能はブランチによる分岐ができません。 そのためJenkinsのMultibranch Pipelineで使う場合には毎回ArtifactがS3にPutされてしまいます。
仕方ないので、Jenkinsfileの中でブランチの情報を環境変数でAWS CodeBuildに連携して、ブランチ名を見て分岐してaws s3コマンドでS3にPutするようにしています。
Jenkinsfile
... stages { stage('codebuild') { steps { awsCodeBuild( credentialsType: 'keys', projectName: 'projectName', region: 'ap-northeast-1', sourceControlType: 'project', sourceVersion: env.BRANCH_NAME, envVariables: "[{BRANCH_NAME,${env.BRANCH_NAME}}]", ) } } ...
buildspec.yml
... phases: ... build: commands: - ./build.sh - ./test.sh package: commands: - | if [ -z "${BRANCH_NAME%%release/*}" ]; then ./package.sh fi post-package: commands: - | if [ -z "${BRANCH_NAME%%release/*}" ]; then aws s3 cp /path/to/artifacts "s3://path_to_artifacts_bucket/${BRANCH_NAME#release/}/artifacts" fi ...
GitLabから直接AWS CodeBuildは呼べない
現在AWS CodeBuildのレポジトリとして公式にサポートしているのはAWS CodeCommit、GitHub、BitBucketのみです。
GitLab、GitBucket等のオンプレ向けのGitホスティングサービスとは、GitHub Enterprise
を選択することで連携できていますが、当然WebHookから直接起動することはできません。
逆に言えば、間にJenkinsを間に挟むことでレポジトリへのPushやPull/Merge Request契機でAWS CodeBuildを実行することができるようになっています。
GitBucketとGitLabにはIssueは立っていますが、まだ取り込まれてはいないようです。
- AWS CodeBuild requires a very long webhook URL and token · Issue #1951 · gitbucket/gitbucket · GitHub
- Integrate GitLab with AWS CodeBuild (#36175) · Issues · GitLab.org / GitLab Community Edition · GitLab
並列数の設定をお忘れなく
同時実行できるビルド数はビルドプロジェクトで定義するCompute Typeによって変わります。そのため強いCompute Typeを使う場合は別途上限緩和申請が必要となります。
またAWS CodeBuildの並列数はJenkinsの同時ビルド実行数に依存するので、Jenkins側の設定でMasterノードの同時実行数の設定を忘れないようにしましょう。
Cacheを使ってビルド時間を削減する
AWS CodeBuildは実行時間単位で課金されるため、なるべく実行時間が少なくなるような工夫が必要です。
AWS CodeBuildにはビルドでキャッシュしたいフォルダをS3に定義できるので、モジュールやテスト結果等キャッシュ可能な内容はcache
ディレクティブに定義しておくと良いでしょう。
buildspec.yml
... cache: paths: - /path/to/cache/*
終わりに
Jenkinsを運用する上で、ビルドで待ち時間が発生する・Agentサーバのメンテナンスコストがかかるという課題に対する解決策として、AWS CodeBuildを活用する事例を紹介しました。
JenkinsをAWSで運用している方はぜひ参考にしてみてください。
おまけ
先日ちょうど @moznion ( id:moznion ) さんもほぼ同じ発表されててとても親近感を感じました。
こちらではAWS CodeBuildに加えてAWS EFS を使う例も紹介されています。