TAD-云原生时代的应用定义

| 导语 一篇关于云原生面向终态的声明式应用生命周期管理的介绍

前言

在 Kubernetes 成为容器编排事实标准的云原生时代,无数开源或者闭源项目,如众星捧月般围绕着它,建立各种生态,涵盖着集群管理,资源管理、平台管理、可观测性领域的运维管理等诸多领域,其中很多优秀项目都如雨后春笋似地出现。然而,作为这个编排系统的终端使用者的应用领域,却似乎是受到了冷落。本文将以应用的第一视角,对腾讯自研的云原生应用定义Tencent Application Definition(简称TAD)进行介绍,简要说明腾讯研发团队基于 K8s 之上的应用层面做的一些探索。

背景

在介绍 TAD 之前,我们需要先介绍一下 TCS。TCS 是腾讯云推出的一款 PaaS 平台产品,可以作为腾讯专有云(TCE)的底座,也可以做为私有化 SaaS 的底座,亦可以作为一款 PaaS 类产品独立输出。在这个基于 K8s(TKE)构建的 PaaS 平台上,如何解决用户零 K8s 基础,从无到有,快速部署应用并管理其监控、日志、服务注册在内生命周期,成为一个关键课题。基于 K8s 功能特性和腾讯自研业务接入的强烈需求,TAD 的研发工作迫在眉睫应运而生。

TAD ,全称 Tencent Application Definition,是 TCS 平台上的应用定义标准,是面向企业级的应用编排解决方案。TAD 作为连接云产品 SaaS 产品和 TCS 平台之间的桥梁,在应用侧承接云产品和 SaaS 产品复杂的部署流程(大量应用间的编排依赖,服务注册与服务发现,跨 AZ 容灾等)和异构的应用形态(容器,虚拟机应用,有状态服务), 面向底层基础架构释放 TCS 强大的平台能力(一体化的日志、监控告警、多租户) 和云原生基础设施带来的应用架构革命(弹性资源,服务网格), 在兼容 Kubernetes 原生与社区开源能力的同时, 也尽量降低 Kubernetes 带来的额外使用复杂性。

原理

TAD 利用 Kubernetes 的 CRD(自定义资源对象)能力,创建了一个新的资源类型 -Application。该资源类型的 Spec(功能定义)包含了业务应用自身部署用到的原生 workload(如 deployment,statefulset,daemonset 等)和自定义 workload(如statefulsetPlus),业务应用依赖的自研中间件服务 MiddlewareWorkload(Kafka,MariaDB 等) 依赖关系,以及运维相关的 trait (如服务发现)等。

以下一个简单的例子可以大致理解 Application 的定义:

代码语言:javascript
复制
apiVersion: infra.tce.io/v1
kind: Application
metadata:
  name: nginx
spec:
  components:
    - name: nginx
      type: deployment # TAD内置工作负载能力的类型名称,用户不需要了解内部细节,只需要按照文档或样例填写 name 和 args 参数值即可
      args:
        image: nginx:latest
        volumes:
          configMap:
            - name: middleware-config  # 将挂载的 ConfigMap 名称
              mountPath: /data # 挂载到容器中的路径
              items:
                - key: config.yaml # 将挂载的 ConfigMap data
                  # path: config.yaml # 如果不声明 path,将直接使用 key 作为 path
      traits:
        - type: service # TAD内置运维能力的类型名称,用户不需要了解内部细节,只需要按照文档或样例填写args 参数值即可(每一个组件中,同一类运维能力只允许使用一次)
          args:
            register:   # 用于注册服务
              id: svcid-1
              port: 8081
              host: svc.tce.io 
            services:
              - name: nginx-svc-1
                ports:
                  - port: 8081
              - name: nginx-svc-2
                ports:
                  - port: 8082
      deps: # TAD 依赖管理能力
        - config: nginx-config # 指明该组件依赖的 Config,只有当该 Config .status.phase==Ready 时才会 Apply 该组件
        - application: other-app # 指明该组件依赖的其他 Application,只有当该 Application status.phase == succeeded 时才会 Apply 该组件
        - service: svcId-from-other-comp # 指明该组件依赖一个全局可用的 ServiceId(基于 TCS/Pajero 服务注册发现能力)
    - name: demo-kafka
      type: middleware # TAD内置工作负载能力的类型名称,middleware 工作负载专门用于创建中间件服务
      args:
        class: kafka
        # serviceId: my-kafka-id # 如果此处没有声明 ServiceId,TAD 将自动生成一个 SvcId,规则:{component.name}-{app.name}
        # 例如,注释掉上面一行,该 kafka 服务将使用自动生成的全局 ServiceId:demo-kafka-nginx
        capacity:
          cpu: "4"
          memory: 8Gi
          storage: 200Gi
    - name: demo-maria
      type: middleware
      args:
        class: mariadb
        ServiceId: my-mariadb-id # 该 maria 服务将使用全局 ServiceId:my-mariadb-id
        capacity:
          cpu: "4"
          memory: 8Gi
          storage: 200Gi

