为什么需要ServiceMesh
UCloud App Engine on Kubernetes(后简称“UAEK”)是UCloud内部打造的一个基于Kubernetes的,具备高可用、跨机房容灾、自动伸缩、立体监控、日志搜集和简便运维等特性计算资源交付平台,旨在利用容器技术提高内部研发运维效率,让开发能将更多的精力投入在业务研发本身,同时,让运维能更从容应对资源伸缩、灰度发布、版本更迭、监控告警等日常工作。
考虑到Kubernetes本来就是为自动部署、伸缩和容器化而生,再加上UCloud UAEK团队完成IPv6组网调研和设计实现后,一个成熟的容器管理平台很快正式在北京二地域的多个可用区上线了。相比于过去申请管理虚拟机部署应用服务,Kubernetes确实带来了实实在在的便利,例如方便灵活的自动伸缩以及×××的微服务架构,只需简单配置即可实现跨可用区容灾等。
然而,微服务化又为系统架构带来许多新的问题,例如服务发现、监控、灰度控制、过载保护、请求调用追踪等。大家已经习惯自行运维一组Zookeeper集群用以实现服务发现和客户端负载均衡,使用UAEK后能否免去运维Zookeeper的工作?为了监控业务运行状态,大家都需要在代码里加上旁路上报逻辑,使用UAEK是否能无侵入零耦合地实现监控上报?
此外,过去很多系统模块间调用缺少熔断保护策略,波峰流量一打就瘫,使用UAEK是否能帮助业务方免去大规模改造呢?过去排查问题,尤其是调用耗时环节排查总是费时费力,使用UAEK能否为定位瓶颈提供方便的工具?
显然,仅凭一个稳定的Kubernetes平台不足以解决这些问题。因此,在UAEK立项之初,团队就把ServiceMesh作为一个必须实现的目标,任何在UAEK上部署的TCP后台服务,都能享受到ServiceMesh带来的这些特性:
SideCar模式部署,零侵入,微服务治理代码与业务代码完全解耦;
与Kubernetes平台融合的服务发现机制和负载均衡调度;
提供灵活,实时,无需重启、能根据7层业务信息进行流量灰度管理功能;
提供统一抽象数据上报API层,用于实现监控和访问策略控制;
使用分布式请求链路追踪系统,快速追溯Bug,定位系统性能瓶颈;
过载保护机制,能在请求量超过系统设计容量时自动触发熔断;
能在服务上线前提供故障模拟注入演习剧本,提前进行故障处理演练;
这样,使用UAEK部署应用服务后,即可从小范围按账号灰度上线开始,通过陆续地监控观察,轻松掌握版本异常回退、扩大灰度范围、全量发布、过载保护、异常请求定位追踪等信息。
为什么是Istio?
关于ServiceMesh的实现,我们重点考察了Istio。通过前期的调研和测试,我们发现Istio的几个特性能很好满足UAEK的需求:
完美支持Kubernetes平台;
控制面和数据转发面分离;
Sidecar部署,掌控所有服务间调用流量,无上限的控制力;
使用Envoy作为Sidecar实现,Envoy使用C++11开发,基于事件驱动和多线程机制运行,性能好并发能力强,媲美NGINX;
对业务的代码和配置文件零侵入;
配置简单,操作方便,API完善。
cdn.xitu.io/2018/8/23/16565d6f6dfeaed9?w=646&h=507&f=png&s=55105">
整个服务网格分成控制面板和数据面两大部分。数据面指的就是注入到应用Pod中的Envoy容器,它负责代理调度模块间的所有流量。控制面分为Pilot,Mixer和Citadel三大模块,具体功能如下:
Pilot负责向Kubernetes API获取并Watch整个集群的服务发现信息,并向Envoy下发集群服务发现信息和用户定制的路由规则策略。
Mixer分为Policy和Telemetry两个子模块。Policy用于向Envoy提供准入策略控制,黑白名单控制,QPS流速控制服务;Telemetry为Envoy提供了数据上报和日志搜集服务,以用于监控告警和日志查询。
Citadel为服务和用户提供认证和鉴权、管理凭据和 RBAC。
此外Istio为运维人员提供了一个叫istioctl的命令行工具,类似kubernetes的kubectl。运维编写好路由规则yaml文件后,使用istioctl即可向集群提交路由规则。
Istio整体工作的原理和流程细节非常复杂,所涉及到的技术栈有一定的深度和广度。这里只概括一下大体过程:
运维人员使用istioctl或者调用API向控制层创建修改路由规则策略。
Pilot向Kube APIServer获取并watch集群服务发现信息。
部署应用程序时,Istio会在pod的部署配置中注入Envoy容器,Envoy会通过iptables nat redirect劫持代理pod中的全部TCP流量。
Envoy会实时从Pilot更新集群的服务发现信息和路由规则策略,并根据这些信息智能调度集群内的流量。
Envoy会在每次请求发送前向Mixer Policy发送Check请求检查该请求是否收策略限制或者配额限制,每次请求接收后会向Mixer Telemetry上报本次请求的基本信息,如调用是否成功、返回状态码、耗时数据。
Citadel实现了双向TLS客户端证书生成与注入,服务端密钥和证书的下发注入,以及K8S RBAC访问控制。
Istio在UAEK环境下的改造之路
经过上述的调研和与一系列测试,UAEK团队充分认可Istio的设计理念和潜在价值,希望通过利用Istio丰富强大的微服务治理功能吸引更多的内部团队将服务迁移到UAEK环境中。
然而,事实上,在UAEK上接入Istio的过程并非一帆风顺。最早开始调研Istio的时候,Istio还在0.6版本,功能并不完善,在UAEK环境中无法开箱即用。
IPv6问题的解决
我们首先碰到的问题是,UAEK是一个纯IPv6网络环境,而Istio对IPv6流量的支持并不完备,部分组件甚至无法在IPv6环境下部署。
在介绍具体改造案例之前,先了解下Istio Sidecar是如何接管业务程序的流量。
如上图所描述,Istio会向应用Pod注入两个容器:proxy-init容器和envoy容器。proxy-init容器通过初始化iptables设置,将所有的TCP层流量通过nat redirect重定向到Envoy监听的15001端口。以入流量为例,Envoy的服务端口接收到被重定向到来的TCP连接后,通过getsocketopt(2)系统调用,使用SO_ORIGINAL_DST参数找到该TCP连接的真实目的地IP地址,并将该请求转发到真实目的IP。
然而,我们发现在IPv6环境下,Envoy无法劫持Pod的流量。通过抓包观察和追溯源码发现,Pod启动的时候,首先会运行一个iptables初始化脚本,完成pod内的nat redirect配置,将容器内的TCP出入流量都劫持到Envoy的监听端口中,但这个初始化脚本没有ip6tables的对应操作并且discard了所有IPv6流量,因此我们修改了初始化脚本,实现了IPv6的流量劫持。
一波刚平,一波又起。完成IPv6流量劫持后, 我们发现所有访问业务服务端口的TCP流量都被Envoy重置,进入Envoy容器中发现15001端口并没有开启。追溯Envoy和Pilot源码发现,Pilot给Envoy下发的listen地址为0:0:0:0:15001, 这是个IPv4地址,我们需要Envoy监听地址的为[::0]:15000,于是继续修改Pilot源码。
经过上述努力,应用服务端程序Pod终于能成功Accept我们发起的TCP连接。但很快,我们的请求连接就被服务端关闭,客户端刚连接上就立刻收到TCP FIN分节,请求依然失败。通过观察Envoy的运行日志,发现Envoy接收了TCP请求后,无法找到对应的4层流量过滤器(Filter)。
深入跟进源码发现,Envoy需要通过getsocketopt(2)系统调用获取被劫持的访问请求的真实目的地址, 但在IPv6环境下Envoy相关的实现存在bug,如下代码所示。由于缺少判定socket fd的类型, getsocketopt(2)传入的参数是IPv4环境下的参数,因此Envoy无法找到请求的真实目的地址,遂报错并立刻关闭了客户端连接。
发现问题后,UAEK团队立刻修改Envoy源码,完善了getsocketopt(2) 的SO_ORIGINAL_DST选项的IPv6兼容性,然后将这一修改提交到Envoy开源社区,随后被社区合并到当前的Master分支中,并在Istio1.0的Envoy镜像中得到更新使用。
到此为止,Istio SideCar终于能在UAEK IPv6环境下正常调度服务间的访问流量了。
此外,我们还发现Pilot、Mixer等模块在处理IPv6格式地址时出现数组越界、程序崩溃的情况,并逐一修复之。
性能评估
Istio1.0发布之前,性能问题一直是业界诟病的焦点。我们首先考察了增加了Envoy后,流量多了一层复制,并且请求发起前需要向Mixer Policy进行一次Check请求,这些因素是否会对业务产生不可接收的延迟。经过大量测试,我们发现在UAEK环境下会比不使用Istio时增加5ms左右的延迟,对内部大部分服务来说,这完全可以接受。
随后,我们重点考察了整个Istio Mesh的架构,分析下来结论是,Mixer Policy和Mixer Telemetry很容易成为整个集群的性能短板。由于Envoy发起每个请求前都需要对Policy服务进行Check请求,一方面增加了业务请求本身的延迟,一方面也给作为单点的Policy增大了负载压力。我们以Http1.1请求作为样本测试,发现当整个网格QPS达到2000-3000的时候,Policy就会出现严重的负载瓶颈,导致所有的Check请求耗时显著增大,由正常情况下的2-3ms增大到100-150ms,严重加剧了所有业务请求的耗时延迟,这个结果显然是不可接受的。
更严重的是,在Istio 0.8以及之前的版本,Policy是一个有状态的服务。一些功能,如全局的QPS Ratelimit配额控制,需要Policy单个进程记录整个Mesh的实时数据,这意味着Policy服务无法通过横向扩容实例来解决性能瓶颈。经过取舍权衡,我们目前关闭了Policy服务并裁剪了一些功能,比如QPS全局配额限制。
前面提到过,Mixer Telemetry主要负责向Envoy收集每次请求的调用情况。0.8版本的Mixer Telemetry也存在严重的性能问题。压测中发现,当集群QPS达到2000以上时,Telemetry实例的内存使用率会一路狂涨。
经过分析定位,发现Telemetry内存上涨的原因是数据通过各种后端Adapter消费的速率无法跟上Envoy上报的速率, 导致未被Adapter处理的数据快速积压在内存中。我们随即去除了Istio自带的并不实用的stdio日志搜集功能,这一问题随即得到极大缓解。幸运的是,随着Istio1.0的发布,Telemetry的内存数据积压问题得到解决,在相同的测试条件下,单个Telemetry实例至少能胜任3.5W QPS情况下的数据搜集上报。
问题、希望与未来
历经重重问题,一路走来,一个生产环境可用的ServiceMesh终于在UAEK环境上线了。在这一过程中,也有部门内其他团队受UAEK团队影响,开始学习Istio的理念并尝试在项目中使用Istio。然而,目前的现状离我们的初心依然存在差距。
Istio依然在高速迭代中,无论是Istio本身还是Envoy Proxy,每天都在演进更新。每一次版本更新,带来的都是更为强大的功能,更为简练的API定义,同时也带来了更复杂的部署架构。从0.7.1到0.8,全新的路由规则v1alpha3与之前的API完全不兼容,新的virtualservice与原先的routerule截然不同,给每位使用者构成了不少麻烦。
如何完全避免升级Istio给现网带来负影响,官方依然没有给出完美平滑的升级方案。此外,从0.8到1.0虽然各个组件的性能表现有显著提升,但从业内反馈来看,并没令所有人满意,Mixer的Check缓存机制究竟能多大程度缓解Policy的性能压力依然需要观察。
值得一提的是,我们发现的不少bug同时也在被社区其他开发者发现并逐一解决。令我们开心的是,UAEK团队不是信息孤岛,我们能感受到Istio官方社区正在努力高速迭代,始终在致力于解决广大开发者关心的种种问题,我们提交的issue能在数小时内被响应,这些,都让我们坚信,Istio是一个有潜力的项目,会向Kubernetes一样走向成功。
从UAEK接入用户的经验来看,用户需要正确地使用好Istio离不开前期深入的Istio文档学习。UAEK后续需致力于要简化这一过程,让用户能傻瓜化、界面化、随心所欲地定制自己的路由规则成为我们下一个愿景。
UAEK团队始终致力于改革UCloud内部研发流程,让研发提升效率,让运维不再苦恼,让所有人开心工作。除了继续完善ServiceMesh功能,下半年UAEK还会开放更多的地域和可用区,提供功能更丰富的控制台,发布自动化的代码管理打包持续集成(CI/CD)特性等等,敬请期待!
作者介绍
陈绥,UCloud资深研发工程师,先后负责监控系统、Serverless产品、PaaS平台ServiceMesh等开发,有丰富的分布式系统开发经验。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。