Kubeflow – TensorFlow 학습하기

텐서플로우 알아 보기

텐서플로우(TensorFlow)는 구글에서 만든 오픈소스 딥러닝 프레임워크입니다. 딥러닝 프로그램을 쉽게 구현할 수 있도록 다양한 기능을 제공해주고 있습니다. 텐서플로우 자체는 기본적으로 C++로 구현 되어 있으며, Python, Java, Go 등 다양한 언어를 지원합니다.

TFJob을 이용해서 텐서플로우로 모델을 학습 할 수 있습니다. TFJob은 쿠버네티스에서 분산 또는 비 분산 텐서플로우 작업을 쉽게 실행할 수 있는 쿠버네티스 사용자 리소스(Custom Resource) 입니다.

TFJob

TFJob은 쿠버네티스에서 텐서플로우를 이용한 학습 작업을 할 수 있게 해주는 쿠버네티스 사용자 리소스 입니다. TFJob의 구현은 tf-operator에 있습니다. tf-operator는 TFJob을 관리합니다. 쿠버네티스에 TFJob이 등록되면, 필요한 포드들을 생성하여 작업을 실행할 수 있도록 도와줍니다.

TFJob은 다음과 같이 YAML 형식으로 표현할 수 있는 쿠버네티스 사용자 리소스입니다.

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-tfjob-dist
  namespace: admin
spec:
  cleanPodPolicy: Running
  tfReplicaSpecs:
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume
    Worker:
      replicas: 2
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume

분산 처리 기술을 사용한 모델 학습

쿠버네티스에서 TFJob을 관리하고 있는 tf-operator 는 텐서플로우를 사용한 모델 학습시 분산 처리가 가능하도록 도와주고 있습니다.

텐서플로우에는 분산 처리 기술을 사용하기 위해서 다음과 같은 역할을 가진 프로세스가 존재합니다.

  • Chief: Chief는 학습을 조정하는 역할을 합니다. 그리고 모델의 체크포인트 같은 작업을 수행할 책임이 있습니다.
  • Ps (Paramter Server) : Ps는 파라미터 서버로서, 모델 파라미터에 대한 분산 데이터 저장소를 제공하는 역할을 합니다.
  • Worker : Worker는 실제 모델 학습 작업을 수행하는 역할을 합니다. 경우에 따라서 0번 Worker가 Chief 역할을 수행 할 수 있습니다.
  • Evaluator : Evaluator는 평가 지표를 계산하는 역할을 합니다. 모델을 학습 할 때 Evaluator를 사용하여 평가 지표를 계산할 수 있습니다.

TFJob은 TF_CONFIG라는 환경 변수를 사용하여 프로세스의 역할 정의합니다.

모델 학습을 시작하려면 TF_CONFIG라는 환경 변수에 다음과 같은 변수가 json 형태로 설정되어 있어야 합니다.

{
	"cluster": {
		"worker": ["host1:port", "host2:port", "host3:port"],
	  "ps": ["host4:port", "host5:port"]
   },
	"task": {"type": "worker", "index": 0}
}
  • cluster : 클러스터 내에 있는 서버들의 정보를 지정합니다.
  • task : 클러스터 내에서 현재 작업이 담당한 역할을 지정합니다. 클러스터 내에서 가질 수 있는 역할은 “chief”, “worker”, “ps”, “evaluator” 중 하나입니다. 단, “ps” 역할은 tf.distribute.experimental.ParameterServerStrategy 전략을 사용할 때만 쓸 수 있습니다

TFJob 이라는 쿠터네티스트 사용자 리소스를 생성하여 모델 학습을 진행 경우에는, tf-operator 가 프로세스의 역할에 맞게 자동으로 환경 변수를 설정해 줍니다. 그래서 분산 작업을 위한 별도의 환경 변수 설정 작업을 하지 않아도 됩니다.

다음은 TFJob에서 생성한 포드의 환경 변수를 일부 출력해 본 것입니다.

spec:
  containers:
  - env:
    - name: TF_CONFIG
      value: '{"cluster":{"chief":["mnist-tfjob-dist-chief-0.admin.svc:2222"],"worker":["mnist-tfjob-dist-worker-0.admin.svc:2222","mnist-tfjob-dist-worker-1.admin.svc:2222"]},"task":{"type":"chief","index":0},"environment":"cloud"}'

텐서플로우 학습 작업 실행하기

