使用 Kubeflow 和 MinIO 在 Azure 上构建机器学习管道

Machine Learning Pipelines with Kubeflow and MinIO on Azure

机器学习 (ML) 计划可能会将计算和存储基础设施推至极限。许多 DataOps 团队依赖于基于 Kubernetes 的混合云架构来满足可扩展性、效率、可靠性、多租户以及对 RESTful API 支持的计算和对象存储需求。DataOps 团队已将工具标准化,这些工具依赖于高性能的与 S3 API 兼容的对象存储来满足其管道、训练和推理需求。

Kubeflow 是 Kubernetes 的标准机器学习工具包,它需要 S3 API 兼容性。Kubeflow 在整个数据科学社区中被广泛使用,但对 S3 API 兼容的对象存储的需求限制了部署选项。当 Azure 或 GCP 缺少对其对象存储产品的 S3 API 支持时,您将如何在这些平台上运行 Kubeflow?

MinIO Kubernetes 原生对象存储与 S3 API 兼容,因此您可以在任何托管的 Kubernetes 服务(Azure Kubernetes ServiceGoogle Kubernetes EngineAmazon Kubernetes Service)以及任何 Kubernetes 发行版(VMware TanzuRed Hat OpenShift,甚至 Minikube)上运行您首选的数据科学工具。

在这篇文章中,我们将使用 Azure Kubernetes Service (AKS) 和 MinIO 作为整个设置的基础存储来设置一个 Kubeflow 集群,并进行端到端测试,我们将部署一个管道,该管道访问 MinIO 上的数据并将生成模型存储在那里。我们将使用的问题是传统的 MNIST 挑战,它包括一个光学字符识别 (OCR) 问题。

设置 Kubernetes 集群

让我们首先设置名为 **KubeFlowMinIO** 的 AKS 集群,该集群在名为 **MinIOKubeFlow** 的资源组中包含四个节点。

export RESOURCE_GROUP_NAME=MinIOKubeFlow
export LOCATION=westus

az group create -n $RESOURCE_GROUP_NAME -l $LOCATION


export NAME=KubeFlowMinIO

export AGENT_SIZE=Standard_D4s_v3

export AGENT_COUNT=4


az aks create -g $RESOURCE_GROUP_NAME -n $NAME -s $AGENT_SIZE -c $AGENT_COUNT -l $LOCATION --generate-ssh-keys


此过程需要几分钟,之后您将拥有一个可运行的 Kubernetes 集群。您只需要使用此集群的访问权限配置本地 **kubectl**。

az aks get-credentials -n $NAME -g $RESOURCE_GROUP_NAME

设置 MinIO

下一步是设置 MinIO Operator 以管理我们在 Azure 上的对象存储。我们已经 简化了 MinIO 在 Kubernetes 上的管理,因此有多种方法可以安装 MinIO operator,您可以选择最适合您工作流程的方法。对于本文,我们将使用 MinIO 的 krew 插件来设置 MinIO Operator 和我们的对象存储。

下载 MinIO Krew 插件。

kubectl krew install minio

然后初始化 operator。

kubectl minio init

现在,让我们进入 MinIO Operator UI 创建我们的第一个租户。输入以下命令以接收本地可访问的端点和登录令牌。

kubectl minio proxy -n minio-operator 

预期输出为

正在启动控制台 UI 的端口转发。

要连接,请打开浏览器并访问 http://localhost:9090

