2017.07.11

Digdag 入門


D. M. です。レガシーの crontab が肥大化して困っています。今日はそのリプレイス候補である Digdag を使ってみた話です。

やりたいこと

crontab は Linux のスケジューラの仕組みで定期バッチの実行用途でよく利用されますが低機能です。順序の依存関係やアラートは毎回独自に作りこまなければいけません。そのため近年は代替スケジューラを利用するケースが多いです。その候補のひとつである Digdag について検証したいと思いました。

Digdag を使うべき人

一般的にバッチスケジューラに求められる要件的には以下のようなものがあります。

・スケジュール実行
・複数バッチの順番の制御(ワークフロー)
・GUIでの管理
・失敗時のアラート
・SLA 機能(長時間実行していたらアラートを飛ばす)
・分散実行
などなど


代替として広く知られているものですと Jenkins でのワークフロープラグインや Rundeck, Luigi などのソフトウェアが挙げられるかと思います。

これら従来のソリューションと Digdag の最大の違いは後者が Workflow as a Code を実現したことです。従来のシステムに比べて yaml 設定ファイルでバッチのワークフロー実行を定義・管理できるようになっています。プログラムコードを管理するのと同じノリでバッチのワークフローを管理したいと考えているモダンな人には Digdag がオススメです。

この記事の目的

Digdag は現状まだドキュメントが少なく公式ドキュメントでよくわからなかった点があったため、やってみて理解した結果をまとめておきたいと考えました。

公式トップはこちら。
https://www.digdag.io/
公式ドキュメント(マニュアル)はこちらです。
http://docs.digdag.io/index.html

はじめて Digdag を知った方はまずは作者の古橋さんのプレゼンを読みましょう。
https://www.slideshare.net/frsyuki/digdag-at-tokyo-rubykaigi-11

以下の古橋さんのインタビューもコンセプトの理解に必読です。
http://regional.rubykaigi.org/tokyo11/interview/frsyuki/

2017年6月現在の最新は version 0.9.12 です。今回の検証では CentOS 7系の上に構築しています。

Digdag セットアップのベストプラクティス

はじめはセットアップが必要ですが、他の記事も多いためサマリーだけを記載します。

インストールはコマンド一発で5分も掛からないので公式を読みましょう。
http://docs.digdag.io/getting_started.html#downloading-the-latest-version
やっていることはコンパイル済みの実行可能ファイル(中身はjar)をPATHが通っているところに置く感じです。

$ curl -o ~/bin/digdag –create-dirs -L “https://dl.digdag.io/digdag-latest”
$ chmod +x ~/bin/digdag
$ echo ‘export PATH=”$HOME/bin:$PATH”‘ >> ~/.bashrc
$ source ~/.bashrc


一緒に入れるべきなのは以下のソフトウェアです。
・jenv
・Supervisor
・Postgres >=9.5

実行には Java の Java8(8u72) 以上が必要です。 Java は脆弱性等の理由で必ずバージョンアップするので jenv で管理するのがオススメです。これは入れたほうが楽。

Digdag はデーモンなのですが本番環境などで安定的な常駐を実現しようと思ったら Supervisor が良いです。ウェブに記事が多くあるのでその手順どおりで簡単に連携できます。

DB は主にバッチのスケジュールや実行結果のステータスを保持するために必要になります。いつ何を実行したのか記録され UI で確認できます。本番環境の冗長構成を意識すると DB は必須なので、検証含めてどの環境でも結局 Postgres を入れて連携するのが良いと思っています。なのでローカルも開発機も Postgres 入れました。

ちなみに 0.9 の digdag ui では Upsert 文を利用しており、 Postgres 9.5 以上が必要です。
Upsert が使えない Postgres に対して Upsert を実行すると Digdag のエラーログに以下が出ますので遭遇した方はバージョンの確認を行うと良いかと思います。

PSQLException: ERROR: syntax error at or near “on”


セットアップが完了するとチュートリアルができます。先ほどのインストールページの最後に出ている3行のコマンドがそれです。3行なので一瞬で終わります。
http://docs.digdag.io/getting_started.html#running-sample-workflow

マニュアルを理解する上でつまづいたところ


チュートリアルの後は、いよいよ自分でバッチ処理などを組み込みたいところですが、その前に実行モードの違いと digdag ui 、基礎的な Digdag 用語を理解する必要があります。これがわかっていないと細かいところで何をどう考えるべきかわからず他のドキュメントも読むことが難しくなるので、私なりの理解をここで整理しておきます。

・実行モード
・digdag ui と専門用語
・実行の仕組みと分散実行
・実行に Docker を利用する


実行モード


実行モードすなわち Digdag の起動方法にはローカルモードとサーバモード・クライアントモードがあります。

ローカルモードの用途はコマンド単発実行用なのでほぼローカルPCでの開発のみとみなしてよさそうです。例えばチュートリアルにあるように個別ワークフローの単発実行です。

$ digdag run mydag.dig


サーバモードは立ち上げるとスケジューラと GUI が同時に動くデーモンです。運用向けなので開発サーバ・本番サーバの用途になります。クライアントモードはそのデーモンに対して CLI でコマンドを実行するときに利用します。リモートからコマンドを叩き、ワークフローの設定ファイルで更新することができます。

