Kuroko2の近況

技術部開発基盤グループの大石です。

先日、弊社主催のイベント CookpadTechKitchen#8 〜舞台裏を支える黒衣たち〜 にて、「Kuroko2の近況とクックパッドのバッチ周りの概況」というテーマで発表させて頂きました。今回はこの発表内容の中でも Kuroko2 についてピックアップして紹介したいと思います (今回の記事ではクックパッドのバッチの概況については特に触れませんが下記資料を参照ください)。

Kuroko2 とは

Kuroko2とは、Ruby製のWebベースのジョブスケジューラーです。2014年にクックパッド社内で開発され、2016年の秋にオープンソースとして公開しました

詳細については、当ブログの クックパッドのジョブ管理システム Kuroko2 の紹介Kuroko2 リポジトリのドキュメント をご覧ください。

また、Kuroko2 のオリジナル作者である弊社高井の社内用のLT資料 The Design Philosophy of Kuroko2 が公開されており、kuroko1 の事例を元に Kuroko2 が採用しているアーキテクチャの背景を知ることができます。 Kuroko2 の内部についても説明されているため、Kuroko2 へコントリビュートする際にとても参考になる良い資料だと思います。

今回はこれらの資料にある背景に加えて Kuroko2 をOSS化するにあたって意識していた Kuroko2 の方針、そして実際に Kuroko2 をカスタマイズする方法を紹介したいと思います。

Kuroko2の方針

ジョブ管理、ワークフロー管理への要求はトレンドや新たな技術の登場によって細かな要求が今後も変わってくることが予想されます。 そのため、Kuroko2 はメンテナンスや拡張が行いやすいシンプルな設計を保つこと、様々な現場にあわせた拡張性を担保するために本体はシンプルに保っていくべきだと考えています。

そのために以下を方針として意識しました。

1. 現実的な運用を見据えた安定した設計を保つ

The Design Philosophy of Kuroko2 p.19 にある通り、Kuroko2 は実際にクックパッドのバッチ運用を通して現実的な現場を意識して作られたものです。 闇雲に最先端の技術を採用するのではなく、あえて管理のしやすさを優先して枯れたアーキテクチャを採用し、安定した運用ができるような設計を保っていくべきだと考えています。

2. 煩雑になりがちなバッチジョブ管理の問題をUIで解決する

Kuroko2 の特徴として、煩雑になりがちなバッチ管理の方法をUIがフレームワーク的にアシストしていることが挙げられます。 具体的には、ジョブ定義の際にジョブの説明を書くためのテンプレートが用意されていたり、ジョブのお気に入り機能、自分の管理するジョブが一覧で見れるダッシュボードなどクックパッドでの運用を通して必要とされたものが実装されています。

この点は Kuroko2 の良い点であり今後も重点的に改善を進めていくべき点だと思っています。ただし、当初よりフロントエンドのアーキテクチャが古くなっており、その改良は課題であると思っています。

3. スケジューラーと汎用性の高いワークフロー管理に徹する

Kuroko2 のコアになる役割は、スケジューラーとワークフロー管理に限定し、新しい役割を定義はしないようにします。 また本体にあるタスク(docs/task.md) は汎用性の高いものだけを定義し、Kuroko2 が利用されているドメインに特化したものはカスタムタスクを利用することで柔軟性を確保するようにします。

4. うまく外の機構と連携する

ワーカーが実行する処理については、現在 command-executor からシェル経由でコマンドが実行されるのみです。この部分についても特定の機構に依存するものを本体にいれるべきではなく、うまく外の機構と連携できるように目指すべきだと考えています。 弊社が採用している例として、DWHに関連する処理についてはSQLバッチフレームワークである Bricolage を利用し、AWS ECS タスクの実行は Hako を利用することでそれぞれのドメインに合わせた連携を行っています。

ただしこの点において、シェル経由は起動コストが高いという問題があり、高頻度のジョブなどで問題が出ているため command-executor と kuroko2 console との間の連携の抽象化をしてもう少し効率的な連携が行えるような改善の必要性があると認識しています。

5. 必要な部分だけに開かれた拡張性

Kuroko2 は Rails の Mountable Engine になっており、それを gem として配布しています。 この方法を採用した理由は、Kuroko2本体の機能は常にアップデート可能なように管理できて、かつ必要に応じてカスタマイズしたかったためです。 具体的には、先述したカスタムタスクの定義や、後述する Kuroko2::ApplicationController を拡張という機能を想定しています。

Kuroko2 はバッチを管理する上で生じるドメインに特化した細かなカスタマイズを行えるようにして、様々な現場に合わせるための拡張性を適切に提供できるようにしたいと考えています。

Kuroko2 を拡張してみる

それでは、先述したカスタムタスクと Kuroko2::ApplicationController への拡張の具体的な方法のサンプルを紹介したいと思います。

カスタムタスク

まず kuroko2 gem をマウントしているRailsアプリケーション内にカスタムタスクを置く場所を作ります (後述する kuroko2.yml の設定でnamespaceは任意に分けることはできますが、今回は簡単のため Kuroko2::Workflow::Task 以下にしています)。

