アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

GKE上のkubernetesで機械学習運用環境(MLops)を作成手順・コマンド・知識メモ

機械学習運用環境(MLops)の一部を話題のkubernetes(k8s)で作成してみた。GCPのGKE上でk8sを利用できるので、今回はGKE上で途中まで構築。

今回参考にするMLopsは下の図。どうもこのサイトによるとメルカリでマイクロサービスとして運用されてるらしい  




このMLopsのk8sの部分だけ(下図)作成してみた。




・作成手順
step1. persistentVolumeを使ってAWSのS3とマウントしたPodをLoad balancerを使って、負荷分散を行う。http://[EXTERNAL-IP]にアクセスして、flaskで’hello, world’を表示させる。


step2.redisインスタンスと結合させたflaskアプリケーション用Podをgoogleチュートリアルに従い、作成


step3.上の図のPersistentVolumeと繋がってるtensorflowの部分の「worker用Pod」を作成。今回はceleryというライブラリのチュートリアルに従い、celeryでworker用Podを作成(途中まで)



途中からAmazon sageMakerの方がMLopsを簡単に作れるという噂で、k8sでの構築はstep3で中断。けど、せっかくなのでアウトプットしたログを記事にまとめておこうと思う。

構築に使用したコードとかファイルはgithubにあげてあリます。

目次
・MLopsの構築

kubernetes、Docker関係知識まとめ

・コマンド関連まとめ



MLopsの構築

step1. flask用Podを作成して、で’hello world'を表示

まずflaskアプリケーションで’hello world’を表示させてみる。flaskはDjangoより便利ってことで使った。


・用意したファイル
app.yaml 、main.py(flaskアプリ本体) 、requirements.txt(インストール用のライブラリ)、Dockerfile、flask_pod.yaml(Pod作成用マニフェストファイル)、sample_lb.yaml(load balancer用ファイル)


プロジェクト ID:sturdy-willow-167902


1. Dockerイメージpush & 確認

まずGCPのホーム画面からcloud shell(コンソール)を開いて(右上のとこにある)、kubernetes Engineを選択。 

cloud shell(コンソール)の起動方法は、GCPメイン画面から[プロジェクト]をクリック。

上段の右から6番目のアイコン「cloud shellにアクセス」をクリックし、「起動」で開ける。





以下コマンドはコンソール上で実行。

# Dockerイメージ作成して、Container Repositoriesにpush
$ export PROJECT_ID="$(gcloud config get-value project -q)”
$ docker build -t gcr.io/${PROJECT_ID}/visit-counter:v1 .
$ gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:v1

#コンソール右上の [ウェブでプレビュー]で ’hello world’ が表示されるか確認
$ docker run --rm -p 8080:8080 gcr.io/${PROJECT_ID}/visit-counter:v1

→ Container Repositoriesに「visit-counter」ができる



2.Podの作成とserviceの定義


ここは単純にkubektlコマンドでPodの作成とサービスの定義(負荷分散)だけ。

そのためにGUIでもCUIでもいいので、kubernetes Engineのクラスタを作成しておく。クラスタを作成したら、コンソールで以下コマンド実行。

# クラスタ操作権限取得
$ gcloud container clusters get-credentials <クラスタ名> --zone=asia-northeast1-a

# 一応、VMインスタンス確認
$ gcloud compute instances list


***注意、マニフェストファイル(flask_pod.yaml)ではイメージ名のプロジェクトIDを展開する参考サイト

asia.gcr.io/$PROJECT_ID/wdpress/hello-world
# $PROJECT_ID は展開する必要があ流ので、上のイメージ名を下のように修正
asia.gcr.io/sturdy-willow-167902/wdpress-123456/hello-world

# Podの作成
$ kubectl apply -f flask_pod.yaml (pod確認  $ kubectl get pod)
# Serviceの定義
$ kubectl apply -f sample_lb.yaml
# EXTERNAL-IP(IPアドレス)の確認
$ kubectl get service

→ http://[EXTERNAL-IP] にブラウザからアクセスして、’hello world’が表示される。



step2.redisインスタンスと結合したflask用Pod作成

redisインスタンスと結合したflaskアプリケーションのPodを作成。
作成するMLopsではこの部分

これはGoogleのチュートリアルがあるが、素でやってもすんなり行かないので、ちょっと修正した。あとはチュートリアル通りに実行。 


チュートリアルで修正した点
インスタンスクラスタのリージョンを同じにする => region=asia-east1