当前用于登录的 JWT:eyJhbGciOiJSUzI1NiIsImtpZCI6IkhWclVWMmc2YjNuZlRKcGY1YUxJTTh1Mjd2d3ZKZmh5dzBKaE10cm5QYUUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJtaW5pby1vcGVyYXRvciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjb25zb2xlLXNhLXRva2VuLTh2cDRxIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNvbnNvbGUtc2EiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI4MDJkMmFlZi02ZTQxLTQyMzctYjIyYS04OGVkNjhhNTFkMWMiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6bWluaW8tb3BlcmF0b3I6Y29uc29sZS1zYSJ9.CxaS7Xy6l63Z90FLDL0XV0FB4iYYD93-EZ9lT6dUxHTkaYIwGzuVAOVYKclIAslpJqvANzurnuCQv2DSYuptBokqNyJqBZ_Mdfxk_BD8k9LNvvhH2B75FXJOlLUvO43HZP-vWqiBLHvhWD86KI5YdCqgXq0KB2Yuw03pIeAkGhdo-QN7EnTVt-mu6OniB6q_oSC61wUoToHCZKbq7OLeg2zzwqo9JGCBvghBbiVFzeMTYAQHdad69PsWjBRBlUKbG7u5eNWiVPiV44r0-fUZxdCr-1JEP9e4Ag-8J2GzIU1-yBIc_Yn1ok59HxXwiT-_fmp2tpe2WsArY7Hwzza2qLoVSkITzPX6eMVbGfRdzbcxd396LcQfg8GJn4Rbs1Z4YCRqMK_DpoQqYOFf-pjZ6Oa90GlZpMVSH_6_H4xxBuuobyn3WK7XyuBxJuFcl7KoIKoa4qwi87eUE139RXPOZKsCrMX-YmKxTAixKlGux2U4jRaN2lav6_y-ayUvHt0syEJqu0uhqdPNVxGIWW0sabJJ0sSfQdacmrBY1VazIYsN2NAL1N2QCwmQvvjRlqpEAWPF_uhuVwGtgcDX8-CxRKtfoY-8gn7ujwCKl1GMpyr-nE8p88eMIxEkaXqBia0erRLwUGTHrS2ymGN0Ii85_2wRZmDuCGA9QiQ01r89ZXU

转发自 0.0.0.0:9090 -> 9090

现在让我们进入 http://localhost:9090 并使用建议的令牌登录。

登录后,我们将看到一个空的租户列表,让我们通过点击右上方的 **+ 创建租户** 来创建一个。

为了在设置过程中保持简单,我们将在这个集群的 **默认** 命名空间中创建一个名为 **machine-learning-cluster** 的租户。当然,您可以将其更改为任何适合您需求的命名空间。然后我们将选择一个存储类,并且由于我们旨在获得高性能的数据存储库,因此我们将使用 Azure 的托管高级存储 以获得 Kubeflow 管道的最佳性能。完成这些字段后,选择 **高级**。在这里,您可以配置高级功能,例如自定义 Docker 注册表、身份提供程序、加密和 Pod 部署。目前,我们将点击 **下一步** 直到到达安全步骤并关闭 TLS,以便我们可以在无需设置域名和外部 TLS 证书的情况下完成本指南。

关闭此租户的 TLS。

现在,我们将告诉 MinIO Operator 我们希望租户有多大。我将选择 4 个节点以匹配我们当前的设置,并选择 **1 TB** 的容量,但您可以根据您的需要进行调整。

最后一步是查看将要发生的事情。只需点击 **创建**,MinIO 就会完成其余工作!

记下自动生成的访问对象存储的凭据,我们将使用这些凭据访问底层存储。

就是这样!您已配置了一个高性能的对象存储,并且只花了短短几分钟。再过几分钟,您将看到租户 **已初始化**,并且可以使用了。

租户详细信息是您可以更新和扩展对象存储的位置。我们还可以看到对象存储和管理对象存储的公共 IP。在本指南中我们不会使用它,但您可以使用它从集群外部开始使用对象存储。

我们已准备好对象存储方面的工作 - 我们已经设置了一个高性能集群,现在我们需要在 Kubeflow 管道中利用它。

设置 Kubeflow

要在 AKS 上设置 Kubeflow,我们将使用命令行实用程序 **kfctl**,该实用程序可以从 kfctl 发布页面 下载。有适用于 Mac 和 Linux 的二进制文件,但如果您使用的是 Windows,则必须从源代码编译该二进制文件。只需确保 kfctl 二进制文件位于您的 PATH 中即可。

运行以下命令,这些命令取自 Azure 上的 Kubeflow 安装 文档。


# 将 KF_NAME 设置为您的 Kubeflow 部署名称。您也可以在创建配置目录时使用此
# 值作为目录名称。
# 例如,您的部署名称可以是 'my-kubeflow' 或 'kf-test'。
export KF_NAME=my-kubeflow

# 设置您希望存储一个或多个 Kubeflow 部署的基本目录路径。例如,/opt/。
# 然后设置此部署的 Kubeflow 应用程序目录。
export BASE_DIR=kubeflowsetup
export KF_DIR=${BASE_DIR}/${KF_NAME}