一个应用对应一个完整的声明文件,就可以把它在 K8s 上所需要的各种零散资源对象一口气定义清楚。虽然看似简单的一个小集成,却可以让业务应用在 K8s 平台上化繁为简,只用关注几个字段的输入,即可享受 K8s 部署分布式应用的各种便利。

TAD 在演进过程中,一直坚持 3 个原则:标准、扩展、开放。

标准:应用声明和定义自研,兼容业界的 OAM 标准,更便于产品化输出和广泛使用。

扩展:组件管理能力和应用运维能力 out-of-tree 独立开发,更便于协作和快速迭代需求。

开放:任何开发者都可以按标准开发,贡献和分享组件管理和运维能力控制器及定义。

核心功能

TAD 应用管理平台对 TAD 应用全生命周期下的管理能力,它最大的特点是屏蔽应用下辖的 K8s 资源细节。

如前文样例应用展示,一个 TAD 应用由一个或者多个组件(Component)数组构成,每个组件由一个工作负载能力、任意数量的运维能力以及任意数量的依赖配置构成。

因此我们定义:

应用(Application) = ∑ 组件(Component)+ ∑ 运维特征(Traits)!

工作负载是每个组件的核心,下表简要列举其中的一些常用workload。

工作负载能力类型名称

描述

底层 K8s 资源

deployment

用于持续运行的无状态的容器化应用的工作负载。

Deployment k8s.apps/v1

statefulset

用于持续运行的有状态的容器化应用的工作负载。

Statefulset k8s.apps/v1

middleware

用于创建 TCS/PaaS (中间件服务)的工作负载,每个中间件工作负载将自动(或手动)生成提供一个全局可用 ServiceId 供访问使用。目前支持的中间件服务详见 middleware 工作负载参数说明。

MiddlewareWorkload, ServcieInstance, ServiceBinding, ServicePlan, etc infra.tce.io/v1

job

用于一次性运行的任务型容器化应用的工作负载

Job k8s.apps/v1

statefulsetplus

TCS 定制化 Statefulset

StatefulsetPlus infra.tce.io/v1

运维能力是指围绕工作负载创建的运维特征,包括但不限于:服务暴露、服务注册、流量管控、sidecar 注入、日志采集、监控等等。如下表的简介:

运维能力类型名称

描述

底层 K8s 资源

service

基于 TCS 自研的服务注册与发现能力(pajero),用于暴露 & 注册(可选)工作负载提供的服务,通过自动(或手动)生成一个全局可用的 ServiceId 供访问使用。

ServiceTrait infra.tce.io/v1

polaris

基于腾讯开源的北极星服务治理能力,用于暴露 & 注册工作负载提供服务,用户必须指定注册到北极星中的服务实例名称。

Service k8s.core/v1

产品化

