Jenkinsの無秩序なジョブをDigdagで再定義する
jenkins で回してる ci のジョブを digdag で書き直してみたけど、フローがひと目で分かるし、git で管理できるし、並列化も簡単だし最高だ。
— Kosuke Adachi (@foostan) October 8, 2016
ということで Jenkins のジョブを Digdag に置き換えて Git で管理すると最高なので、今困っている人はやりましょう。1日あれば多分終わります。 今回試したのは CI のジョブですが、どんなジョブでも応用できると思います。
詳しく
こないだ Rebuild 152 聴いていたらその会話の中に「Jenkinsおじさん」ってワードが出てきたんですよ。
Jenkinsをそれなりの規模で使っている人ならお馴染みだと思うんですが、Jenkinsって自由度が高くてジョブの編集も簡単にできるから気をつけないとジョブがカオスな状態になってきて、 管理できる人が限られてしまいます。ジョブを編集したいと思ったらその「管理している人」に許可を取って、これでいいですかね?ってジョブの内容を確認してもらってWeb上から直接ジョブを書き換えると… いわゆるこの「管理している人」が「Jenkinsおじさん」なわけですが、この管理方法だとJenkinsおじさんにとってもその他の利用者にとってもジョブの編集が怖くなってくるんですよね。 あと、経験上こういうフローで編集したジョブってだいたい初回はちゃんと動かなくて結局みんなに迷惑かけちゃいます。つらい。
脱 Jenkins おじさん
まずは Git で管理
とりあえずジョブは Git で管理できるようにして、GitHub とかでちゃんと Pull Request 作ってレビューしてもらう体制を整えたいです。Git で管理するようにすれば過去の変更は全て追えるようになるし、何か問題が起きたときは特定しやすくなります。Pull Request を利用したレビューも普段から使い慣れているので同じようにやるだけですね。
これだけでカオスな状態になりにくい環境になると思います。
次に肝心のジョブの定義についてですが、いくつか方法があると思います。
現在のジョブをそのままファイルに書き出す
レポジトリにシェルスクリプトのファイルを置くだけです。 あとは Jenkins ジョブ上でこのファイルを実行すればOK。 単純でかつ簡単にできますが、そもそもシェルスクリプトの内容が複雑になってきてそろそろ書き換えたかったので今回はパス(シェルスクリプトが苦手ってのもある)。
Jenkins2 にして Jenkinsfile を記述する
Jenkins2には既にしてあったので最初はこれが最有力な選択肢でした。 ただ検証してみたら Jenkinsfile を書くための学習コストが結構高かったのと、GitHub pull request builder plugin が Pipeline ジョブに未対応( Jenkinsfile を使うには Pipeline への対応が必要)だったのでやめました。
GitHub pull request builder plugin 使わないのであれば、Pipeline、Multiple Pipeline、Github Organization あたりのジョブで Jenkinsfile が使えるので良いと思います。 またこれらのジョブはステップ毎の実行結果や実行時間を表示してくれていい感じなので試してみるのをおすすめします。 新しいUIとして Blue Ocean というものも出てきてるのでついでに見てみるのもいいです(現在はベータ版ですがプラグインとしてインストールできます)。
Digdag を使う
Rebuild 152 でも話題になってましたが Digdag が今回の用途としては筋が良さそうでした。 Digdag はざっくり説明するとシェルスクリプトやPython、Rubyなどで記述したタスクを yaml(正確には yaml を拡張した dig ファイル) で定義したフローで実行するシンプルなワークフローエンジンです。 詳しくはドキュメント読んだり、検索すれば関連記事が沢山出てくるのでそれらを参照してもらうと良いと思います。
Jenkins のジョブを Digdag で再定義する
対象のアプリケーションは Rails で、フロントエンドのテストとバックエンドのテストが独立しているとします。 また、バックエンドのテストの一環として RSpec の他に Rubocop を実行することとします。 なお、フロントエンドのテストは js ファイルが変更された場合、バックエンドのテストは rb ファイルが変更された場合のみ実行させるとします。
この場合 Digdag では以下のように定義することが出来ます。
test.dig
timezone: UTC _export: rb: require: 'tasks/test' +setup: rb>: Test.setup +test: +frontend: if>: ${diff_js} _do: rb>: Test.frontend +backend: if>: ${diff_rb} _do: +setup: rb>: Test.setup_for_backend +backend_test: +execute_rubocop: rb>: Test.execute_rubocop +execute_rspec: rb>: Test.execute_rspec
下記はタスクの例です。
tasks/test.rb
class Test def setup Digdag.env.store(diff_js: diff?('.js')) Digdag.env.store(diff_rb: diff?('.rb')) end def frontend # 省略 end def setup_for_backend run "bundle install" end def execute_rubocop # 省略 end def execute_rspec run 'bundle exec rspec' end private def diff?(file) # 省略 end def run(command) start_time = Time.now puts "run `#{command}`".blue system command finished_time = Time.now if $?.success? puts "finished `#{command}` in #{(finished_time - start_time).round 2} seconds".blue else raise "failed `#{command}` in #{(finished_time - start_time).round 2} seconds".blue end end end class String def blue; "\e[34m#{self}\e[0m" end end
Jenkins側のジョブは
digdag run test.dig -a --project .jenkins
だけで大丈夫です( .jekins
に test.dig
がある場合)。
小ネタですが def run
のところで実行時間を測っておくと
00:00:31.677 finished `bundle install` in 3.45 seconds
のように出力されるので便利です。
おわりに
Digdag でフローを定義することによってジョブの見通しがよくなりました。 Digdag なら Jenkins 以外の Circle CI や Travis CI などの SaaS でも普通に動くはずのなのでサービスの選択肢が広がっていいですね。
学習コストも低く1日あれば試せるのでJenkinsおじさん問題で困っている方は検討してみてはいかがでしょうか。