Kubernetes ベースの機械学習基盤 Kubeflow をラップトップにデプロイする

機械学習を業務に適用するには、データの収集、機械学習モデルの作成、機械学習モデルを利用する API 開発、API を利用するアプリ開発、プロダクションへの適用、運用時の経年変化によるモデルの修正 といったプロセスで進めることになります。

Kubeflow は機械学習モデル作成、API 開発とデプロイまでをカバーする基盤ソフトウェアです。その実体は数多くの OSS の 集合体 (best-of-breed *1 ) となっています。

www.kubeflow.org

Kubeflow はその名の通り Kubernetes ネイティブなアプリなので Kubernetes クラスターにデプロイして使用します。GCP / EKS / AKS といったメジャーなマネージド Kubernetes はもちろん、ローカルの Minikube、kind *2 などにデプロイ可能です。

残念ながら現状、Docker for Desktop は Kubernetes のバージョンが 1.10.11 とやや古いので、最新版の Kubeflow はデプロイできません。Minikube だと VirtualBox 仮想マシンの分のオーバーヘッドがあるので Docker for Desktop のバージョンアップが待たれます*3

MiniKF を使ったデプロイ

Kubeflow には MiniKF という Minikube ベースのディストリビューションが提供されており vagrant up するだけで試すことができます。Windows でも Mac でも Linux でも OK ですが、起動するだけでメモリは 12GB 以上必要です。

www.kubeflow.org

VirtualBox と Vagrant をインストールしておきます。

www.vagrantup.com

www.virtualbox.org

次に適当なディレクトリで vagrant init / up します。

$ vagrant init arrikto/minikf
$ vagrant up

VirtualBox は headless mode で動きますので Window は出ません。完了したら、次のようなメッセージが表示されます。

f:id:kondoumh:20190614131617p:plain

ブラウザで 10.10.10.10 を開くと次のような画面が出ます。

f:id:kondoumh:20190614131941p:plain

Web の画面にターミナルが出ていて、このまま Kubernetes クラスターの起動と Kubeflow のデプロイ作業を続けることになります。

指示通りブラウザで作業を継続してもいいですが、インストール対象のマシンがリモートの Linux だったりして SSH で作業している場合などは、仮想マシンをポートフォワードしてもターミナルとの通信でエラーになってしまいます。その場合、仮想マシンに SSH 接続して作業を続行できます。vagrant ssh でマシンに入って minikf コマンドを叩きます。

$ vagrant ssh

Last login: Wed Jun 12 13:42:56 2019 from 10.0.2.2


Welcome to MiniKF! Type "minikf" to ensure
everything is up and running.

$ minikf

f:id:kondoumh:20190614132347p:plain

初回は10分以上かかります。完了すると Web 画面がこのようになります。

f:id:kondoumh:20190614140904p:plain

10.10.10.10:8080 で Kubeflow のダッシュボードが開きます。

f:id:kondoumh:20190614141130p:plain

デプロイされた Kubeflow 環境に別マシンから接続したい場合、VirtualBox の設定でポートフォワーディングすれば OK です。

f:id:kondoumh:20190614141403p:plain

名前 プロトコル ホスト IP ホストポート ゲスト IP ゲストポート
任意 TCP 0.0.0.0 空きポート 10.10.10.10 8080

Vagrantfile に以下のように記述することも可能です。

Vagrant.configure("2") do |config|
  :
  config.vm.network "forwarded_port", guest: 8080, host: 8080, host_ip: "0.0.0.0"
  :
end

起動した状態で kubernetes の Service や Pod を見てみます。

vagrant@minikf:~$ kubectl -n kubeflow get svc

NAME                                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
ambassador                               ClusterIP   10.101.145.42    <none>        80/TCP              17d
ambassador-admin                         ClusterIP   10.101.77.15     <none>        8877/TCP            17d
argo-ui                                  NodePort    10.111.146.214   <none>        80:32457/TCP        17d
centraldashboard                         ClusterIP   10.106.93.62     <none>        80/TCP              17d
jupyter-0                                ClusterIP   None             <none>        8000/TCP            17d
jupyter-lb                               ClusterIP   10.107.106.12    <none>        80/TCP              17d
jupyter-web-app                          ClusterIP   10.105.85.161    <none>        80/TCP              17d
katib-ui                                 ClusterIP   10.108.218.43    <none>        80/TCP              17d
kf-controller                            ClusterIP   10.103.123.244   <none>        80/TCP              17d
minio-service                            ClusterIP   10.111.170.236   <none>        9000/TCP            17d
ml-pipeline                              ClusterIP   10.104.125.126   <none>        8888/TCP,8887/TCP   17d
ml-pipeline-tensorboard-ui               ClusterIP   10.97.28.36      <none>        80/TCP              17d
ml-pipeline-ui                           ClusterIP   10.99.46.227     <none>        80/TCP              17d
mysql                                    ClusterIP   10.99.108.198    <none>        3306/TCP            17d
notebooks-controller                     ClusterIP   10.98.198.91     <none>        443/TCP             17d
profiles                                 ClusterIP   10.104.136.216   <none>        443/TCP             17d
studyjob-controller                      ClusterIP   10.109.150.25    <none>        443/TCP             17d
tf-job-dashboard                         ClusterIP   10.99.61.120     <none>        80/TCP              17d
vizier-core                              NodePort    10.108.253.190   <none>        6789:31426/TCP      17d
vizier-core-rest                         ClusterIP   10.107.7.132     <none>        80/TCP              17d
vizier-db                                ClusterIP   10.100.73.125    <none>        3306/TCP            17d
vizier-suggestion-bayesianoptimization   ClusterIP   10.110.135.67    <none>        6789/TCP            17d
vizier-suggestion-grid                   ClusterIP   10.108.29.137    <none>        6789/TCP            17d
vizier-suggestion-hyperband              ClusterIP   10.100.113.40    <none>        6789/TCP            17d
vizier-suggestion-random                 ClusterIP   10.102.11.44     <none>        6789/TCP            17d


