基于Nginx Ingress Controller网关实现推理服务的灰度发布

在Raw Deployment部署模式下,应用的灰度发布需要基于网关实现。本文以Nginx Ingress Controller网关为例,介绍如何实现推理服务的灰度发布,并最终平稳地完成从v1到v2版本推理服务的升级。

前提条件

安装ack-kserve️

步骤一:部署v1版本的推理服务

  1. 执行以下命令,部署一个v1版本的推理服务,模型名称为canary。

    arena serve kserve \
        --name=model-v1 \
        --image=kube-ai-registry.cn-shanghai.cr.aliyuncs.com/ai-sample/kserve-canary:1.0.0 \
        --cpu=1 \
        --memory=2Gi \
        "python app.py --model_name=canary"

    预期输出:

    inferenceservice.serving.kserve.io/model-v1 created
    INFO[0002] The Job model-v1 has been submitted successfully 
    INFO[0002] You can run `arena serve get model-v1 --type kserve -n default` to check the job status 

    输出结果表明v1版本的推理服务已部署成功。

  2. 创建v1版本模型的模型服务Service。

    1. 创建并拷贝以下内容到model-svc.yaml文件中,用于创建Service。

      展开查看model-svc.yaml文件

      apiVersion: v1
      kind: Service
      metadata:
        name: model-svc
      spec:
        ports:
        - port: 80
          protocol: TCP
          targetPort: 8080
        selector:
          serving.kserve.io/inferenceservice: model-v1
        type: ClusterIP
    2. 执行以下命令,创建Service。

      kubectl apply -f model-svc.yaml

      预期输出:

      service/model-svc created

      输出结果表明Service已创建成功。

  3. 通过Nginx Ingress访问一个名为model-v1的Inference Service,以验证model-v1是否正确部署。

    1. 执行以下命令,从kube-system命名空间中获取名为nginx-ingress-lb的服务(Service),并使用jsonpath提取负载均衡器(LoadBalancer)的IP地址。

      NGINX_INGRESS_IP=`kubectl -n kube-system get svc nginx-ingress-lb -ojsonpath='{.status.loadBalancer.ingress[0].ip}'`
    2. 执行以下命令,获取模型服务(Inference Service)的HOSTNAME。

      SERVICE_HOSTNAME=$(kubectl get inferenceservice model-v1 -o jsonpath='{.status.url}' | cut -d "/" -f 3)
    3. 执行以下命令,向模型服务发起POST请求,以访问model-v1服务。

      curl -H "Host: $SERVICE_HOSTNAME" -H "Content-Type: application/json" \
           http://$NGINX_INGRESS_IP:80/v1/models/canary:predict -X POST \
           -d '{"data": "test"}'

      预期输出:

      {"id":"bd73dcde-2dd5-4cfb-8097-8ee3531e0880","model_name":"canary","model_version":null,"outputs":[{"name":"output-0","shape":[1,1],"datatype":"STR","data":["model-v1"]}]}%  

      模型输出结果返回了一个JSON响应,包含预测ID、模型名称、输出等信息。表明model-v1已成功部署。

步骤二:部署v2版本的推理服务

  1. 执行以下命令,部署一个v2版本的推理服务,模型名称仍为canary。

    arena serve kserve \
        --name=model-v2 \
        --image=kube-ai-registry.cn-shanghai.cr.aliyuncs.com/ai-sample/kserve-canary:1.0.0 \
        --cpu=1 \
        --memory=2Gi \
        "python app-v2.py --model_name=canary"

    预期输出:

    inferenceservice.serving.kserve.io/model-v2 created
    INFO[0002] The Job model-v2 has been submitted successfully 
    INFO[0002] You can run `arena serve get model-v2 --type kserve -n default` to check the job status 

    输出结果表明v2版本的推理服务已成功部署。

  2. 创建Service。

    1. 创建并拷贝以下内容到model-v2-svc.yaml文件中,用于创建Service。

      展开查看model-v2-svc.yaml文件

      apiVersion: v1
      kind: Service
      metadata:
        name: model-v2-svc
      spec:
        ports:
        - port: 80
          protocol: TCP
          targetPort: 8080
        selector:
          serving.kserve.io/inferenceservice: model-v2
        type: ClusterIP
    2. 执行以下命令,创建Service。

      kubectl apply -f model-v2-svc.yaml

      预期输出:

      service/model-v2-svc created

      输出结果表明Service已创建成功。

  3. 通过Nginx Ingress访问一个名为model-v2的推理服务,以验证model-v2是否已正确部署。

    1. 执行以下命令,从kube-system命名空间中获取名为nginx-ingress-lb的Service,并使用jsonpath提取LoadBalancer的IP地址。

      NGINX_INGRESS_IP=`kubectl -n kube-system get svc nginx-ingress-lb -ojsonpath='{.status.loadBalancer.ingress[0].ip}'`
    2. 执行以下命令,获取模型服务(Inference Service)的HOSTNAME。

      SERVICE_HOSTNAME=$(kubectl get inferenceservice model-v2 -o jsonpath='{.status.url}' | cut -d "/" -f 3)
    3. 执行以下命令,向模型服务发起POST请求,以访问model-v2服务。

      curl -H "Host: $SERVICE_HOSTNAME" -H "Content-Type: application/json" \
           http://$NGINX_INGRESS_IP:80/v1/models/canary:predict -X POST \
           -d '{"data": "test"}'

      预期输出:

      {"id":"89918594-b995-4d2d-b016-9fbe140ffc0a","model_name":"canary","model_version":null,"outputs":[{"name":"output-0","shape":[1,1],"datatype":"STR","data":["model-v2"]}]}%     

      模型输出结果返回了一个JSON响应,包含预测ID、模型名称、输出等信息。表明model-v2已成功部署。

