GCP(Google Cloud Platform)を活用した機械学習バッチ運用環境(2016)

この記事は[リクルートライフスタイル Advent Calendar 2016](http://qiita.com/advent-calendar/2016/recruitlifestyle)の6日目の記事です。
データエンジニアリンググループ・素人イケメン機械学習エンジニアの[@_stakaya](https://twitter.com/_stakaya)です。
主にデータ分析や機械学習を活用し、まだここにない出会いを求めて日々🍻充しながらドラムを叩いています。

## はじめに
[弊グループ](http://engineer.recruit-lifestyle.co.jp/techblog/2016-11-01-cet-camp-report/)では、Capture EveryThingの略称である「CET」というプロジェクトを行っており(以下、CETと呼称)、サービス改善によるユーザ体験の向上や高付加価値の提供、およびそれによるCVR向上を目的とし、各種ログやトランザクションデータの収集、また収集したデータを徹底活用したデータ分析・機械学習の実施、およびそれら収集したデータを基にしたアプリケーション開発を行っています。

CET内では大まかに

0. 何のデータを新規に収集・集約し、どうビジネスに活かすかを考えるデータプランナー
0. ビッグデータの収集を行うための基盤を構築・運用するインフラエンジニア([2日目担当](http://qiita.com/mia_0032/items/241e611ef2180ad444ff)の[mia_0032](http://qiita.com/mia_0032)氏など)
0. 収集されたデータを分析する機械学習エンジニア/データサイエンティスト(25日目担当の[awesome-tomomoto](http://qiita.com/tomomoto)氏など)
0. 分析されたモデルや収集されたデータに関わる実装を行うアプリケーションエンジニア(10日目担当の[techeten](http://qiita.com/techeten)氏など)

という職種の方々が活躍しており、私は主に3, 4の役割を担当しています。

CET自体は”エンジニア・ファースト”を掲げたエンジニア色が非常に強いプロジェクトであり、データサイエンティストと呼称されるような職種であっても、プロダクション運用されるコードをガンガン書きますし、簡単なバッチ処理くらいであれば、インフラ・アプリケーションエンジニアの同僚が作ってくれたCI環境・バッチ管理基盤をフルに活用し、自分たちで構築・運用してしまいます。

そこでこの記事では、CETにおいて、データ分析・機械学習系のバッチが機械学習エンジニア/データサイエンティストの手によってどのように開発・運用されているかについて紹介します。

## GCP(Google Cloud Platform)を活用した機械学習バッチ運用環境
一昔前[^past]だと、不具合が出た際や、コードを最新のものにする際には、バッチ処理マシン自体にSSHでログインして手動で設定するなどなど手の温もり溢れる運用だったのですが、いい加減面倒になったため、大規模なリファクタリングをしようと考えていました。
[^past]: といっても一年も経っていないですが…
そのタイミングで丁度、グループでの[リファクタリング祭り（開発合宿）](http://engineer.recruit-lifestyle.co.jp/techblog/2016-11-01-cet-camp-report/)が開催されたため、これはいい機会だとバッチ運用環境を大幅に改善しはじめ、インフラ・アプリケーションエンジニアの同僚が作ってくれたCI環境・バッチ管理基盤を活用し、現在では大分その姿を変えることができました。

現在の弊チームでの機械学習バッチ運用の姿を図で表したものが下図になります。
CETとしての方針もあり、大部分の機能を[GCP(Google Cloud Platform)](https://cloud.google.com/)に寄せており、AWSは、データソースとしてのRedShift、外部協働者へのデータ提供手段としてのS3、イベントトリガー or 定期実行での処理結果監視担当のLambdaくらいの活用に留まっています。
<img width="1080" alt="ml-batch.png" src="https://qiita-image-store.s3.amazonaws.com/0/147221/c6760ae4-3f3b-441f-a816-13588e983111.png">

以下、この図となるべく対応するように、CETではどのように機械学習バッチを運用しているのかを説明していきます[^2]。
[^2]: ここで言及するバッチ処理はリアルタイム/ストリーミングな処理ではなく、所謂、一日に数回程度しか動かさないバッチ処理を想定しています

### バッチ処理マシンイメージの作成
データ分析・機械学習用のバッチ処理マシンイメージを作っており、実際のバッチ処理の際にはそのイメージに基づいたバッチ処理マシンを動的に生成し、バッチ処理を実行しています。バッチ処理マシンイメージは

0. [Ansible](https://www.ansible.com/)を用いて、バッチ処理マシンイメージのコードを書く
1. コードを社内のGithubにPushしたタイミングでCI環境([Drone](https://drone.io/), OSS版)が自動でビルド＆テスト実行
2. テストにパスした段階でコードをMasterにマージし、ステージング環境で再チェック
3. 問題なさそうであれば、タグを打つ
4. タグがPushされた段階で、以下の処理がDroneにより自動実行される
   0. [Packer](https://www.packer.io/)とAnsibleを用い、マシンイメージを生成
   0. 生成されたマシンイメージを[Compute Engine](https://cloud.google.com/compute/)に保存

という手順で生成されます。また、Droneが担当した作業については基本的にSlackに通知を飛ばすようにしています。

バッチ処理マシンイメージではBigqueryやRedShift、S3などのデータリソースへのアクセスのみが担保されているように設計されており、RやPythonでの実際の機械学習計算の際には別途作成しておいた機械学習用のDockerコンテナを用いて計算させるような設計にしています。このDockerコンテナは[Container Registry](https://cloud.google.com/container-registry/)に格納しており、必要に応じてバッチ処理マシンに`docker pull`されます。これはよくありがちな”俺の手元での計算結果とバッチ処理サーバでの計算結果が違う！”問題を避けるためです[^docker]。

[^docker]: ・・・という理由に加えて、流行りモノであるDockerやコンテナに手を出したかったという想いもあります

### バッチ処理コードの管理
データ分析・機械学習用のバッチ処理コードも基本的にはGithub上で管理していますが、Github Enterpriseで運用している都合上、定期的にメンテナンスがはいるため、本番で実行されるコードは全て[Cloud Storage](https://cloud.google.com/storage/)にも保存するようにしています。ここでもDroneが活躍しています。
具体的には以下のような手順になります。

0. コードを書いてGithubにPushする
1. Droneで動作するテストにパスし、問題がないならMasterにマージ
2. リリースしたいタイミングでタグを打つ
3. タグがPushされたタイミングで、Droneにより、該当するタグのコードがCloud Storage上にアップロードされる

ここでタグを打たれてGoogle Storageに保存されたコードが実際のバッチ運用で使用されるコードになります。

### バッチの作成・管理
さてこれで、バッチ処理マシンイメージとそのコードの準備ができましたので、実際にバッチ処理としてまとめ上げていきます。バッチ処理自体の作成・管理には[Rundeck](http://rundeck.org/)を用いており、下記の内容についても全てRundeck上のJOBとして記載しています。Rundeck自体は機械学習バッチ専用に設けているわけではなく、他の用途にも使用されています。

Rundeck上で動いている処理なのか、バッチ処理マシン上で動いている処理なのかわかりにくいため、その辺も明示して手順を記載すると


0. Rundeckから、作成しておいた機械学習バッチ処理マシンイメージを用いて、適切な大きさのインスタンスを生成する
1. バッチ処理マシン上に、Container RegistryからR・PythonなどのDockerコンテナを必要に応じて取得する
2. バッチ処理マシン上に、処理に必要なコードをGoogle Storageから取得する
3. バッチ処理マシン上で、バッチ処理を実行し、計算結果をGoogle StorageやS3、Bigtableなどに格納する
4. Rundeckから、バッチ処理マシンを破棄する

という手順になります。これで、バッチ処理をする度にマシンの生成・破棄を行うことで余計なバッチ処理マシンが常に動いていることなく、限りなく面倒を見る必要のあるサーバのない機械学習バッチ運用が可能となりました。

また、各所に格納された計算結果は、APIサーバを通して各サービスに展開されたり、またS3に出力されて外部の協働者の方々に使用して頂くということも多々あります。また、結果の中には"学習済みの機械学習モデル"も含まれ、この学習済みモデルはまた他のデータ分析・機械学習バッチ処理の際に活用されるというケースもあります。

## まとめと今後
本記事ではCETプロジェクトにおいて、どのように機械学習系のバッチ処理が運用されているのかについて説明しました。
このままでも結構快適なのですが、意外とRundeckでのJOBの記述や管理が冗長になってきているため、そこをいかに改良していこうかと、（私ではなく主に）インフラエンジニアの同僚が[Digdag](https://github.com/treasure-data/digdag)や[AirFlow](https://github.com/apache/incubator-airflow)、また、先週話題になった[AWS Batch](https://aws.amazon.com/jp/batch/)なども含めて、今後のバッチ管理システムをどのように改善していくべきか検討を開始してくれています[^new-batch]。
来年のアドベントカレンダーや[リクルートライフスタイルのテックブログ](http://engineer.recruit-lifestyle.co.jp/techblog/)などで、この辺の話をする際には、またガラっと変化があるような予感がしております。

[^new-batch]: この領域も日進月歩なので、あっという間にまた変わりそう&システム構成図を書き直す手間が増えるといえば増えるのですが私は日々の生活をより楽にする方に賭けたい…

この記事が皆さんの何かお役に経てば幸いです。