Kubernetes上で動作する機械学習モジュール配信管理基盤Rekcurd (ex Drucker) Update (PyPI, LDAP対応, etc…)

こんにちは、Clova開発室でNLU (Natural Language Understanding) を担当している 服部 (keigohtr) です。この記事は LINE Advent Calendar 2018 の24日目の記事です。

こちらの記事では、以前LINE Engineering Blog「夏休みの自由研究 -Summer Homework-」でご紹介した「Clovaにおける機械学習モジュールの管理運用基盤Druckerについて」からのアップデートを紹介します。Rekcurd全体の概要や開発背景は先日行われたJapan Container Days v18.12で私が登壇したこちらの資料が分かりやすいと思います。

はじめに

これまでは「Drucker」という名称を使っておりましたが、諸事情により「Rekcurd」と名称を改めました。引き続きよろしくお願い致します。ちなみに「Rekcurd」は「Drucker」を逆から読んだものになります。RekcurdはApache license 2.0で利用できます。GitHub上で公開していますので、是非とも使って頂いてフィードバックスターをください。なお、本記事ではところどころに「Drucker」という記述がありますが、まだ「Rekcurd」へのrenamingの途中なのでご了承ください。

Rekcurdとは

Rekcurdは機械学習モジュールの「運用」に特化したソフトウェア群です。詳細な説明は以前の記事を参照してください。RekcurdRekcurd dashboardRekcurd clientという3つのプロジェクトがあり、それぞれ「機械学習モジュールの配信を簡単に」「機械学習モデルの管理と運用を簡単に」「既存のシステムへの統合を簡単に」というコンセプトで設計しています。Clova開発室では一部を除いた全ての機械学習モジュールをRekcurdで運用しています。RekcurdはKubernetesに対応しており、Rekcurd dashboardを用いることでKubernetesに詳しくなくてもKubernetesの優れた機能 (e.g. Auto healing, Auto scaling, Rolling update, etc…) を使うことが出来ます。

最初のリリース (2018.8.8) v0.2.0 から 現在 (2018.12.5) v0.4.3 までのアップデート

PyPI対応 (Rekcurd, Rekcurd client)

v0.4.0 からPyPIに対応しました。RekcurdとRekcurd clientを pip installできます。このアップデートによりRekcurdはflaskのように簡単に機械学習モジュールをgRPCサービスとして配信できるようになりました。またRekcurd clientはRekcurdに対するSDKのように使えるようになりました。

Rekcurdの使い方

サンプルプロジェクトとしてRekcurd-exampleが利用できますので参考にしてください。

まずRekcurdをpip installします。

pip install Rekcurd

続いて機械学習モジュールをRekcurd化します。loggerはdrucker.logger.logger_interfaceを継承すれば独自のものが使えます。実装が必要な場所は load_model methodと predict methodで、前者に機械学習モジュールのモデル読み込み部分を、後者に機械学習モジュールの推論部分を、それぞれ記述します。サンプルコードはこちらを見てください。

from drucker.logger import JsonSystemLogger
from drucker import Drucker
from drucker.utils import PredictLabel, PredictResult, EvaluateResult, EvaluateDetail
# 中略

class MyApp(Drucker):
    def __init__(self, config_file: str = None):
        super().__init__(config_file)
        self.logger = JsonSystemLogger(self.config)
        self.load_model()

    def load_model(self) -> None:
        assert self.model_path is not None, \
            'Please specify your ML model path'
        try:
            self.predictor = joblib.load(self.model_path)
        except Exception as e:
            self.logger.error(str(e))
            self.logger.error(traceback.format_exc())
            self.predictor = None
            if not self.is_first_boot():
                os._exit(-1)

    def predict(self, input: PredictLabel, option: dict = None) -> PredictResult:
        try:
            if option is None:
                option = {}
            label_predict = self.predictor.predict(
                np.array([input], dtype='float64')).tolist()
            return PredictResult(label_predict, [1] * len(label_predict), option={})
        except Exception as e:
            self.logger.error(str(e))
            self.logger.error(traceback.format_exc())
            raise e

    def evaluate(self, file: bytes):
        pass

最後に、Rekcurd化された機械学習モジュールをgRPCサービスにします。サンプルコードはこちらを見てください。

# 前略
def serve():
    app = MyApp("./settings.yml")
    system_logger = JsonSystemLogger(app.config)
    service_logger = JsonServiceLogger(app.config)
    system_logger.info("Wake-up drucker worker.")

    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

    drucker_pb2_grpc.add_DruckerDashboardServicer_to_server(
        DruckerDashboardServicer(logger=system_logger, app=app), server)
    drucker_pb2_grpc.add_DruckerWorkerServicer_to_server(
        DruckerWorkerServicer(logger=service_logger, app=app), server)
    server.add_insecure_port("[::]:{0}".format(app.config.SERVICE_PORT))
    server.start()
    try:
        while True:
            time.sleep(_ONE_DAY_IN_SECONDS)
    except KeyboardInterrupt:
        system_logger.info("Shutdown drucker worker.")
        server.stop(0)


