つばくろぐ @takamii228

知は力なり

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とは

f:id:takamii228:20180906234114p:plain

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に記載してソースレポジトリと一緒に管理しています。

f:id:takamii228:20180906234505p:plain

GitLabへのPushやMerge RequestをトリガーにJenkinsが実行され、Jenkinsで実行されるビルドの中でAWS CodeBuildを実行します。

JenkinsからAWS CodeBuildを呼び出すのはJenkinsのAWS CodeBuild Pluginを使います。これを使うことでAWS CodeBuildのログもJenkins側のログから見ることができます。

github.com

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は立っていますが、まだ取り込まれてはいないようです。

並列数の設定をお忘れなく

同時実行できるビルド数はビルドプロジェクトで定義するCompute Typeによって変わります。そのため強いCompute Typeを使う場合は別途上限緩和申請が必要となります。

  • 同時実行ビルドの最大数の制限は、コンピューティングタイプによって異なります。一部のコンピューティングタイプでは、デフォルトは 20 です。新規アカウントの場合、この制限は 1 ~ 5 です。同時ビルドの制限の引き上げをリクエストするには、または「アカウントのアクティブなビルドは X 以上持つことはできません」というエラーが発生した場合は、AWS サポートに連絡してください。

また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で運用している方はぜひ参考にしてみてください。

github.com

おまけ

先日ちょうど @moznion ( id:moznion ) さんもほぼ同じ発表されててとても親近感を感じました。

こちらではAWS CodeBuildに加えてAWS EFS を使う例も紹介されています。

moznion.hatenadiary.com

参考資料

JenkinsのMultibranch PipelineでGitLab PluginのaddGitLabMRCommentが動かない

はじめに

tag pushに続いて、Jenkins Multibranch Pipeline + GitLab Pluginネタです。

takamii.hatenablog.com

GitLabプラグインDSLの中にaddGitLabMRCommentとあったので、Merge RequestのCIの結果をJenkinsからGitLabにコメント連携できるやんけ、と思って設定してみました。

https://jenkins.io/doc/pipeline/steps/gitlab-plugin/#addgitlabmrcomment-add-comment-on-gitlab-merge-request

動かない。

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の連携方法はこちらをご覧ください↓

qiita.com

Issueが上がってた

あれれーと思って調べていたらIssueが上がっていた。

github.com

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.

github.com

IntegrationのところでTestしたら409が返ってきた。

stackoverflow.com

ちーん。

GitLabつらいのでGHEつかいたい。

JenkinsのMultibranch PipelineではGitLabのtag pushビルドが走らない

Multibranch PipelineでTagが拾えるようになったらしい

Multibranch PipelineジョブのJenkinsfileの中で、when句にtagが使えるらしい。

jenkins.io

これを使えばレポジトリへのtag Pushを契機にリリース資材をライブラリに追加したりS3に配置したりなんてことが可能になる!と思ったので試してみました。

GitLabでは自動実行されなかった

GitLabとJenkinsの連携手順は以下を参照。

Webhookの発行のところのTriggerのTag Pushにチェックを入れればGitLabからJenkinsへWebhookが飛ぶようになります。

qiita.com

実際に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に対する記述はありません。

github.com

ソースコードを読んでみても、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;
...

他にも困っている人がいた

ググると騒いでいる人々を発見。やはりできないっぽい。

回避方法

仕方ないのでブランチPushでブランチ名からtag名を取得して、Jenkinsfile内部でtagをレポジトリへpushするように設定した。

やり方はこちらを参考にした。

int128.hatenablog.com

GitLab APIGitHub APIは結構違う

ドキュメントを見るとGitLabとGitHubAPIのパラメータは構造もパラメータ名も結構違う。

世の中の大半はGitHubで動いてるっぽいので、GitLab勢にとっては世知辛い

GitLabのWebhookをGitHub API互換に変換する何かがあるとみんな幸せになったりするのだろうか🤔🤔🤔