# 设置部署 Kubeflow 时要使用的配置文件。
# 以下配置默认安装 Istio。注释掉配置文件中的
# Istio 组件以跳过 Istio 安装。
# 请参阅 https://github.com/kubeflow/kubeflow/pull/3663
export CONFIG_URI="https://raw.githubusercontent.com/kubeflow/manifests/v1.2-branch/kfdef/kfctl_k8s_istio.v1.2.0.yaml"

mkdir -p ${KF_DIR}
cd ${KF_DIR}
kfctl apply -V -f ${CONFIG_URI}

按照配置,此过程大约需要八分钟,所以请泡一杯咖啡,并使用以下命令监控完成情况。

kubectl get all -n kubeflow

一旦所有 Pod 都处于运行状态,我们就可以继续构建利用 MinIO 的 Kubeflow Pipeline 了。

通过运行以下端口转发命令并访问 http://localhost:8080 打开 Kubeflow 仪表盘。

kubectl port-forward svc/istio-ingressgateway -n istio-system 8080:80

然后通过创建一个名为 machine-learning 的命名空间来完成 Kubeflow 设置。

配置命名空间后,Kubeflow 仪表盘将打开。

让我们设置一个 Jupyter Notebook 服务器,并从那里进行配置。使用 Tensorflow 1.15 镜像,创建一个名为 setup-pipeline 的 Notebook。

服务器准备就绪后,连接到它,然后创建一个名为 Setup PipelinePython 3 Notebook。

最后一步是配置您的 Docker 账户。Kubeflow 会将您在整个 Pipeline 中构建的每个新模型推送到 Docker,您可能会很快达到每小时 100 个请求的限制。当您使用 Docker 账户时,此限制将提高到每小时 200 个请求。

USER=<DOCKERUSER>; PASSWORD=<DOCKERPASSWORD>; echo -n $USER:$PASSWORD | base64 |  xargs echo -n |xargs -0 printf '{
    "auths": {
        "https://index.docker.io/v1/": {
            "auth": "%s"
        }
    }
}\n' > /tmp/config.json && kubectl create --namespace ${NAMESPACE} configmap docker-config --from-file=/tmp/config.json && rm /tmp/config.json

运行 Kubeflow Pipeline

现在回到我们的 Notebook。从这里开始,我们将遵循 Kubeflow 团队提供的 适用于普通 Kubernetes 的优秀示例。我们将学习如何将模型提交到 Kubeflow 进行分布式训练,以及如何部署和服务它们。

为了使此 Notebook 正常运行,您将需要几个文件,主要是**model.py、k8s_util.py、notebook_setup.py、requirements.txt 和 Dockerfile.model**,用于构建您的模型,将其提交到 Kubeflow 然后部署它。让我们从以下代码片段开始,将这些文件下载到我们的 Notebook 中。

import urllib.request
import shutil

file_list = ["https://raw.githubusercontent.com/kubeflow/examples/master/mnist/k8s_util.py","https://raw.githubusercontent.com/kubeflow/examples/master/mnist/Dockerfile.model","https://raw.githubusercontent.com/kubeflow/examples/master/mnist/model.py","https://raw.githubusercontent.com/kubeflow/examples/master/mnist/notebook_setup.py","https://raw.githubusercontent.com/kubeflow/examples/master/mnist/requirements.txt"]

for url in file_list:
    file_name = url.split("/").pop()
    with urllib.request.urlopen(url) as response, open(file_name, 'wb') as out_file:
        shutil.copyfileobj(response, out_file)

现在,让我们准备命名空间并配置 MinIO 凭据。对于我们的端点,我们将使用内部 Kubernetes 服务名称**minio.default.svc.cluster.local**,对于**DOCKER_REGISTRY**,我们将输入我们的 Docker 用户名。

from kubernetes import client as k8s_client
from kubernetes.client import rest as k8s_rest
from kubeflow import fairing  
from kubeflow.fairing import utils as fairing_utils
from kubeflow.fairing.builders import append
from kubeflow.fairing.deployers import job
from kubeflow.fairing.preprocessors import base as base_preprocessor

DOCKER_REGISTRY = "miniodev"
namespace = fairing_utils.get_current_k8s_namespace()

from kubernetes import client as k8s_client
from kubernetes.client.rest import ApiException

