搭配 Kubernetes 使用 TensorFlow Serving

本教學課程說明如何使用在 Docker 容器中執行的 TensorFlow Serving 元件,以提供 TensorFlow ResNet 模型,以及如何使用 Kubernetes 部署 Serving 叢集。

如要進一步瞭解 TensorFlow Serving,建議您參閱TensorFlow Serving 基本教學課程TensorFlow Serving 進階教學課程

如要進一步瞭解 TensorFlow ResNet 模型,建議您閱讀TensorFlow 中的 ResNet

第 1 部分:設定

開始之前,請先安裝 Docker

下載 ResNet SavedModel

讓我們清除本機模型目錄,以防先前已有目錄

rm -rf /tmp/resnet

深度殘差網路 (簡稱 ResNet) 提供了身分對應的突破性概念,以便訓練非常深度的卷積神經網路。在我們的範例中,我們將下載適用於 ImageNet 資料集的 ResNet TensorFlow SavedModel。

# Download Resnet model from TF Hub
wget https://tfhub.dev/tensorflow/resnet_50/classification/1?tf-hub-format=compressed -o resnet.tar.gz

# Extract SavedModel into a versioned subfolder ‘123’
mkdir -p /tmp/resnet/123
tar xvfz resnet.tar.gz -C /tmp/resnet/123/

我們可以驗證是否已安裝 SavedModel

