使用Dubbo+Kubernetes部署线上的TensorFlow Serving服务

Author: xidianwangtao@gmail.com

摘要:本文介绍了在Kubernetes集群中,使用Dubbo+Zookeeper来完成TensorFlow Serving服务的注册与发现、负载均衡的方案,以及使用KubeDNS+Kube2LVS的方案。

背景

TensorFlow Serving服务在Kubernetes集群中的部署方案,如果是从零开始建设,那么可以通过Kubernetes原生的Service+KubeDNS实现服务的注册与发现,并通过对接LVS集群进行负载均衡。因此我们在TaaS中开发了Kube2LVS模块,负责对TensorFlow Serving服务进行ListAndWatch,实现TensorFlow Serving Service Info动态reload到LVS config中。

但是在TensorFlow Serving on Kubernetes发布之前,用户已经通过裸机部署的方式在线上部署了Serving服务,用户采用Dubbo框架来进行Serving服务的注册与发现、LB,因此为了兼容已有的架构,我们最终选择使用Dubbo+Zookeeper的方式来取代前面基于KubeDNS+Kube2LVS的方案。

需要说明:

  • 我们为TensorFlow Serving服务单独提供了一个CaaS集群,目前并没有和训练集群混合部署。
  • CaaS集群的网络方案要求Pod IP对集群外可见,Cisco Contiv(OVS + Vlan)和Calico(BGP)均可。

KubeDNS + Kube2LVS

Architecture

Overview

  • 在IDC部署基于OSPF + Quagga + Tunnel的LVS集群;
  • 开发LVS配置热更新的HTTP API,提供给Kube2LVS调用;
  • 为了方便管理和部署,我们的线上TensorFlow Serving使用原则:一个TensorFlow Serving实例只加载一个Model,暴露一个Port;
  • 上线初期,为了保证(验证)Serve Model的高可用,同一个Model需要一部分副本部署在物理服务器上,另外一部分副本部署在CaaS集群中。业务请求经过LVS分发到对应的物理服务器和CaaS集群节点,均提供IP + Port给LVS集群;
  • LVS集群中,会给每个Model从VIP Pool中分配一个对应的VIP;
  • TaaS平台开发Kube2LVS模块,负责ListAndWatch CaaS集群中TensorFlow Serving Service的CUD事件,然后调用LVS HTTP接口更新LVS配置;
  • TensorFlow Serving Services以NodePort方式暴露到集群外部,外部访问TensorFlow Serving服务只能通过CaaS集群中的Edge Node,在Edge Node通过kube-proxy经过iptables 4层路由转发到后端真正的TensorFlow Serving容器。Edge Node是Kubernetes节点,但是不部署任何业务容器,只做流量入口及流量分发,通过Node Taint和Node Label的方式实现。
  • Edge Node流量过大,可以通过Ansible分钟级扩容(事先准备好服务器);
  • 通过TaaS中对每个Serving服务的监控,如果发现某个Model的副本数不够,可以通过在TaaS平台上秒级手动扩容到期望的副本数;
  • TensorFlow Serving部署的CaaS集群需独立部署(与TensorFlow Training的CaaS集群物理隔离);

Request & Response路径

  • Request的路径:
Client ——> LVS ——> Edge Node ——> TensorFlow Serving Instance(Model);
  • Response路径:
TensorFlow Serving Instance(Model)——> Client

高可用

方案保证了请求的全链路高可用,包括以下三个方面:

  • LVS的高可用

LVS集群通过OSPF + Quagga来部署,每个LVS集群部署两个LVS实例来保证LVS的高可用。

  • Edge Node的高可用

在LVS集群中,给每个Model分配一个VIP,并4层负载到后端至少2个Edge Node上,这样保证Edge Node这一层的高可用;

  • TensorFlow Serving 4 Model_N的高可用

    • 每个Model会通过至少2个TensorFlow Serving实例来加载并提供服务,防止单点。
    • 每个TensorFlow Serving实例都能接受和处理请求,具备负载均衡的能力。
    • 同一个Model的不同TensorFlow Serving实例会由CaaS自动调度到不同的物理服务器或者机架,防止物理服务器或者机架掉电等引发的单点故障。
    • 如果CaaS中的某个TensorFlow Serving实例down了,那么CaaS会自动发现这一事件,并会自动再重启一个TensorFlow Serving实例。
    • TensorFlow Serving实例只有部分部署在CaaS集群中,还有部分部署在CaaS集群之外的物理服务器上(由用户自己部署),在LVS层面配置好负载均衡,防止不可预知的整个CaaS集群故障引发单点故障;
    • 待稳定运行一段时间后,将所有的TensorFlow Serving实例部署到CaaS集群中;