TFJob을 정의한 후 학습 작업을 생성해 보겠습니다.

모델 코드 작성하기

텐서플로우로 학습할 모델을 작성해 보겠습니다.

다음은 텐서플로우 케라스를 사용하여 mnist 숫자 이미지를 분류하는 파이썬 코드인 mnist-dist.py입니다. Gtf.distribute.experimental.MultiWorkerMirroredStrategy 를 사용해서 분산 학습을 지원하고 있습니다.

mnist-dist.py

from __future__ import absolute_import, division, print_function, unicode_literals

import os
import json
import tensorflow as tf
import tensorflow_datasets as tfds


def build_and_compile_model():
    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28, 1)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10)
    ])
    model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  optimizer=tf.keras.optimizers.Adam(),
                  metrics=['accuracy'])
    return model


@tfds.decode.make_decoder(output_dtype=tf.float32)
def decode_image(example, feature):
    return tf.cast(feature.decode_example(example), dtype=tf.float32) / 255


def train():
    print("TensorFlow version: ", tf.__version__)

    tf_config = os.environ.get('TF_CONFIG', '{}')
    print("TF_CONFIG %s", tf_config)
    tf_config_json = json.loads(tf_config)
    cluster = tf_config_json.get('cluster')
    job_name = tf_config_json.get('task', {}).get('type')
    task_index = tf_config_json.get('task', {}).get('index')
    print("cluster={} job_name={} task_index={}}", cluster, job_name, task_index)

    BATCH_SIZE = 64

    tb_dir = '/app/data/logs'
    model_dir = '/app/data/export'
    version = 1
    export_dir = os.path.join(model_dir, str(version))

    strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()
    mnist = tfds.builder('mnist', data_dir='/app/mnist')
    mnist.download_and_prepare()

    mnist_train, mnist_test = mnist.as_dataset(
        split=['train', 'test'],
        decoders={'image': decode_image()},
        as_supervised=True)
    train_input_dataset = mnist_train.cache().repeat().shuffle(
        buffer_size=50000).batch(BATCH_SIZE)
    # eval_input_dataset = mnist_test.cache().repeat().batch(BATCH_SIZE)

    options = tf.data.Options()
    options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.OFF
    train_input_dataset = train_input_dataset.with_options(options)

    print("Training...")

    with strategy.scope():
        multi_worker_model = build_and_compile_model()

    num_train_examples = mnist.info.splits['train'].num_examples
    train_steps = num_train_examples // BATCH_SIZE
    train_epochs = 10

    callbacks = [
        tf.keras.callbacks.TensorBoard(log_dir=tb_dir),
    ]

    history = multi_worker_model.fit(train_input_dataset, epochs=train_epochs, steps_per_epoch=train_steps,
                                     callbacks=callbacks)

    print("\\ntraining_history:", history.history)

    multi_worker_model.save(export_dir)


if __name__ == '__main__':
    train()

모델 컨테이너 이미지 만들기

모델 학습용 컨테이너 이미지를 만들기 위해서 Dockerfile을 생성하겠습니다. 텐서플로우 데이터셋을 사용하기 때문에 pip를 이용해서 추가해주었습니다.

다음은 텐서플로우2.1을 기반 이미지로 해서, 모델 파일을 추가하는 Dockerfile 입니다.

Dockerfile

FROM tensorflow/tensorflow:2.1.0-py3

RUN pip install tensorflow-datasets==2.0.0

RUN mkdir -p /app
ADD mnist-dist.py /app/

다음 명령어를 실행하면 kkangwoo/tfjob-dist:0.0.1 라는 이름의 컨테이너 이미지를 빌드 할 수 있습니다.

docker build -t kangwoo/tfjob-dist:0.0.1 .

빌드한 컨테이너 이미지를 컨테이너 레지스트리로 업로드 하겠습니다.

docker push kangwoo/tfjob-dist:0.0.1

PVC 생성하기

텐서플로우 학습 작업 중 생성되는 매트릭과, 학습이 완료된 모델을 저장하기 위해서 별도의 볼륨을 만들어서 사용하겠습니다.

다음은 100메가의 저장 용량을 가진 볼륨 생성을 요청하는 PVC 매니페스트입니다.

tfjob-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: tfjob-data-volume
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi

kubectl을 사용해서 admin 네임스페이스에 pvc를 생성합니다.

kubectl -n admin apply -f tfjob-pvc.yaml

TFJob 생성하기