api_client = k8s_client.CoreV1Api()
minio_service_endpoint = "minio.default.svc.cluster.local"

s3_endpoint = minio_service_endpoint
minio_endpoint = "http://"+s3_endpoint
minio_username = "AXNENHDUBB2LU24Y"
minio_key = "GPONOCU0IDQZBMP55TTELR00D4HGFPJK"
minio_region = "us-east-1"

logging.info(f"Running in namespace {namespace}")
logging.info(f"Using docker registry {DOCKER_REGISTRY}")
logging.info(f"Using minio instance with endpoint '{s3_endpoint}'")

接下来,我们将通过安装依赖项和下载所需数据来准备本地 Notebook。所有这些都可以在一个代码块中完成,但我使用了与示例 Notebook 相同的分离代码块,以便您更容易理解。

import logging
import os
import uuid
from importlib import reload
import notebook_setup
reload(notebook_setup)
notebook_setup.notebook_setup(platform='none')

import k8s_util
# 强制重新加载 kubeflow;由于 kubeflow 是一个多命名空间模块
# 在 notebook_setup 中执行此操作可能不够
import kubeflow
reload(kubeflow)
from kubernetes import client as k8s_client
from kubernetes import config as k8s_config
from kubeflow.tfjob.api import tf_job_client as tf_job_client_module
from IPython.core.display import display, HTML
import yaml

# TODO(https://github.com/kubeflow/fairing/issues/426): 一旦默认的
# Kaniko 镜像更新到比 0.7.0 新的镜像后,我们应该去掉这段代码。
from kubeflow.fairing import constants
constants.constants.KANIKO_IMAGE = "gcr.io/kaniko-project/executor:v0.14.0"

from kubeflow.fairing.builders import cluster

# output_map 是一个用于将额外文件添加到笔记本的映射。
# 它是一个从源位置到上下文内位置的映射。
output_map =  {
    "Dockerfile.model": "Dockerfile",
    "model.py": "model.py"
}

preprocessor = base_preprocessor.BasePreProcessor(
    command=["python"], # 基类将设置此项。
    input_files=[],
    path_prefix="/app", # 与我们不预处理任何文件无关
    output_map=output_map)

preprocessor.preprocess()

# 使用 Tensorflow 镜像作为基础镜像
# 我们使用自定义的 Dockerfile
from kubeflow.fairing.cloud.k8s import MinioUploader
from kubeflow.fairing.builders.cluster.minio_context import MinioContextSource

minio_uploader = MinioUploader(endpoint_url=minio_endpoint, minio_secret=minio_username, minio_secret_key=minio_key, region_name=minio_region)
minio_context_source = MinioContextSource(endpoint_url=minio_endpoint, minio_secret=minio_username, minio_secret_key=minio_key, region_name=minio_region)

cluster_builder = cluster.cluster.ClusterBuilder(registry=DOCKER_REGISTRY,
                                                base_image="", # base_image 在 Dockerfile 中设置
                                                preprocessor=preprocessor,
                                                image_name="mnist",
                                                dockerfile_path="Dockerfile",
                                                context_source=minio_context_source)
cluster_builder.build()
logging.info(f"构建镜像 {cluster_builder.image_tag}")

此时,您可以访问您的个人 Docker 镜像仓库,并确认已为 MNIST 模型创建了一个新的 Docker 镜像。

下一步是创建一个 MinIO Bucket。

mnist_bucket = f"{DOCKER_REGISTRY}-mnist"
minio_uploader.create_bucket(mnist_bucket)
logging.info(f"Bucket {mnist_bucket} 已创建或已存在")

接下来,我们只需构建一个 **TFJob** 和 **Deployment** 来训练我们的模型,使用 **TensorBoard** 检查它,最后提供服务,所有中间步骤都存储在您的 MinIO 租户上。

让我们开始逐步浏览这些代码块,请记住,这些代码是从 kubeflow vanilla kubernete 示例 中逐字复制的。

train_name = f"mnist-train-{uuid.uuid4().hex[:4]}"
num_ps = 1
num_workers = 2
model_dir = f"s3://{mnist_bucket}/mnist"
export_path = f"s3://{mnist_bucket}/mnist/export"
train_steps = 200
batch_size = 100
learning_rate = .01
image = cluster_builder.image_tag