$ ls /tmp/resnet/*
saved_model.pb  variables

第 2 部分:在 Docker 中執行

提交映像檔以進行部署

現在我們要取得 Serving 映像檔,並提交所有變更至新的映像檔 $USER/resnet_serving,以進行 Kubernetes 部署。

首先,我們以精靈模式執行 Serving 映像檔

docker run -d --name serving_base tensorflow/serving

接下來,我們將 ResNet 模型資料複製到容器的模型資料夾

docker cp /tmp/resnet serving_base:/models/resnet

最後,我們提交容器以提供 ResNet 模型

docker commit --change "ENV MODEL_NAME resnet" serving_base \
  $USER/resnet_serving

現在讓我們停止 Serving 基礎容器

docker kill serving_base
docker rm serving_base

啟動伺服器

現在讓我們啟動搭載 ResNet 模型的容器,使其準備好提供服務,並公開 gRPC 埠 8500

docker run -p 8500:8500 -t $USER/resnet_serving &

查詢伺服器

對於用戶端,我們需要複製 TensorFlow Serving GitHub 存放區

git clone https://github.com/tensorflow/serving
cd serving

使用 resnet_client_grpc.py 查詢伺服器。用戶端會下載圖片,並透過 gRPC 傳送以分類為 ImageNet 類別。

tools/run_in_docker.sh python tensorflow_serving/example/resnet_client_grpc.py

這應該會產生如下輸出

outputs {
  key: "classes"
  value {
    dtype: DT_INT64
    tensor_shape {
      dim {
        size: 1
      }
    }
    int64_val: 286
  }
}
outputs {
  key: "probabilities"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 1001
      }
    }
    float_val: 2.41628322328e-06
    float_val: 1.90121829746e-06
    float_val: 2.72477100225e-05
    float_val: 4.42638565801e-07
    float_val: 8.98362372936e-07
    float_val: 6.84421956976e-06
    float_val: 1.66555237229e-05
...
    float_val: 1.59407863976e-06
    float_val: 1.2315689446e-06
    float_val: 1.17812135159e-06
    float_val: 1.46365800902e-05
    float_val: 5.81210713335e-07
    float_val: 6.59980651108e-05
    float_val: 0.00129527016543
  }
}
model_spec {
  name: "resnet"
  version {
    value: 123
  }
  signature_name: "serving_default"
}

成功了!伺服器成功分類了貓咪圖片!

第 3 部分:在 Kubernetes 中部署

在本節中,我們將使用第 0 部分中建構的容器映像檔,透過 KubernetesGoogle Cloud Platform 中部署 Serving 叢集。

GCloud 專案登入

在這裡,我們假設您已建立並登入名為 tensorflow-servinggcloud 專案。

gcloud auth login --project tensorflow-serving

建立容器叢集

首先,我們建立 Google Kubernetes Engine 叢集以進行服務部署。

$ gcloud container clusters create resnet-serving-cluster --num-nodes 5

應該會輸出類似如下的內容

Creating cluster resnet-serving-cluster...done.
Created [https://container.googleapis.com/v1/projects/tensorflow-serving/zones/us-central1-f/clusters/resnet-serving-cluster].
kubeconfig entry generated for resnet-serving-cluster.
NAME                       ZONE           MASTER_VERSION  MASTER_IP        MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
resnet-serving-cluster  us-central1-f  1.1.8           104.197.163.119  n1-standard-1  1.1.8         5          RUNNING

設定 gcloud 容器指令的預設叢集,並將叢集憑證傳遞至 kubectl

gcloud config set container/cluster resnet-serving-cluster
gcloud container clusters get-credentials resnet-serving-cluster

應該會產生

Fetching cluster endpoint and auth data.
kubeconfig entry generated for resnet-serving-cluster.

上傳 Docker 映像檔

現在讓我們將映像檔推送至 Google Container Registry,以便我們可以在 Google Cloud Platform 上執行。

首先,我們使用 Container Registry 格式和專案名稱標記 $USER/resnet_serving 映像檔,

docker tag $USER/resnet_serving gcr.io/tensorflow-serving/resnet

接下來,我們設定 Docker 以使用 gcloud 作為憑證輔助程式

gcloud auth configure-docker

接下來,我們將映像檔推送至 Registry,

docker push gcr.io/tensorflow-serving/resnet

建立 Kubernetes Deployment 和 Service

部署包含 3 個 resnet_inference 伺服器副本,由 Kubernetes Deployment 控制。這些副本透過 Kubernetes Service 以及 外部負載平衡器 從外部公開。

我們使用範例 Kubernetes 設定 resnet_k8s.yaml 建立它們。

kubectl create -f tensorflow_serving/example/resnet_k8s.yaml

輸出

deployment "resnet-deployment" created
service "resnet-service" created

如要查看 Deployment 和 Pod 的狀態

$ kubectl get deployments
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
resnet-deployment    3         3         3            3           5s
$ kubectl get pods
NAME                         READY     STATUS    RESTARTS   AGE
resnet-deployment-bbcbc   1/1       Running   0          10s
resnet-deployment-cj6l2   1/1       Running   0          10s
resnet-deployment-t1uep   1/1       Running   0          10s

如要查看服務狀態

$ kubectl get services
NAME                    CLUSTER-IP       EXTERNAL-IP       PORT(S)     AGE
resnet-service       10.239.240.227   104.155.184.157   8500/TCP    1m

可能需要一段時間才能讓所有項目都啟動並執行。

$ kubectl describe service resnet-service
Name:           resnet-service
Namespace:      default
Labels:         run=resnet-service
Selector:       run=resnet-service
Type:           LoadBalancer
IP:         10.239.240.227
LoadBalancer Ingress:   104.155.184.157
Port:           <unset> 8500/TCP
NodePort:       <unset> 30334/TCP
Endpoints:      <none>
Session Affinity:   None
Events:
  FirstSeen LastSeen    Count   From            SubobjectPath   Type        Reason      Message
  --------- --------    -----   ----            -------------   --------    ------      -------
  1m        1m      1   {service-controller }           Normal      CreatingLoadBalancer    Creating load balancer
  1m        1m      1   {service-controller }           Normal      CreatedLoadBalancer Created load balancer

服務外部 IP 位址會列在 LoadBalancer Ingress 旁邊。

查詢模型

我們現在可以從本機主機查詢位於外部位址的服務。

$ tools/run_in_docker.sh python \
  tensorflow_serving/example/resnet_client_grpc.py \
  --server=104.155.184.157:8500
outputs {
  key: "classes"
  value {
    dtype: DT_INT64
    tensor_shape {
      dim {
        size: 1
      }
    }
    int64_val: 286
  }
}
outputs {
  key: "probabilities"
  value {
    dtype: DT_FLOAT
    tensor_shape {
      dim {
        size: 1
      }
      dim {
        size: 1001
      }
    }
    float_val: 2.41628322328e-06
    float_val: 1.90121829746e-06
    float_val: 2.72477100225e-05
    float_val: 4.42638565801e-07
    float_val: 8.98362372936e-07
    float_val: 6.84421956976e-06
    float_val: 1.66555237229e-05
...
    float_val: 1.59407863976e-06
    float_val: 1.2315689446e-06
    float_val: 1.17812135159e-06
    float_val: 1.46365800902e-05
    float_val: 5.81210713335e-07
    float_val: 6.59980651108e-05
    float_val: 0.00129527016543
  }
}
model_spec {
  name: "resnet"
  version {
    value: 1538687457
  }
  signature_name: "serving_default"
}

您已成功在 Kubernetes 中部署 ResNet 模型 Serving 作為服務!