Jenkinsfile、書いてますか?

インフラの駒崎です。Jenkins の Pipeline スクリプトについてのお話です。

早速ですが Jenkins の Pipeline スクリプト、使われていますでしょうか。

もしかしたら以前ちょっと書いていたけどやめてしまったとか、従来の GUI 設定のほうが楽だ、となんとなく敬遠してしまっている方もいるのではないでしょうか。

私が実際そうだったのですが、最近になってやっと Jenkinsfile - Pipeline スクリプトが身近に感じられてきましたので、現状の簡単なまとめを書いてみたいと思います。少しでも似た状況の方へのヒントやきっかけになれば幸いです。

Pipeline スクリプトは難しい?

私は正直、2016年に Jenkins 2 の目玉機能として Pipeline が出た当初は、とっつきにくい…わからん…と思っておりました。Jenkins を上っ面でなんとなく使っていた自分のような人間には難しかったです。

しかしその後リリースされた Declarative Pipeline による Syntax 刷新や、Pipeline 関連のプラグイン、ドキュメントの活発な更新などを経て、今では旧来の GUI によるポチポチと同等かそれ以上に手軽になっていると感じます。

Declarative Pipeline とは

Pipeline スクリプトには現状 2 種類の記法があります。

  • Jenkins v2.0 と共にリリースされた Scripted Pipeline
  • より簡潔に書けるようになった Declarative Pipeline (2017/02~)

Pipeline の公式ドキュメントにも比較が説明されていますが、 前者は Groovy のコードを直接扱えることで高い柔軟性を持つ反面、スクリプト感が全面に出ており扱いにくさもあります。後者の Declarative Pipeline はシンプルに基本的なジョブ定義を記述でき、また script ブロックを使うことで部分的に Scripted Pipeline 同様の処理をさせることもできます。

これから新しく書きはじめる場合は Declarative Pipeline を使うようにするのがベターかと思います。

Web 上の情報を調べる際はどちらに言及しているのか意識しておくほうが混乱が少ないでしょう。基本的には pipeline {} で始まっているものが Declarative Pipeline、node {} で始まっているものが Scripted Pipeline です。

スクリプトの開発

なんと言っても公式ドキュメントの情報が充実しておりますが、 一部抜粋、補足して紹介いたします。

Generator

スクリプトの書き方に詰まったら Generator が便利です。現在は 2種類の Generator が用意されており、各所に "Pipeline Syntax" のリンクとして置かれています。

  • Snippet Generator
    • <jenkins host>/job/<job name>/pipeline-syntax/
    • steps ブロック内に書く個別の step を生成
  • Declarative Directive Generator
    • <jenkins host>/job/<job name>/directive-generator/
    • Declarative Pipeline で使うディレクティブを生成

スクリプトの大枠はここからの切り貼りで揃えられると思います。step の引数やディレクティブのオプションがわからないときに便利です。

Lint & BlueOcean Editor

Declarative Pipeline は Jenkins 本体による lint ツール が用意されています。

直接 SSH するのが難しい環境の場合は HTTP の口も用意されていますがこちらは少々複雑です。

私はよくこちらの npm パッケージを使わせていただいております。コマンド一発で lint を叩けます。製作者様に感謝。

他の方法として、Blue Ocean プラグイン を導入しているのであれば、Blue Ocean Editor を使うこともできます。2018/07 現在、エディタのフル機能は Git レポジトリをソースにしたジョブに限られているようですが、<jenkins host>/blue/organizations/jenkins/pipeline-editor/ に直接アクセス & Ctrl+s とすることで、Git レポジトリに関係なくエディタを使うことができます。

テキストエディタ上では前述の lint 同等のチェックをしてくれます。lint が通ったものについては各 stage を視覚化して表示します。

ただしこの使い方では直接ジョブへ保存はできないので、手元のコードをコピペで貼り付けてチェックしたり、全体の流れを確認する程度になります。

lint 全般は Scripted Pipeline には対応しません。また Declarative Pipeline 中の script {} ブロック中も検出できませんので、各種エディタや IDE による groovy スクリプトのチェックを組み合わせるのがよいでしょう。

Replay

lint ではロジックのミスは拾えませんので、ちょっとした手直しを繰り返すことも多いかと思います。

Replay は実行済みの Pipeline スクリプトを直接ブラウザで編集して再実行することができる強力な機能です。実行済みのスクリプトに一部変更を加えて動作を確認できます。

編集した分の保存はされないので、修正を確認できたら元のスクリプトに反映する必要があります。

Jenkinsfile をちまちま commit & run しては履歴を汚してしまいますので、Replay でしっかり直してから一発で修正反映できるのは嬉しい機能です。

事例集

いくつか実際に使っているサンプルを紹介いたします。
挙げ始めればきりがありませんが、Pipeline スクリプト独特のものや、私の主観で特に便利さを感じたものを挙げてみます。

Declarative Pipeline の発表から1年以上も経過し Web 上の情報も多いのでいまさら感もありますがそこはご容赦ください。

credentials / sshagent / withCredentials

特に業務利用においては、秘密鍵やアクセストークン、ユーザ/パスワードペアといった機密情報を扱うジョブを作る機会も多いでしょう。
こういった機密情報は Jenkins 本体の認証情報 (Credentials)に登録し、ジョブ設定からは登録済みの Credentials を ID で引いてきて使うことになります。従来のジョブでは GUI の select から選択になるので生の ID を気にすることはありませんでしたが、Pipeline スクリプトでは ID が直接コードに乗ることになります。