・RESERVED_IP_RANGE の部分を、実際のインスタンスの予約済み IP 範囲に置き換える(参考サイト

マニフェストファイル(YAMファイル)のイメージ名のプロジェクトIDを展開



この3点だけ修正。あとはチュートリアル通りに実行。

プロジェクトID:sturdy-willow-167902



1.redisインスタンス作成

$ gcloud config set core/project sturdy-willow-167902
$ gcloud redis instances create myinstance --size=2 --region=asia-east1

# インスタンス情報確認
$ gcloud redis instances describe myinstance --region=asia-east1

>> authorizedNetwork: projects/sturdy-willow-167902/global/networks/default
createTime: '2018-12-05T02:19:31.887986552Z'
currentLocationId: asia-east1-a
host: 10.0.0.3
locationId: asia-east1-a
memorySizeGb: 2
name: projects/sturdy-willow-167902/locations/asia-east1/instances/myinstance
port: 6379
redisVersion: REDIS_3_2
reservedIpRange: 10.0.0.0/29
state: READY
tier: BASICtier: BASIC

# redisインスタンスを削除するとき
$ gcloud redis instances delete myinstance ―region=asia-east1




2.CUIクラスタ作成

$ gcloud config set core/project sturdy-willow-167902
$ gcloud config set compute/zone asia-east1
$ gcloud container clusters create visitcount-cluster --num-nodes=3 --enable-ip-alias

# クラスタの権限get
$ gcloud container clusters get-credentials visitcount-cluster --zone asia-east1 ―project sturdy-willow-167902





3.RESERVED_IP_RANGE の部分を、実際のインスタンスの予約済み IP 範囲に置き換え

$ git clone https://github.com/bowei/k8s-custom-iptables.git
$ cd k8s-custom-iptables/
$ TARGETS="10.0.0.0/29 192.168.0.0/16" ./install.sh
$ cd ..





4.Dockerイメージ作成とpush

$ export PROJECT_ID="$(gcloud config get-value project -q)”
$ docker build -t gcr.io/${PROJECT_ID}/visit-counter:v1 .
$ gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:v1




5. configmapの作成

$ kubectl create configmap redishost ―from-literal=REDISHOST=10.0.0.3
$ kubectl get configmaps redishost -o yaml



6.pod作成, service定義、アクセス

$ kubectl apply -f visit-counter.yaml
$ kubectl get service visit-counter

# アクセスして動作確認
$ curl http://104.199.211.24 (http://[EXTERNAL-IP])


# configmap削除
$ kubectl delete configmap redishost



Step3.celeryでworker用のPodを作成

ここでは下図のPersistentVolumeとつながるworkerのPodを作成。

今回は一応、簡易版てことでceleryというライブラリのチュートリアルのコードを入れたPodを作成する、予定だった。 

ところがamazon sagemakerというのが便利との噂を聞いて、k8sからamazon sagemakerに切り替えてMLopsを構築した。ので、ここのworker用のPod作成は途中で中断。

worker用のPodは、step.1と2のmain.pyをceleryで実装したflaskアプリに書き換えて、celeryをインストールするよう改良。dockerfileからいつも通りPodを作ればいいかと思う。

step2のredisインスタンスのURL(http://104.199.211.24)をceleryCELERY_BROKER_URLCELERY_RESULT_BACKENDの部分に入れる。

from flask import Flask
flask_app = Flask(__name__)
flask_app.config.update(
    CELERY_BROKER_URL='redis://localhost:6379',
    CELERY_RESULT_BACKEND='redis://localhost:6379'
)
celery = make_celery(flask_app)


最終的に作ったPodが

$ Kubectl get pod 

で確認したとき、「Running」になってればいいと思う。

ちなみに、Amazon sageMakerでのMLops構築手順は別記事にまとめた。

trafalbad.hatenadiary.jp




kubernetes、Docker関係知識まとめ

PersistentVolumeについて

k8sのpersistentvolume (以下PV)はPodとは独立した外部ストレージ。

通常のk8sのPVはクラスタの外部の別の場所に作成するが、GCP上ではPVは代替ストレージの「GCEPesistentDisk」がすでに存在してる。PVCで用途に応じて容量・タイプなどを適宜追加・拡張する。




・PVのアクセスモード

  • > ReadWriteOnce: 1つのノードからR/Wでマウントできる
  • > ReadOnlyMany: 複数のノードからReadOnlyでマウントできる
  • > ReadWriteMany: 複数のノードからR/Wでマウントできる


・PVCの作成時、PVのストレージタイプの「標準」か「SSD」をStorageClassで指定

SSD」はマニフェストファイルではstorageClassName: ssdで指定。デフォルトは「標準」



app.yamlPython ランタイムについて

=>アプリケーションのコードと依存関係をインストールして実行するソフトウェアスタック。
標準ランタイムは、app.yaml で以下のように宣言(Docker でビルド)
runtime: python
env: flex


=>アプリケーションの app.yaml で使用するバージョン(Python2 or 3)をruntime_config に指定

runtime: python
env: flex
runtime_config:
    python_version: 3

=>ライブラリのインストール
同じディレクトリ内の requirements.txt ファイルを検索し、pipでrequirements.txt 内のライブラリを全てインストールする。






・PV<=> PVC <=> Pod間の結合関係
PVとPVCとPod間のどこが結合してるか?の関係について、


PV:metadata.name: nfs001, storageClassNameフィールドのslow

PVC:metadata.name: nfs-claim1, storageClassNameフィールドのslow

Pod:metadata.name: my-nginx-pod, persistentVolumeClaim.claimNameフィールドのnfs-claim1

つまりPVとPod、PVCとPodの結合関係は
PVとPod: slow(PV) <=> slow(Pod)
PVCとPod:nfs-claim1(PVC)<=> nfs-claim1(Pod)



Nginx => フリー&オープンソースなWebサーバのdockerイメージ




apiVersion=>簡単にいうと、「fieldや構成を定義しやすくするもの」らしい




DockerfileのENVの環境変数について
ENV命令は、環境変数と値のセット。値はDockerfile から派生する全てのコマンド環境で利用でき、 上書き、変数扱いも可能。

ex: 環境変数に値をセットする場合、

ENV MES Good-bye 

は MES=Good-bye
を意味する。 $MESで環境変数をあらわせる。




・gunicorn
例えば myapp.py というファイル内に app という名前でFlaskアプリがある場合、以下のように gunicorn で起動。

$ gunicorn myapp:app

デフォルトのPORTとHOSTは127.0.0.1と8000



gunicornのportについて
This will start one process running one thread listening on 127.0.0.1:8000.

I wish to run gunicorn on port 80.
→ gunicorn -b 0.0.0.0:80 backend:app




・flask

if __name__ == ‘__main__':
   app.run(debug=False, host='0.0.0.0', port=80)

host='0.0.0.0' の指定が大事。これがなければ、外部からアクセスすることができない。参考サイト



・os.environ.get
環境変数 key が存在すればその値を返し、存在しなければ default を返す。
key、default、および返り値は文字列。
もし環境変数がなくともKeyErrorがraiseされず、第二引数(キーワード引数)のdefaultの値が返る。(指定しなければNoneが返る)



・PORT、HOST
REDISHOST→ネットワークに接続されたコンピュータのこと(redisインスタンス自体)
REDISPORT→redisインスタンスに接続するドア(番号)




・configMap
# マニフェストファイル内での部分

env:
     - name: REDISHOST
        valueFrom:
          configMapKeyRef:
            name: redishost
            key: REDISHOST

keyのREDISHOSTが環境変数

# configmap作成コマンド
$ kubectl create configmap <map-name> <data-source>

# 例
$ kubectl create configmap redishost ―from-literal=REDISHOST=10.0.0.3

redishostがmap-nameで、それ以下がdata-source

上のコマンドで環境変数のREDISHOSTが10.0.0.3に変更された。

REDISHOST=redisインスタンス自体を環境変数に代入。Dockerfileのデフォルトのクラスタ環境変数をredisインスタンスのPORT(自体)で上書き。




コマンド関連まとめ



(ターミナル上での)k8sクラスタ作成コマンド

$ gcloud container clusters create <クラスタ名> --machine-type=n1-standard-1 ―num-nodes=3

ex: gcloud container clusters create mlops --machine-type=n1-standard-1 --num-nodes=3



クラスタ環境設定の権限取得(cloud shell上で作成したクラスタを操作できる)

$ gcloud container clusters get-credentials <名前> ―zone=<ゾーン>

ex:$ gcloud container clusters get-credentials mlopss --zone=asia-northeast1-a



kubectlコマンドでクラスタ内のノードとpod関係の操作

$ kubectl get nodes (ノードの確認)
$ kubectl get pod  (Podの確認)
# Serviceの情報を確認(クラスタ内で通信可能なIPアドレス)
$ kubectl get services sample-clusterip

# Pod内へのログイン
$ kubectl exec -it sample-pod /bin/bash
→sample-podはmetadata.nameフィールド名



・dockerコマンド関係

$ docker rmi <イメージID>  -f (dockerイメージの削除) 
$ docker images (dockerイメージ確認)
$ docker ps(コンテナが正常に起動しているか確認)

# コンテナ停止、削除
$ docker stop [コンテナ名]  (全コンテナ停止 docker stop $(docker ps -q))
$ docker rm [コンテ名] (全コンテナ削除 docker rm $(docker ps -q -a))



・Podのマウント先からkubectl cpでデータのコピー

apiVersion: v1
kind: Pod
metadata:
  name: dataaccess
spec:
  containers:
  - name: alpine
    image: alpine:latest
    volumeMounts:
    - name: mypvc
      mountPath: /data
  volumes:
  - name: mypvc
    persistentVolumeClaim:
      claimName: mypvc

=>コピーコマンドとS3の例

# コピーコマンド
$ kubectl cp dataaccess:/data data/

# S3からコピー
$ kubectl cp sample-tensorflow:s3://image/data