train_spec = f"""apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: {train_name} 
spec:
  tfReplicaSpecs:
    Ps:
      replicas: {num_ps}
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
        spec:
          serviceAccount: default-editor
          containers:
          - name: tensorflow
            command:
            - python
            - /opt/model.py
            - --tf-model-dir={model_dir}
            - --tf-export-dir={export_path}
            - --tf-train-steps={train_steps}
            - --tf-batch-size={batch_size}
            - --tf-learning-rate={learning_rate}
            env:
            - name: S3_ENDPOINT
              value: {s3_endpoint}
            - name: AWS_ENDPOINT_URL
              value: {minio_endpoint}
            - name: AWS_REGION
              value: {minio_region}
            - name: BUCKET_NAME
              value: {mnist_bucket}
            - name: S3_USE_HTTPS
              value: "0"
            - name: S3_VERIFY_SSL
              value: "0"
            - name: AWS_ACCESS_KEY_ID
              value: {minio_username}
            - name: AWS_SECRET_ACCESS_KEY
              value: {minio_key}
            image: {image}
            workingDir: /opt
          restartPolicy: OnFailure
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
        spec:
          serviceAccount: default-editor
          containers:
          - name: tensorflow
            command:
            - python
            - /opt/model.py
            - --tf-model-dir={model_dir}
            - --tf-export-dir={export_path}
            - --tf-train-steps={train_steps}
            - --tf-batch-size={batch_size}
            - --tf-learning-rate={learning_rate}
            env:
            - name: S3_ENDPOINT
              value: {s3_endpoint}
            - name: AWS_ENDPOINT_URL
              value: {minio_endpoint}
            - name: AWS_REGION
              value: {minio_region}
            - name: BUCKET_NAME
              value: {mnist_bucket}
            - name: S3_USE_HTTPS
              value: "0"
            - name: S3_VERIFY_SSL
              value: "0"
            - name: AWS_ACCESS_KEY_ID
              value: {minio_username}
            - name: AWS_SECRET_ACCESS_KEY
              value: {minio_key}
            image: {image}
            workingDir: /opt
          restartPolicy: OnFailure
    Worker:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
        spec:
          serviceAccount: default-editor
          containers:
          - name: tensorflow
            command:
            - python
            - /opt/model.py
            - --tf-model-dir={model_dir}
            - --tf-export-dir={export_path}
            - --tf-train-steps={train_steps}
            - --tf-batch-size={batch_size}
            - --tf-learning-rate={learning_rate}
            env:
            - name: S3_ENDPOINT
              value: {s3_endpoint}
            - name: AWS_ENDPOINT_URL
              value: {minio_endpoint}
            - name: AWS_REGION
              value: {minio_region}
            - name: BUCKET_NAME
              value: {mnist_bucket}
            - name: S3_USE_HTTPS
              value: "0"
            - name: S3_VERIFY_SSL
              value: "0"
            - name: AWS_ACCESS_KEY_ID
              value: {minio_username}
            - name: AWS_SECRET_ACCESS_KEY
              value: {minio_key}
            image: {image}
            workingDir: /opt
          restartPolicy: OnFailure
"""

接下来,我们通过 Kubernetes Python SDK 提交作业。

tf_job_client = tf_job_client_module.TFJobClient()

tf_job_body = yaml.safe_load(train_spec)
tf_job = tf_job_client.create(tf_job_body, namespace=namespace) 

logging.info(f"创建作业 {namespace}.{train_name}")

from kubeflow.tfjob import TFJobClient
tfjob_client = TFJobClient()
tfjob_client.wait_for_job(train_name, namespace=namespace, watch=True)

然后,我们获取作业的日志。

tfjob_client.get_logs(train_name, namespace=namespace)

我们准备在 MinIO 上检查模型。我们可以通过我们的笔记本或 MinIO 控制台来完成此操作。首先,我将展示如何通过笔记本完成此操作。

from botocore.exceptions import ClientError

try:
    model_response = minio_uploader.client.list_objects(Bucket=mnist_bucket)
    # 最小检查以查看是否至少创建了存储桶
    if model_response["ResponseMetadata"]["HTTPStatusCode"] == 200:
        logging.info(f"{model_dir} 位于 {mnist_bucket} 存储桶中")
except ClientError as err:
    logging.error(err)