そのため認証情報を登録するときには、ID を空欄にせずわかりやすい名前を付けることをおすすめします。空欄でも登録できますがその場合の ID はランダム文字列になるため、あらかじめ名前を指定したほうがスクリプト側の可読性が高まりますし、もしも Jenkins 側で情報が吹っ飛んだ場合や別 Jenkins にジョブを移す場合にも同じ ID をつければスクリプトそのままで使用できます。

docker を使いたい

docker を使いたい、と言う場合は多くの場合下記のどちらかではないでしょうか。どちらも Docker pipeline プラグインでサポートされています。

  • ビルド環境を docker で用意したい
  • docker イメージをビルドしたい

Declarative Pipeline が使える状況であれば Docker pipeline プラグインは依存関係に含まれているので、docker コマンドが使用可能なノードさえ用意しておけば簡単に docker を使ったジョブを扱うことができます。

例によって公式ドキュメント が非常に参考になります。

以下の例では docker コマンドが使えるノードを label 'docker-host' で参照できるものとします。

ビルド環境に docker コンテナを使う

公開されているイメージや、自前の Dockerfile で定義したイメージをジョブ実行環境に使うには、agent ブロックで docker {} または dockerfile {} を指定すればOKです。

自動的に WORKSPACE をコンテナ内にマウントし、環境変数をよい具合に docker run -e に渡してくれますので、docker 内であることをあまり意識せずに steps を書くことができます。

以下は Ubuntu の2種類のバージョンで並列ビルドを行う例です。

環境変数の引き継ぎにより先に挙げた sshagent() {} も非 docker 時と同じように使えています。また、ビルドした成果物を保存したいような場合は、WORKSPACE 以下に出力すればホストに残るので普段通り扱うことができます。

注意点としてはデフォルトでは Jenkins ユーザの UID で docker run が実行されるので、パーミッションの都合があれば必要に応じて args '-u 0:0' などを追加するとよいでしょう。

他には永続化ストレージの例として、以下は bundle install の結果を docker volume 内にキャッシュして2回目以降のビルドを高速化します。

いずれも表現力の高い構文で記述できるようになっていると思います。 docker 環境を気軽に使えるとノードの実行環境準備に手間がかかりませんし、テストやビルド環境を Jenkinsfile (+Dockerfile) に記述できるので、関心が対象レポジトリに(ほぼ)閉じることにもなります。

他にも、例えば本体のアプリとは別の DB コンテナを立ち上げたい場合には、Running sidecar containers のパターンもドキュメントに記載されています。ですが、個人的には複数のコンテナを扱いたくなったらそれ用の docker-compose 設定を用意するほうがシンプルかとは思います。

docker イメージをビルドして push する

弊社でもよく使っている Amazon ECR へ push する例です。

docker メソッドは script ブロック内で呼び出します。

こちらも簡単ですね!

GitHub Pull Request をビルドしたい

従来のジョブでは Github Pull Request Builder を使っていた方も多いのではないでしょうか。完全に同じ機能ではありませんが、Pipeline スクリプト向けには GitHub Branch Source プラグイン が使えます。organization を指定しておけばレポジトリごとにジョブを作る必要なく、自動的に Jenkinsfile が置いてあるブランチをスキャンしてビルドさせることもできます。

新規ジョブの作成時に GitHub Organization を指定し、検出条件や Credentials を設定するだけです。もちろん GitHub Enterprise でも使えます。

詳細設定については GtHub Branch Source や旧名の GitHub Organization Folder 等で検索いただければ日本語記事を書かれている方も多いので本稿では割愛いたします。文末のリンクも合わせてご参照ください。

このケースでは Jenkinsfile 側の Declarative Pipeline スクリプトで特に明記しなくても、ビルドの成否によって Pull Request (commit) のステータス更新が行われます。
また、ソースの checkout も自動的に行われます。

まとめると、Pull Request をビルドしてステータスを更新するには、最初に organization に紐づくジョブを1つ作っておき、Jenkinsfile の置いてある branch で Pull Request すればよい、ということになります。

Jenkinsfile のスクリプトは最小でこれだけで OK です。

他のジョブにパラメータを渡してビルドしたい

Pipeline スクリプトではビルド中に得られた値を簡単に別のジョブに渡せます。従来のジョブでは Parameterized Trigger プラグインなどを用いて中間ファイルを介するなどワンステップが必要でしたが、Pipeline スクリプト中ではグローバル変数や環境変数を使って値を使い回すことができます。

代入には script {} が必要なのでややスクリプト感はでてしまうものの、まあまあ簡単です。

さいごに

最近は CI/CD as a Service の選択肢も多く、Jenkins を立てるよりも外部サービスを使うほうが手軽に目的を達成できる場合もあると思います。Jenkins サーバの運用は決して楽ではないので、運用しないならそれに越したことはないと思います。しかし、諸々の事情により使えないケースもありますし、とりあえず Jenkins を導入する障壁は低いので、今も CI/CD の有力な候補であると思います。

グリーでも Jenkins は多くのプロジェクトで使用されていますが、Pipeline スクリプトの活用事例はまだまだ少ないです。
自分もまだ人になにか言うほど Pipeline スクリプトを使いこんではいませんが、便利さの割に情報が少なめかなと感じましたので、もうすこし広まって活発に情報が流れてほしいなと思います。

参考