小鹏汽车技术中台实践:云平台篇

今天我们不讨论该不该做技术中台,只说说中台给我们带来了什么。

小鹏汽车的 智能 离不开复杂系统的支撑,其特有的 互联网 基因要求业务能够应对市场的迅速变化:快速响应、快速试错、快速创新。同时,为了给客户提供优质服务,系统需要更高的可靠性,降本增效也是公司快速发展过程中特别关注的。技术中台正好契合了公司上述所有需求。

小鹏汽车的技术中台分为微服务中台和云平台两大部分,其中云平台为技术中台提供底层的基础支撑,基于主流的容器技术 Docker 和 Kubernetes 构建;微服务中台基于 SpringCloud 打造,提供微服务的管理、治理、监控、统一标准化配置。我们在上一篇文章《 小鹏汽车技术中台实践 :微服务篇 》中介绍了微服务平台的实践,本文将重点分享云平台的实践经验。

Havok 云简介

小鹏汽车的云平台(内部代号:Havok 云)基于 OpenShift 3.11 / Kubernetes 1.11,涵盖了容器化部署平台、日志中心、监控中心等基础设施。Havok 云的愿景是让开发、测试、运维对 Kubernetes 认知零门槛。本着降本增效的原则,Havok 云平台通过封装标准化业务部署单元,以效率和可用性为目标,提供生产级应用最佳实践,简化应用在 Kubernetes 的生命周期管理。最新版本的 Havok 云平台于 2019 年 6 月完成研发上线。目前接入到 Havok 云平台的几百个微服务业务应用 100% 使用 Havok 云的标准部署方式,所有应用均能享受微服务中台以及云平台提供的能力。

2019 年 10 月 17 日阿里云联合微软全球发布开放应用模型(OAM),该架构设计模型与目前 Havok 云核心的 Logan-App-Operator 的架构设计模型不谋而合,在应用层面屏蔽了 Kubernetes 的相关概念,采用 Operator + CRD 模式定义了应用以及运维能力。通过这样的架构模型,能用高效、标准的方式连接应用开发人员、运维人员、测试人员和应用的最终用户,分离不同用户的关注点,简单高效。

容器化部署平台

目前几乎所有云端应用都运行在 Havok 云上,大多数业务应用是 Java 应用,还有部分 Python、PHP、NodeJS 应用 。容器化部署平台提供用户友好的 UI、日志聚合、跨集群部署、历史修订版等能力,封装了标准的 Boot 概念,统一了开发、测试、运维术语。开发人员只需专注业务,通过 CICD 标准化构建就能将业务应用部署到云平台。

Logan-App-Operator

在较早期,为了让业务快速实现容器化部署,我们在每一个业务应用的项目源码中都加入了一个 openshift.yaml 的配置文件,该文件约 300 行,其实就是一些 Deployment、Service 之类的配置,冗长而且重复。底层架构的配置分散在业务应用中,每一次架构的升级都需要让业务配合着改配置然后发布版本。而一些充满好奇心的开发人员还会去试着改动配置参数,这就很尴尬了。前期项目数量少问题不明显,但是随着公司业务的高速发展,这个做法明显不能满足业务的需求。因此我们决定将这个文件去掉,将底层架构的升级都收敛在云平台上,从而有了 Logan-App-Operator 这个子项目。

Logan-App-Operator 通过封装标准化业务部署单元,以效率和可用性为目标,提供生产级应用最佳实践,简化应用在 Kubernetes 的生命周期管理。

  • 基于 operator-sdk ,通过 CRD 和 Controller 扩展 Kubernetes API;
  • 支持多种语言项目:Java、Python、PHP、Web/NodeJS;
  • 将部署架构经验封装到产品,屏蔽 Kubernetes 技术细节,以业务应用为粒度管理;
  • 封装标准 Boot 概念(应用视角):统一架构提升,标准化开发、测试、运维术语;
  • 动态注入内容:配置在 configmap,可全局调整,下一次部署生效(例如:sidecar、环境变量、资源配置、harbor 地址等)。