现在我将展示如何使用 Operator 控制台来完成此操作。在 Operator GUI 中进入租户详细信息,然后点击控制台 URL。

在此处,登录到 MinIO 控制台,进入对象浏览器并浏览 **miniodev-mnist** 存储桶,在那里我们可以看到检查点和模型本身。

让我们探索训练过程。使用 **TensorBoard**,我们将创建一个部署。

tb_name = "mnist-tensorboard"
tb_deploy = f"""apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mnist-tensorboard
  name: {tb_name}
  namespace: {namespace}
spec:
  selector:
    matchLabels:
      app: mnist-tensorboard
  template:
    metadata:
      labels:
        app: mnist-tensorboard
        version: v1
    spec:
      serviceAccount: default-editor
      containers:
      - command:
        - /usr/local/bin/tensorboard
        - --logdir={model_dir}
        - --port=80
        image: tensorflow/tensorflow:1.15.2-py3
        env:
        - name: S3_ENDPOINT
          value: {s3_endpoint}
        - name: AWS_ENDPOINT_URL
          value: {minio_endpoint}
        - name: AWS_REGION
          value: {minio_region}
        - name: BUCKET_NAME
          value: {mnist_bucket}
        - name: S3_USE_HTTPS
          value: "0"
        - name: S3_VERIFY_SSL
          value: "0"
        - name: AWS_ACCESS_KEY_ID
          value: {minio_username}
        - name: AWS_SECRET_ACCESS_KEY
          value: {minio_key} 
        name: tensorboard
        ports:
        - containerPort: 80
"""
tb_service = f"""apiVersion: v1
kind: Service
metadata:
  labels:
    app: mnist-tensorboard
  name: {tb_name}
  namespace: {namespace}
spec:
  ports:
  - name: http-tb
    port: 80
    targetPort: 80
  selector:
    app: mnist-tensorboard
  type: ClusterIP
"""

tb_virtual_service = f"""apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: {tb_name}
  namespace: {namespace}
spec:
  gateways:
  - kubeflow/kubeflow-gateway
  hosts:
  - '*'
  http:
  - match:
    - uri:
        prefix: /mnist/{namespace}/tensorboard/
    rewrite:
      uri: /
    route:
    - destination:
        host: {tb_name}.{namespace}.svc.cluster.local
        port:
          number: 80
    timeout: 300s
"""

tb_specs = [tb_deploy, tb_service, tb_virtual_service]

k8s_util.apply_k8s_specs(tb_specs, k8s_util.K8S_CREATE_OR_REPLACE)

现在让我们通过访问 http://localhost:8080/mnist/machine-learning/tensorboard/ 来探索 **TensorBoard**。

如您所见,训练过程简短且平淡无奇,但您学习了如何直接从 MinIO 读取训练数据。

最后,让我们部署此模型并稍作尝试。

deploy_name = "mnist-model"
model_base_path = export_path

# The web ui defaults to mnist-service so if you change it you will
# need to change it in the UI as well to send predictions to the mode
model_service = "mnist-service"

deploy_spec = f"""apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mnist
  name: {deploy_name}
  namespace: {namespace}
spec:
  selector:
    matchLabels:
      app: mnist-model
  template:
    metadata:
      # TODO(jlewi): Right now we disable the istio side car because otherwise ISTIO rbac will prevent the
      # UI from sending RPCs to the server. We should create an appropriate ISTIO rbac authorization
      # policy to allow traffic from the UI to the model servier.
      # https://istio.ac.cn/docs/concepts/security/#target-selectors
      annotations:       
        sidecar.istio.io/inject: "false"
      labels:
        app: mnist-model
        version: v1
    spec:
      serviceAccount: default-editor
      containers:
      - args:
        - --port=9000
        - --rest_api_port=8500
        - --model_name=mnist
        - --model_base_path={model_base_path}
        command:
        - /usr/bin/tensorflow_model_server
        env:
        - name: modelBasePath
          value: {model_base_path}
        - name: S3_ENDPOINT
          value: {s3_endpoint}
        - name: AWS_ENDPOINT_URL
          value: {minio_endpoint}
        - name: AWS_REGION
          value: {minio_region}
        - name: BUCKET_NAME
          value: {mnist_bucket}
        - name: S3_USE_HTTPS
          value: "0"
        - name: S3_VERIFY_SSL
          value: "0"
        - name: AWS_ACCESS_KEY_ID
          value: {minio_username}
        - name: AWS_SECRET_ACCESS_KEY
          value: {minio_key} 
        image: tensorflow/serving:1.15.0
        imagePullPolicy: IfNotPresent
        livenessProbe:
          initialDelaySeconds: 30
          periodSeconds: 30
          tcpSocket:
            port: 9000
        name: mnist
        ports:
        - containerPort: 9000
        - containerPort: 8500
        resources:
          limits:
            cpu: "4"
            memory: 4Gi
          requests:
            cpu: "1"
            memory: 1Gi
        volumeMounts:
        - mountPath: /var/config/
          name: model-config
      volumes:
      - configMap:
          name: {deploy_name}
        name: model-config
"""

