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 を使う例も紹介されています。
参考資料
JenkinsのMultibranch PipelineでGitLab PluginのaddGitLabMRCommentが動かない
はじめに
tag pushに続いて、Jenkins Multibranch Pipeline + GitLab Pluginネタです。
GitLabプラグインのDSLの中にaddGitLabMRComment
とあったので、Merge RequestのCIの結果をJenkinsからGitLabにコメント連携できるやんけ、と思って設定してみました。
動かない。
JenkinsfileのDeclarativeのpostの中でsuccessとfailureそれぞれにaddGitLabMRComment
を追加してみましたが動きません。
pipeline { agent any post { success { updateGitlabCommitStatus name: 'build', state: 'success' updateGitlabCommitStatus name: 'test', state: 'success' mattermostSend color: "good", message: ":smile: [${env.JOB_NAME}](${env.BUILD_URL}) のビルドに成功しました" addGitLabMRComment comment: ":smile: [${env.JOB_NAME}](${env.BUILD_URL}) のビルドに成功しました" //追記 } failure { updateGitlabCommitStatus name: 'build', state: 'failed' updateGitlabCommitStatus name: 'test', state: 'failed' mattermostSend color: "danger", message: ":dizzy_face: [${env.JOB_NAME}](${env.BUILD_URL}) のビルドに失敗しました" addGitLabMRComment comment: ":dizzy_face: [${env.JOB_NAME}](${env.BUILD_URL}) のビルドに失敗しました" //追記 } } ....
JenkinsとGitLabの連携方法はこちらをご覧ください↓
Issueが上がってた
あれれーと思って調べていたらIssueが上がっていた。
bugとなっていて、結びに以下のコメントが。
I think that's because this is a Multibranch Pipeline job. For those, MR hooks won't trigger.
どうやらMerge Requestのhookイベントがうまく連携できていない模様。
Multibranch PipelineがMerge Requestのフックがうまくうけとれないっぽい。
よく見たらREADMEに書いてあった
Multibranch Pipelineと相性悪いんかなと思ってREADME読んでたら書いてあるやんけ・・・。
Due to this, the plugin just listens for GitLab Push Hooks for multibranch pipeline jobs; merge Request hooks are ignored.
IntegrationのところでTestしたら409が返ってきた。
ちーん。
GitLabつらいのでGHEつかいたい。
JenkinsのMultibranch PipelineではGitLabのtag pushビルドが走らない
Multibranch PipelineでTagが拾えるようになったらしい
Multibranch PipelineジョブのJenkinsfileの中で、when
句にtagが使えるらしい。
これを使えばレポジトリへのtag Pushを契機にリリース資材をライブラリに追加したりS3に配置したりなんてことが可能になる!と思ったので試してみました。
GitLabでは自動実行されなかった
GitLabとJenkinsの連携手順は以下を参照。
Webhookの発行のところのTriggerのTag Push
にチェックを入れればGitLabからJenkinsへWebhookが飛ぶようになります。
実際にtag pushしてみましたが、Git PluginがTagに対応しているので、Tagに応じてジョブをdiscoverするものの自動実行されませんでした。
ログを見る限りWebhookは飛んでいるもののJenkinsがうまく受け取れていないようです。
GitLabプラグインの中を見てみる
README.mdにtag pushについては以下のことしか書いてなかった。
In order to build when a new tag is pushed:
1. In the GitLab webhook configuration, add 'Tag push events'
2. In the job configuration under 'Source code management':
i. Select 'Advanced...' and add '+refs/tags/:refs/remotes/origin/tags/' as the Refspec
ii. You can also use 'Branch Specifier' to specify which tag need to be built (example 'refs/tags/${TAGNAME}')
これはフリースタイルでジョブを作ったときの設定で、Multibranchに対する記述はありません。
ソースコードを読んでみても、TagPushTreggerに関するコードは見当たらなかったので実装がないっぽい。
gitlab-plugin/GitLabPushTrigger.java at master · jenkinsci/gitlab-plugin · GitHub
... private boolean triggerOnPush = true; private boolean triggerOnMergeRequest = true; private boolean triggerOnPipelineEvent = false; private boolean triggerOnAcceptedMergeRequest = false; private boolean triggerOnClosedMergeRequest = false; private boolean triggerOnApprovedMergeRequest = false; ...
他にも困っている人がいた
ググると騒いでいる人々を発見。やはりできないっぽい。
- [JENKINS-45838] MR/PR or Tag push from Gitlab to multibranch pipeline - Jenkins JIRA
- Jenkins users - Multibranch pipeline / Gitlab integration: tag push events
回避方法
仕方ないのでブランチPushでブランチ名からtag名を取得して、Jenkinsfile内部でtagをレポジトリへpushするように設定した。
やり方はこちらを参考にした。
GitLab APIとGitHub APIは結構違う
ドキュメントを見るとGitLabとGitHubのAPIのパラメータは構造もパラメータ名も結構違う。