上图为 Logan-App-Operator 的架构流程,很标准的 Operator 做法:定义 CRD,结合统一配置并复用 Kubernetes 原生的能力,将应用资源最终映射到 Kubernetes 原生的资源上,从而实现应用的容器化部署。因为有 Operator 的统一应用配置管理,对于可用性、反亲和性的统一提升以及管理也就得以实现,不再需要让业务应用配合发布版本。原来冗长的 openshift.yaml 配置文件只剩下寥寥几行,将该应用部署的配置整合进 CI/CD 中,最终就实现了对整个 openshift.yaml 的回收。

复制代码

apiVersion:app.logancloud.com/v1
kind:JavaBoot
metadata:
name:demo-javaboot
spec:
image:"harbor/javaboot-sample"
version:"1.0"

除了应用部署之外,我们针对前文微服务中台的技术栈做了一些适配工作,让开发效率更高效。

  1. 为支持跨语言的微服务调用,微服务中台采用了 spring-cloud-netflix-sidecar 的 sidecar 模式,Logan-App-Operator 通过统一配置模板的方式让非 Java 应用可以动态注入 initcontainer、container 来实现 sidecar 模式。
  2. 微服务之间存在大量的服务依赖调用,而开发环境都在 Kubernetes 上,在网络方面对本地开发调用来说并不友好,因此在开发环境部署的服务都自动开启了 NodePort 以便本地开发调试服务。
  3. 针对 AI 类应用服务,提供共享 GPU 能力的支持,提高 GPU 的利用率。
  4. 提供 Deployment 和 Statefulset 负载类型支持,避免重复造轮子。
  5. Boot 粒度支持历史修订版回滚能力,友好的 UI 更方便查看 Boot 应用生命周期演进过程。
  6. Boot 粒度自动弹性伸缩 HPA 特性支持,可以更优雅地使用 HPA。
  7. Boot 存储 PVC 支持,支持应用挂载 NAS 存储。
  8. 跨集群 Boot 应用支持:多集群浏览 Boot 信息、应用配置多集群同步、应用故障自动部署迁移。
  9. 对 Boot 的重启支持。

Knative Serverless 的演进

据 Gartner 预测,到 2023 年,70% 的 AI 任务会通过容器、Serverless 等计算模型构建。Serverless 非常适合使用在按需付费、CICD、ML、区块链智能合约等场景。Knative 就是基于 Kubernetes 平台,用于构建、部署和管理现代 Serverless 工作负载的项目,也是我们一直关注的项目。通过比较发现,基于 Logan-App-Operator 可以非常容易地演进到 Knative Serverless。

复制代码

# Havok 云平台中标准的 JavaBoot 应用定义
apiVersion:app.logancloud.com/v1
kind:JavaBoot
metadata:
name:demo-javaboot
spec:
image:"harbor/javaboot-sample"
version:"1.0"

---
# 标准的 knative Service 定义
apiVersion:serving.knative.dev/v1
kind:Service
metadata:
name:demo-javaboot
spec:
template:
spec:
containers:
-image:harbor/javaboot-sample:1.0

两者的 CRD 定义非常相似,开发人员只需要指定应用名称、镜像和版本就可以完成应用的定义,通过 kubectl apply 即可完成应用的部署。应用同样支持 Revision 历史管理,又一个不谋而合。基于不要重复造轮子的原则,我们正在尝试将标准 Boot 定义映射到 Knative 的资源定义上,从而让业务应用无缝地迁移到 Serverless 计算模型上。在节省资源的同时,AI 和数据业务对灰度发布、AB Testing 的强烈需求将由 Knative 来满足。

GPU 调度的演进

目前社区里面的 GPU 应用大多数都是使用 nvidia-dockerk8s-device-plugin 来实现在 Kubernetes 上使用 GPU 资源,这也是 Nvidia 官方支持的方案。但是该方案目前不支持 Containers 共享同一块 GPU 卡。如果用取巧的环境变量方案来实现 GPU 共享,又会出现资源争抢以及不均衡的问题。对于 AI 类的在线推理场景,应用 独享 GPU 卡将会造成极大的资源浪费 。在我们面对的场景中,对 GPU 算力的需求和对显存的需求是成正比的,因此我们在阿里云的 gpushare-device-plugingpushare-scheduler-extender 基础上实现了多 Containers 共享多块 GPU 卡,即显存级别的多 GPU 资源调度,从而既满足了业务需求也提升了资源利用率。