tf-operator를 사용해서 텐서플로우로 작성한 모델을 학습 하려면 TFJob을 정의해야합니다.

  1. tfReplicaSpecs를 설정합니다. “tfReplicaSpecs”은 텐서플로우의 분산 학습시 사용하는 프로세스들을 정의하는데 사용합니다. Chief를 1개로 설정하고, Worker를 2개로 설정하였습니다.. Kubeflow 클러스터에 istio가 설치되어 있기 때문에, 자동으로 istio-proxy가 포드에 주입됩니다. 이것을 방지하기 위해서 어노테이션에 sidecar.istio.io/inject: “false” 을 추가해 주었습니다. 그리고 앞에 생성한 볼륨을 마운트 하였습니다.
tfReplicaSpecs:
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume
    Worker:
      replicas: 2
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume

다음은 TFJob 을 생성한 위한 메니페스트입니다.

tfjob-dist.yaml

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-tfjob-dist
spec:
  tfReplicaSpecs:
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume
    Worker:
      replicas: 2
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume

다음 명령어를 실행하면 admin 네임스페이스에 tfjob-dist 이라는 이름의 TFJob을 생성할 수 있습니다.

kubectl -n admin apply -f tfjob-dist.yaml

텐서플로우 학습 작업 확인하기

생성한 TFJob은 다음 명령어를 실행해서 확인 해 볼 수 있습니다.

kubectl -n admin get tfjob

생성된 TFJob이 있다면, 다음과 같은 응답 결과를 얻을 수 있습니다.

NAME               STATE       AGE
mnist-tfjob-dist   Succeeded   19m

TFJob이 생성되면, tf-operator 에 의해서 포드들이 생성됩니다. TFJob 매니페스트에 정의한 개수대로 chief, worker 포드가 생성되게 됩니다.

생성된 포드들은 다음 명령어를 실행해서 확인 해 볼 수 있습니다.

kubectl -n admin get pod -l tf-job-name=mnist-tfjob-dist

생성된 포드들이 남아 있다면, 다음과 같은 응답 결과를 얻을 수 있습니다.

NAME                    READY   STATUS    RESTARTS   AGE
mnist-tfjob-dist-chief-0    0/1     Completed   0          20m
mnist-tfjob-dist-worker-0   0/1     Completed   0          20m
mnist-tfjob-dist-worker-1   0/1     Completed   0          20m

TFJob은 작업이 끝난 후, 관련 포드들을 삭제해버립니다. 그래서 작업이 완료되면 포드가 조회되지 않을 수 있습니다. 작업이 완료되어도 포드들을 남겨 두고 싶다면, TFJob 매니페스트의 spec 부분에 “cleanPodPolicy: None” 를 추가하시면 됩니다.

TFJob spec의 CleanPodPolicy는 작업이 종료 될 때 포드 삭제를 제어할 때 사용합니다. 다음 값들 중 하나를 사용할 수 있습니다.

  • Running : 작업이 완료되었을 때, 실행이 끝난(Completed) 포드들은 삭제하지 않고, 실행중인(Running) 포드들만 삭제합니다.
  • All : 작업이 완료되었을 때, 실행이 끝난 포드들을 즉시 삭제합니다.
  • None : 작업이 완료되어도 포드들을 삭제하지 않습니다.

다음은 cleanPodPolicy를 추가한 메니페스트 예제입니다.

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-tfjob-dist
spec:
spec:
  cleanPodPolicy: None
...

TFJob의 작업 상태를 알고 싶으면 describe 명령어를 사용할 수 있습니다.

다음 명령어를 실행하면 admin 네임스페이스에 mnist-tfjob-dist 이라는 이름의 TFJob의 상태를 조회할 수 있습니다.

kubectl -n admin describe tfjob mnist-tfjob-dist

다음은 예제 작업에 대한 샘플 출력입니다.

