MLOps

[Lecture] Deploying Machine Learning Models in Production Week2 - 2. Scaling Infrastructure

HJChung 2021. 11. 7. 19:02
머신러닝 모델을 만들면 Product에 모델을 배포하고, inference 요청에 대한 응답(모델의 예측값을 사용자에게 응답)을 할 수 있도록 모델 API를 제공해야 한다. 즉, model serving을 해야 한다. 
어떤 식으로 모델을 배포하고 운영할 수 있는지 소개하는 <Deploying Machine Learning Models in Production> 과정의 Week 2: Model Serving: Patterns and Infrastructure - Scaling Infrastructure 내용에 대해서 배운 것을 정리해 보았다. 

Scaling Infrastructure 

Why Horizontal Over Vertical Scaling

  • Benefit of elasticity: Shrink or growth no of nodes based on load, throughput, latency requirements.
  • Application never goes offline: No need for taking existing servers offline for scaling
  • No limit on hardware capacity: Add more nodes any time at increased cost

scaling을 하고자 할 때 고려해야 할 것

  • Can I manually scale? ex) 난 VM instance만 scaling하고 싶어!
  • Can I autoscale? ex) 난 사용량에 따라서 내 applicaton을 자동적으로 scale up, down 하고 싶어! 
  • What does latency and conts look like? 

그래서 최종적으로 I manage my additional VMs to ensure that they have the content on them that I want 할 수 있어야 하고, 

new machine(new VM instance)를 빠르게 실행가능한 상태로 준비시켜야 한다. 

=> 그 방법 중 하나가 containerism이다. 

containerism with Docker and Container Orchestration

관련 이슈

아이돌 가수의 팬들이 어떤 웹사이트에 10분마다 접속해서 퀴즈를 푼다. 즉, 사용량이 거의 0에 가깝다가 10분마다 갑자기 수십만의 접속량이 생기곤 하는 것이다. 즉, 비용이랑 성능 측면에서 balance를 맞춰서 scaling 해야 하는 복잡한 문제이다.

so the traffic for this app would look something like this!

Sony Music and Google solved this problem very inexpensively. Laurence isn’t allowed to share how much it cost for the cloud services, but, when he and several of the other engineers went out for celebration drinks after the success of the project, the bar bill was more expensive than the cloud bill. (And they didn’t drink a lot!) (ㅋㅋ)

오 어떻게 scaling 했는지 보자!

Kubernetes 배우기

https://kubernetes.io/docs/tutorials/kubernetes-basics/

여기서 cluster 생성, 배포 및 적용, 확장, 업데이트 등의 작업을 실습해 볼 수 있고, 쿠버네티스가 작동하는 방식을 이해할 수 있다.

Keynote: The Challenges of Migrating 150+ Microservices to Kubernetes, Sarah Wells 발표도 참고!

 

Kubeflow 배우기

https://www.kubeflow.org/ 에서 Get Started를 눌러서 tutorial을 보자. 

Kubeflow 101에서 제공하는 짧은 영상들도 참고!

 

실습 - hands-on practice with Kubernetes in preparation

0. prerequisite

실습 환경: macOS
실습 코드: github repo C4_W2_Lab_2_Intro_to_Kubernetes

 

1. Installation

먼저 local kubernetes cluster를 setup 해야한다.

설치해야 하는 것은 

  • curl: use this to query your model later. (How to install)
  • Virtualbox: Minikube를 virtual machine 안에서 돌도록 만들어볼 것이기 때문에 필요하다. (Docker를 사용해도 되지만 여기선 VM을 사용했다.) (How to install)
  • kubectl: Kubernetes clusters와 interacting 할 수 있는 Command line 툴이다. (How to install)
  • Minikube: 실습 수준에서 완전하게 동작하는 쿠버 네티스를 간단하고 빠르게 할 수 있는 방법은 Minikube를 사용하는 방법이다. https://kubernetes.io/docs/tutorials/kubernetes-basics/에서도 실습용으로 Minikube를 사용하고 있다. 단, sinle node cluster에서만 실행할 수 있으므로 production 배포용으로는 적합하지 않다. (How to install)

2. Architecture

해당 실습에서 만들어볼 application은 아래와 같은 구조이다.   

tensorflow/serving docker image로 serving한 model server container가 실행 중인 Deployment node를 생성하고, 

Deployment는 외부 Service를 통해 inference 요청과 응답을 주고받을 수 있게 한다. 

tensorflow/serving:2.6.0 Dockerfile

ADD file ... in /

CMD ["bash"]

ARG TF_SERVING_VERSION_GIT_BRANCH=master

ARG TF_SERVING_VERSION_GIT_COMMIT=head

LABEL maintainer=gvasudevan@google.com

LABEL tensorflow_serving_github_branchtag=2.6.0

LABEL tensorflow_serving_github_commit=04d47f8aa567f429185a4416ef58f7fe11a21a43

|2 TF_SERVING_VERSION_GIT_BRANCH=2.6.0 TF_SERVING_VERSION_GIT_COMMIT=04d47f8aa567f429185a4416ef58f7fe11a21a43 /bin/sh -c