当前我们实现的方案主要还是软隔离,需要君子协定来约束,在开发、测试、部署等过程中难免出现人为操作失误,因此还需要一个针对应用规范的 GPU 资源使用监控方案。GPU 的监控,除 GPU 设备自身的监控外,针对应用自身的使用情况,Nvidia 官方也有相关的开源解决方案 gpu-monitoring-tools 。但是该方案是完全基于 k8s-device-plugin ,即 GPU 为 Containers 独享情况下的监控,并不适合显存粒度的方案监控。因此我们将该项目作为 code bases,进一步细化到应用级别,实现了将应用申请的资源以及实际使用的资源作为监控指标上报到监控系统。

基于 Nvidia 的 MPS 技术可以实现 GPU 资源的超卖,进而做到高度共享 GPU,实现硬隔离,进一步提升 GPU 资源利用率。但是 MPS 技术需要采用 Nvidia Volta 架构 Tesla V100 类型的卡,硬件成本较高,不太适合当前的发展阶段,但这仍然是我们在关注的方向。

Kubeflow 的演进

Kubeflow 是 Kubernetes 的机器学习工具包,是运行在 Kubernetes 之上的一套技术栈。但以目前 Kubeflow 发展所处的阶段还很难直接将其打造为一个多租户平台化体系化的机器学习平台。 幸好 Kubeflow 这套技术栈包含了很多组件,组件之间的关系比较松散,我们可以配合起来用,也可以单独用其中的一部分。

我们基于 Kubeflow 和已有的 Havok 云打造了一套多租户的机器学习流程平台,更多细节分享敬请期待后续的文章。

实时跨机房日志聚合

为帮助开发人员快速排查应用问题,日志查看是必不可少的手段。基于 EFK 的日志中心更适合在事后排查问题,而对于开发阶段或线上问题现场排查,类似 tail 方式的日志查看更为合适。Kubernetes 原生的日志查看只能针对单个 pod 的单个 container,无法满足实时排查问题时,单个应用多个 pod 多个 container 的情况。甚至有部分应用已经实现了跨机房的部署,就更加无法满足实时查看多个机房应用日志的需求了。

基于降本增效的考虑,为解决该需求,我们借力于开源社区,组合了多个开源组件实现该方案。

  • https://github.com/boz/kail 可以聚合单个 Kubernetes 集群的所有 container 实时日志,支持多种方式筛选 pod。但是该项目本身没有抽象成一个可以扩展的类库,对于扩展的支持较差。目前我们将该类库作为 code base,进行项目所需要的二次开发,另外还增加了 metrics 信息暴露。
  • https://github.com/boz/kcache 类似 K8s.io/client-go/tools/cache 的 Kubernetes 对象缓存库,提供事件驱动机制,可以通过订阅方式捕获对象的增删改事件。 Kail 利用该库来实现对 pod 增删改事件的捕获,毕竟在启动日志查看后 pod 增删改也是常见的事情,无需重新启动也是极好的体验。
  • https://github.com/gorilla/websocket Golang 社区比较著名的 WebSocket 库,大量的项目都采用该库来实现 WebSocket,OpenShift WebConsole 也是采用该库实现的 WebSocket Proxy,虽然性能不是最好的,但是应用最广泛。

有了以上的配件,让汽车跑起来就变得很容易了。给 Kail 加上 WebSocket,再加上多机房连接多租户服务,实时跨机房日志聚合就完成了。同时,为了让日志查看更加方便,我们还增加了关键字过滤、下载等功能。

统一用户体系集成

整个技术中台存在大量的第三方组件,某些组件没有用户体系例如 Zipkin、Prometheus,某些组件拥有独立的用户体系例如 Grafana、Jenkins 等,某些组件通过 OauthProxy 集成在 Openshift 的账号体系里面例如 Kibana、Havok WebConsole。研发人员每天都在频繁使用这些系统提高研发效率。怎样基于自有的用户体系将各个开源系统的用户体系以及权限整合在一起给研发人员带来一站式的开发体验就成了一个问题。