Name:         mnist-tfjob-dist
Namespace:    admin
Labels:       <none>
Annotations:  ...
API Version:  kubeflow.org/v1
Kind:         TFJob
Metadata:
...
Spec:
...
Status:
  Completion Time:  2020-03-07T02:17:17Z
  Conditions:
    Last Transition Time:  2020-03-07T02:16:10Z
    Last Update Time:      2020-03-07T02:16:10Z
    Message:               TFJob mnist-tfjob-dist is created.
    Reason:                TFJobCreated
    Status:                True
    Type:                  Created
    Last Transition Time:  2020-03-07T02:16:12Z
    Last Update Time:      2020-03-07T02:16:12Z
    Message:               TFJob mnist-tfjob-dist is running.
    Reason:                TFJobRunning
    Status:                False
    Type:                  Running
    Last Transition Time:  2020-03-07T02:17:17Z
    Last Update Time:      2020-03-07T02:17:17Z
    Message:               TFJob mnist-tfjob-dist successfully completed.
    Reason:                TFJobSucceeded
    Status:                True
    Type:                  Succeeded
  Replica Statuses:
    Chief:
      Succeeded:  1
    Worker:
      Succeeded:  2
  Start Time:     2020-03-07T02:16:11Z
Events:        <none>

텐서플로우 학습 작업 삭제하기

작업이 완료되어도 TFJob은 삭제되지 않습니다.

다음 명령어를 실행하면 admin 네임스페이스에 mnist-tfjob-dist이라는 이름의 TFJob을 삭제할 수 있습니다.

kubectl -n admin delete tfjob mnist-tfjob-dist

텐서보드

TensorBoard는 텐서플로우의 실행 및 그래프를 검사하고, 이해하기 위한 웹 응용 도구로서, 텐서플로우에서 기록한 로그를 그래프로 시각화하여 보여줍니다. 텐서보드는 텐서플로우에 포함되어 있습니다.

텐서보드를 실행하기 위해서 쿠버네티스 디플로이먼트 리소스를 정의해 보겠습니다.

“tensorflow/tensorflow:2.1.0-py3” 라는 컨테이너 이미지를 사용하여 텐서보드를 실행하겠습니다. 텐서보드는는 실행 될 때, 주어진 파리미터 값에 의해 로그를 읽어옵니다.

다음은 텐서보드에서 사용하는 몇 가지 파라미터입니다.

  • port : 텐서보드에 접근하기 위한 포트입니다.
  • logdir : 로그가 저장된 디렉토리 경로입니다.
  • path_prefix : 접속 URL에서 사용하는 경로 접두사입니다. 예를 들어 /foo/bar의 경로 접두사를 사용하면, http://localhost/이 아닌 http://localhost:6006/foo/bar/ 를 통해서 TensorBoard에 접속할 수 있습니다.
- command:
        - /usr/local/bin/tensorboard
        - --logdir=/app/data/logs
        - --path_prefix=/namespace/admin/tensorboard/mnist-dist/
        - --port=80

앞서 실행한 TFJob에서 텐서플로우 로그를 쿠버네티스의 퍼시스턴스 볼륨에 저장하였습니다. tfjob-data-volume 라는 퍼시스턴스 볼륨 클레임 이름을 가지고 있습니다. 해당 볼륨을 텐서보드에 마운트 해줍니다.

        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

다음은 디플로이먼트 매니페스트입니다.

mnist-dist-tensorboard-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mnist-dist-tensorboard
  name: mnist-dist-tensorboard
  namespace: admin
spec:
  selector:
    matchLabels:
      app: mnist-dist-tensorboard
  template:
    metadata:
      labels:
        app: mnist-dist-tensorboard
    spec:
      serviceAccount: default-editor
      containers:
      - command:
        - /usr/local/bin/tensorboard
        - --logdir=/app/data/logs
        - --path_prefix=/namespace/admin/tensorboard/mnist-dist/
        - --port=80
        image: tensorflow/tensorflow:2.1.0-py3
        name: tensorboard
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

kubectl을 사용해서 admin 네임스페이스에 디플로이먼트 리소스를 생성합니다.

kubectl -n admin apply -f mnist-dist-tensorboard-deployment.yaml

텐서보드에 접속하기 위해서 쿠버네티스 서비스를 리소스를 정의합니다.

mnist-dist-tensorboard-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mnist-dist-tensorboard
  name: mnist-dist-tensorboard
  namespace: admin
spec:
  ports:
  - name: http
    port: 80
    targetPort: 80
  selector:
    app: mnist-dist-tensorboard
  type: ClusterIP

kubectl을 사용해서 admin 네임스페이스에 서비스 리소스를 생성합니다.

kubectl -n admin apply -f mnist-dist-tensorboard-service.yaml

Kubeflow 클러스터에는 istio가 설치되어 있었습니다. 텐서보드를 istio-ingressgateway를 통해서 접근할 수 있도록 하기위해서 VirtualService 라는 사용자 리소스를 정의하겠습니다.

