使用 TensorFlow Serving 与 Kubernetes

本教程展示了如何使用在 Docker 容器中运行的 TensorFlow Serving 组件来服务 TensorFlow ResNet 模型,以及如何使用 Kubernetes 部署服务集群。

要详细了解 TensorFlow Serving,我们建议您阅读 TensorFlow Serving 基础教程TensorFlow Serving 高级教程

要详细了解 TensorFlow ResNet 模型,我们建议您阅读 TensorFlow 中的 ResNet

第 1 部分:设置

在开始之前,请先 安装 Docker

下载 ResNet SavedModel

让我们清除本地模型目录,以防我们已经有一个

rm -rf /tmp/resnet

深度残差网络(简称 ResNets)提出了身份映射的突破性思想,从而能够训练非常深的卷积神经网络。在我们的示例中,我们将下载 ResNet 的 TensorFlow SavedModel,用于 ImageNet 数据集。

# 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 中运行

提交用于部署的映像

现在,我们想要获取一个服务映像,并 提交所有更改到一个新的映像 $USER/resnet_serving,用于 Kubernetes 部署。

首先,我们以守护进程的方式运行一个服务映像

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

现在,让我们停止服务基础容器

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 部分中构建的容器映像,在 Google Cloud PlatformKubernetes 中部署一个服务集群。

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 container 命令的默认集群,并将集群凭据传递给 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

接下来,我们将镜像推送到注册表中。

docker push gcr.io/tensorflow-serving/resnet

创建 Kubernetes 部署和服务

部署包含 3 个 resnet_inference 服务器副本,由 Kubernetes 部署 控制。这些副本通过 Kubernetes 服务外部负载均衡器 向外部公开。

我们使用示例 Kubernetes 配置 resnet_k8s.yaml 创建它们。

kubectl create -f tensorflow_serving/example/resnet_k8s.yaml

输出结果

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

查看部署和 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 地址列在负载均衡器入口旁边。

查询模型

现在,我们可以从本地主机查询服务在其外部地址上的地址。

$ 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"
}

您已成功将 ResNet 模型作为服务部署到 Kubernetes 中!