在对接 SaaS 服务私有化输出和支持云产品接入的过程中,TAD 基于这些腾讯专有云的场景积累了大量的生产级复杂应用的编排和治理的实践,TAD 团队希望这些能力同样可以给 TCS 的客户带来价值,帮助客户更好地在 TCS 平台上进行业务云原生化改造。同时我们选择了产品化的方式,将专有云中应用编排与管理的能力沉淀到 TCS 的一款白屏化云产品 --- 应用中心。

应用中心的核心能力分为应用的定义(模板管理)和发布运维(应用管理)两个部分:

多种异构工作负载

设置组件的工作负载,可选的工作负载包括容器、有状态中间件和非容器工作负载(即将支持):

  • 容器工作负载:基于 Kubernetes 封装的各类容器工作负载,支持 Deployment、Statefulset 等原生类型,以及 StatefulsetPlus 这样的扩展类型工作负载。
  • 中间件工作负载:经过腾讯专有云支撑服务预封装的各类工作负载,如数据库、缓存、消息队列、存储、网络等多种工作负载。
  • 非容器工作负载:基于虚拟机或者裸金属的工作负载,这些工作负载可以和 POD 中的服务一样,具备 TAD 的各种运维能力。

设置工作负载的详细信息, 例如容器工作负载的镜像地址、环境变量、网络、数据卷等。MySQL 工作负载的数据库名称、用户名称等。熟悉 Kubernetes 的同学 都知道 Kubernetes 中容器的配置非常复杂,于是将容器配置做了划分为:容器配置、数据卷、网络设置、调度策略、更多(监控日志) 这五个步骤分布处理。 如果用户的容器配置足够简单,也能简化填写过程。

应用间的编排依赖

在生产实践中我们发现,一个复杂的业务系统中应用之间往往会存在错综复杂的依赖关系。例如业务容器运行需要使用数据库和缓存服务,那么应用部署时应该优先部署数据库和 redis 组件,当数据库和 Redis 部署就绪后渲染出包含数据库访问凭证的配置信息,此时才能进行业务容器的部署。 同样业务服务之间的也会存在依赖关系,如服务 A 作为服务 B 的访问下游, 服务 B 需要等待服务 A 部署完成,并且完成域名注册后才能进行部署。

在 TAD 可以通过依赖配置声明应用内组件与跨应用组件的依赖关系,并在界面上通过一个有向无环图实现依赖关系的可视化, 由于有向无环图的不闭合特性,同样可以保证组件之间不会出现循环依赖。

开放兼容

为了兼容社区标准,用户在应用中心中制作的所有模板都能够以 Helm Chart 形式导入与导出,模板格式和平台解耦,做到一次定义到处部署。 用户除了在应用中心自制的 TAD 应用模板,也可以上传第三方或自己编写的 Helm Chart 应用,并在 TAD 应用中心里无缝运行。

在 TAD 的应用市场里也内置了部分开箱即用的开源应用,同时用户也可以将自己制作的组件发布到应用市场, 实现租户之间的共享。

应用生命周期管理

TAD 支持了应用完整的生命周期管理,从应用的发布,到持续更新运维。完整的生命周期包括以下:

  • 可观测性(应用状态、监控、日志、报警)。
  • 持续发布 (应用分批、灰度发布、原地升级)。
  • 配置管理 (动态配置信息、版本化分发、配置灰度发布)。
  • 应用编排 (依赖编排、分组编排、容灾调度)。
  • 资源弹性 (HPA / VPA)。

分批发布

持续发布是应用必不可少的部分, TAD 应用中心提供自动分批、指定步骤分批、和手动分批三种分批发布的模式,并和 Ingress、Istio 等上层的网关做了深度的权重集成。

  • 手动发布模式,用户可以手动调整发布的副本数和流量权重,一旦有问题随时可回滚,对适合与对稳定性要求高和操作敏感的用户,同时手动发布模式也可以作为底层能力给用户构建自己的发布策略。
  • 分批模式里, 用户可以设置 MaxSurge和MaxUnavailable 定义每批次的发布副本数量,同时定义发布间隔, 每批次的权重比例。发布过程中可以随时暂停发布和继续。
  • 指定步骤分批模式,用户可以更细粒度地指定发布过程中每一步执行的操作,包括副本数、权重比、暂停时间、增加人工确认等。