资源隔离和稳定性

通过裸机在线上部署的TensorFlow Serving实例目前都是单独占用一台物理服务器,如果该Model的负载不高,则会造成一定的资源浪费。部署到CaaS集群后,可以支持单台服务器启动多个TensorFlow Serving实例。Kubernetes提供以下集中资源隔离机制,来保证单个TensorFlow Serving实例资源的同时,也能做好各个实例之间的资源隔离,防止某个Model完全抢占了其他Model Server的资源,也达到了提供资源使用率的目的。

  • 通过kubernetes limitrange(足够大)默认使得单台服务器只能部署一个TensorFlow Serving;
  • 通过单独给需要的TensorFlow Serving容器配置resource requests(cpu & memory)来保证当服务器资源资源不够用出现争抢时,这个TensorFlow Serving最少可用的资源;
  • 如果服务器资源有空闲,则它上面的任何TensorFlow Serving实例都能尽量去利用空闲的资源,提高资源使用率。也就是给容器配置resource limits,否则可能会出现被Linux Kernel OOM Killer杀死的风险。
  • CaaS集群中的每台服务器,都会设置给linux系统进程和kubernetes组件预留的资源,以此保证其上面的TensorFlow Serving再怎么榨取服务器资源,也不会影响linux系统和关键组件的运行,防止服务器被TensorFlow Serving搞垮和集群雪崩。关于这部分的详细内容,请参考从一次集群雪崩看Kubelet资源预留的正确姿势

弹性伸缩

项目初期,只提供用户手动干预的方式进行Scale:

  • Edge Node的Scale up/down

需要对Edge Node的网络IO进行监控和告警,当网络IO遇到瓶颈时,准备好物理服务器(两个万兆网卡做Bond),然后通过Ansible自动化部署CaaS相关组件,组件启动后就能作为Edge Node提供流量入口服务和分发的能力了,之后就能添加到LVS配置中作为LVS后端服务。

  • TensorFlow Serving实例的Scale up/down

当某个Model Serve的请求量太大,通过监控发现后端的TensorFlow Serving Replicas的负载过高产生告警。用户收到告警后,登录TaaS平台进行扩容操作,增加Replicas数,CaaS会自动创建对应TensorFlow Serving容器并加载Model对外提供服务,以此降低每个实例的负载并提升了处理能力。

线上运行成熟后,根据经验我们可以实现基于定制化的HPA(对接Prometheus)TensorFlow Serving实例的 Auto Scale up/down,全程自动化处理,无需人为干预。

Dubbo + Zookeeper

方案

方案注意事项

  • 使用Kubernetes Deployment(replicas=1)来管理一个模型的Serving实例,同一个模型的副本数用户可以在TaaS上配置,注意:

    • 每个副本都对应一个Deployment和Service。Deployment的replicas设置为1,TaaS按照创建顺序,给同一个模型的多个Serving副本的Deployments、Services和Pods打上对应的Label:Index:$N, Model:$Name
    • Deployment和Service的命名规则为:$ModelName-$Index
    • 每个Serving实例的创建,都是按照先创建Service,再创建Deployment的顺序。先创建Service是为了先拿到Kubernetes自动分配的NodePort。
    • 如此,每个Pod对应一个Deployment和Service,Deployment负责该Pod的Self-Healing,Service类型为NodePort,负责NodePort的自动分配和代理。(注意,这里不能使用Headless Service,因为Headless Service不支持NodePort类型。)
  • 每个Pod内两个业务容器,一个是TensorFlow Serving容器,负责加载HDFS上的Model并提供grpc接口调用,TaaS上提供用户配置TensorFlow Serving的模型加载策略,默认加载lastest模型;另外一个是Tomcat业务容器,业务jar包在这里启动并进行热更新,jar包实现不同的特征抽取组合进行预测,启动时向集群外的Zookeeper集群注册自己所在节点NodeIP和NodePort。

    • 通过downward-api的方式向Pod内注入NodeIP的env。由于先创建Service拿到NodePort,通过给Pod注入env的方式将NodePort注入到Pod内。如此,tomcat容器内就能拿到对应的NodeIP和NodePort,从而启动前去更新dubbo的配置文件。
    • 为了兼容一机多实例的场景,不能使用hostNetwork:true共享Host网络命名空间,否则必然会导致tomcat和Serving无法启动的问题。
  • 如何进行一机单实例部署?

    • 上线初期,按照一机单实例进行部署,通过给Pod内的container设置resource.request接近Node Allocatable,使得Kubernetes调度时一个宿主机只能容下一个Pod。
  • 如何进行一机多实例部署?

    • 稳定运行一段时间后,如果发现集群的资源利用率较低,那么考虑一机多实例的方式进行部署。只需要将Pod对应的resource.request减小到合理的值,使得Kubernetes调度时一个宿主机能容下多个Pod。