mnist-dist-tensorboard-virtualservice.yaml

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: mnist-dist-tensorboard
  namespace: admin
spec:
  gateways:
  - kubeflow/kubeflow-gateway
  hosts:
  - '*'
  http:
  - match:
    - uri:
        prefix: /namespace/admin/tensorboard/mnist-dist/
    route:
    - destination:
        host: mnist-dist-tensorboard.admin.svc.cluster.local
    timeout: 300s

kubectl을 사용해서 admin 네임스페이스에 서비스 리소스를 생성합니다.

kubectl -n admin apply -f mnist-dist-tensorboard-virtualservice.yaml

다음 주소로 텐서보드에 접근할 수 있습니다.

http://kubeflow-address/namespace/admin/tensorboard/mnist-dist/

텐서플로우 서빙

Serving은 학습이 완료된 모델을 실제 서비스에 적용하는 것입니다. 텐서플로우 서빙을 이용하면 텐서플로우에서 생성한 모델을 서비스 환경에 쉽고 빠르게 적용할 수 있습니다.

텐서플로우 서빙을 이용해서 모델 서버 실행하기

TensorFlow 모델 서버를 실행하기 위해서 쿠버네티스 디플로이먼트 리소스를 정의해 보겠습니다.

“tensorflow/serving:2.1.0” 라는 컨테이너 이미지를 사용하여 모델 서버를 실행하겠습니다. 모델 서버는 실행 될 때, 주어진 파리미터 값에 의해 모델을 로드하게 됩니다. 모델 서버에 모델이 로드 되면, REST나 GRPC를 사용하여 추론 요청을 시작할 수 있습니다.

다음은 모델 서버에서 사용하는 몇 가지 파라미터입니다.

  • port : GRPC 요청에 사용할 포트입니다.
  • rest_api_port : REST 요청에 사용할 포트입니다.
  • modeL_name : REST 요청의 URL에서 이를 사용합니다.
  • mode_base_path : 모델을 저장 한 디렉토리 경로입니다. containers: – args: – –port=9000 – –rest_api_port=8500 – –model_name=mnist – –model_base_path=/app/data/export command: – /usr/bin/tensorflow_model_server image: tensorflow/serving:2.1.0

앞서 TFJob을 사용하여, 학습된 모델을 쿠버네티스의 퍼시스턴스 볼륨에 저장하였습니다. tfjob-data-volume 라는 퍼시스턴스 볼륨 클레임 이름을 가지고 있습니다. 해당 볼륨을 모델 서버에 마운트 해줍니다.

        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

다음은 디플로이먼트 매니페스트입니다.

mnist-model-server-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mnist-model-server
  name: mnist-model-server
spec:
  selector:
    matchLabels:
      app: mnist-model-server
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "false"
      labels:
        app: mnist-model-server
        version: v1
    spec:
      serviceAccount: default-editor
      containers:
      - args:
        - --port=9000
        - --rest_api_port=8500
        - --model_name=mnist
        - --model_base_path=/app/data/export
        command:
        - /usr/bin/tensorflow_model_server
        image: tensorflow/serving:2.1.0
        imagePullPolicy: IfNotPresent
        livenessProbe:
          initialDelaySeconds: 30
          periodSeconds: 30
          tcpSocket:
            port: 9000
        name: mnist
        ports:
        - containerPort: 9000
        - containerPort: 8500
        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

kubectl을 사용해서 admin 네임스페이스에 디플로이먼트 리소스를 생성합니다.

kubectl -n admin apply -f mnist-model-server-deployment.yaml

모델 서버에서 정상적으로 모델을 로드 했는지 여부를 확인하려면, 포드의 로그를 조회하면 됩니다.

kubectl -n admin logs -l app=mnist-model-server

정상적으로 모델이 로드 되었다면, 다음과 같은 응답 결과를 학인 수 있습니다.