サーバモードの起動では必要なオプションは全部 config へ集約して書いておきます。ファイル名は任意です。起動は以下のようなコマンドになります。

$ digdag server –config /digdag/digdag-server.properties –log /digdag/logs/server.log –log-level debug

–log で server の処理実行ログを出力できます。どのタスクがいつ動いたなどがテキストで出るのと、エラーについてもここに出ます。デフォルトは標準出力です。
–log-level で出力レベルが指定できます。内部の処理ログなので中身の挙動を確認したいとき以外は変更する必要はありません。選べるレベルは trace, debug, info, warn, error です。ちなみに trace にしても SQL 等は出ませんでした。
config で指定できるパラメータは公式の以下のページを読みましょう。ミニマムでも db の設定と log の位置は明示的に書いておいたほうがあとあと他の人が見たときにわかりやすいので必ず定義しましょう。
https://docs.digdag.io/command_reference.html#server-mode-commands

database.type = postgresql
database.user = digdagtest
database.password = digdagtestpass
database.host = 127.0.0.1
database.port = 5432
database.database = digdagtestdb
server.access-log.path = /digdag/logs


ここからはサーバモードで立ち上げている前提で話を進めていきます。

digdag ui と専門用語


digdag ui の URL は以下です。(公式の記載がちょっとわかりづらいですが digddag server 立ち上げ時のログで明示されます)

http://127.0.0.1:65432


ui は digdag server の立ち上げで同時に立ち上がりますので個別に起動する必要ありません。 API の受付も同じポートで口が開きます。
私が混乱したのは Digdag の github 側に説明文です。
https://github.com/treasure-data/digdag#develop-digdag-ui
こっちには npm run dev で立ち上げるコマンドが出ていますが、こちらはポートが 9000 です。 65432 ではないので迷うところですが通常は使わないのでこちらは無視してください。

立ち上げると以下のような画面が見られます。
digdag ui トップページ
digdag_ui1
この画面には基本的な Digdag 用語がいくつか出てきます。まずは「ワークフロー」です。ワークフロー = タスクの一連のまとまりで dig ファイル(≒yml)1つという認識で良いかと思います。 dig ファイル内では複数のタスクを + で順序を定めて羅列します。
次に「プロジェクト」ですが、ひとつ以上のワークフローの束+関連ファイルをまとめたものです。プロジェクトはヴァージョン管理されており個別のヴァージョンは「リビジョン」と呼ばれます。 dig ファイルの変更を反映するたびにリヴィジョンは変更されます。
この画面の環境ではプロジェクト new-project の下に ワークフロー workflow 1, 2 が紐づけています。リビジョンは Workflow の dig ファイルを変更したタイミングで新しくなります。Status リンクから個別実行結果(アテンプト)が確認できます。 Workflow の名称リンクから dig ファイルの編集が可能です。

個別ワークフローの編集ページ
digdag_ui3

Edit 画面
digdag_ui_4
この画面の Add file からワークフローを追加できます。

セッションとアテンプト


パッと見でよくわからなかった用語は「セッション」と「アテンプト」です。公式の文章をそのまま引用します。

Sessions and attempts
A session is a plan to run a workflow which should complete successfully. An attempt is an actual execution of a session. A session has multiple attempts if you retry a failed workflow.


「”セッション”は、正常に完了するべきワークフローを実行する計画です。”アテンプト”はセッションの実際の実行です。失敗したワークフローを再試行すると、1セッションに複数のアテンプトができます。」

つまり セッション = 計画 、アテンプト = 実行 です。実行時に失敗するケースがあるのでその状態を管理するために概念を分けているわけです。

個別アテンプトの確認ページ
digdag_ui2
この画面では実行結果について個別タスクの成功失敗ステータスと実行時間を確認できます。

digdag scheduler と digdag server の違い

公式の以下のページにはスケジューラは digdag scheduler を使えと書いてあります。
http://docs.digdag.io/scheduling_workflow.html#running-scheduler
ただ動かしてみると digdag server でも digdag scheduler でもスケジューラは使えるので個人的には digdag server だけで用は足りると思ってます。また digdag scheduler では digdag ui が起動しません。 digdag ui を立ち上げる必要がない場合だけは scheduler で立ち上げるというのもあるかと思いますが、こちらに関しては現段階で用途を明示した例がないので、あまり確証がないです。特別な検証用途がない限りサーバモード digdag server を起動すれば scheduler と ui が利用できるためこっちに統一したほうが良さそうです。

細かい話ですが dig ファイルのスケジュール記述で以下のような書き方は弾かれます。
例えば 10 分ごとにファイル取り込みスクリプトを動かしたい処理の例。以下はNGです。
#10分毎。NGな書き方(数字じゃないと駄目みたいなエラーメッセージが出る)
cron>: */10 * * * *
#毎分。NGな書き方(こっちも数字じゃないと駄目みたいなエラーメッセージが出る)
cron>: * * * * *
逆にこんな風にべた書きするのは可能です。
cron>: 10,20,30 * * * *
また1分ごとに動かしたいときは以下のように書きます。
minutes_interval>: 1