因此我们专门开发了一个 Admin-Svr 组件,用于和各个开源或自研的系统集成。

  • 无用户体系:无用户体系的系统一加上 HTTP Basic authentication,由 Admin-Svr 完成自动向后端提交 Authorization Header。
  • 自有用户体系:建立系统之间用户、角色、资源的映射关系,由 Admin-Svr 完成对开源系统的模拟登录获取到对应的 Token,建立用户联系。
  • Openshift Oauth Proxy:构建在 Openshift Oauth Proxy 之下的系统相当于自有用户体系中的特例,均对接到了 Openshift 的用户体系,即 Kubernetes 的 RBAC。依赖 Openshift Oauth Client 来完成模拟登录,建立用户联系。

除了完成与多方系统的对接之外,其还具备如下特性:

  • 集成方案对第三方系统零入侵,可跟随第三方系统做版本升级、修复漏洞等系统演进。
  • 配合 OpenResty,零入侵魔改第三方系统 UI 呈现方式,带给用户高级定制体验。
  • 与自服务体系整合,新建用户,新建项目,自动创建第三方系统相关资源。
  • Cookies 超时同步、关联系统自动登出等等。

小结

Havok 云容器化部署平台目前仍然在演进当中。以 Boot 为粒度,按节奏丰富业务应用在 Kubernetes 上的最佳实践。

  • Boot 粒度支持混沌测试,基于 Chaos Mesh 已经在测试环境中落地,敬请期待我们后续在混沌工程方向的分享;
  • Boot 粒度自动弹性伸缩:VPA、CA;
  • Boot 粒度调试工具注入;
  • 更多 Operator:Namespace 自服务、中间件(Redis 等)。

日志中心

OpenShift 本身带了一套很基础的 EFK 架构的日志中心方案,为快速实现满足业务需求,我们的日志中心也以此为基础开展。日志中心通过 Fluentd 收集应用程序日志到 Elasticsearch,提供 Kibana 的可视化 UI 方便相关人员快速通过日志排查问题;同时也作为日志数据分发的汇聚点,通过 Kafka、Logstash 等组件将日志分发到阿里云的 ODPS 和实时计算平台。

系统架构

日志中心主要包含 Fluentd、Elasticsearch、Kibana、Kafka、Logstash。和网络上能找到的基于 Kubernetes 的 EFK 架构差不多,毕竟搭建 Demo 跑通流程容易,但是怎么能和原有体系无缝整合并且能随着架构一起演进会更加重要,这里主要说一些不同点。

  1. 接入微服务中台的应用统一遵循中台日志规范,按统一格式输出到标准输出 / 错误。通过 Docker 的 json-file driver 统一落盘到 Node(Kubernetes 的工作节点)上。注意 Docker 日志的保留时间一定要长一些,避免故障或维护等情况下的日志数据丢失。
  2. Fluentd 以 DaemonSet 方式运行在 Node 节点上,挂载日志目录对数据进行采集,通过 fluent-plugin-viaq-data-model 解析出 container 对应的 namespace 以及 Elasticsearch 索引时间前缀等元数据信息,将整体数据同时分发给 Elasticsearch 和 Kafka。Fluentd 缓冲区的 buffer 也需要长一些,避免后端存储故障或维护的情况下的日志数据丢失。
  3. Elasticsearch 按 namespace 和天的粒度存储日志到索引中。基于性能考虑我们利用 NodeSelector 来使用 Node 节点本地的 SSD 数据盘,经过系统性的优化大多数的查询响应都能达到毫秒级别。另外基于成本考虑目前仅存储 7 天的数据,毕竟好些业务应用一天增量就几百 GB,成本是不得不考虑的因素。
  4. 最重要的还是 ACL 权限的能力 ,毕竟 Elasticsearch 企业版版本才提供收费的第三方的 ACL 权限整合功能。但是开源才是王道,借力开源社区,Elasticsearch 整合 SearchGuard 提供 ACL 基础能力,利用 SearchGuard 开启 HTTPS 证书验证,Fluentd 以及 Prometheus Exporter 均基于证书验证与 Elasticsearch 交互,确保系统安全。系统之间的解决了,用户的呢?因为我们整个用户体系都是基于 Kubernetes 的 RBAC,为提供顺滑的用户体验,用户访问 Kibana 查询日志也需要在这个权限体系下完成。因此我们利用 OpenShift-Elasticsearch-Plugin 将 Kubernetes RBAC 的身份认证和权限等信息同步到 SearchGuard 的 ACL 体系中,再由 Kibana 利用 Oauth-Proxy 串通 Elasticsearch 身份认证。同时为了让用户体验更加流畅,在 Kibana 中自动生成项目的 Index Pattern,不同项目组仅能查看项目组内的数据。

