Kubernetes在喜马拉雅的实践:测试环境稳定性
作者:互联网
宋银涛 分布式实验室
喜马拉雅是专业的音频分享平台,汇聚了有声小说、有声读物、相声小品等数亿条音频。目前平台激活用户超6亿,主播用户超过1000万,大V用户超过100万,活跃用户日均收听时常170分钟,5年时间估值增长1000倍。作为一个产品驱动的互联网公司,公司的业务线扩展迅猛,需求的迭代十分迅速。前期开发在测试环境的发布也相当的频繁,那么所带来的稳定性问题十分突出。因此,我们在2016年底启动了容器化项目,将公司的应用进行容器化改造。在容器化背景之下,如何保证测试环境开发效率的同时兼顾环境的稳定性。
容器化的价值:资源+效率==>效果
即使用更少的资源+更高的开发效率,产生更好的效果。在测试环境当中,更高的开发效率显得更加重要。
容器化的实践
这里我们先来简单介绍一下容器化在喜马拉雅的实践。
给喜马拉雅带来的好处
容器化之前
在进行容器化之前,开发在测试环境会碰到这些问题:
1、新项目创建
开发在接到新的需求之后,创建的新项目,本地开发完成之后,需要部署到测试环境。他们将不得不面对这些问题:
代码开发完成
找运维人员分配机器
自己在Jenkins上创建Task
这一套流程下来,至少半个工作日过去了。时间都耗费在这些没有意义的事情上,开发效率相当低下。
2、物理机冲突
由于历史原因,作为一个创业公司,业务发展迅速,开发团队人员极速上升。运维团队相对精简,没有足够的精力运维管理测试环境。导致测试环境的管理相当混乱,主要问题如下:
共享账号
端口冲突
进程莫名被kill
由于大家共享物理机SSH账号,权限管理混乱。大家都可以随意的配置自己的服务,为了确保自己的服务正常上线,随意占用别人服务的端口,kill掉被人的服务。
这也是导致测试环境不稳定的一个重要因素。
3、断电重启
由于历史原因,老园区每1~2个月都会进行停电检修。机房服务器断电,导致整个测试环境不可用,电力恢复之后,需要开发手动拉起自己负责的业务。整个流程下来,半个工作日就过去了。
当然,现在公司有钱了,有了自己的办公大楼,新建了UPS机房,保证24小时不断电,这些问题是不存在的。
4、小结
针对这些问题,容器化的进程势在必行。因此,2016年底我们引入了容器技术进行容器化改造,来解决这些问题。
容器化之后
对测试环境进行容器化之后,我们取得了以下收益:
分钟级创建、发布新项目;
每个应用独享"物理机",不会存在端口冲突、服务莫名被销毁;
断电服务自动拉起。
容器化现状
整体架构
在架构层面,CMDB-DOCKER(容器化管理平台)如何能与上层业务和底层Kubernetes平台更好地分层和解耦,是我们在设计之初就优先考虑的问题。
我们期望它既要能对业务使用友好,又能最大限度地发挥Kubernetes的调度能力,使得业务层和使用方毋需关注资源关系细节,所求即所得。
同时使发布、配置、负载等逻辑层与底层的Kubernetes平台解耦分层,并保持兼容原生Kubernetes API来访问Kubernetes集群。
从而可以借助于统一的、主流的、符合业界规范的标准,来解决基础架构面临的复杂、多样、不统一的管理需求。
自上而下来看,集群管理与调度平台面向全公司服务,有各个主要业务线、统一的OPS平台,CMDB-DOCKER不可能针对每个平台定制化接口和解决方案,所以需要将多样的业务和需求抽象收敛,最终统一通过CMDB-DOCKER API来屏蔽CMDB-DOCKER系统的细节,做到CMDB-DOCKER与上层业务方的解耦。「CMDB-DOCKER」 API是对业务层和资源需求的抽象,是外界访问「CMDB-DOCKER」的唯一途径。
解决了上层的问题后,我们再来看与下层Kubernetes平台的解耦。
CMDB-DOCKER接到上层资源请求后,首先要进行一系列的初始化工作,包括参数校验、资源余量、IP和Hostname的分配等等,之后向Kubernetes平台实际申请分配机器资源,最终将资源交付给用户,Kubernetes API进一步将资源需求收敛和转换,让我们可以借助于Kubernetes的资源管理优势。此外,因为完全兼容Kubernetes API,可以让我们借助社区和生态的力量,共同建设和探索。
可以看到,CMDB-DOCKER API和Kubernetes API将我们整个系统分为三层,这样可以让每一层都专注于各自的模块。
支持的功能
喜马拉雅在容器化工作上主要支持了以下功能:
1、多语言支持。
目前支持Java、Python、Nodejs、PHP。
2、现有技术栈兼容。
为了无缝迁移传统服务,我们需要做一些兼容性的工作,整合现有的技术栈。
下面对喜马拉雅主要的中间件做一些简单的介绍:
Mainstay:微服务治理框架,采用自定义Thrift协议的RPC框架(类似Dubbo);
southgate:API Gateway公司自研的网关系统,管理公司所有客户端请求;
cmdb-docker:容器化管理平台。
主要解决的问题:
由于容器化以后,实例在重调度、扩缩容、重发布之后,IP是不断变化的,这些中间件要动态的感知到这些事件,对这些实例进行一些实例销毁、新增,服务上下线的操作。
为此,我们开发了Nile Agent组件用于同步这些信息。
主要流程如下:
服务发布时,注入nile sidecar容器和应用容器共同发布;
nile sidecar启动之后,通过liveness、readiness接口,检查App Service的状态;
App Service可用之后,将其实例信息(IP、port、应用名称等)注册到ZooKeeper,同时,调用网关、注册中心的上线接口,上线服务;
中间件通过监听ZooKeeper的事件,来对这些中间件的实例信息进行更新、服务发现。
3、无损发布。
无损发布主要解决的就是应用重新发布时,保证旧实例在新发布的实例就绪时再销毁。在销毁的过程中,需要先进行服务的下线操作,再进行零流量检测,最终销毁容器。
这里主要是用了Kubernetes提供的Liveness、Readiness探针功能,在新的发布ready之后,在进行旧实例的销毁。同时,还使用了容器生命周期管理的Hook preStop功能,在容器销毁之前,下线服务进行零流量检测,没有流量之后销毁旧的实例。
4、静态IP支持。
由于RPC微服务框架只提供了Java版本的SDK,C/C++应用通过IP直连的方式访问服务,因此需要提供固定IP的功能。
5、隔离组支持。
测试环境频繁的发布、发布代码存在bug,经常导致测试环境不稳定,开发效率受到严重的影响。因此,我们提供了隔离组的功能,用来确保测试环境的稳定性。这部分具体内容在下面将会详细说明。
6、监控、告警。
我们提供了应用实例的健康监测功能,在服务不可用时将会发送钉钉消息给相关负责人。
测试环境稳定性解决方案
存在的问题
导致测试环境不稳定的原因主要有以下几个方面:
开发发布问题代码,导致服务调用异常;
开发开启了远程debug的调试功能,导致服务短时间内不可用。
导致服务不可用。间接导致开发人员在定位问题时浪费较长。
具体场景如下:
测试人员发现某个服务接口返回500,其调用链路为A->B->C->D。如下图所示:
沟通+排查问题时间太长。比如,测试发现A服务提供的接口有问题,找A,A花了五分钟发现是B返回的结果存在问题,B又花了5min,以此类推,最终发现是D的问题。这样花费了这么多的5min,开发效率可想而知。
经常会出现开发5分钟,联调两小时的状况。
隔离组
概念
我们在了解隔离组的概念之前,先回顾一下Git的功能分支模型。
Git功能分支模型:
当有多个需求需要同时进行开发的时候,从master分支中拉取feature分支进行开发;
多个需求开发完成之后,合并多个feature分支,通过集成测试之后,发布到线上的环境;
测试确认无误之后,分支合并到master分支。
在开发测试的过程中,可能会存在的问题:
多个feature分支测试代码发布到测试环境麻烦,开发之间会存在冲突。比如,A在测试feature1的功能时,B发布了他的最新代码feature2。两者之间,如果没有有效的沟通或者约定,频繁发布会导致互相影响、降低开发效率。
那么,测试环境急需对这些分支的部署进行隔离,确保多人协作开发的效率,各自在开发、测试的时候不会互相影响。为此,我们提出了隔离组的概念,实现了测试环境的逻辑上的"软隔离"。
隔离组的定义与划分:
隔离组:逻辑上的一组服务实例的集合,由中间件的路由策略来保证它的隔离性。
关于隔离组的划分如下:
stable隔离组:永久存在的、稳定的实例集合,提供稳定的基础环境。和线上的release分支代码一致,该隔离组和线上部署的服务一致,永久保证高可用,任何时候都能请求成功;该隔离组保证了测试环境的稳定性。
feature隔离组:一组临时的实例集合。和产品提的需求有关,调用链路上相关的项目都会从master分支上拉取新建feature分支用于该需求的开发,项目发布时指定隔离组信息,重新部署一个实例;和stable隔离组的实例共存。
stable隔离组的特点:
和线上发布实例一致;
线上发布成功后,自动触发测试环境的stable实例的发布;
测试环境,开发人员不能发布该隔离组实例;
关闭远程debug功能。
feature隔离组的特点:
临时实例,生命周期开发可控,到期自动回收;
feature分支发布;
提供远程debug功能。
隔离组与Git功能分支模式的关系:
示例:
如上图,正常请求的调用链路如下:
AppClient—>southgate(API Gateway)—>A1—>B1—>C1。
在新的迭代周期,产品提出了两个需求,开发分别在feature1、feature2进行开发。同时,在发布实例时指定不同的隔离组信息。确保三个隔离组的实例同时存在(该特点直接采用Kubernetes中的service可以多版本发布deployment的特点)。
主要的路由规则如下:
App Client在没有指明隔离组的请求中,该请求流量全部打到stable隔离组的实例上面;
App Client的请求头中指定了隔离组的信息,则选择相应的隔离组实例,如不存在转发到stable隔离组中,同时,请求中注入隔离组信息用于后续调用链的路由。如上图中的feature1隔离组,网关收到请求后,发现A服务存在对应的隔离组feature1的A2实例。则请求达到该实例上面,再往下调用B服务的实例时,发现并不存在隔离组实例,则将请求转发到Stable隔离组实例B1中。由于B1收到的请求中携带feature1的隔离组标识,在调用C服务时,发现存在feature1隔离组的实例C2,将请求路由到C2实例中。
从上面可以看出,隔离组的实现主要依赖流量的路由功能,具体的实现方案如下。
实现方案
隔离组,即多版本实例的发布主要依赖Kubernetes多版本发布功能,这里不再赘述。
路由转发实现软隔离,主要是对我们公司中间件的改造,支持根据隔离组标签的路由。
具体的实现如下:
在现有技术栈兼容的部分,我们提过通过ZooKeeper同步实例的信息,其中就包含了隔离组的信息。这样中间件(微服务注册中心、API网关)中,包含了对应实例的isolation信息。
API Gateway:当收到App客户端请求时,解析请求头里面的isolation字段,选取对应隔离组的实例,请求路由到该实例中。如果没有isolation字段,则转发到stable隔离组的实例当中,同时将隔离组信息注入到后续的请求调用中,用于进一步的路由;
RPC client:在filter中注入isolation信息到RPC请求消息的body的attachment中。同时,根据isolation信息选择对应的实例,将请求发送到该实例当中。如果不存在对应的隔离组实例,则转发到stable隔离组实例里面;
RPC server:获取到RPC请求消息之后,消息解包,从attachment中获取isolation信息,缓存在本地,用于后续RPC请求的路由,用于选择下一跳服务的实例,以此类推。
优化
为了提升开发效率,迅速定位调用链的问题服务,解决调用链路黑盒问题。我们做了以下优化:
服务依赖拓扑查询:方便开发、测试迅速定位问题服务;
隔离组可视化:轻松定位上下游关系,方便调试。
依赖拓扑查询:
隔离组可视化:
鼠标悬浮到实例上,可获取实例详细信息(IP、端口、健康状态、负责人等)。
测试环境开发效率提升
测试环境除了稳定性之外,还需要注重开发效率。为此,我们开发了一款测试环境的发布工具,用于本地代码的编译、镜像构建、服务发布。借鉴了Kubernetes的配置即发布的思想,可以让开发迅速将自己的服务根据自己的配置文件,发布到容器环境(非stable隔离组),大大提升开发效率。
该工具提供了以下功能:
一键构建、打包服务,并发布到容器化环境(测试);
测试环境实例状态查看;
测试环境实例快捷登录,查看日志;
多语言支持。
工具相关命令详情:
发布配置文件(以Spring Maven项目为例):
apiVersion: 0.0.1apps:- appName: demo-web alias: web build: language: java maven: prepareCommand: mvn clean install -am -Dmaven.test.skip=true -Ptest-out pom:demo-web/pom.xml war: demo-web/target/demo-web.war deploy: isolation: feature1 expire: 1h instances: 2
该配置文件指定了项目的构建、隔离组、实例数等信息,在项目的根目录下执行:
barge deploy -m demo-web
该项目的代码将会在本地进行编译、构建、镜像打包、push到镜像仓库、部署到测试环境的Kubernetes集群中。成功部署之后,将会给用户发布信息。
执行barge info -m demo-web命令将会显示项目的实例信息,及其健康状态。barge exec -m demo-web 命令将远程登录到容器里面。该工具简化了测试环境的发布流程,实例状态的查看。以类似于mvn命令的方式,进行测试环境服务的发布、管理,避免浏览器的频繁切换。(当然,线上环境并不支持该操作)
总结
今天主要和大家分享了喜马拉雅在容器化方面进行的一些工作。介绍了容器化给我们带来的收益:减少维护成本、提升开发效率。其次,列举了导致我们测试环境不稳定的一些因素:1. 多分支并行开发的频繁发布;2. 开发远程debug,服务短暂不可用;3. 开发发布问题代码导致服务不可用。为此,我们提出了隔离组的概念,实现测试环境的"软隔离"。在隔离组中:首先,我们保证一整套和线上发布一致的stable隔离组,该隔离组保证测试环境稳定。当需要,开发新的分支代码时,创建新的隔离组,保证stable隔离组的实例不受影响,确保了环境的稳定性。其实现原理比较简单,主要是打通了中间件之间的流量管理,通过隔离组信息来进行请求路由。最后,介绍了我们在测试环境使用的小工具,通过简单的命令行方式进行服务的快速发布、日志查看,提升开发效率。
标签:容器,喜马拉雅,服务,隔离,Kubernetes,实例,开发,测试环境 来源: https://blog.51cto.com/u_15127630/2766421