OSSのジョブ管理システム Kuroko2を使ってみた
私の所属するプロジェクトで、OSSのジョブ管理ツールKuroko2を導入しました。
簡単にKuroko2の特徴、設定手順をご紹介します。
導入の経緯
プロジェクトが始まった時、一定間隔で起動する小さなジョブが一つか二つあるだけなのでcronで動かしていました。
ですが、リリースから時間が経ちサービスが成長するにつれ次第にジョブも増えていきました。
「こっちのDBから1日1回データを同期して画面に反映して欲しいです!」
「FTP接続してこっちのファイル取得してほしいんだけど...」
「対象のデータがあったらS3にJSON書き出してくれない?」
その場しのぎで次々にcron起動のジョブを増やしていったところ、ある日
崩壊した...
(青:使用可能メモリ、緑:使用済みメモリ)
ジョブ管理システム Kuroko2の導入
自前でジョブ管理システムを構築することも検討しましたが、時間がないことやすでにOSSで良いものが沢山あることからOSSのジョブ管理システムを検討しました。
必要としている機能は以下です。
- 依存関係をつけてジョブを実行できる
- 複数のサーバのジョブを管理できる
- timeout値を設定できる
- Webの管理画面を持っている
- エラー通知の仕組みがある
- 操作が複雑すぎないこと(自分たちのバッチシステムが大規模でないため)
上記の条件をもとに検討していたところ、クックパッド社で使われていたRuby製のジョブ管理システムであるKuroko2を検証して問題がなかったため導入しました。
Kuroko2は上記の機能以外にも、
- ジョブの実行履歴やメモリ消費、ジョブ実行時間の推移が見やすい
- ジョブの定義も独自のDSLで書くことができる
など操作性に優れていることもKuroko2に決めた要因の一つでした。
Kuroko2の仕組み
詳細はこのGithubリポジトリのドキュメントに書いてありますが、一部抜粋します。
以下4つのコンポーネントによって作られています。
Web UI
ジョブを定義、実行、実行結果を参照することができるRailsアプリケーションです。
Job-Scheduler
スケジュール登録されたジョブの実行をWorkflow-Processorに伝えます。
Workflow-Processor
ジョブが実行するコマンドをCommand-Executorへ伝えます。
Command-Executor
コマンド実行をするワーカーです。複数のワーカーが起動します(デフォルト:4)
導入
install
Kuroko2はgemとして提供されています。 以下のコマンドを実行することでKuroko2のアプリケーションが作成されます。
$ rails new my_kuroko2 --database=mysql --skip-turbolinks --skip-javascript -m https://raw.githubusercontent.com/cookpad/kuroko2/master/app_template.rb
設定
アプリケーションの設定はconfig/kuroko2.yml
に記述します。
URL
アプリケーションのURLを設定します。ここのURLは、後述する認証のリダイレクトURLにも指定します。
default: &default url: 'http://localhost:5000'
認証
Kuroko2はユーザー認証にGoogle OAuthの仕組みを利用します。本記事の最後に手順を書いておきましたので参考にしてください。
起動
Procfileがすでに用意されているので foreman
コマンドで実行します。
$ foreman start
機能
ダッシュボード
ダッシュボード画面でジョブの一覧を確認できます
ジョブ詳細画面
ジョブ詳細画面ではジョブの実行履歴、メモリ消費と処理時間の推移が確認できます。
実行履歴
メモリ消費
実行時間
ジョブの定義
ジョブ作成画面で定義します。
実行コマンドの設定
Scriptフィールドに実行するコマンドをタスクを使って定義します。
試しにHello Worldと出力するジョブを定義します。
ここに書いた execute
がコマンド実行のタスクです。Kuroko2では各タスクを使ってジョブの動作を定義していきます。
ジョブがエラー、処理中の場合の時の設定
ジョブの前回実行がエラーの場合、またはまだ処理中の場合に起動しないようにするか多重実行するか定義できます。
デフォルトタスクの説明
デフォルトタスクのうち、自分たちがよく使うものを説明します。(詳細はKuroko2のドキュメントを参照してください)
タスク名 | 役割 |
---|---|
execute | コマンドを実行する |
env | 環境変数を設定する |
rails_env | RAILS_ENVを設定します(staging or production) |
fork | タスクを並列実行します |
noop | 何もしないタスクです、テストのためのもの |
queue | ワーカー(command-executor)が動くqueue名を指定します。インスタンス毎にワーカーのジョブを分ける場合に設定します(後で詳しく説明します)。 |
timeout | timeout値を設定します。この値を過ぎてもジョブが完了しなかった場合、TERM signalにより実行が終了します |
expected_time | 想定の実行時間を設定します。この値を過ぎてもジョブが完了しなかった場合、管理者に通知を送ることができます |
ジョブのステータス
Kuroko2のジョブのステータスは以下の4つに分かれています。
ステータス | 説明 |
---|---|
Working | 処理中 |
Error | 異常終了 |
Success | 正常終了 |
Cancel | 途中でキャンセルした場合 |
エラーステータスになったジョブの終了方法
ジョブ実行がエラーになった場合、運用者がオペレーションを実行し正しく終了させます。 以下の3つの処理の中から選びます。
- Cancel
- Retry
- Skip
以下の詳細画面で操作します
カスタムタスクの定義
デフォルトタスク以外のコマンドを実行したい場合、カスタムタスクを定義することができます。
カスタムタスクを定義することで以下のようなことを実現できます。
- RailsプロジェクトのディレクトリにあるRubyスクリプトを、Railsアプリケーションを起動した状態で実行する
- 特定の環境変数を渡す
プログラムの作成
カスタムタスクを定義するには lib/kuroko2/workflow/task
の下にrubyのプログラムを作成します。
ここではmy_project_runner.rb
というプログラムを作成しました。
module Kuroko2 module Workflow module Task class MyProjectRunner < Execute def chdir '/Users/username/Document/rails-app' end def before_execute env = token.context['ENV'] || {} env.merge!({ KEY1: ENV['KEY_1'], KEY2: ENV['KEY_2'] }) token.context['ENV'] = env end def shell "RAIIS_ENV=production bundle exec rake #{Shellwords.escape(option)}" end end end end end
chdir
メソッドで実行ディレクトリを設定、before_execute
でジョブ実行時に必要な環境変数を設定しています。
shell
メソッドで、画面から指定したパラメータを受けてコマンド実行します。
設定ファイルに記述
kuroko2.yml
に作成したカスタムタスクを追加します。
custom_tasks: my_project_runner: 'MyProjectRunner'
ジョブ実行コマンドの定義
ジョブ作成画面のScriptフィールドに作成したカスタムタスクを使ってジョブを定義します。
以下のジョブ定義はbundle exec rake sample:task
を実行しています。
複数インスタンスでワーカーを起動
前述しましたが、ワーカー(command-executor)を複数インスタンスで実行できます。
例として、FTPサーバとBatchサーバの両方でワーカーを起動する場合を挙げます。
ユーザーが操作する画面のプロセスはCMSサーバですが、ワーカーが動くのはFTPサーバとBatchサーバです。
サーバに環境変数を設定することで、FTPサーバで実行するコマンドとBatchサーバで実行するコマンドを分けて定義することができます。
ワーカーが動くそれぞれのインスタンスの環境変数 QUEUE
にユニークな名称を定義します。
画面では、実行コマンドの前にqueue:
を記述することで定義できます。
その他
Google OAuthの設定
少し長くなってしまいますが手順を書いておきます。
1. プロジェクトを作成
Google Developers Consoleにアクセスしてプロジェクトを作成しましょう。
2. APIを有効にする
Google+ APIを有効にします
3. 認証情報を作成する
OAuthクライアントIDを作成する。プルダウンメニューからOAuthクライアントIDを選択してください。ここで生成したclient_idとclient_secretは後でkuroko2.ymlに設定します。
続いてリクエスト元URLと承認後のリダイレクトURLを設定します。URLには先ほどkuroko2.ymlに設定した値を入力します。
4. APIキーの定義
kuroko2.ymlにGoogle Developers Consoleで作成したclient_id, client_secretを設定します。プロジェクト初期状態では環境変数に設定すればそのまま読み込むようになっています。
app_authentication: google_oauth2: client_id: '<%= ENV["GOOGLE_CLIENT_ID"] %>' client_secret: '<%= ENV["GOOGLE_CLIENT_SECRET"] %>'
まとめ
プロジェクトが進むにつれて増えてきたジョブをどう管理するか悩んでいましたが、Kuroko2を使うことで多重起動の抑止やジョブの依存関係をつけた実行、エラー時のリトライ、管理者への通知といった機能を実現することができました。
バッチジョブはサービスの中でも重要性が高く、エラーが発生した場合は原因を追究してすぐに復旧する必要があります。
Kuroko2の画面でジョブ実行履歴をすぐに参照できることや、画面からリトライ、スキップを実行することができるので運用作業も非常にやりやすくなりそうです。
参考情報
クックパッドのジョブ管理システム kuroko2 の紹介
OSS になった Kuroko2 をどこよりも早く導入したので紹介したいブログ
Kuroko2のドキュメント