步骤三:配置灰度策略

通过以下示例介绍Nginx Ingress如何基于客户端请求和服务权重来实现应用服务的灰度发布。关于通过Nginx Ingress实现灰度发布的背景信息及原理说明,请参见通过Nginx Ingress实现灰度发布和蓝绿发布

  1. 创建Ingress,以管理进入集群的流量。

    1. 创建并拷贝以下内容到model-ingress.yaml文件中,用于创建Ingress。

      展开查看model-ingress.yaml文件

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: model-ingress
      spec:
        rules:
        - host: model.example.com # 替换为您实际业务的Host。
          http:
            paths:
            # 老版本服务。
            - path: /
              backend:
                service: 
                  name: model-svc
                  port:
                    number: 80
              pathType: ImplementationSpecific
    2. 执行以下命令,创建Ingress。

      kubectl apply -f model-ingress.yaml

      预期输出:

      ingress.networking.k8s.io/model-ingress created

      输出结果表明Ingress已创建成功。

  2. 配置灰度发布策略。

    场景一:基于客户端请求的流量切分场景

    1. 创建并拷贝以下内容到gray-release-canary.yaml文件中,用于配置灰度发布策略。

      本示例以允许特定请求(带有头部foo: bar)被定向到名为model-v2-svc的新版本服务为例,介绍如何基于客户端请求的流量切分。

      展开查看gray-release-canary.yaml文件

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: gray-release-canary
        annotations:
          # 开启Canary。
          nginx.ingress.kubernetes.io/canary: "true"
          # 请求头为foo。
          nginx.ingress.kubernetes.io/canary-by-header: "foo"
          # 请求头foo的值为bar时,请求才会被路由到新版本服务model-v2中。
          nginx.ingress.kubernetes.io/canary-by-header-value: "bar"
      spec:
        rules:
        - host: model.example.com
          http:
            paths:
            # 新版本服务。
            - path: /
              backend:
                service: 
                  name: model-v2-svc
                  port:
                    number: 80
              pathType: ImplementationSpecific
    2. 执行以下命令,部署灰度发布策略。

      kubectl apply -f gray-release-canary.yaml

      预期输出:

      ingress.networking.k8s.io/gray-release-canary created

      输出结果表明灰度发布策略已经部署成功。

    场景二:基于服务权重的流量切分场景

    1. 创建并拷贝以下内容到gray-release-canary.yaml文件中,用于配置灰度发布策略。

      本示例以将50%的流量被导向名为model-v2-svc的新版本服务,而剩余流量则继续流向未在此配置中明确指定的其他服务或默认服务为例,介绍如何基于服务权重的流量切分场景。

      展开查看gray-release-canary.yaml文件

      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: gray-release-canary
        annotations:
          # 开启Canary。
          nginx.ingress.kubernetes.io/canary: "true"
          # 仅允许50%的流量会被路由到新版本服务new-nginx中。
          # 默认总值为100。
          nginx.ingress.kubernetes.io/canary-weight: "50"
      spec:
        rules:
        - host: model.example.com
          http:
            paths:
            # 新版本服务。
            - path: /
              backend:
                service: 
                  name: model-v2-svc
                  port:
                    number: 80
              pathType: ImplementationSpecific
    2. 执行以下命令,部署灰度发布策略。

      kubectl apply -f gray-release-canary.yaml

      预期输出:

      ingress.networking.k8s.io/gray-release-canary created

      输出结果表明灰度发布策略已经部署成功。

  3. 验证灰度策略是否已生效。

    场景一:基于客户端请求的流量切分场景

    1. 执行以下命令,从kube-system命名空间中获取名为nginx-ingress-lb的Service,并使用jsonpath提取LoadBalancer的IP地址。

      NGINX_INGRESS_IP=`kubectl -n kube-system get svc nginx-ingress-lb -ojsonpath='{.status.loadBalancer.ingress[0].ip}'`
    2. 执行以下命令,验证无特定请求头(默认版本访问)的服务响应。

      # 以下代码的Host为Ingress中定义的业务Host。
      curl -H "Host: model.example.com" -H "Content-Type: application/json" \
           http://$NGINX_INGRESS_IP:80/v1/models/canary:predict -X POST \
           -d '{"data": "test"}'

      预期输出:

      {"id":"4d8c110d-c291-4670-ad0a-1a30bf8e314c","model_name":"canary","model_version":null,"outputs":[{"name":"output-0","shape":[1,1],"datatype":"STR","data":["model-v1"]}]}%  

      输出结果返回了model-v1的结果,表明默认情况下服务能够正确提供model-v1的预测结果。即流量流向了model-v1。

    3. 执行以下命令,验证带有"foo: bar"请求头(期望访问金丝雀版本)的客户端请求。

      curl -H "Host: model.example.com" -H "Content-Type: application/json" \
           -H "foo: bar" \
           http://$NGINX_INGRESS_IP:80/v1/models/canary:predict -X POST \
           -d '{"data": "test"}'

      预期输出:

      {"id":"4d3efc12-c8bd-40f8-898f-7983377db7bd","model_name":"canary","model_version":null,"outputs":[{"name":"output-0","shape":[1,1],"datatype":"STR","data":["model-v2"]}]}%   

      输出结果返回了model-v2的结果,表明带有特定请求头的流量被正确地导向了灰度版本,即灰度发布策略已生效。

    场景二:基于服务权重的流量切分场景

    1. 执行以下命令,从kube-system命名空间中获取名为nginx-ingress-lb的Service,并使用jsonpath提取LoadBalancer的IP地址。

      NGINX_INGRESS_IP=`kubectl -n kube-system get svc nginx-ingress-lb -ojsonpath='{.status.loadBalancer.ingress[0].ip}'`
    2. 执行以下命令,验证流量的流向。

      curl -H "Host: model.example.com" -H "Content-Type: application/json" \
           http://$NGINX_INGRESS_IP:80/v1/models/canary:predict -X POST \
           -d '{"data": "test"}'

      重复执行以上命令,您可以观察到大约有50%的流量被路由到新版本(model-v2)的服务上,另一半流量则流向稳定版本(model-v1)的服务。即灰度策略已生效。

