Jenkins 2.0について書く記事の3回目です。今回は、前回書いたJenkins Pipelineを構成するDSLについて書きます。
フリースタイルプロジェクトのジョブ設定
1.x時代から存在したフリースタイルプロジェクトでは、Web上のUIを通してジョブを設定しました。この方法は、前回書いたように、わかりやすい反面いくつか問題点もありました。
- 機能が増えるにつれ設定UIが複雑になりがち
- 1つのジョブにつき1つのノードしか使えない
- デプロイパイプラインを構築するためには複数ジョブをつなげないといけない
- 設定の変更履歴を管理しづらい
Scripted Pipeline
2.0のPipelineでは、GroovyによるDSLが導入され、Pipeline as Codeとしてジョブを設定できるようになりました。DSLによる柔軟な表現力により、上記の問題が改善されました。
- 必要な設定だけコードとして書ける
- 1つのジョブの中で複数のノードを扱える
- 1つのジョブでデプロイパイプラインを表現できる
- バージョン管理しやすい
加えて、masterが再起動してもビルドを再開できる耐久性や、ユーザーからの入力を受け付ける仕組みといった、これまでにない機能を使えるようになりました。
しかし、初期のパイプラインの構文(Scripted Pipeline)は、これまでのJenkinsの設定や他のCIツールにおけるyamlのようなわかりやすい設定に慣れたユーザーにとっては親しみにくいものでした。
withEnv(["GIT_COMMITTER_NAME = jenkins","GIT_COMMITTER_EMAIL = jenkins@jenkins.io"]) {
node('has-docker') {
try {
checkout scm
def myImage = docker.build 'my-environment:snapshot'
myImage.inside {
stage('Build') {
sh 'mvn clean install -Dmaven.test.failure.ignore=true'
}
stage('Archive') {
archive "*/target/**/*"
junit '*/target/surefire-reports/*.xml'
}
}
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
mail to:"me@example.com", subject:"SUCCESS: ${currentBuild.fullDisplayName}", body: "Yay, we passed."
}
}
catch (exc) {
mail to:"me@example.com", subject:"FAILURE: ${currentBuild.fullDisplayName}", body: "Boo, we failed."
}
finally {
deleteDir()
}
}
}
上の例は、Jenkins公式ブログの記事からの引用です。
内容としては、Dockerコンテナ内でmavenプロジェクトをビルドしてメール通知を飛ばすといったものですが、直観的には理解しにくいです。失敗したときにメール通知を投げるために try/catch
を書かなきゃいけないというのは、プログラマなら理解はできますが、CIのジョブ設定のためにこれを書くのはなかなか辛いものがあります。
Declarative Pipeline
上記のScripted Pipelineの問題点を解決するために、ジョブの設定を記述するための新しい構文が作られました。それが、Declarative Pipelineです。
Declarative Pipelineは元々はPipeline Model Definition Pluginとして開発されていましたが、今年の2/1に1.0になりました。それと同時にPipeline Pluginのdependenciesとなり、一緒にインストールされるようになりました。
Declarative Pipelineは、よりシンプルにセクションと呼ばれるブロックベースで記述します。
pipeline {
agent label:'has-docker', dockerfile: true
environment {
GIT_COMMITTER_NAME = "jenkins"
GIT_COMMITTER_EMAIL = "jenkins@jenkins.io"
}
stages {
stage("Build") {
steps {
sh 'mvn clean install -Dmaven.test.failure.ignore=true'
}
}
stage("Archive"){
steps {
archive "*/target/**/*"
junit '*/target/surefire-reports/*.xml'
}
}
}
post {
always {
deleteDir()
}
success {
mail to:"me@example.com", subject:"SUCCESS: ${currentBuild.fullDisplayName}", body: "Yay, we passed."
}
failure {
mail to:"me@example.com", subject:"FAILURE: ${currentBuild.fullDisplayName}", body: "Boo, we failed."
}
}
}
こちらのDeclarative Pipelineの例も先ほどと同じ記事からの引用で、同じ内容のジョブ設定を表します。
pipeline {...}
で囲われたブロック内でDeclarative Pipelineの構文は有効になり、それぞれのブロックが1つ1つのジョブの設定を表します。例えば、 agent
はビルドを行うノードの設定を表しています。
Scripted Pipelineで try/catch
で表現されていたビルド後の処理の設定は、Declarative Pipelineでは post
というブロックでわかりやすく表現されるようになっています。
もし、Declarative Pipelineの中でScripted Pipelineのように if
や try/catch
のようなプログラム的な命令処理を書きたい場合は、 script
というブロックを使うことによって記述できるようになっており、両方の構文のメリットを組み合わせた記述も可能になっています。なので、今後はDeclarative Pipelineをメインとして使い、Scripted Pipelineは柔軟な表現を使いたい場面だけで使うようになるのではないかと思います。
Declarative Pipelineについての詳細なドキュメントは、現時点ではJenkins公式のドキュメントかPluginのGitHub Wikiを読むのがいいと思います。
まとめ
- Jenkins PipelineにはScripted PipelineとDeclarative Pipelineの2つの構文が存在する
- Scripted Pipelineは柔軟な表現ができる一方で複雑だった
- Declarative Pipelineはシンプルな設定ができ、Scripted Pipelineの構文を使うこともできる
2.0がリリースされたころはDSLの複雑さが自分としても懸念でしたが、Declarative Pipelineの登場によりかなり書きやすくなったのではないかと思います。yamlほどシンプルではないかもしれませんが、そのぶん複雑なパイプラインを表現できるところが魅力だと思います。
Web上にはScripted Pipelineの構文しかない時点で書かれた情報があるので、そのあたりは注意が必要です。
次回は、少しパイプラインから離れてGitHub連携について書く予定です。