話題の記事

OSSのジョブ管理システム Kuroko2を使ってみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

私の所属するプロジェクトで、OSSのジョブ管理ツールKuroko2を導入しました。
簡単にKuroko2の特徴、設定手順をご紹介します。

導入の経緯

プロジェクトが始まった時、一定間隔で起動する小さなジョブが一つか二つあるだけなのでcronで動かしていました。
ですが、リリースから時間が経ちサービスが成長するにつれ次第にジョブも増えていきました。

「こっちのDBから1日1回データを同期して画面に反映して欲しいです!」
「FTP接続してこっちのファイル取得してほしいんだけど...」
「対象のデータがあったらS3にJSON書き出してくれない?」

その場しのぎで次々にcron起動のジョブを増やしていったところ、ある日

non-memory-err

崩壊した...
(青:使用可能メモリ、緑:使用済みメモリ)

ジョブ管理システム Kuroko2の導入

自前でジョブ管理システムを構築することも検討しましたが、時間がないことやすでにOSSで良いものが沢山あることからOSSのジョブ管理システムを検討しました。
必要としている機能は以下です。

  • 依存関係をつけてジョブを実行できる
  • 複数のサーバのジョブを管理できる
  • timeout値を設定できる
  • Webの管理画面を持っている
  • エラー通知の仕組みがある
  • 操作が複雑すぎないこと(自分たちのバッチシステムが大規模でないため)

上記の条件をもとに検討していたところ、クックパッド社で使われていたRuby製のジョブ管理システムであるKuroko2を検証して問題がなかったため導入しました。
Kuroko2は上記の機能以外にも、

  • ジョブの実行履歴やメモリ消費、ジョブ実行時間の推移が見やすい
  • ジョブの定義も独自のDSLで書くことができる

など操作性に優れていることもKuroko2に決めた要因の一つでした。

Kuroko2の仕組み

詳細はこのGithubリポジトリのドキュメントに書いてありますが、一部抜粋します。

kuroko2-architecture

以下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

機能

ダッシュボード

ダッシュボード画面でジョブの一覧を確認できます

kuroko2-dashboad

ジョブ詳細画面

ジョブ詳細画面ではジョブの実行履歴、メモリ消費と処理時間の推移が確認できます。

実行履歴

kuroko2-results

メモリ消費

kuroko2-memory-usage

実行時間

kuroko2-exec-time

ジョブの定義

ジョブ作成画面で定義します。  

kuroko2-job-definition

実行コマンドの設定

Scriptフィールドに実行するコマンドをタスクを使って定義します。
試しにHello Worldと出力するジョブを定義します。

kuroko2-script

ここに書いた execute がコマンド実行のタスクです。Kuroko2では各タスクを使ってジョブの動作を定義していきます。

ジョブがエラー、処理中の場合の時の設定

ジョブの前回実行がエラーの場合、またはまだ処理中の場合に起動しないようにするか多重実行するか定義できます。

kuroko2-next-exec

デフォルトタスクの説明

デフォルトタスクのうち、自分たちがよく使うものを説明します。(詳細は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つの処理の中から選びます。

  1. Cancel
  2. Retry
  3. Skip

以下の詳細画面で操作します kuroko2-job-detail

カスタムタスクの定義

デフォルトタスク以外のコマンドを実行したい場合、カスタムタスクを定義することができます。
カスタムタスクを定義することで以下のようなことを実現できます。

  • 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 を実行しています。

kuroko2-custom

複数インスタンスでワーカーを起動

前述しましたが、ワーカー(command-executor)を複数インスタンスで実行できます。
例として、FTPサーバとBatchサーバの両方でワーカーを起動する場合を挙げます。

Kuroko2_001

ユーザーが操作する画面のプロセスはCMSサーバですが、ワーカーが動くのはFTPサーバとBatchサーバです。
サーバに環境変数を設定することで、FTPサーバで実行するコマンドとBatchサーバで実行するコマンドを分けて定義することができます。
ワーカーが動くそれぞれのインスタンスの環境変数 QUEUE にユニークな名称を定義します。
画面では、実行コマンドの前にqueue: を記述することで定義できます。

kuroko2-custom-task

その他

Google OAuthの設定

少し長くなってしまいますが手順を書いておきます。

1. プロジェクトを作成

Google Developers Consoleにアクセスしてプロジェクトを作成しましょう。

2. APIを有効にする

kuroko2-google-api-dashboad

Google+ APIを有効にします

3. 認証情報を作成する

OAuthクライアントIDを作成する。プルダウンメニューからOAuthクライアントIDを選択してください。ここで生成したclient_idとclient_secretは後でkuroko2.ymlに設定します。

kuroko2-google-auth

続いてリクエスト元URLと承認後のリダイレクトURLを設定します。URLには先ほどkuroko2.ymlに設定した値を入力します。

kuroko2-google-api-redirecturl

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のドキュメント