vagrant@minikf:~$ kubectl -n kubeflow get pod
NAME                                                        READY   STATUS    RESTARTS   AGE
ambassador-6776b669f8-hx252                                 1/1     Running   6          17d
argo-ui-5dd54b58dd-zt8b8                                    1/1     Running   5          17d
centraldashboard-655cc9b87d-nfqvw                           1/1     Running   5          17d
jupyter-0                                                   1/1     Running   5          17d
jupyter-web-app-54c87dc6bd-jv474                            1/1     Running   5          17d
katib-ui-555897fdf7-zk6p8                                   1/1     Running   5          17d
kf-controller-bbc79fdbb-2pq6v                               1/1     Running   5          17d
metacontroller-0                                            1/1     Running   5          17d
minio-6f647575fb-97xzm                                      1/1     Running   4          17d
ml-pipeline-77ff6b76d8-wt9g5                                1/1     Running   5          17d
ml-pipeline-persistenceagent-6d4788b46f-8t9rz               1/1     Running   9          17d
ml-pipeline-scheduledworkflow-5cfd76dd45-2hhdb              1/1     Running   5          17d
ml-pipeline-ui-6586fc8cd9-mztfn                             1/1     Running   5          17d
ml-pipeline-viewer-controller-deployment-67cfc45b8c-kd78p   1/1     Running   9          17d
mysql-6ff979f77f-wldk4                                      1/1     Running   4          17d
notebooks-controller-68585fc8b6-r7j5m                       1/1     Running   8          17d
profiles-5bb5b5dc8-zkktm                                    1/1     Running   8          17d
pytorch-operator-55759ffb8d-kdjgn                           1/1     Running   5          17d
spartakus-volunteer-bbb4d6d7b-567pz                         1/1     Running   5          17d
studyjob-controller-fcd7fbcd6-m4dvc                         1/1     Running   8          17d
tf-job-dashboard-6ff5599fb-qfmnt                            1/1     Running   5          17d
tf-job-operator-79f967f447-f8skb                            1/1     Running   5          17d
vizier-core-76b54db47f-gdlwp                                1/1     Running   23         17d
vizier-core-rest-569dc8b789-v6hdn                           1/1     Running   5          17d
vizier-db-f5f495b5c-vs7l7                                   1/1     Running   4          17d
vizier-suggestion-bayesianoptimization-fbfd657b9-p9v72      1/1     Running   5          17d
vizier-suggestion-grid-66b858dfc5-rp2x9                     1/1     Running   5          17d
vizier-suggestion-hyperband-97cbfd98c-vqwt7                 1/1     Running   5          17d
vizier-suggestion-random-66c6f8889-fhrkb                    1/1     Running   5          17d
workflow-controller-c59cc89bc-z72tv                         1/1     Running   7          17d

25の Service、 29の Pod が起動してます。

vagrant@minikf:~$ docker ps | grep kubeflow | wc -l
60

コンテナの数にして60! かなりの規模ですね。試しに MiniKF を使わず素の Minikube 環境を作ってデプロイしてみたところ、起動するだけで 15GB 以上メモリを使ってしまいました。これでは様々なタスク用の Pod を起動する余裕はありません。MiniKF は起動直後は 12-3 GB ぐらいでちょっと余裕があるので、かなりチューニングされている模様です。

Jupyterlab、 Tensorflow、Pipeline などの機能が統合されています。argo-ui という Service が上がっています。argo は Kubernetes native なワークフローエンジンで CI/CD パイプラインの基盤にもなっています。

argoproj.github.io

JupyterLab の起動

JupyterLab を使ってみます。

Notebooks メニューで Notebook Servers メニューを開いて New Server ボタンをクリック。