以上の Digdag の基本的な用語とその意味は公式のコンセプトのページに書かれていますので詳細についてはぜひそちらをご確認ください。(平易な英語で書かれいますが苦手な方は Google 翻訳を使うと非常にわかりやすい日本語の文章になりますのでそれを読むのがオススメ)
http://docs.digdag.io/concepts.html

実行の仕組みと分散実行


Digdag の実行は crontab による実行とは大きく違います。ポイントは実行状態を中央のDBのキューで管理しており複数のデーモンが互いに処理を重複して行わないように制御できている点です。

サーバモードでの簡易的な処理フローです。

1.スケジューラ(スケジュールエグゼキュータ)がワークフローを開始する。
2.ワークフロー(ワークフローエグゼキュータ)はタスクをキューに入れる。(キューは postgres です)
3.エージェントはタスクキューからタスクを取得する(ここでタスクはロックされる)
4.タスクを実行し、完了したらタスクはキューから削除される。
タスク実行中に問題があって終了した場合、キューのロックは解放され、別のエージェントがタスクを実行する。


※ プロセスをエージェントに特化させたい場合スケジュールエグゼキュータと ワークフローエグゼキュータ は –disable-executor-loop で無効化できます。逆にエージェントを無効化するには –disable-local-agent があります。

※上記の詳細な説明は以下の公式ページにあります。
http://docs.digdag.io/internal.html

crontab で行う場合は2台で同じ処理を行うと特別な制御を実装するか、またはどちらかをスタンバイ状態にしておく必要がありましたが、 digdag の場合はそれぞれが動いている状態でも問題はないです。

ちなみに digdag server –log-level debug にすると以下のようにキューしている部分のログが確認できます。

2017-06-30 19:04:00.335 +0900 [DEBUG] (workflow-executor-0): Queuing task of attempt_id=5681: id=58785 +workflow2+setup
2017-06-30 19:04:00.740 +0900 [DEBUG] (workflow-executor-0): Queuing task of attempt_id=5681: id=58786 +workflow2+disp_current_date
2017-06-30 19:04:01.279 +0900 [DEBUG] (workflow-executor-0): Queuing task of attempt_id=5681: id=58787 +workflow2+task1
2017-06-30 19:04:01.912 +0900 [DEBUG] (workflow-executor-0): Queuing task of attempt_id=5681: id=58788 +workflow2+task2


この制御があるため分散実行が容易に実現されています。分散実行構成は「1つの postgres + 複数の digdag server 」というかたちになり単純に DB に接続してワークフローを実行できる digdag server を増やせばそれだけで処理が分散できるようになります。 digdag 自体はキューのロック取得したら処理をするので、お互いの処理がかぶってしまうことがない作りになっています。

実行に Docker を利用する


前提として Digdag 実行環境に docker のインストールが必要です。これは yum などで簡単に入りますので Docker の公式を参照してください。

次は自分が使いたい docker images をインストールするのですが、 digdag の場合これは不要です。 dig で以下の記載があれば image を初回にダウンロードしてくれます。

_export:
  docker:
    image: ubuntu:14.04


1点注意なのが docker で動作するのは sh, py, rb などスクリプト実行の場合のみだということです。 for_each, echo などでは docker での処理が始まりませんでした。なので 0.9 のデフォルトのチュートリアルでできる echo を含む以下の部分は docker 実行されていない感じでした。

+repeat:
  for_each>:
  order: [first, second, third]
  animal: [dog, cat]
  _do:
  echo>: ${order} ${animal}
  _parallel: false

スクリプト実行オペレータに embulk> があるのですが、どうも公式ページによると廃止になるようです。細かいオプション指定などを考えると sh> のほうが使いやすかったのかも知れません。
http://docs.digdag.io/operators/embulk.html

This operator is obsoleted. embulk> operator is going to be removed, or rewritten with backward incompatibility.


digdag 再開時の癖


停止後の再起動時の挙動は単純に crontab と同じと思うと違う結果になります。例えば以下のユースケース。

1.毎時0分ごとに実行してる。
2.障害で3時間停止。
3.復旧後、停止していた3時間の3回分が実行される!

crontab 運用だと停止中の3時間は実行されないままですが、digdag sever の場合は起動したらさかのぼって実行されます。これには結構ビックリしました。厳密に状態を DB で管理しているのはわかったんですが、空いた時間があっても実行されるということを理解していないと、仕様によっては面倒なことになるやもしれないかなと。
特に開発環境でバッチを停止しているケースなどがあり、久しぶりに動かすと止まっていた期間のタスクがキューに一気に入ります。DB の中身をどうにかすればいいのですが安易にいじるのは危険なので、正しく回避する方法を確認したいと思っています。
この癖がなじまない場合は digdag の scheduler の機能は使わず jenkins など既存のソリューションに任せて、 digdag は単純に順番の制御やアラート配信などに特化していくのも一案かと思います。
「digdag と Jenkins は共存共栄」
http://qiita.com/tunanosuke/items/d7b85338b37dd179185b

最後に


次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。