COPY file:aeebbdd256d6982dd13cf4adee13d7d1b05e2796d9d7dd69eef6192a20b5e635 in /usr/bin/tensorflow_model_server

EXPOSE 8500

EXPOSE 8501

ENV MODEL_BASE_PATH=/models

|2 TF_SERVING_VERSION_GIT_BRANCH=2.6.0 TF_SERVING_VERSION_GIT_COMMIT=04d47f8aa567f429185a4416ef58f7fe11a21a43 /bin/sh -c

ENV MODEL_NAME=model

|2 TF_SERVING_VERSION_GIT_BRANCH=2.6.0 TF_SERVING_VERSION_GIT_COMMIT=04d47f8aa567f429185a4416ef58f7fe11a21a43 /bin/sh -c

ENTRYPOINT ["/usr/bin/tf_serving_entrypoint.sh"]

 

3. Start Minikube

Minikube runs inside a virtual machine. That implies that the pods you will create later on will only see the volumes inside this VM.

Thus, if you want to load a model into your pods, then you should first mount the location of this model inside Minikube's VM. (model은 이전 단계에서 사용한 'half_plus_two' 라는 model를 사용할 것이다.)

 

해당 모델을 사용하기 위해 우선 마운트 할 경로에 모델을 복사한다. 

cp -R ./saved_model_half_plus_two_cpu /var/tmp

이제 Minikube를 실행해보자. 

minikube start --mount=True --mount-string="/var/tmp:/var/tmp" --vm-driver=virtualbox

 

4. Create Objects with YAML files

위의 Architecture에서도 살펴본 pods나 deployments, service object를 정의하기 위해서 yaml 파일을 사용한다. 

yaml
|-autoscale.yaml
|-configmap.yaml
|-deployment.yaml
ㄴservice.yaml

 

4-1. Config Maps - configmap.yaml 
configmap
환경에 따라 변하는 값들을 외부에서 결정할 수 있게 하는데, 이 값들을 관리하는 파일을 configmap이다. 
그 후 pod생성 시 contianer의 환경 변수에 이 configmap에 있는 값들을 읽어서 로직을 처리할 수 있도록 한다. 
(비슷한 역할로 secret이 있다.)

apiVersion: v1
kind: ConfigMap # ConfigMap을 생성
metadata:
  name: tfserving-configs # 이 ConfigMap의 이름
data:
  # define MODEL_NAME and MODEL_PATH for tensorflow/serving image use model
  # ENTRYPOINT ["/usr/bin/tf_serving_entrypoint.sh"]에서 해당 값들을 가져다 사용하게 된다. 
  MODEL_NAME: half_plus_two 
  MODEL_PATH: /models/half_plus_two

그리고 object를 생성

kubectl apply -f yaml/configmap.yaml

 

4-2. Create a Deployment - deployment.yaml

deployment

- kind: application에 대한 deployment를 생성한다. 

- envForm: envForm에 앞서 정의한 configmap.yaml을 사용하여 환경변수를 정의한다. 
- ports: 그리고 배포된 application에 HTTP 요청/응답을 받을 수 있도록 port 8501번을 연다. 

- volumeMounts, volumes: 사용할 모델은 앞서 <3. Start Minikube> 단계에서 /var/tmp/saved_model_half_plus_two_cpu에 위치하도록 했다. 이를 container로 volume mount 해준다. 

- resources: CPU와 메모리 제한을 정의해준다. 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tf-serving-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tf-serving
  template:
    metadata:
      labels:
        app: tf-serving
    spec:
      containers:
      - name: tf-serving
        image: tensorflow/serving
        envFrom:
        - configMapRef: # deployment object를 생성시 configMap을 reference할 건데, 이때 refer할 configMap의 이름이 앞에서 configmap.yaml에서 정의한 tfserving-configs이다. 
            name: tfserving-configs
        ports:
        - containerPort: 8501
        volumeMounts:
          - name: tf-serving-volume # mount할 volume의 이름과 container내 mount path
            mountPath: /models/half_plus_two
        resources:
          requests:
            memory: 32M
            cpu: 5m
          limits:
            memory: 64M
            cpu: 10m

      volumes: # mount할 volume의 내용
        - name: tf-serving-volume
          hostPath:
            path: /var/tmp/saved_model_half_plus_two_cpu
            type: Directory

그리고 object 생성

kubectl apply -f yaml/deployment.yaml

 

4-3. Expose the deployment through a service - service.yaml

service

service는 자신의 cluster의 IP를 가진다. 그리고 이 service의 IP를 pod에 연결시켜놓으면 이 IP를 통해서 Pod에 접근할 수 있다. 
외부에서 Pod로 접근할 때 왜 serive를 통해서 접근하는 걸까? 바로 Pod로 접근하면 안 될까?
Pod는 어떤 이유로든 언제든 죽을 수 있고, 재생성된다. 이때 Pod의 IP는 재생성이 되면서 바뀐다. 그러나 service는 사용자가 지우거나 변경하지 않는 한 고정되어있다. 

Service의 종류에는 여러 가지가 있고, 이 방식에 따라 Pod에 접근할 수 있다. 
여기서 사용해볼 것은 NodePort 타입의 service이다. 