if __name__ == '__main__':
    serve()

Rekcurd clientの使い方

サンプルコードがこちらから参照できます。

まず、Rekcurd clientをpip installします。

pip install Rekcurd-client

あとはRekcurd化された機械学習モジュールに接続するだけです。loggerはdrucker_client.logger.logger_interfaceを継承すれば独自のものが使えます。

from drucker_client import DruckerWorkerClient
from drucker_client.logger import logger

host = 'localhost:5000'
client = DruckerWorkerClient(logger=logger, host=host)
input = [0,0,0,1,11,0,0,0,0,0,
         0,7,8,0,0,0,0,0,1,13,
         6,2,2,0,0,0,7,15,0,9,
         8,0,0,5,16,10,0,16,6,0,
         0,4,15,16,13,16,1,0,0,0,
         0,3,15,10,0,0,0,0,0,2,
         16,4,0,0]
response = client.run_predict_arrint_arrint(input)

Rekcurd dashboardを使ってKubernetes上にデプロイしたRekcurdサービスの場合、DruckerWorkerClientを下記のように使うことも出来ます。

domain = 'example.com'
app = 'drucker-sample'
env = 'development'
client = DruckerWorkerClient(logger=logger, domain=domain, app=app, env=env)

LDAP認証 (Rekcurd dashboard)

v0.3.0 からLDAP認証によるアクセス制御に対応しました。Rekcurd dashboardのsettings.ymlに下記項目を追加することで利用できます。フロントエンドとバックエンドの連携にはJWT (Json Web Token) を用いています。

settings.yml

auth:
  secret: 'super-secret'
  ldap:
    host: 'ldap.example.com'
    port: 389
    bind_dn: 'CN=manager, DC=example, DC=com'
    bind_password: 'foobarbaz'
    search_filter: '(CN=%s)'
    search_base_dns:
      - 'OU=user, DC=example, DC=com'

Kubernetes上にデプロイしたRekcurdサービスのバックアップ (Rekcurd dashboard)

v0.3.3 からKubernetes上で起動しているRekcurd関連のDeployment・Service・Ingress・HPAの設定をバックアップできるようになりました。Kubernetesクラスタが何かしらの原因で完全に破壊された場合でも、バックアップしたDeployment・Service・Ingress・HPAのyamlファイルを新しいKubernetesクラスタにデプロイすることで、Rekcurd関連のアプリケーション群を再構築できます。バックアップ実行のトリガーはRekcurd dashboardの /api/kubernetes/dump のエンドポイントに対する POST リクエストで、Rekcurd dashboardのsettings.ymlに追加した kube.datadir で指定したディレクトリにyamlファイルを保存します。現状、バックアップ用のエンドポイントはLDAP認証のスコープ外にしているので、batchで処理することができます。kube.datadirをGitレポジトリにしてbatchでgit add & commitすればバージョン管理も出来ます。

settings.yml

kube.datadir: 'kube-config'

backup

curl -X POST -H "Content-Type: application/json" localhost:18080/api/kubernetes/dump

gRPC specのバージョニング対応 (all)

v0.3.0 からgRPC specの破壊的な変更をバージョニングで切り分けるようにしました(参照)。この変更でKubernetes上にデプロイしたRekcurdサービスのアクセスポイントにRekcurd gRPC specのバージョン番号が追加されました。バージョン番号は後方互換性が切れるタイミング (Minor/Major release) で更新されます。Rekcurd clientを使う場合、Rekcurd clientに紐付いたRekcurd gRPC specのバージョンを読み込んでアクセスポイントを自動生成します。

gRPC specにバージョン情報を埋め込むために、gRPCのextend google.protobuf.FileOptionsを使いました。gRPCはカスタムオプションを定義することができ、1000番以降のextension numberを自由に使えます。

drucker.proto

// 前略
import "google/protobuf/descriptor.proto";
extend google.protobuf.FileOptions {
    string drucker_grpc_proto_version = 50000;
}
option (drucker_grpc_proto_version) = "v2";
 enum EnumVersionInfo {
    // Version info. The largest number is the latest version.
    v0 = 0;
    v1 = 1;
    v2 = 2;
}
// 後略

Travis対応 (Rekcurd, Rekcurd client)

v0.4.0 からTravisに対応しました。READMEにバッチも追加したので、build statusやpypi package version、unittestのcode coverage、対応しているpython versionが表示されています (RekcurdRekcurd client) 。

その他 (all)

DB migration対応とたくさんのバグ修正を行いました。

今後の予定 (~v1.0)