$ cd your_kurko2_rails_apps/
$ mkdir -p lib/kuroko2/workflow/task/

次にカスタムタスク本体のコードを上記のディレクトリ以下に置きます。

module Kuroko2
  module Workflow
    module Task
      class MyProjectRunner < Execute
        def chdir
          '/home/alice/my_project'
        end

        def shell
          "./bin/rails runner -e production #{Shellwords.escape(option)}"
        end
      end
    end
  end
end

この例では、ワーカーに設置された任意のRailsアプリケーションの中で option で渡された任意のコードを実行できます。

このカスタムタスクを kuroko2.yml で設定して、利用可能な状態にします。

....
  custom_tasks:
    my_project_runner: MyProjectRunner
....

以上の設定で、kuroko2 script 内で

 env: VAL1=A
 env: VAL2=B
 my_project_runner: MyProject::Batch.run

のようにKuroko2とは別のRailsアプリケーション内のバッチを実行するための専用のカスタムタスクを設定することができます。

クックパッドでもこのようなアプリケーション毎に設定などをまとめた専用のカスタムタスクを定義していたり、Hako を利用した Docker アプリケーションへのオプション定義のショートカットとして利用しています。 また、実験的なタスクを試したりすることもできるので、Kuroko2 本体にコントリビュートする際にも事前に検証などが行なえます。

Kuroko2::ApplicationController の拡張

次に Kuroko2::ApplicationController を拡張する方法を紹介します。

ここでなぜこのような拡張が必要なのかという理由を先に述べておきます。 クックパッドではアプリケーションのエラートラッキングに Sentry を採用しており、kuroko2 gem をマウントしたアプリケーションも他のアプリケーションと同様にエラーが発生した場合に Sentry で管理したいということがあったためです。

それでは、実際に拡張する例を書いてみます。

カスタムタスクと同様に以下の拡張コードを your_kurko2_rails_apps/lib 以下に置きます。(アプリケーションがロードできる場所であればどこでもよいです)

module ControllerExtention
  extend ActiveSupport::Concern
  included do
    before_action :additional_before_action
  end

  private

  def additional_before_action
    if signed_in?
      # do something
    end
  end
end

次に、こちらもカスタムタスクと同様に kuroko2.yml に設定を行います。

...
extentions:
  controller:
    - ControllerExtention
...

今回の例はユーザーがログインしている場合になにかを行うような例にしました。 先述したクックパッドで行っている Sentry を用いたエラートラッキングでは、before_action でログインユーザーやエラーが発生したときに必要なコンテキストを設定するようなことを行っています。

Mountable Engine を採用した利点として、 kuroko2 gem がマウントされたRailsアプリケーションからある程度のカスタマイズが可能になる点です。 いまは ApplicationController だけですが、必要があればこのような拡張性はある程度確保したいと考えています。

Kuroko2 のこれから

方針の中でもすこし触れましたが、具体的には、

  • UIをモダンに改善する
  • command-executor と kuroko2 console 間の連携の抽象化
  • command-executor 自体を Rails に依存しないようにして、軽量化する
  • ドキュメントの充実

など、Kuroko2 の方針に追いつけていない部分がまだまだあると認識しています。 ひとまずはこれらの課題に対して取り組んでいく必要があると考えています。

イベントで出た一部の質問とその回答

権限管理は実装しないのか

実装する予定はいまのところありません。

ジョブの実行前は確認のモーダルウィンドウが出るので誤って実行されにくいUIをしています。 さらにクックパッドではジョブを管理しているチーム以外にもたとえば障害が起きたときなど SRE や開発基盤がジョブの description をみて適宜リトライを行うようなオペレーションを行っています。DWHの領域では部署をまたがったジョブの連携が行われておりお互いにアクセスできる必要があります。またジョブに対する操作が行われた場合、いつ誰が実行・リトライしたかをログとして記録しています。 このため厳しく権限管理をするよりも性善説にたって誰でもアクセスできるようにしておいたほうがメリットが大きいと考えています。

Kuroko2::ApplicationController の拡張を使ってカスタマイズという形で実装することは可能だと思うので、必要があればこちらの方法をおすすめします。

補足: 現在認証については Google の G Suite のみがサポートされていますが、この点については将来的に他の認証方法などの対応は必要になってくるのかなとは感じています。

command-executor がメモリを食う

Railsに依存しすぎている部分がたしかにあるので対応したいです。 必要以上にRailsに依存しないようにすることと、方針の中で触れた command-executor の抽象化も含めて今後の課題だと認識しています。

最後に

Kuroko2 の設計方針とこれからの課題、拡張方法について紹介しました。 この記事で興味を持たれたり、導入してみたいという方は是非 Kuroko2 までコントリビュートお待ちしています。 また、実際に導入したなどの事例やフィードバックなどもご連絡頂けると幸いです。

f:id:eisuke-oishi:20170614172135p:plain

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/