此外应用中心即将支持容器的原地升级, 适用于有状态应用的场景,在更新时无需重建 Pod 而是直接更新容器信息,从而避免重复进入调度和 Volume 挂载等流程,提高发布的变更效率。

版本化的配置管理

在现代应用的 12 条原则 (The Twelve-Factor App ) 中第三条就提到,应用的配置应该储存在环境中而非应用里(Store config in the environment), 应用配置通常包括外部服务的配置、第三方服务的访问信息、每次部署特有的环境信息(如域名等)。

Kubernetes 中虽然提供 ConfigMap 和 Secret 对象用于描述应用的配置信息, 但在实际生产实践中,Kubernetes 的原生能力远不能满足应用对配置文件的需求:

  • 外部或中间件服务的访问凭证,根据中间件实例的相关信息生成动态内容,例如 MySQL 的访问地址和账号密码。
  • 基于不同环境,不同客户需求/环境规划动态渲染的配置信息,例如包含域名地址、服务的规格等。
  • 当配置的内容更新后, 灰度升级使用了该配置的业务应用。

于是在 TAD 中,基于专有云场景的需求扩展配置资源,TAD 提供一个新的 Config 资源, Config 资源既能声明静态的配置文件,也支持生命动态的渲染模板,并基于内部的服务发现渲染出真实的配置内容,并生成相关资源(ConfigMap/Secret)。

同时对 Config 的内容做了版本化的记录和管理,用户可以为业务设置 Config 的固定版本,也可以选择一直使用最新版本。 版本信息变化,由用户决定是否应用触发灰度/手动更新。

客户场景

  • TCE/TCS 的云产品:CRedis、TDSQL、TSF、Coding 等。
  • SaaS 产品:医疗防疫通、视频云。目前已支持若干省市的防疫通应用部署。
  • 内部支撑: 公有云 CVM、VPC 等 IaaS 云产品云原生化交付底座。

后续规划

多集群管理

越来越多的公司在生产环境中运维着一套以上 Kubernetes 集群,无论是为了突破单集群的规模瓶颈,还是为了多云/混合云等更先进的部署架构,我们都能看到多集群逐渐发展成为常态。

如何高效地管理多套集群,既能利用多集群带来的架构灵活性和隔离性,又能通过和单集群一致的部署体验降低运维复杂度,同时合理提高多套集群的资源利用率。

目前在 TAD 应用中心中支持多集群发布的第一步,可以将应用下发到指定的 TCS 子集群,团队也正在推进研发多集群的应用的编排调度策略,适用于多地域发布的多集群复制模式,以及基于容量的多集群调度模式。希望基于 TAD 提供应用多集群发布的能力,同时给应用提供和单集群一样既高效又简单丝滑的使用体验。

服务网格能力产品化

服务网格是用于处理服务间通信的基础设施层,它负责通过包含现代云原生应用程序的复杂服务拓扑来可靠地传递请求。通过服务网格能将过去依赖特定语言,特定框架或 SDK 的服务治理能力下沉到基础设施。

而 Istio 一直最被诟病的就是其引入的复杂性 ,虽然社区一直致力于通过架构精简,提供各种工具等手段降低使用门槛,但其基于 label 的松耦合匹配机制,复杂的 API 和概念一直都令许多有意使用服务网格的公司望而却步 。

TAD 封装了 Istio 的能力,通过 TAD 自身的应用视角,将 Istio 相关资源根据应用维度聚合,并通过抽象简化了 Istio API,简化 Istio 使用的。后续也计划将基于 TAD 的服务网格能力通过应用中心产品化,给在 TCS 场景里提供轻量化的服务治理能力。

TAD