apiVersion: v1
kind: Service
metadata:
  name: tf-serving-service
  namespace: default
  labels:
    app: tf-serving
spec:
  type: NodePort # service의 type: NodePort
  # NodePort타입의 특징은 cluster에 연결된 '모든' node에게 꼭같은 port(nodePort)가 할당되어 
  # 외부에서 어느 node건 간에 '그 node의 IP:nodePort'로 접속 해서 service에 연결이 가능하다.
  # 그러면 이 service 는 자신과 연결되어 있는 Pod에 traffic을 전달해준다. 
  ports:
  # node's port 30001. Requests sent to this port will be sent to the containers' specified targetPort which is 8501.
  - port: 8501
    targetPort: 8501
    nodePort: 30001 # NodePort지정 (30000 ~ 32767)
    protocol: TCP
    name: tf-serving-http
  selector:
    app: tf-serving
kubectl apply -f yaml/service.yaml

 

여기까지 하였다면 curl 명령어를 통해서 inference request가 잘 오가는지 확인할 수 있다. 

curl -d '{"instances": [1.0, 2.0, 5.0]}' -X POST $(minikube ip):30001/v1/models/half_plus_two:predict

아래와 같이 reponse가 왔다면 application이 성공적으로 실행 중이며 cluster 외부에서 service를 통해 Pod에 접근할 수 있는 것이다

{
    "predictions": [2.5, 3.0, 4.5]
}

 

4-4. Horizontal Pod Autoscaler - autoscale.yaml

kubernetes의 autoscaler에는 3가지 종류가 있다. 
- HPA: Horizontal Pod Autoscaler (Pod의 개수를 늘림)
- VPA: Vertical Pod Autoscaler (Pod의 resource를 늘림)
- CA: Cluster Autoscaler (Cluster에 Node를 추가)

 

여기서는 Kubernetes는 metric기반으로 컨테이너 오케스트레이션을 Horizontal Pod Autoscaler 방식으로 하는 방법에 대해서 정리해보았다. 

Autoscaler가 실행되기 전에 상태를 생각해 보자. 
Controller가 있고, replicas 수에 따라 Pod가 만들어져서 운영이 되고 있는 상태이다. 
<4-2. Create a Deployment - deployment.yaml>에서 deployment controller의 replicas를 보면 1이라고 되어있다(replicas: 1)
즉, 1개의 Pod가 운영되고  <Expose the deployment through a service - service.yaml>에서 service도 연결되어 있어서 이 Pod로 모든 traffic이 오가고 있는 상황이다. 
그런데 traffic이 많아져서 운영 중인 Pod의 resource를 모두 사용하게 되는 경우가 왔다면, 
결국 Pod가 죽을 수도 있다. 

이때 HPA 기능을 사용해서 Autoscale를 하면 된다. 
HPA를 만들고 사용해서 미리 Contoller에 연결한다. 
그러면 HPA가 Pod의 resource상태를 모니터링하고 있다가 resource가 많이 사용되고 있는 상황에서 controller의 replica 수를 늘려준다. 그럼 contoller는 Pod를 replica 수에 맞게 늘리게 되고, traffic은 배분되어 resource 사용량도 배분된다. (scale out)

반대로 traffic이 감소해서 resource 사용량이 감소하게 되면 Pod는 삭제된다. (scale in)

 

먼저 Minikube에서 제공하는 metric server를 실행시킨다. 

minikube addons enable metrics-server

HPA controller

 

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  labels:
    app: tf-serving
  name: tf-serving-hpa
  namespace: default
spec:
  maxReplicas: 3
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: tf-serving-deployment
  targetCPUUtilizationPercentage: 20
kubectl apply -f yaml/autoscale.yaml

 

여기까지 했다면 
request.sh

#!/bin/bash

while sleep 0.01;

do curl -d '{"instances": [1.0, 2.0, 5.0]}' -X POST $(minikube ip):30001/v1/models/half_plus_two:predict;

done

을 실행해서 Stress Test를 하고 아래의 명령어로 시각적으로 CPU tuilization을 확인해보며 autoscaling을 확인해 볼 수 있다. 

minikube dashboard

여기까지 잘 되었다면 targetCPUUtilizationPercentage: 20이므로 CPU utilization이 20% 이상이 되면 HPA가 trigger 되어 replicas를 늘려져야 한다. 

 

5. Tear Down

실습 후 모든 resource를 삭제한다.

kubectl delete -f yaml

 

 

Wrap Up


지금까지 YAML configuration과 kubectl를 사용해서 Kubernetes에서 ML model inference를 위해 Tensorflow Serving container를 실행시켜보고 scaling 해보는 것을 정리해보았다. 

현재 업무가 inference application이 실행되는 instance를 효과적으로 scaling 하여서 Minimizing Latency, Cost and Maximizing Throughput의 balance가 맞게 서비스할 수 있도록 scaler를 구현하는 것인데

이렇게 kubernetes의 Horizontal Pod Autoscaler 방식을 활용하는 방법도 있다는 것을 정리 보아서 추후에 많은 도움이 될 것 같다!