跨机房搜索

为了让开发人员在使用日志中心时能更方便地查询到多个集群的日志数据,我们调研了 Elasticsearch 跨机房搜索的多种方案。

1)Cross-cluster Search

基于 Elasticsearch 官方提供的 Cross-cluster Search 特性即可实现跨机房搜索,而且该方案还能和我们的 ACL 方案无缝整合。但是我们目前的 Elasticsearch 版本为 v5.6,还不支持该特性,该方案只能放弃。随着系统的演进,我们希望后续能将 Elasticsearch 版本升上去,从而采用该特性来实现跨机房的搜索。借力开源社区,保持对开源技术的敏感度是技术团队不变的品质。

2) 多机房分发

从采集的源头上由 Fluentd 将数据分发一份到另外一个机房,或者是由 Kafka 利用镜像模式同步一份数据到另外一个机房。这绝对是理论上可行的方案,但是需要双倍的跨机房流量、双倍的存储和计算资源,如若再增加第三个机房则资源又要再翻一翻。难道要搞多个 TB 级别的而且有备份功能的日志中心?每天 TB 级别增量,从成本角度基本放弃该方案,仅作为兜底方案考虑。

3)Tribe Node

经过一翻调研后,我们发现 Elasticsearch v5.6 版本仍然保留了 Tribe Node 特性,只需要两边集群都增加一个 TribeNode,就能够以较低的成本实现在单个集群的 Kibana 中查询多个 Elasticsearch 集群,并且该方案与我们的 ACL 方案仍然能无缝整合。为了进一步节省跨机房流量,我们对 OpenShift-Elasticsearch-plugin 做了二次开发,默认将 Kibana 的 Index Pattern 设置为本机房的 Pattern。

优化工作

1) Fluentd 优化

前期 Fluentd 数据分发到大数据平台是采用 Http Plugin 的方式发送给 Logstash,但是我们发现数据发送经常延时。我们对 Logstash 进行压测发现,Logstash 的 Http Input 表现不错,能抗下每天 TB 级别的流量压力。后来又分析了 Fluentd 的 Http Plugin,发现该 Plugin 既不提供数据批量发送的配置,也没有连接池特性,每发送一条数据就会发起一次 HTTP 请求。随着数据量增长,会出现明显的瓶颈,最终造成数据发送延迟。

在这个问题上,我们曾经考虑过是否能利用 Fluentd 的多进程机制提高这个吞吐量,但是实验后发现效果并不理想。因此将该流程修改为发送到 Kafka 作为数据的缓冲区,以 Kafka 的吞吐量和扩展性足以支撑数据量增长带来的挑战。

后期我们还计划进一步优化链路,将 Fluentd 到 Elasticsearch 的链路修改为 Fluentd->Kafka->Logstash->Elasticsearch 这样的链路,避免在 Kubernetes 集群 Node 数量进一步扩大后 Elasticsearch 成为瓶颈导致出现丢数据的风险。这也是 Elasticsearch 社区比较推荐的一种优化链路。

2) OpenShift-elasticsearch-plugin 插件优化

OpenShift-elasticsearch-plugin 插件出于数据一致性的考虑,在社区升级到 5.x 版本的 Elasticsearch 时,社区将 ACL 信息缓存的功能去掉了,修改为每一个查询请求都通过 Kubernetes API 获取用户的 ACL 信息同步到 SearchGuard 做身份和权限认证。这无形中会对 Kubernetes 的 API-Server 造成不必要的压力,同时也会拖慢整个搜索查询流程。

