databricks mlflow model を Kubernetes にデプロイする


# はじめに

Databricks のMLflow Registry に登録されているモデルを、Kubernetes(EKR) にデプロイしてみたいと思います。

今回試すフローは以下の通りです。予めモデルが作成されており、Databricks MLflow Registoryに登録された状態から今回はスタートします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/926fa97f-8ea6-7f69-0dcc-f144bc3a888c.png)


## 1.  MLflow モデルの確認

今回利用するモデルは、**e2ejmaru_churn_model** というモデル名のものを利用します。version は今回**staging** にしております。ちなみにモデル登録していない、MLflow Expreriment/Run の状態でも同じステップで image化することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/2b080aaf-3ba9-9597-0253-6a937f58443c.png)

参考までに、上記の図の **Serving**というタブを開いて、Model Serving機能を有効にすると、そのままモデルサービングできますが、今回は敢えて外部のコンテナ上で稼働させてみます。



## 2. Docker Imageを作成する

ローカルのPC上でDocker Imageを作成するため、MLflowとDockerをインストールする必要があります。

### 2-1. Dockerをインストール

こちらからDockerをダウンロード＆インストール。詳細は省略します。
https://docs.docker.com/get-docker/

### 2-2.MLflowをインストール
```
$ pip install mlflow
```

### 2-3. Databricks MLflowに関する環境変数設定

```
$ export MLFLOW_TRACKING_URI='databricks'
$ export DATABRICKS_HOST='https://xxxxxxxx.cloud.databricks.com/'   <== workspaceのURL
$ export DATABRICKS_TOKEN='dapixxxxxxxxxxxxxxxxxxxxxxxxxdc559'  <== User Access Token
```

ユーザーアクセストークンは、Databricks の [Settings] - [User Settings] - [User Access Token] から発行できます


### 2-4. Docker Image 作成

model name: e2ejmaru_churn_model
stage : staging
image name: jmaru_docker_image

```
mlflow models build-docker -m "models:/e2ejmaru_churn_model/staging" \
-n "jmaru_docker_image"
```

image の確認
`docker images`

参考情報
https://www.mlflow.org/docs/latest/cli.html#mlflow-models-build-docker

### 2-5. Docker Imageが作成できたら、ローカルDocker上にデプロイしてテストしてみましょう。

予め今回のモデルにあう[テスト用のjsonデータ](https://sajpstorage.blob.core.windows.net/public-data/data.json)を用意しておきました。

```
## コンテナのデプロイ　(ホストの5001ポートでListen)
docker run -p 5001:8080 jmaru_docker_image

## 用意したテストデータを使って予測（今回は５つのでデータを予測した結果が返ってきます）
curl http://127.0.0.1:5001/invocations \
 -H 'Content-Type: application/json; format=pandas-records' \
 -d@data.json
```

結果は、以下のように予測結果(0 or 1)が返ってきました。
[1, 1, 1, 1, 0]

参考情報
https://www.mlflow.org/docs/latest/models.html#deploy-mlflow-models


## 3. コンテナリポジトリー(ECR)にイメージを登録する

今回はコンテナリポジトリーとしてAWS ECRを利用します。そのため予め[プライベートリポジトリー](https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/repository-create.html)を作成しておきます。

今回は、**jmaru_repo** というリポジトリーを作成しました。

次にこちらの手順に従ってイメージをレポジトリーにPushします。
https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-push-ecr-image.html

```
# Dockerクライアントの認証
aws ecr get-login-password --region ap-northeast-1　|docker login \
 --username AWS \
 --password-stdin (aws_account_id).dkr.ecr.ap-northeast-1.amazonaws.com

# Amazon ECR レジストリ、リポジトリ、およびオプションのイメージタグ名を組み合わせたタグをイメージに付与
docker tag (docker_image_id) (aws_account_id).dkr.ecr.ap-northeast-1.amazonaws.com/jmaru_repo

# image のPush
docker push (aws_account_id).dkr.ecr.ap-northeast-1.amazonaws.com/jmaru_repo
```

無事に登録できると、ECR上から確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2206424/1b8f8d91-b4d4-6825-30e8-f699ca6a84da.png)

## 4. コンテナの作成

予めローカル端末に[ekscltをインストール](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eksctl.html)しておきます。

次に、コンテナをデプロイするためのkubernetesクラスターを用意します。

#### Kubernetes Cluster作成
参考：https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-eksctl.html

```
eksctl create cluster \
--name jmarukube \
--nodegroup-name standard-workers \
--node-type t3.medium \
--nodes 1 \
--nodes-min 1 \
--nodes-max 2
```

#### Containerのデプロイ

```
kubectl create deployment jmaru-app \
 --image=(aws_account_id).dkr.ecr.ap-northeast-1.amazonaws.com/jmaru-repo:latest \
 --port 8080
```
`kubectl get deployments` で確認

#### Load Balancerを使って外部公開する

```
kubectl expose deployment jmaru-app --type=LoadBalancer --port 80 --target-port 8080
```
`kubectl get services` で expose DNS名の確認



# 接続テスト

はい、これでモデルをk8s上にデプロイできたので、先ほど（ローカルでテストした時）の[サンプルデータ](https://sajpstorage.blob.core.windows.net/public-data/data.json)を使って、テストしてみましょう。

```
curl -X POST -H "Content-Type:application/json; format=pandas-records" \
 -d@data.json (exposed_LB_dns_name):80/invocations
```

結果は以下のとおりローカルのDocker上でテストした時と同じ結果が返ってきました。
[1, 1, 1, 1, 0]