运维 Istio 在 UAEK 中的实践改造之路

ucloudcn · 2018年08月23日 · 6011 次阅读

为什么需要 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 完善。

整个服务网格分成控制面板和数据面两大部分。数据面指的就是注入到应用 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 等开发,有丰富的分布式系统开发经验。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号