f:id:kondoumh:20190614152413p:plain

Server 名を入力します。CPU やメモリ、ディスクなどのリソース割り当ても可能です。入力したら SPAWN をクリックしてサーバーを起動します。

f:id:kondoumh:20190614152613p:plain

しばらくするとサーバーが構築されます。

f:id:kondoumh:20190614152933p:plain

CONNECT をクリックすると JupyterLab が起動します。

f:id:kondoumh:20190614153127p:plain

普通に使えます。

f:id:kondoumh:20190614153152p:plain

この状態で、Pod を見てみると、JupyterLab 用の Pod が1つ増えていました。コンテナも2つ増加。

vagrant@minikf:~$ kubectl -n kubeflow get pod
NAME                                                        READY   STATUS    RESTARTS   AGE
  :
hoge-0                                                      1/1     Running   0          26m
jupyter-0                                                   1/1     Running   5          17d
jupyter-web-app-54c87dc6bd-jv474                            1/1     Running   5          17d
   :

vagrant@minikf:~$ docker ps | grep kubeflow | wc -l
62

Kubernetes のリソースとして JupyterLab の Server や Notebook がデプロイされサービスとして提供されていることが分かります。

Pipelines を動かしてみる

Pipelines も少し見てみます。

サンプルのパイプラインが最初からいくつかデプロイされてます。

f:id:kondoumh:20190615112216p:plain

コインを投げて結果を出力するサンプル。Pipeline の DSL である Condition (条件分岐) のデモのようです。

f:id:kondoumh:20190615112711p:plain

コインをトスする部分は Python のワンライナーをシェルで実行して、ファイルと標準出力に結果を出しています。

f:id:kondoumh:20190615113456p:plain

Condition DSL はこのように記述するようです。

f:id:kondoumh:20190615113702p:plain

実行した結果。condition-4 と condition-6 が発動して print-4 が実行されたみたいです。

f:id:kondoumh:20190615113915p:plain

ログ出力結果もパイプラインのノードごとに見ることができます。

f:id:kondoumh:20190615114308p:plain

kubectl で Pod の状態を見るとパイプライン用の Pod がデプロイ・実行されて完了状態になっています。

vagrant@minikf:~$ kubectl -n kubeflow get pod
NAME                                                            READY   STATUS              RESTARTS   AGE
  :
conditional-execution-pipeline-lk5qp-1114233284                 0/2     Completed           0          88m
  :

以上は、超簡単なサンプルでしたが、本格的な機械学習のパイプラインサンプルも入っています。TensorFlow を用いて、データの検証、変換、モデルの学習、モデルを使った予測、モデルの評価、モデルの Serving などを実行できるようです。

f:id:kondoumh:20190615115341p:plain

実行してみましたが、Pod の状態が、ContainerCreating のまま進みません。

vagrant@minikf:~$ kubectl -n kubeflow get pods

tfx-taxi-cab-classification-pipeline-example-5fq7b-4292043047   0/2     ContainerCreating   0          148m

Pod の詳細を describe してみたら、GCP の credentials ボリュームのマウントに失敗しているということで、ローカルの Minikube で動かすのは難しそうです。

vagrant@minikf:~$ kubectl -n kubeflow describe pod tfx-taxi-cab-classification-pipeline-example-5fq7b-4292043047
Name:               tfx-taxi-cab-classification-pipeline-example-5fq7b-4292043047
    :
Events:
  Type     Reason       Age                    From               Message
  ----     ------       ----                   ----               -------
  Warning  FailedMount  8m44s (x62 over 146m)  kubelet, minikube  Unable to mount volumes for pod "tfx-taxi-cab-classification-pipeline-example-5fq7b-4292043047_kubeflow(54dd9cc1-8f0c-11e9-97d8-0800271ba290)": timeout expired waiting for volumes to attach or mount for pod "kubeflow"/"tfx-taxi-cab-classification-pipeline-example-5fq7b-4292043047". list of unmounted volumes=[gcp-credentials]. list of unattached volumes=[podmetadata docker-sock gcp-credentials mlpipeline-minio-artifact pipeline-runner-token-8t2qs]
  Warning  FailedMount  2m27s (x80 over 149m)  kubelet, minikube  MountVolume.SetUp failed for volume "gcp-credentials" : secret "user-gcp-sa" not found

おわりに

Kubeflow はこのように、分析環境やワークフローなどをまとめて面倒見てくれる統合環境ですが、使いこなす上ではデータサイエンティストと Kubernetes に精通したエンジニアが協力し合うチーム体制が必要になりそうです。

*1:開発元に拘らず複数のソフトウェアを組み合わせて構成されたソリューション。Suites と対比される。

*2:https://github.com/kubernetes-sigs/kind コンテナ上に Kubernetes 環境を構築するツール

*3:macOS の場合 Minikube の --vm-driver オプションに hyperkit すればデプロイできそうですが未確認です。