2020-03-07 00:58:07.813234: I tensorflow_serving/model_servers/server.cc:86] Building single TensorFlow model file config:  model_name: mnist model_base_path: /app/data/export
2020-03-07 00:58:07.814324: I tensorflow_serving/model_servers/server_core.cc:462] Adding/updating models.
2020-03-07 00:58:07.814334: I tensorflow_serving/model_servers/server_core.cc:573]  (Re-)adding model: mnist
2020-03-07 00:58:07.918328: I tensorflow_serving/core/basic_manager.cc:739] Successfully reserved resources to load servable {name: mnist version: 1}
2020-03-07 00:58:07.918351: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: mnist version: 1}
2020-03-07 00:58:07.918357: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: mnist version: 1}
2020-03-07 00:58:07.918373: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:31] Reading SavedModel from: /app/data/export/1
2020-03-07 00:58:07.920422: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:54] Reading meta graph with tags { serve }
2020-03-07 00:58:07.920439: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:264] Reading SavedModel debug info (if present) from: /app/data/export/1
2020-03-07 00:58:07.945844: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:203] Restoring SavedModel bundle.
2020-03-07 00:58:07.977914: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:152] Running initialization op on SavedModel bundle at path: /app/data/export/1
2020-03-07 00:58:07.983624: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:333] SavedModel load for tags { serve }; Status: success: OK. Took 65247 microseconds.
2020-03-07 00:58:07.984126: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:105] No warmup data file found at /app/data/export/1/assets.extra/tf_serving_warmup_requests
2020-03-07 00:58:07.984397: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: mnist version: 1}
2020-03-07 00:58:07.995156: I tensorflow_serving/model_servers/server.cc:358] Running gRPC ModelServer at 0.0.0.0:9000 ...
2020-03-07 00:58:07.997145: I tensorflow_serving/model_servers/server.cc:378] Exporting HTTP/REST API at:localhost:8500 ...

모델 서버에 접속하기 위해서 쿠버네티스 서비스를 리소스를 정의합니다.

mnist-model-server-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mnist-model-server
  name: mnist-model-server
spec:
  ports:
  - name: grpc-tf-serving
    port: 9000
    targetPort: 9000
  - name: http-tf-serving
    port: 8500
    targetPort: 8500
  selector:
    app: mnist-model-server
  type: ClusterIP

kubectl을 사용해서 admin 네임스페이스에 서비스 리소스를 생성합니다.

kubectl -n admin apply -f mnist-model-server-service.yaml

텐서플로우 서빙에서 로드된 모델에게 요청 하기

아래 예제 코드를 실행하기 위해서는 request와 tensorflow-datasets라는 파이썬 패키지가 필요합니다. 설치가 되어 있지 않다면 다음 명령어로 설치할 수 있습니다.

pip install request
pip install tensorflow-datasets==2.0.0

주피터 노트북 사용자 인터페이스의 메뉴에서 File > New > Notebook 을 클릭하여 노트북 환경에서 새 노트북을 생성하겠습니다.

먼저 요청에 사용할 테스트 데이터를 생성하는 코드를 작성합니다.

import os
import json
import numpy as np
import matplotlib.pyplot as plt
import subprocess
import tensorflow as tf
import tensorflow_datasets as tfds

dataset = tfds.load("mnist", shuffle_files=True, as_supervised=False)
train_dataset, test_dataset = dataset["train"], dataset["test"]
test_data = []
iterator = iter(test_dataset.batch(5))
test_data.append(next(iterator))

이미지와 내용을 화면에 출력하기 위해서 show() 함수를 작성합니다.

def show(idx, title):
  plt.figure()
  plt.imshow(test_images[idx].reshape(28,28))
  plt.axis('off')
  plt.title('\\n\\n{}'.format(title), fontdict={'size': 16})

test_data의 0번째에 있는 이미지와 라벨을 테스트 삼아 출력해 보겠습니다.

show(0, 'An Example Image: {}'.format(test_labels[0]))

모델 서버에 요청할 데이터를 json을 사용해서 작성합니다.

import json
data = json.dumps({"signature_name": "serving_default", "instances": test_images.tolist()})
print('Data: {} ... {}'.format(data[:50], data[len(data)-52:]))

모델 서버에 POST 형식으로 요청 데이터를 전송하고, 예측 결과를 받습니다.

import requests
headers = {"content-type": "application/json"}
json_response = requests.post('<http://mnist-model-server:8500/v1/models/mnist:predict>', data=data, headers=headers)
predictions = json.loads(json_response.text)['predictions']

예측 결과를 show() 함수를 사용하여 화면이 출력합니다.

for i in range(0, len(predictions)):
    show(i, 'The model thought this was number {} , and it was actually number {}'.format(
  np.argmax(predictions[i]), test_labels[i]))

정상적으로 처리 되었다면, 다음과 비슷한 응답 결과를 얻을 수 있습니다.