読者です 読者をやめる 読者になる 読者になる

SIerだけど技術やりたいブログ

5年目のSIerのブログです

jenkinsのpipeline入門(jenkinsfile)

本記事について、Jenkinsのpipelineが最近(2017/2/20時点で)DSLがごっそり入れ替わったっぽいので内容が古いです。注意。


Jenkins2では、Groovy DSLを用いたpipelineの記述ができるようになったらしい。若干の時代遅れ感があるけど、最近仕事で使う機会があり、土日にわからないところを整理したのでメモる。

ジョブ定義を画面からぽちぽちするなんて時代遅れ!技術者として恥ずかしい!これこそがモダン!とか煽るつもりはまったくないけど、ジョブ定義の柔軟性が高いし、パイプラインに人の承認フローが組み込めたり、実行ノードを簡単に選べるし、何よりコード管理できるので、普通に良さそうだと思いました。情報が少ないことを除けばな!

Jenkins2のインストー

公式サイトからwarを落とすなりdockerで起動するなりして、Jenkinsをインストールする。(今回利用したのはver 2.32.1)

f:id:kimulla:20170128200815p:plain

ジョブを作る。

f:id:kimulla:20170128201726p:plain

通常はジョブをJenkinsfileに記述し、リポジトリのトップディレクトリに置いておく。
が、今回は勉強が目的なのでここにジョブを記述する。

f:id:kimulla:20170130224408p:plain

とりあえず動かす

とりあえず動かしてみる。

node {
    print "Hello World"
}

実行結果は

f:id:kimulla:20170130224816p:plain

コンソール出力で確かめる。

f:id:kimulla:20170130224632p:plain

とりあえず動いた。


node

実行ノードを指定できる。Jenkinsが複数のslaveを持つときに、nodeの引数にslave名を指定する。

slaveをひとつ作って試してみる。

左側のビルド実行状態 > 新規ノード作成 から、以下の通り作成する。

f:id:kimulla:20170130225138p:plain

slaveが作れた。

f:id:kimulla:20170130225159p:plain

nodeの引数にslave名を指定する。

node('slave') {
    print "hello world"
}

実行してコンソール出力を確かめると、slaveで実行されていることがわかる。

Started by user root
[Pipeline] node
Running on slave in /tmp\jenkins/slave\workspace\pipeline-sample
[Pipeline] {
[Pipeline] echo
hello world
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

以下のように否定も書ける。

node('!master') {
    ....
}

環境変数の参照

job内では複数の環境変数を参照できる。

node {
    print "BUILD_NUMBER: ${env.BUILD_NUMBER}"
    print "BUILD_ID: ${env.BUILD_ID}"
    print "WORKSPACE: ${env.WORKSPACE}"
    print "JENKINS_URL: ${env.JENKINS_URL}"
    print "BUILD_URL: ${env.BUILD_URL}"
    print "JOB_URL: ${env.JOB_URL}"
}

実行してコンソール出力を確かめる。

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] echo
BUILD_NUMBER: 2
[Pipeline] echo
BUILD_ID: 2
[Pipeline] echo
WORKSPACE: /var/jenkins_home/workspace/pipeline-sample
[Pipeline] echo
JENKINS_URL: http://192.168.11.100:18080/
[Pipeline] echo
BUILD_URL: http://192.168.11.100:18080/job/pipeline-sample/2/
[Pipeline] echo
JOB_URL: http://192.168.11.100:18080/job/pipeline-sample/
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

参照できる環境変数の一覧は以下から確認できる。

f:id:kimulla:20170130230007p:plain

f:id:kimulla:20170130230017p:plain

f:id:kimulla:20170130230037p:plain


変数の定義

Groovyなので、変数定義や展開ができる。

def MESSAGE='sample'

node {
    print MESSAGE
    print "展開される ${MESSAGE}"
    print '展開されない ${MESSAGE}'
}

実行してコンソール出力を確かめる。ダブルクオートは中の変数が展開されるけど、シングルクオートは展開されないことがわかる。(Groovyの構文覚えないといけない)

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] echo
sample
[Pipeline] echo
展開される sample
[Pipeline] echo
展開されない ${MESSAGE}
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