完整流程

TaaS实现的流程如下:

  1. 页面上提供用户配置预测模型的相关信息,包括:
    • 模型名称
    • 模型的HDFS路径
    • TensorFlow Serving模型加载策略相关配置(默认latest)
    • 期望实例个数N
    • 每个实例的请求资源值(默认为24cpu,128GB,也就是一机单实例)
  2. TaaS顺序(index从1到N)的为每个实例按照如下逻辑进行实现:
    • 先创建一个NodePort类型的Service(加上对应的Label:Index:$N, Model:$Name),注意不要指定nodePort的值。
    • 然后查询这个Service的nodePort值。
    • 再封装Deployment对象,把获取到的nodePort注入到pod的env中。通过downward-api事先注入不确定的NodeIP,调用Kubernetes接口创建该Deployment。
  3. 接着Kubernetes会调度到合适的节点,将Pod内的容器启动。tomcat启动前会获取NodeIP和NodePort,并更新到dubbo配置文件中,并自动上报到集群外的Zookeeper集群。
  4. Zookeeper会将新的或者发生变更的服务信息自动通知client,client根据负载均衡策略选择其中一个RealServer。
  5. client选择某个RealServer的NodeIP和NodePort后,发起预测请求。请求到对应的节点后,经过节点的iptables(kube-proxy根据Service自动维护)转发到后端的Pod。
  6. Pod内的tomcat处理后,对TensorFlow Serving发出grpc请求进行预测。整个请求的数据原路返回。

注意:pod中tomcat和serving两个容器启动的顺序是有要求的:先启动serving容器,再启动tomcat容器。tomcat容器启动前,先去检测localhost中serving服务是否启动成功,如果未启动,则循环等待。

健康检查及流量自动接入与摘除

  • tomcat服务启动时会自动往ZK注册服务,通过Session长连接的方式来维护ZK的服务列表。如果长连接断了,那么ZK会自动从服务列表中删除这个实例的信息。通过这种方式完成服务的自动接入与摘除。
  • 给tomcat容器配置Kubernetes Liveness Probe,通过模拟用户的请求(会调用tensorflow serving服务)来判断服务是否可用。如果探针失败,则kubelet会自动重启tomcat容器,重启过程中,与ZK的Session长连接会断开,ZK就会自动摘除这个实例。重启后,会重新注册服务,完成自动接入。通过这种方式来防止服务Hang住假死的问题。
  • 给tomcat容器配置Readiness Probe吗,如果探针失败,会从Service中摘除这个实例,client的请求即使到了所在的节点,iptables也不会转发这个请求到对应的Pod。
  • tensorflow serving容器的健康检查,配置和tomcat一样的liveness probe。如果探测失败,kubelet自动重启serving容器。注意探测的周期不要太短,建议分钟级别。

高可用

  • tomcat服务down了或者Hang住的情况。

    • tomcat服务down了,与ZK的长连接就断了,ZK会摘除这个实例,ZK接着通知client,client之后就不会将请求发到这个实例了,直到重新注册成功。
    • tomcat服务down了,那么liveness probe就会失败,kubelet会重启tomcat,触发重新注册。
    • tomcat服务Hang住的情况,Session没断的话,ZK是无法感知的。但是不要紧,liveness probe会失败,kubelet会重启tomcat,触发重新注册。
  • tensorflow serving服务down了或者Hang住的情况。

    • tensorflow serving容器配置了liveness probe的话,如果探测失败,kubelet会重启这个容器。
  • 实例所在的服务器down了的情况下。

    • 实例所在的节点down了,会导致Session断开,ZK感知到这一事件并自动摘除对应实例。
    • 节点down了后大概5min时间,会在其他节点重新启动一个实例,新实例启动后往ZK中注册服务。由于线上都是多副本部署的,这个实例5min内不可用不要紧,其他副本能正常提供服务即可。
  • 实例所在节点与ZK的网络挂了的情况下。

    • 网络挂了,Session就断了,ZK感知到这一事件并自动摘除对应实例。

总结

本文介绍了两种使用Kubernetes部署TensorFlow Serving服务,并完成服务发现与负载均衡的方案。基于KubeDNS+Kube2LVS的方案使用Kubernetes原生的特性,基于Dubbo+Zookeeper的方案则使用Dubbo的服务发现与软负载特性。当然,还有很多的实现细节需要读者自己思考,有需求的同学可以找我讨论。

转载于:https://my.oschina.net/jxcdwangtao/blog/1827631

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值