在我们的实践当中,去掉缓存会让搜索耗时上升到 5-10S,大量的时间都消耗在和 Kubernetes 的交互以及 SearchGuard 的 ACL 数据写入上。从我们目前的阶段来看,ACL 信息在短时间内的不一致其实都是可以接受的。首先这是一个对内的日志中心,并非 TO C 的产品,ACL 信息的变更本身就不会很频繁,并不要求严格的 ACL 数据一致性,能达到最终一致性即可。再次因为整个技术中台的体系庞大,自服务体系也已经成熟,用户权限的变更早已整合到自服务体系中,也就能更快地通知到日志中心。

因此我们在社区最新版本的基础上做了优化,将 ACL 信息定期从 Kubernetes API-Server 同步到 SearchGuard 的 ACL。这么一来,每一次查询都只是在 Elasticsearch 内部完成相关的校验逻辑,避免了冗长的系统交互流程,Elasticsearch 又愉快地进入了毫秒级响应的时代。

具体和社区的交流可查看:

监控中心

系统架构

如上图所示,监控中心主要包括集群监控和业务监控两部分:

  • 集群监控:基于 cluster-monitor-operator,统一管理监控组件和资源。主要监控 apiserver、kube-controllers、etcd、kube-state-metrics、node-exporter 等
  • 业务监控:基于 prometheus-operator,使用简单的声明式配置创建和管理 Prometheus 实例、告警规则等。主要监控各类业务应用及中间件,并通过联邦的方式聚合集群监控的指标数据,便于多维度业务监控及可视化管理。

随着业务的快速增长及集群规模的不断扩大,我们正考虑结合 Thanos 开源项目,实现 Prometheus 实例的分布式动态扩展,跟踪日益增长的时间序列数据及更复杂的环境。

告警自服务

基于 prometheus-operator 的动态资源对象更新机制,我们开发了监控告警的自服务,该系统解决如下问题:

  • 应用开发者(以下称为 Developer)可直接通过 Portal 自助完成 Alert 规则的生命周期管理,不再依赖于管理员统一配置,大幅提高业务应用上线监控效率;
  • Developer 可在所管辖的应用中快速复用管理员提供的公共规则,并根据实际需求调整告警阈值;
  • 通过增加权限隔离,Developer 配置的规则只对所管理的应用生效,不会干预到其他应用或规则;
  • 提供表达式的合法性校验及 Dry-run 机制,可在线体验告警样式。

平台系统

子系统 自服务中的功能内容
Prometheus 由于 Prometheus Operator 允许通过 CRD 对象(PrometheusRule)来管理告警规则,且规则变化能被动态加载进 Prometheus。Alert 产生后发送到 Alertmanager,然后由 Alertmanager 调用运维告警系统的 WebHook 来完成告警发送。
Logan Admin Server 作为 Portal 的后台服务,保存 Developer 编辑的规则数据,并同步至 Prometheus。同时实现将项目的告警方式和接收人同步到运维告警系统。
Logan Portal 在现有自服务项目列表中,Developer 选择某个项目可以进行告警规则管理,与 logan-admin-svr 进行交互来实现相关功能。

运维告警系统

运维告警系统记录自服务的项目告警人配置,给每个 bootName 和 namespace 根据 level(优先级)记录了对应的告警方式、接收人。一旦告警生成,该系统根据告警信息中的 bootName、namespace、level 等标签来确定告警方式和接收人,从而发送告警。

优化工作

1)Prometheus 配置更新不生效

在 prometheus-operator 中,通常使用 service-monitoradditionalScrapeConfigs 配置监控 targets 等,针对一些个性化的配置,我们会在 Kubernetes 中直接修改 secret 来更新 prometheus.yaml 配置,但出现修改后不生效的现象。

问题原因:由于直接修改 secret 后,导致配置文件的 inode 发生了变化,watcher 无法再接收到更新事件,因此不会进行 reload 响应。

解决方案:inode 变化时,watcher 将接收到一个’op=CHMOD’事件,在该事件中判断 inode 是否变化,如已变更,则将新配置文件增加到 watcher 中,使之生效。

2)Jenkins 监控数据项不够丰富且采集不准确

只有构建总次数,缺乏成功、失败次数的指标,无法跟踪业务项目的代码质量情况。同时统计各业务 CICD 次数时发现应用有编译但数据却未变化。

