Jenkins Pipelineのハマりどころ(僕がハマったところ)

  • 3
    いいね
  • 0
    コメント

最近はCIといえばConcourseとかが流行っていますがJenkinsもpipelineがデフォルトで入るようになって昔とは変わってきました。

昔は「GUIついてるだけのcron」とか「結局Jenkins内のshellスクリプトが秘伝のタレになる」とか言われていました。
今はJenkinsfileというスクリプト(GroovyによるDSL)をプロジェクトのルートに置いておけばそこに対する更新そのものもgit等のvcsからJenkinsの作業内容そのものをバージョン管理できます。
更にはJenkinsfileの中で適切な記述をすることで操作を並列化できるのでビルド時間の短縮もできます。
そしてビルドの様子が(プラグインを設定すれば)いい感じに可視化されるので使っていて楽しいです。

簡単な使い方に関してはこのスライドとかこのスライドとかを見てもらうとして、この記事では実際に使い込んでみて僕がハマった内容と対策を共有します。

parallel でパラメータ化した物を並列化したいんだけど?

Jenkinsfileには並列実行のための仕組みがあります。

parallel(
'production': {
  sh "mkdir production"
  sh "cd production && ../configure"
},
'test': {
  sh "mkdir test"
  sh "cd test && ../configure"
})

これは parallel という関数にmapを渡しているに過ぎないので、増えてくるようであれば当然こう書きたくもなります。

ダメな記述
def configs = [:]
for (conf in ["production", "test", "development", "deployment"]) {
  configs[conf] = {
    sh "mkdir ${conf}"
    sh "cd ${conf} && ../configure"
  }
}
parallel(configs)

これは文法上セーフですが期待通りには動きません。forループの中ではconfという参照に配列の中身が順番に渡されているだけなので、configsの右辺のブロックの中を評価するタイミングの頃にはクロージャで包まれた値が指す参照先は変わっています。上の例だとループを抜けるタイミングでは conf"deployment" なので conf="deployment" のブロックが4回現れるだけになります。
そういう時はクロージャの中からちゃんと値が指せるようにバインドしてやれば解決できます。

正しい記述
def configs = [:]
for (conf in ["production", "test", "development", "deployment"]) {
  def c = conf  // これで解決
  configs[c] = {
    sh "mkdir ${c}"
    sh "cd ${c} && ../configure"
  }
}
parallel(configs)

if文でビルドの通るパスを調整したいんだけど?

stage("hoge") と記述すれば名前の付いたステージを作れます。
ビルドのパラメータ化 にチェックを入れれば、ビルド時にパラメータを与える事ができるので「普段はいつものパスだけど、手動でvalgrindを使う版のテストもキックしたい」みたいな時にパラメータから挙動を変える事ができます。

bparam.png

なのでついうっかりこんな記述をしそうです

ダメな記述
if (test) {
  stage('test stage') {
    sh './some_test_script.sh'
  }
}

Jenkinsfileは宣言的な記述をする物で、手続き的な記述は推奨されていません。

実はまだダメな記述
stage('test stage') {
  if (test) {
    sh './some_test_script.sh'
  } else {
    echo 'test is skipped.'
  }
}

こんな感じに、ifによる分岐はステージの中に書きましょう。
変数の状態に限らず、stageの状態は一致するように書けばおそらく混乱はしにくいはず。
(この仕様は後々変わるかも知れないので別の書き方が推奨される場合はコメントで教えてください)

で、更にビルドのパラメータには真偽値や文字列が与えられるので真偽値でスイッチをしたくなりますが、変数を真偽値として扱ってはいけません。

if (test) {  // これは常に真になる
  echo "test is ${test}"  // -> test is false.
  echo "type is ${test.class}"  // type is class java.lang.String
}

文字列しか渡せないつもりで扱いましょう。
文字列の "true"true に変えてくれる便利メソッド toBoolean() が便利です。

if (test.toBoolean()) {
  echo "test is true"  // falseの時はそもそもここに至れない
}

Parallelの中でstageしたいんだけど?

できません 少なくとも手元のバージョン(2.7.4)では。
http://stackoverflow.com/questions/36872657/running-stages-in-parallel-with-jenkins-workflow-pipeline
https://issues.jenkins-ci.org/browse/JENKINS-27394 in Review
https://issues.jenkins-ci.org/browse/JENKINS-33185 Open

そのうち解決されると思いますが、visualizationまで追いつくのはもう少し掛かりそう。