作者: 刘洋(炎寻) EDAS-OAM 架构与开发负责人 邓洪超 OAM spec maintainer 孙健波(天元) OAM spec maintainer 随着以 K8s 为主的云原生基础架构遍地生根,越来越多的团队开始基于 K8s 搭建持续部署、自助式发布体验的应用管理平台。然而,在 K8s 交付和管理应用方面,目前还缺乏一个统一的标准,这最终促使我们与微软联合推出了首个云原生应用标准定义与架构模型 - OAM。本文作者将从基本概念以及各个模块的封装设计与用法等角度出发来详细解读 OAM。 OAM 主要有三个特点: 开发和运维关注点分离:开发者关注业务逻辑,运维人员关注运维能力,让不同角色更专注于领域知识和能力; 平台无关与高可扩展:应用定义与平台实现解耦,应用描述支持跨平台实现和可扩展性; 模块化应用部署和运维特征:应用部署和运维能力可以描述成高层抽象模块,开发和运维可以自由组合和支持模块化实现。 OAM 综合考虑了在公有云、私有云以及边缘云上应用交付的解决方案,提出了通用的模型,让各平台可以在统一的高层抽象上透出应用部署和运维能力,解决跨平台的应用交付问题。同时,OAM 以标准化的方式沟通和连接应用开发者、运维人员、应用基础设施,让云原生应用交付和管理流程更加连贯、一致。 角色分类 OAM 将应用相关的人员划分为 3 个角色: 应用开发:关注应用代码开发和运行配置,是应用代码的领域专家,应用开发完成后打包(比如镜像)交给应用运维; 应用运维:关注配置和运行应用实例的生命周期,比如灰度发布、监控、报警等操作,是应用运维专家; 平台运维:关注应用运行平台的能力和稳定性,是底层(比如 Kubernetes 运维/优化,OS 等)的领域专家。 核心概念 OAM 包含以下核心概念: 服务组件(Component Schematics) 应用开发使用服务组件来声明应用的属性(配置项),运维人员定义这些属性之后就能按照组件声明得到运行的组件实例,组件声明包含以下信息: 工作负载类型(Workload type):表明该组件运行时的工作负载依赖; 元数据(Metadata):面向组件用户的一些描述性信息; 资源需求(Resource requirements):组件运行的最小资源需求,比如最小内存,CPU 和文件挂载需求; 参数(Parameters):可以被运维人员配置的参数; 工作负载定义(Workload definition):工作负载运行的一些定义,比如可运行包定义(ICO images, Function等)。 应用边界(Application Scopes) 运维人员使用应用边界将组件组成松耦合的应用,可以赋予这组组件一些共用的属性和依赖,应用边界声明包含以下信息: 元数据(Metadata):面向应用边界用户的一些描述性信息。 类型(Type):边界类型,不同类型提供不同的能力; 参数(Parameters):可以被运维人员配置的参数。 运维特征(Traits) 运维人员使用运维特征赋予组件实例特定的运维能力,比如自动扩缩容,一个 Trait 可能仅限特定的工作负载类型,它们代表了系统运维方面的特性,而不是开发的特性,比如开发者知道自己的组件是否可以扩缩容,但是运维可以决定是手动扩缩容还是自动扩缩容,特征声明包含以下信息: 元数据(Metadata):面向特征用户的一些描述性信息; 适用工作负载列表(Applies-to list):该特征可以应用的工作负载列表; 属性(Properties):可以被运维人员配置的属性。 工作负载类型和配置(Workload types and configurations) 描述特定工作负载的底层运行时,平台需要能够提供对应工作负载的运行时,工作负载声明包含以下信息: 元数据(Metadata):面向工作负载用户的一些描述性信息; 工作负载设置(Workload Setting):可以被运维人员配置的设置。 应用配置(Application configuration) 运维人员使用应用配置将组件、特征和应用边界的组合在一起实例化部署,应用配置声明包含以下信息: 元数据(Metadata):面向应用配置用户的一些描述性信息; 参数覆盖(Parameter overrides):可以理解为变量定义,可以被组件、特征、应用边界的参数引用; 组件设置(Component):构成应用的全部组件都在这里设置; 绑定组件的运维特征配置(Trait Configuration):绑定的特征列表及其参数。 OAM 认为: 一个云原生应用由一组相互关联但又离散独立的组件构成,这些组件实例化在合适的运行时上,由配置来控制行为并共同协作提供统一的功能。 更加具体的说: 一个 Application 由一组 Components 构成,每个 Component 的运行时由 Workload 描述,每个 Component 可以施加 Traits 来获取额外的运维能力,同时我们可以使用 Application scopes 将 Components 划分到 1 或者多个应用边界中,便于统一做配置、限制、管理。 整体的运行模式如下所示: 1 组件、运维特征、应用边界通过应用配置(Application Configuration)实例化,然后再通过 OAM 的实现层翻译为真实的资源。 怎么用? 使用 OAM 来管理云原生应用,其核心主要是围绕着“四个概念,一个动作”。 2 四个概念 应用组件(包含对工作负载的依赖声明);【开发人员关心】 工作负载;【平台关心】 运维特征;【平台关心】 应用边界;【平台关心】 一个动作 下发应用配置;【运维人员关心】 下发应用配置之后 OAM 平台将会实例化四个概念得到运行的应用。 一个 OAM 平台在对外提供 OAM 应用管理界面时,一定是预先已经准备好了“运维特征,应用边界”供运维人员使用,准备好了“工作负载”供开发者使用,同时开发者准备“组件”供运维人员使用。因此角色划分就很明了: 开发者提供组件,使用平台的工作负载; 运维人员关注一个动作实例化四个概念; 平台方需要提供工作负载,运维特征,应用边界,并且托管用户的应用组件; 3 例子 如何使用上面“四个概念,一个动作”来部署一个 OAM 应用呢? 我们假想一个场景,用户的应用有两个组件:frontend 和 backend,其中 frontend 组件需要域名访问、自动扩缩容能力,并且 frontend 要访问 backend,两者应该在同一个 vpc 内,基于这个场景我们需要有: 开发者创建 2 个组件 frontend 的 workload 类型是 Server 容器服务(OAM 平台提供该工作负载): apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: frontend annotations: version: v1.0.0 description: "A simple webserver" spec: workloadType: core.oam.dev/v1.Server parameters: - name: message description: The message to display in the web app. type: string value: "Hello from my app, too" containers: - name: web env: - name: MESSAGE fromParam: message image: name: example/charybdis-single:latest backend 的 workload 是 Cassandra(OAM 平台提供该工作负载): apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: backend annotations: version: v1.0.0 description: "Cassandra database" spec: workloadType: data.oam.dev/v1.Cassandra parameters: - name: maxStalenessPrefix description: Max stale requests. type: int value: 100000 - name: defaultConsistencyLevel description: The default consistency level type: string value: "Eventual" workloadSettings: - name: maxStalenessPrefix fromParam: maxStalenessPrefix - name: defaultConsistencyLevel fromParam: defaultConsistencyLevel OAM 平台提供 Ingress Trait apiVersion: core.oam.dev/v1alpha1 kind: Trait metadata: name: Ingress spec: type: core.oam.dev/v1beta1.Ingress appliesTo: - core.oam.dev/v1alpha1.Server parameters: properties: | { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "host": { "type": "string", "description": "ingress hosts", }, "path": { "type": "string", "description": "ingress path", } } } OAM 平台提供网络应用边界 apiVersion: core.oam.dev/v1alpha1 kind: ApplicationScope metadata: name: network annotations: version: v1.0.0 description: "network boundary that a group components reside in" spec: type: core.oam.dev/v1.NetworkScope allowComponentOverlap: false parameters: - name: network-id description: The id of the network, e.g. vpc-id, VNet name. type: string required: Y - name: subnet-ids description: > A comma separated list of IDs of the subnets within the network. For example, "vsw-123" or ""vsw-123,vsw-456". There could be more than one subnet because there is a limit in the number of IPs in a subnet. If IPs are taken up, operators need to add another subnet into this network. type: string required: Y - name: internet-gateway-type description: The type of the gateway, options are 'public', 'nat'. Empty string means no gateway. type: string required: N apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: my-vpc-network spec: variables: - name: networkName value: "my-vpc" scopes: - name: network type: core.oam.dev/v1alpha1.Network properties: - name: network-id value: "[fromVariable(networkName)]" - name: subnet-ids value: "my-subnet1, my-subnet2" 应用运维部署整个应用 使用应用配置将上面的组件、边界、特征组合起来即可部署一个应用,这个由应用的运维人员提供: apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: custom-single-app annotations: version: v1.0.0 description: "Customized version of single-app" spec: variables: - name: message value: "Well hello there" - name: domainName value: "www.example.com" components: - componentName: frontend instanceName: web-front-end parameterValues: - name: message value: "[fromVariable(message)]" traits: - name: Ingress properties: - name: host value: "[fromVaraible(domainName)]" - name: path value: "/" applicationScopes: - my-vpc-network - componentName: backend instanceName: database applicationScopes: - my-vpc-network 通过以上完整的使用流程我们可以看到:应用配置是真正部署应用的起点,在使用该配置的时候,对应的组件、应用边界、特征都要提前部署好,运维人员只需做一个组合即可。 服务组件(Component Schematic) 服务组件的意义是让开发者声明离散执行单元的运行时特性,可以用 JSON/YAML 格式来表示,其声明遵循 Kubernetes API 规范,区别在于: 定义字段是 Kubernetes 的子集,因为 OAM 是更高的抽象; OAM Spec 的底层运行时可以不是 Kubernetes 下面我们来看看具体的组件声明如何定义。 定义 顶层属性 属性 类型 必填 默认值 描述 apiVersion string Y 特定oam spec版本,比如core.oam.dev/v1 kind string Y 类型,对于组件来说就是 ComponentSchematic metadata Metadata Y 组件元数据 spec Spec Y 组件特定的定义属性 Metadata 属性 类型 必填 默认值 描述 name string Y labels map[string]string N k/v对作为组件的lebels annotations map[string]string N k/v对作为组件的描述信息 Spec 属性 类型 必填 默认值 描述 parameters []Parameter N 组件的可配置项 workloadType string Y 简明语义化的组件运行时描述,以K8s为例就是指定底层使用StatefulSet还是Deployment这样 osType string N linux 组件容器运行时依赖的操作系统类型,可选值: - linux - windows arch string N amd64 组件容器运行时依赖的CPU架构,可选值: - i386 - amd64 - arm - arm64 containers []Container N 实现组件的OCI容器们 workloadSettings []WorkloadSettings N 需要传给工作负载运行时的非容器配置声明 上面的 workloadType 后面会有详细说明,这里简要来说就是开发者可以指定该 field 告诉运行时该组件如何被执行。工作负载命名的规范是 GROUP/VERSION.KIND,和 K8s 资源类型坐标一致,比如: core.oam.dev/v1alpha1.Singleton core.oam.dev 表示是 oam 内置的分组,oam 内置的表示任何 OAM 实现都支持该类型的工作负载,v1alpha1 表示依旧是 alpha 状态,类型是 Singleton,这表示运行时应该只运行一个组件实例,无论谁提供。 alibabacloud.com/v1.Function alibabacloud.com 表示该对象是一个运营商特定的实现,不一定所有平台都实现,版本 v1 表示实现已经稳定,类型是 Function 表示运行时是 Alibaba Functions 提供的。 streams.oam.io/v1beta2.Kafka 表示该对象是一个第三方组织实现,不一定所有平台都实现,版本 v1beta2 表示实现已经趋于稳定,类型是 Kafka 表示运行时是开源组件 Kafka 提供的。 工作负载主要分为两类: 核心工作负载类型 属于 core.oam.dev 分组,所有对象都需要 oam 平台实现,都是容器运行时,该 Spec 目前定义了如下核心工作负载类型: 名字 类型 是否对外提供服务 是否可以多副本运行 是否常驻 Server core.oam.dev/v1alpha1.Server Y Y Y Singleton Server core.oam.dev/v1alpha1.SingletonServer Y N Y Worker core.oam.dev/v1alpha1.Worker N Y Y Singleton Worker core.oam.dev/v1alpha1.SingletonWorker N N Y Task core.oam.dev/v1alpha1.Task N Y N Singleton Task core.oam.dev/v1alpha1.SingletonTask N N N Server:定义了容器运行时可以运行 0 或多个容器实例,该工作负载提供了冗余的可扩缩容的多副本常驻服务,在 K8s 平台可以使用 Deployment 或者 Statefulset 加上 Service 来实现; Singleton Server:定义了容器运行时只能运行一个容器实例,该工作负载提供了无法冗余的单副本常驻服务,在 K8s 平台可以使用副本为 1 的 Statefulset 加上 Service 来实现; Worker:定义了容器运行时可以运行 0 或多个容器实例,该工作负载提供了冗余的可扩缩容的多副本常驻实例但是不提供服务,在 K8s 平台可以使用 Deployment 或者 Statefulset 来实现; Singleton Worker:定义了容器运行时只能运行一个容器实例,该工作负载提供了无法冗余的单副本常驻实例但是不提供服务,在 K8s 平台可以使用副本为 1 的 Statefulset 来实现; Task:定义了非常驻可冗余的容器运行时,运行完就退出,可以赋予扩缩容特性,在 K8s 平台可以使用 Job 来实现; Singleton Task:定义了非常驻非冗余的容器运行时,运行完就退出,在 K8s 平台可以使用 completions=1 的 Job 来实现。 核心工作负载必须给定 container 部分,实现核心工作负载的 OAM 平台必须不依赖 workloadSettings。 扩展工作负载类型 扩展工作负载类型是平台运行时特定的,由各个 OAM 平台自定提供,当前版本的 Spec 不支持用户自定义工作负载类型,只能是平台方提供,扩展工作负载可以使用非容器运行时,workloadSettings 的目的就是为了扩展工作负载类型的配置。 工作负载的定义是包罗万象的,他允许任何部署的服务作为工作负载,无论是容器化还是虚拟机,基于此,我们可以将缓存,数据库,消息队列都作为工作负载,如果组件指定的工作负载在平台没有提供,应该快速失败将信息返回给用户。 除了 WorkloadType,可以看到组件 Spec 内嵌了 3 种结构体,下面来看看它们的定义: 1.Parameter 定义了该组件的所有可配置项,其定义如下: 属性 类型 必填 默认值 描述 name string Y description string N 组件的简短描述 type string Y 参数类型,JSON定义的boolean, number, ... required boolean N false 参数值是否必须提供 default 同上面的type N 参数的默认值 2.Container 属性 类型 必填 默认值 描述 name string Y 容器名字,在组件内必须唯一 iamge string Y 镜像地址 resources Resources Y 镜像运行最小资源需求 env []Env N 环境变量 ports []Port N 暴露端口 livenessProde HealthProbe N 健康状态检查指令 readinessProbe HealthProbe N 流量可服务状态检查指令 cmd []string N 容器运行入口 args []string N 容器运行参数 config []ConfigFile N 容器内可以访问的配置文件 imagePullSecrets string N 拉取容器的凭证 其中 Resources 的定义如下: 属性 类型 必填 默认值 描述 cpu CPU Y 容器运行所需的cpu资源 memory Memory Y 容器运行所需的memory资源 gpu GPU N 容器运行所需的gpu资源 volumes []Volume N 容器运行所需的存储资源 extended []ExtendedResource N 容器运行所需的外部资源 其中 CPU/Memory/GPU 都是数值型,不赘述,Volume 的定义如下: 属性 类型 必填 默认值 描述 name string Y 数据卷的名字,引用的时候使用 mountPath string Y 实际在文件系统的挂载路径 accessMode string N RW 访问模式,RW或RO sharingPolicy string N Exclusive 挂载共享策略,Exclusive或者Shared disk Disk N 该数据卷使用底层磁盘资源的属性 Disk 指定存储是否需要持久化,最小的空间需求,定义如下: 属性 类型 必填 默认值 描述 required string Y 最小磁盘大小需求 ephemeral boolean N 是否需要挂载外部磁盘 ExtendedResource 描述特定实现的资源需求,比如 OAM 运行时平台可能提供特殊的硬件,这个字段允许容器声明需要这个特定的 offering: 属性 类型 必填 默认值 描述 required string Y 需要的条件 name string Y 资源名字,比如GV.K Env,Port,HealthProbe 和 K8s 类似,这里不再赘述。 3.WorkloadSetting 工作负载的附加配置,用于非容器运行时(当然,也可以用于容器运行时工作负载的附加字段)。 属性 类型 必填 默认值 描述 name string Y 参数名 type string N string 参数类型,用于实现的hint value any N 参数值,如果没有fromParam则用该值 fromParam string N 参数引用,覆盖value 这组配置会传给运行时,一个运行时可以返回错误,如果特定的限制没有满足,比如: 丢失了期望的 k/v 对; 不认识的 k/v 对; 例子 使用核心工作负载 Server 的组件声明 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: frontend annotations: version: v1.0.0 description: > Sample component schematic that describes the administrative interface for our Twitter bot. spec: workloadType: core.oam.dev/v1alpha1.Server osType: linux parameters: - name: username description: Basic auth username for accessing the administrative interface type: string required: true - name: password description: Basic auth password for accessing the administrative interface type: string required: true - name: backend-address description: Host name or IP of the backend type: string required: true containers: - name: my-twitter-bot-frontend image: name: example/my-twitter-bot-frontend:1.0.0 digest: sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b resources: cpu: required: 1.0 memory: required: 100MB ports: - name: http value: 8080 env: - name: USERNAME fromParam: 'username' - name: PASSWORD fromParam: 'password' - name: BACKEND_ADDRESS fromParam: 'backend-address' livenessProbe: httpGet: port: 8080 path: /healthz readinessProbe: httpGet: port: 8080 path: /healthz 使用扩展工作负载的组件声明 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: alibabacloudFunctions annotations: version: v1.0.0 description: "Extended workflow example" spec: workloadType: alibabacloud.com/v1.Function parameters: - name: github-token description: GitHub API session key type: string required: true workloadSettings: - name: source value: git://git.example.com/function/myfunction.git - name: github_token fromParam: github-token 总结 组件声明是由开发者或者 OAM 平台给出,透出应用运行的可配置项、依赖的平台和工作负载,可以看成是一个声明了运行环境的函数定义,运维人员填写函数参数之后,组件就会按照声明的功能运行起来。 应用边界(Application scopes) 应用边界通过提供不同形式的应用边界以及共有的分组行为来将组件组成逻辑的应用,应用边界具备以下通用的特征: 应用边界应该描述该组组件实例的共有行为和元数据; 一个组件可以同时部署到多个不同类型的应用边界中; 应用边界类型可以决定组件是否可以部署到多个相同的应用边界类型实例; 应用边界可以用于不同组件分组以及不同 infra 能力之间的连接机制,比如 networking 或者外部能力(如验证服务); 下图说明