制御文

Groovyなので、制御文も書ける。

node {
    for (i in 0..9) {
        println i
    }
}

ただセキュリティの観点から、Groovy DSLは色々と機能を絞ったサンドボックス上で実行されるので、デフォルトだとRejectedAccessExceptionになる。

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter createRange java.lang.Object java.lang.Object boolean
	at org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectStaticMethod(StaticWhitelist.java:192)
	at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onStaticCall(SandboxInterceptor.java:142)
	at org.kohsuke.groovy.sandbox.impl.Checker$2.call(Checker.java:180)
	at org.kohsuke.groovy.sandbox.impl.Checker.checkedStaticCall(Checker.java:177)

実行したければ Use Groovy Sandbox のチェックを外すか、

f:id:kimulla:20170130230516p:plain

Jenkinsの管理からScriptを許可する。

f:id:kimulla:20170130230420p:plain

f:id:kimulla:20170130230428p:plain


ここらへんは詳しく書いている人がいたのでそっちを参照する。

arasio.hatenablog.com


Groovyのメソッド

Groovyのメソッドも書ける。(やっぱりセキュリティにひっかかったりする)

node {
    println new Date().format("yyyyMMddHHmmssSSS")
}

実行してコンソール出力を確かめると、確かにメソッドが実行できる。

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] echo
20170128044540483
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

stage

テスト→ビルド→デプロイ、みたいにビルドのステージを定義できる。

node {
    stage('build') {
        print "build"
    }
    
    stage ('test') {
        print "test"
    }
    
    stage ('deploy') {
        print "deploy"
    }
}

実行するとステージごとにステータスが見れる。

f:id:kimulla:20170130231201p:plain


あるステージで失敗した場合、ステージの表示が赤になる。
error という処理を途中で中断したいときに使うコマンドを使って失敗させてみる。

node {
    stage('build') {
        print "build"
    }
    
    stage ('test') {
        error "failed"
    }
    
    stage ('deploy') {
        print "deploy"
    }
}

f:id:kimulla:20170130231304p:plain

コンソール出力からもERRORが起きてることがわかる。

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] echo
build
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] error
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
ERROR: failed
Finished: FAILURE

throw new Exceptionでも同じことができる。
ただしコンソール出力にスタックトレースが出る点がerrorコマンドと異なる。

node {
    stage('build') {
        print "build"
    }
    
    stage ('test') {
        throw new Exception("failed")
    }
    
    stage ('deploy') {
        print "deploy"
    }
}
Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] echo
build
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (test)
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new java.lang.Exception java.lang.String
	at org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectNew(StaticWhitelist.java:187)
	at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onNewInstance(SandboxInterceptor.java:130)
	at org.kohsuke.groovy.sandbox.impl.Checker$3.call(Checker.java:191)

※ errorコマンドも内部ではAbortExceptionという例外をスローしてるっぽいけど、正直よくわかってない。


失敗しても処理を継続したい場合

何を失敗とするのかはstepによるけど、各stepは、失敗した場合にExceptionをスローするようになっている。

失敗しても処理を継続したい場合は、try-catchすればいい。

node {
    stage('stage1') {
        try{
            print "try"
            error "failed"
        } catch(Exception e) {
            print "catch"
        } finally {
            print "finally"
        }
    }
}

ただしこの場合は例外をにぎりつぶしてるので、ビルド結果がsuccessになってしまう。