アクセスコントロール機能の実装

アクセスコントロール機能はRekcurd dashboardにとって極めて重要な機能です。この機能はKubernetesクラスタや機械学習モジュールに対して「閲覧権限」や「編集権限」を行うユーザー・グループを制御します。Rekcurd dashboardは複数のKubernetesクラスタを扱うことができますので、Rekcurd dashboard上で社内の全ての機械学習モジュールを管理すれば、例えば「関係者外秘」の機械学習モジュールを管理したり、特定の部門間や企業間で機械学習モジュールを共有したり出来ます。現在、最初のアクセスコントロール機能の実装をコードレビューしています。

また、認証認可 (e.g. Oauth) の機能を追加します。この機能は機械学習モジュールのContinuous Delivery/Continuous Deploymentを安全に行うために必須の機能です。Rekcurd dashboardは全ての機能がWebAPIから利用できます (しかもSwaggerUIもある) ので、例えば定期実行で生成した最新の機械学習モデルをアップロードしたり、development/staging/production環境へ機械学習モジュールをbatchでデプロイしたりできます。現状でもRekcurd dashboardのLDAP認証機能を無効にすればこれらのことは出来るのですが、事故の元になるため好ましくありません。そこでOauthのような認可機能を設けて、アクセストークンによってリクエストの正当性をセキュアに判断する必要があります。

Service meshの適用

Service meshに対応します。Service meshは近年Kubernetes界隈で注目されている概念で、Kubernetes上のネットワークトラフィックを管理する役割を持ちます。Service meshの特徴は、既存のアプリケーションに変更を加えることなくアプリケーションに対するネットワークトラフィックを管理できる、点です。具体的にはKubernetes Podのサイドカーとして立ち上がり、Proxyを立ててPodに対する全てのトラフィックに干渉します。なので、例えばPodがアクセスできる外部サービスを制限したり、Kubernetes Service間のルーティングをしたり、Load balanceしたり、などが出来ます。

Service meshの実装は色々とありますが、現時点ではIstioが最大勢力です。そして我々はRekcurdのIstio対応を始めました。Istioは多機能ですが、特にgRPCプロトコルのLoad balanceが出来る点がRekcurdにとって大きなメリットです。gRPCは一度貼ったネットワークコネクションを維持しようとするので、意図した比率でABテストが出来なかったり特定のPodに負荷が集中したりしました。Service meshはこれらの課題を解決できます。また、Rekcurd dashboardのIstio対応も進めています。Rekcurd dashboardのWebUIからIstioの設定を変更できるようにし、Istioの難解な設定を誰でも簡単に調整できるようにします。

主要なCloud/Bare metal上のKubernetes環境対応

主要なKubernetes環境をサポートします。現在は我々のKubernetes環境 (Rancher1.6, Rancher2.1, minikube) で動作確認およびドキュメンテーションが完了しています。今後、AWS/GCPなどの主要なCloud Kubernetes環境での動作保証をしていきます。また、kubeadmなどで構築したBare metal Kubernetes環境もサポートする予定です。

機械学習モデルのテスト機能およびテストデータの管理機能

Rekcurdには機械学習のモデルの性能を評価するためのメソッドがありますが、Dashboard側の実装がないために現在は活用できていません。この機械学習モデルのテスト機能はRekcurd dashboardからテストデータを指定し、RekcurdでPrecision/Recallなどの性能指標を計算し、Rekcurd dashboardで可視化するという使い方をします。この機能は性能の確認だけではなく、Continuous Delivery/Continuous Deploymentにおける判断基準としても用いることができます。将来的にはテストデータの管理とバージョニングができるようにします。

その他

SeldonのようなgRPC specの汎化、kubeflow連携、GPU対応、Dockerfile/Docker image自動生成、AB test/canary release対応など、必要なものを順番に対応していく予定です。

おわりに

今回の記事では、我々が開発している機械学習モジュールの管理運用基盤 Rekcurd のアップデート情報を紹介しました。主要なアップデートはPyPI対応とLDAP対応です。RekcurdはKubernetesと組み合わせることで誰でも簡単に機械学習モジュールを配信、運用、活用できるソリューションです。RekcurdはOSSとしてGitHubにてApache license 2.0で公開していますので、是非フィードバックスターをください。そして一緒に開発していただいて、機械学習モジュールの運用面を盛り上げていきましょう!

最後に、Clova開発室では、研究・開発・企画・営業といった全てのポジションを積極採用中です。また個人的にはRekcurdを一緒に開発してくれるフロントエンドエンジニア・サーバーサイドエンジニア・機械学習エンジニアのポジションを強く求めていますので、興味のある方はぜひ応募してください!

明日の最終日は overlast さんによる「名前文字列を読み仮名に変換する話(仮)」です。お楽しみに!

Related Post