service_spec = f"""apiVersion: v1
kind: Service
metadata:
  annotations:   
    prometheus.io/path: /monitoring/prometheus/metrics
    prometheus.io/port: "8500"
    prometheus.io/scrape: "true"
  labels:
    app: mnist-model
  name: {model_service}
  namespace: {namespace}
spec:
  ports:
  - name: grpc-tf-serving
    port: 9000
    targetPort: 9000
  - name: http-tf-serving
    port: 8500
    targetPort: 8500
  selector:
    app: mnist-model
  type: ClusterIP
"""

monitoring_config = f"""kind: ConfigMap
apiVersion: v1
metadata:
  name: {deploy_name}
  namespace: {namespace}
data:
  monitoring_config.txt: |-
    prometheus_config: {{
      enable: true,
      path: "/monitoring/prometheus/metrics"
    }}
"""

model_specs = [deploy_spec, service_spec, monitoring_config]

k8s_util.apply_k8s_specs(model_specs, k8s_util.K8S_CREATE_OR_REPLACE)

就是这样,但模型尚未提供服务。让我们部署一个示例 UI 以便我们可以对其进行操作。

ui_name = "mnist-ui"
ui_deploy = f"""apiVersion: apps/v1
kind: Deployment
metadata:
  name: {ui_name}
  namespace: {namespace}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mnist-web-ui
  template:
    metadata:
      labels:
        app: mnist-web-ui
    spec:
      containers:
      - image: gcr.io/kubeflow-examples/mnist/web-ui:v20190112-v0.2-142-g3b38225
        name: web-ui
        ports:
        - containerPort: 5000       
      serviceAccount: default-editor
"""

ui_service = f"""apiVersion: v1
kind: Service
metadata:
  annotations:
  name: {ui_name}
  namespace: {namespace}
spec:
  ports:
  - name: http-mnist-ui
    port: 80
    targetPort: 5000
  selector:
    app: mnist-web-ui
  type: ClusterIP
"""

ui_virtual_service = f"""apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: {ui_name}
  namespace: {namespace}
spec:
  gateways:
  - kubeflow/kubeflow-gateway
  hosts:
  - '*'
  http:
  - match:
    - uri:
        prefix: /mnist/{namespace}/ui/
    rewrite:
      uri: /
    route:
    - destination:
        host: {ui_name}.{namespace}.svc.cluster.local
        port:
          number: 80
    timeout: 300s
"""

ui_specs = [ui_deploy, ui_service, ui_virtual_service]

k8s_util.apply_k8s_specs(ui_specs, k8s_util.K8S_CREATE_OR_REPLACE)

现在访问 http://localhost:8080/mnist/machine-learning/ui/?ns=machine-learning,您将看到一个漂亮的 UI,它与直接从 MinIO 提供服务的模型进行交互。

MinIO 赋能数据科学和数据运营无处不在

好了!我们完成了本大型指南的结尾,该指南解释了如何在 Azure Kubernetes Service 上设置 MinIO,然后部署 Kubeflow 以开箱即用地与 MinIO 协同工作。由于高度自动化,设置 AKS、MinIO 和 Kubeflow 的基础构建块是最简单的部分。这使您可以专注于更重要的任务,例如构建机器学习管道以在 Kubeflow 上平稳运行,利用直接来自 MinIO 的大型数据集,以及直接从对象存储中存储和部署模型。


下载 MinIO 开始使用,如果您有任何疑问,请加入我们的 Slack 频道 或发送邮件至 hello@min.io。我们随时为您提供帮助。