问题原因:由于应用数非常多,每天大量编译,每次编译都会生成一条 job 记录,为避免 job 记录历史数据过多,我们设定了一个月的保存期限,而 jenkins-prometheus-plugin 在收集 metrics 时,根据 job 记录进行统计,这就导致了历史数据的 metrics 不准确。同时,此方式也存在性能问题。

解决方案:修改 jenkinsci/prometheus-plugin ,增加成功、失败次数统计指标项,参见 pr#75 。针对统计值不准确的问题,在内存中增加 map 保留各应用的 jobCount(当前统计值)及 latestJobNum(最近一次的 job 记录编号),每次收集时,从各应用的最新 job 开始,若 jobNum(当前 job 记录编号)比 latestJobNum 大,则 jobCount 值加 1,并更新 latestJobNum,然后取下一个 job,直到没有新 job 时退出。

3)集群监控资源及告警规则修改不生效

由于集群监控组件自带的部分告警规则有错误,例如: alert:PrometheusTSDBWALCorruptions 中指标 tsdb_wal_corruptions_total 应该为 prometheus_tsdb_wal_corruptions_totalalert:PrometheusTSDBReloadsFailing 的告警描述与配置不一致等。另外,我们针对 Grafana 做过定制开发,需修改 Grafana 的镜像版本。而直接修改这些资源对象后,会自动被恢复。

问题原因:集群监控的资源及配置由 cluster-monitor-operator 生成并控制,资源对象硬编码在 cluster-monitor-operator 中(例如 Grafana 的镜像版本等),无法通过修改资源对象以更新其配置。

解决方案:关停 cluster-monitor-operator ,使不再控制及恢复相关资源的修改,prometheus-server 不受影响,依然由 prometheus-operator 进行控制。

4)跨集群指标怎么办

在业务监控中,部分 alertrule 依赖于集群监控的指标,而这些指标数据是跨集群跨数据中心的,可以通过 prometheus-federation 将需要使用到的监控数据聚合到业务监控,从而进行快速计算。

Kubernetes 版本升级

基于 OpenShift 的 Havok 云,底层使用 Kubernetes 作为容器编排组件。Kubernetes 社区火热,每 3 个月会发布一个 Release 版本,当前已更新到 1.18;同时 OpenShift4 开始支持 Service Mesh、Serverless,这也是我们一直关注和持续跟进的项目,当前最新版为 v4.4。由于线上业务繁多,环境复杂,同时对可用性要求非常高,频繁的集群版本升级不是我们所期望的演进方式。我们选择紧跟新版本特性,根据不同阶段的业务发展需求,适时对 Kubernetes 版本进行升级,以下是我们的一次升级实践。

背景

Havok 云早期基于 OpenShift 3.9,对应 Kubernetes 的版本为 1.9.1,随着小鹏汽车业务快速发展,Havok 云容器化部署平台无法复用新特性及基于新特性的开发,例如:基于 CRD subresource 等特性开发 Boot 应用的 HPA,当时 OpenShift 最新 Release 版为 3.11,能满足业务迭代需求。同时,为进一步提升平台支撑能力,提高系统的可靠性、安全性、易用性,保持与社区同步,同时借力开源社区,避免重复造轮子,我们启动了 Kubernetes 版本升级工作。

升级方案

云平台支撑了小鹏汽车技术中台的整个微服务体系,包括注册中心、API 网关、日志中心、监控中心、配置中心、调度平台等,覆盖了互联网及信息化 90% 以上的后端项目,因此升级方案必须保证服务的可用性,同时兼顾成本。

1)重建集群

第一个可选的升级方案是基于新版本重新初始化一个集群,然后再逐步迁移各业务应用。这是最保守但相对有保障的方式,相当于把之前做过的工作再做一遍;但缺点也很明显,一方面需要大量采购新机器进行集群初始化,同时重新部署并配置技术中台的几十个组件,再逐个与各业务研发侧对接,进行服务迁移,整个过程成本高、周期长。

2)蓝绿部署