步骤四:删除老版本服务

系统运行一段时间后,当新版本服务已经稳定并且符合预期后,需要下线老版本的服务 ,仅保留新版本服务在线上运行。

  1. 拷贝以下内容到已创建的model-svc.yaml文件中,来修改已创建的Service,使其指向新版本model-v2服务。

    展开查看model-svc.yaml文件

    apiVersion: v1
    kind: Service
    metadata:
      name: model-svc
    spec:
      ports:
      - port: 80
        protocol: TCP
        targetPort: 8080
      selector:
        serving.kserve.io/inferenceservice: model-v2 # 将model-v1修改为model-v2。
      type: ClusterIP
  2. 执行以下命令,重新部署Service。

    kubectl apply -f model-svc.yaml 

    预期输出:

    service/model-svc configured

    输出结果表明Service已成功修改。

  3. 执行以下命令,从kube-system命名空间中获取名为nginx-ingress-lb的Service,并使用jsonpath提取LoadBalancer的IP地址。

    NGINX_INGRESS_IP=`kubectl -n kube-system get svc nginx-ingress-lb -ojsonpath='{.status.loadBalancer.ingress[0].ip}'`
  4. 重复执行以下命令,查看路由访问情况。

    curl -H "Host: model.example.com" -H "Content-Type: application/json" \
         http://$NGINX_INGRESS_IP:80/v1/models/canary:predict -X POST \
         -d '{"data": "test"}'

    预期输出:

    {"id":"a13f2089-73ce-41e3-989e-e58457d14fed","model_name":"canary","model_version":null,"outputs":[{"name":"output-0","shape":[1,1],"datatype":"STR","data":["model-v2"]}]}%  

    重复执行以上命令后,您可以观察到100%的请求(流量)被路由到新版本(model-v2)的服务上。

  5. 执行以下命令,删除旧版本服务。

    # 删除灰度策略。
    kubectl delete ingress gray-release-canary
    # 删除model-v1推理服务。
    arena serve delete model-v1
    # 删除model-v2的Service。
    kubectl delete svc model-v2-svc

    执行完以上这些命令后,表示您已完成了从旧版本到新版本过渡过程中的清理工作。