f:id:kimulla:20170130231623p:plain

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] stage
[Pipeline] { (stage1)
[Pipeline] echo
try
[Pipeline] error
[Pipeline] echo
catch
[Pipeline] echo
finally
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

currentBuild.result

例外ハンドリングしつつ全体のステータスをfailにしたい場合は、currentBuild.resultを利用する。

node {
    stage('stage1') {
        try{
            print "try"
            error "failed"
        } catch(Exception e) {
            currentBuild.result = 'FAILURE'
        } 
    }
    stage('stage2') {
        print "stage2"
    }
}

こうすると、次のステージに進みつつ全体をFAILUREにできる。

f:id:kimulla:20170130231659p:plain


ステージは進めるけどステージごとに成功、失敗を表示させる、というのはできないらしい。ただし、JenkinsのJIRAに上がってるのでそのうち対応される感はある。
https://issues.jenkins-ci.org/browse/JENKINS-26522

もしステージ内でエラーハンドリングして次のステージに進まずに失敗させたければ、例外を投げるかerrorを使うと良さそう。


sh

引数に与えられたシェルスクリプトを実行する。

node {
    sh "date"
}  

実行してコンソール出力を確かめる。

Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] sh
[pipeline-sample] Running shell script
+ date
Wed Jan 18 22:57:12 UTC 2017
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

shは別シェルとして実行されるため、シェル変数は引き継がれない点に注意。

node {
    sh "HOGE=xxx"
    sh "$HOGE"
}
Started by user root
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] sh
[pipeline-sample] Running shell script
+ HOGE=xxx
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
groovy.lang.MissingPropertyException: No such property: HOGE for class: groovy.lang.Binding
	at groovy.lang.Binding.getVariable(Binding.java:63)
	at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:224)
        at org.kohsuke.groovy.sandbox.impl.Checker$4.call(Checker.java:241)
	at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:238)
	at org.kohsuke.groovy.sandbox.impl.Checker.checkedGetProperty(Checker.java:221)
	at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.getProperty(SandboxInvoker.java:28)

exit codeが0以外の場合、例外が投げられる。

node {
    stage('stage1') {
        sh "true"
    }
    
    stage('stage2') {
        sh "false"    
    }
}

f:id:kimulla:20170130232701p:plain

コマンドを実行した結果が欲しい場合は以下のようにする。

node {
    OS = sh returnStdout: true, script: 'uname'
    print OS
}
[Pipeline] node
Running on master in /var/jenkins_home/workspace/pipeline-sample
[Pipeline] {
[Pipeline] sh
[pipeline-sample] Running shell script
+ uname
[Pipeline] echo
Linux

[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

input

パイプラインに、人のチェックを組み込める。

node {
    stage('test') {
        print "this is test"
    }
    
    stage('permit'){
        input 'Ready to go?'
    }
    stage('deploy') {
        print "start"
    }
}

実行してみると、permitのstageで止まる。

f:id:kimulla:20170130232841p:plain

permitのstageにカーソルを合わせると、人のチェックができる。

f:id:kimulla:20170130232923p:plain

input ステップを実行する前に、mattermostやmailに通知しておけばチェック依頼も自動化できる。当然、mattermostやmailに通知するステップもある。


その他のStep

便利なステップが山ほどある。
https://jenkins.io/doc/pipeline/steps/

が、もっとお手軽に、Pipeline Syntaxでstepを作成できる。

f:id:kimulla:20170130233145p:plain

値を打ち込めばDSLを生成してくれる。

f:id:kimulla:20170130233204p:plain


その他のpluginを呼ぶには

調べてもよくわからなかったけど、現状、pipeline pluginに対応したものしか呼べないらしい。

stackoverflow.com

なので今時点(2017/1/30)では、emotional-jenkins-pluginを入れてJenkinsおじさんを怒らせることができないっぽい。

なんだって?jenkinsおじさんが怒ってくれないだと?使うわけないだろ!こんなもん!!!…とお怒りの皆さま、安心してください。

emotional-jenkins-pluginにpull requestが出されてるので、そのうち使えるんじゃないかと思います。
https://github.com/jenkinsci/emotional-jenkins-plugin/pull/2

またpipelineはJenkinsの標準機能なので、他のpluginもそのうち対応されていくと思いたい。

最後に

柔軟性が高いし、パイプライン中に人の承認フローが組み込めたり、実行ノードが簡単に選べるので良さげ。ただし、あまり柔軟性を持たせてもビルド職人を生むだけなので、意識的にDSLに閉じた操作にとどめたほうが無難だと思いました。