另一个升级方案是蓝绿部署。蓝绿部署方式须先升级 Master 和 Etcd 服务器,然后通过加入新节点创建并行环境,待验证完新的部署后,将流量从旧的节点(蓝色部署)切换到新的节点(绿色部署),然后下线旧节点。此过程中,如果检测到问题,可快速回退。缺点是需采购新机器作为 buffer,另外流程复杂性高,涉及的过程包括:1)初始化加入新节点;2)将旧节点上的应用驱逐到新节点;3)下线旧节点;4)将旧节点重新初始化并重复 1~3 步。

3)就地升级

第三个可选升级方案是就地升级。集群升级将在单个集群中的所有主机上执行,和蓝绿部署一样,须先升级控制平面,然后是数据平面,在节点升级开始之前,把 pods 从节点中驱逐出去,并在其他正在运行的节点上重新创建。这种方式直接基于原环境进行,无需添加冗余节点,不涉及应用迁移,因此成本低、速度快,但伴随的是风险高,相当于给高速运行中的汽车换引擎;另外由于无法跨版本进行,只能逐个版本升级,如果出现问题,回退很困难。

综合上述方案,由于成本太高,首先排除集群重建方式,针对蓝绿部署和就地升级方式,两者的差异主要在 node 节点,而 node 节点升级不涉及全局影响,风险可控,为降低采购新节点的成本,避免复杂的升级流程,最终选择采取就地升级的方式。

升级过程

1)可用性

可用性对于车企来讲尤为重要。线上环境的升级,首先必须确保业务的可用性。我们使用官方推荐的先驱逐 pod 再升级 kubelete 的方式,此过程会使得每个应用都重启一遍,为避免期间应用中断,我们要求每个业务应用必须 scale 到 2 个以上的 pod,同时反亲和性生效,即每个业务的多个 pod 在不同的节点上。另外,提前定义升级队列,从每个业务节点组抽取 1 个节点到待升级队列,避免同组业务节点挨近升级而导致可能出现的同一个业务的 2 个 pod 都未就绪的问题,同时该方式有效扩大并发,一次性升级整个队列,提高升级效率。增加自动验证过程,在队列升级完后,检查所有业务应用的可用实例数与升级前一致,然后再进行下一个队列的升级,该过程同样是为保证每个业务至少有一个 pod 是可用状态。

2)效率提升

本次升级跨越 2 个版本,由于架构变化很大,不支持跨版本升级,意味着同样的过程需要做两次,时间也至少是双倍,为提升效率,我们对官方升级方案中的 Task 进行拆解,将在升级过程中针对每个待升级节点的前置准备或检查等独立抽出来,统一在升级前做好,例如:更新 package 原数据,刷新缓存等,避免节点升级时串行阻塞。将耗时长且受环境因素影响存在未知风险的步骤提前验证并处理,例如:升级 Master 时的迁移 API 对象。适当加大个别步骤的重试次数,例如:节点重启后 Ready 状态检查,控制平面镜像拉取等,避免因网络或机器性能原因导致的重试超限而使得整个任务失败重新再跑一次的错误。针对技术中台的一些能够版本兼容的组件,例如日志收集、监控告警等,在主流程升级完后,再单独进行升级,避免整个过程拉的很长。

3)快速回退及恢复

开发回退脚本和一键备份及还原程序,保证每一个向前的步骤都是可以回退的,即使整个集群升级失败,也是可以快速还原的。

Kubernetes 在快速迭代中,这次的经验也为我们下一次的升级演进打下了坚实的基础。

总结

云平台作为整个基础设施的底座,能有效地促进各种资源整合,实现资源共享,提高资源的利用率。随着云平台的演进,我们将跟随并借力开源社区促进 Service Mesh、Serverless、Edge Computing、Chaos Engineering 等技术在小鹏汽车的落地与实践。敬请期待我们后续其他方面的分享。

“生命不止,奋斗不息”,小鹏汽车的技术中台还将持续演进。

作者介绍:

谭恒亮:YAML 程序员,曾在欢聚时代等公司任职。目前就职小鹏汽车,在基础架构团队从事技术中台的研发。

蒋学军:曾在亚信科技等公司任职。目前就职小鹏汽车,在基础架构团队从事技术中台的研发。

延伸阅读: 《小鹏汽车技术中台实践 :微服务篇》