初识OAM模型,交付一个简单的应用
“部署即代码”即用代码描述一个应用的部署计划。KubeVela就是实现这一目标的平台,让我们可以编写一个符合OAM模型的yaml来描述应用的部署。这些概念我们先不急着理解。
现实场景中,我们部署一个应用可能会涉及到申请基础设施,然后要知道部署到哪个集群上(环境),以及需要先部署某些需要依赖的中间件。
例如部署一个简单的web服务,我们需要依赖redis中间件。假设现在我们用kubeVela来部署这样一个应用,我们需要用OAM模型来描述它,编写一个application.yaml文件。
一、OAM模型的结构
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: oam-app
spec:
components:
workflow:
OAM模型将基础设施、中间件、微服务等抽象成组件,一个应用由一到多个组件组成,而工作流则负责编排组件的部署。默认情况下,即便我们不声明工作流步骤,kubeVela也会提供默认的工作流步骤来部署组件。
二、描述web服务的部署
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: web-demo-app
spec:
components:
- name: web-demo-service
type: webservice
properties:
image: xxx.xxx.xx/web-demo-service:1.0
ports:
- port: 8080
expose: true
redis:
endpoints:
- {}
workflow:
- type: apply-component
name: deploy-web-demo-service
properties:
component: web-demo-service
这段代码描述了web-demo-app这个应用由一个组件组成,并且声明了一个工作流步骤来部署这个组件:
- web-demo-service组件:一个名为web-demo-service类型为webservice的组件,通过properties.image指定使用xxx.xxx.xx/web-demo-service:1.0镜像,properties.ports指定暴露8080端口。
- deploy-web-demo-service工作流步骤:类型为apply-component的工作流步骤可用于部署类型为webservice的组件,通过properties.component指定该工作流步骤用于部署哪个组件。
三、描述依赖的中间件部署
假设部署redis我们使用kubevela提供redis-operator addon插件封装的redis-failover组件,实际部署应用的话需要先安装这个addon插件。
现在我们需要给app添加一个redis-failover组件,并且拿到redis组件部署后输出的Endpoints(访问redis集群的ip和端口)传递给web服务(web-demo-service)。
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: web-demo-app
spec:
components:
- name: web-demo-service
type: webservice
properties:
image: xxx.xxx.xx/web-demo-service:1.0
ports:
- port: 8080
expose: true
redis:
endpoints:
- {} # 待部署后生成
- name: sample-redis
type: redis-failover
properties:
replicas: 1
workflow:
- type: apply-component
name: deploy-sample-redis
properties:
component: sample-redis
- name: read-redis-output
type: read-object
dependsOn:
- deploy-sample-redis
outputs:
- name: redisoutput
valueFrom: output.value.subsets
properties:
apiVersion: v1
kind: Endpoints
name: rfs-sample-redis
- type: apply-component
name: deploy-web-demo-service
properties:
component: web-demo-service
dependsOn:
- read-redis-output
inputs:
- from: redisoutput
parameterKey: values.redis.endpoints
我们给描述文件添加了sample-redis组件,然后增加了deploy-sample-redis和read-redis-output这两个工作流步骤,而deploy-web-demo-service工作流步骤也有所修改。
现在我们来理解一下这三个工作流步骤:
- deploy-sample-redis:类型为apply-component,负责部署redis组件。
- read-redis-output:类型为read-object,用于读取一个k8s资源,并且可以将读取的数据传递给下一个工作流步骤。该工作流步骤通过dependsOn声明了依赖deploy-sample-redis工作流步骤,也就是这个工作流步骤得等到deploy-sample-redis工作流步骤完成后才会被执行。而outputs用于指定传递给下一个工作流步骤的参数名和值。properties则配置指定读取的k8s资源。outputs下的变量值可以通过output.value引用这个资源。此案例中,output.value.subsets就是引用读取的Endpoints资源的subsets字段的值。
- deploy-web-demo-service:我们增加了dependsOn,要求等到read-redis-output工作流步骤完成后才能执行。然后通过inputs,将上一个工作流步骤传递的redisoutput变量,传递给组件的properties的redis.endpoints。
四、指定部署在哪个k8s集群
kubevela通常以独立的k8s集群部署,而预发、线上环境都是独立的k8s集群,现在假设我们需要让kubevela将应用部署到线上环境的k8s集群,线上k8s集群名为"prod-cluster"。
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: web-demo-app
spec:
components:
- name: web-demo-service
type: webservice
properties:
image: xxx.xxx.xx/web-demo-service:1.0
ports:
- port: 8080
expose: true
redis:
endpoints:
- {} # 待部署后生成
- name: sample-redis
type: redis-failover
properties:
replicas: 1
workflow:
- type: apply-component
name: deploy-sample-redis
properties:
component: sample-redis
cluster: prod-cluster
- name: read-redis-output
type: read-object
dependsOn:
- deploy-sample-redis
outputs:
- name: redisoutput
valueFrom: output.value.subsets
properties:
apiVersion: v1
kind: Endpoints
name: rfs-sample-redis
cluster: prod-cluster
- type: apply-component
name: deploy-web-demo-service
properties:
component: web-demo-service
cluster: prod-cluster
dependsOn:
- read-redis-output
inputs:
- from: redisoutput
parameterKey: values.redis.endpoints
当然,除了在工作流步骤中指定工作流的依赖关系实现组件按依赖顺序部署,还可以直接在组件中声明这种关系,而参数的传递也同样可以在组件中声明。
除了指定部署到prod-cluster集群外,相同的目标,描述文件可以改成:
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: web-demo-app
spec:
components:
- name: web-demo-service
type: webservice
dependsOn:
- sample-redis
properties:
image: xxx.xxx.xx/web-demo-service:1.0
ports:
- port: 8080
expose: true
redis:
endpoints:
- {} # 待部署后生成
inputs:
- from: redisoutput
parameterKey: properties.values.redis.endpoints
- name: sample-redis
type: redis-failover
properties:
replicas: 1
output:
- name: redisoutput
valueFrom: |
import "vela/op"
#Endpoints: op.#Read & {
value: {
kind: "Endpoints"
apiVersion: "v1"
metadata: {
name: "rfs-sample-redis"
namespace: "default"
}
}
}
#Endpoints.value.subsets
workflow:
- type: deploy
name: deploy-to-prod-cluster
由于没有一个叫read-object的组件,所以这里用到了valueFrom支持的另一种写法:CUE表达式。通过CUE代码读取Endpoints资源,并将Endpoints资源的subsets输出。
现在我们通过OAM模型中的应用策略来指定部署集群。
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: web-demo-app
spec:
components:
- name: web-demo-service
type: webservice
dependsOn:
- sample-redis
properties:
image: xxx.xxx.xx/web-demo-service:1.0
ports:
- port: 8080
expose: true
redis:
endpoints:
- {} # 待部署后生成
inputs:
- from: redisoutput
parameterKey: properties.values.redis.endpoints
- name: sample-redis
type: redis-failover
properties:
replicas: 1
output:
- name: redisoutput
valueFrom: |
import "vela/op"
#Endpoints: op.#Read & {
cluster: "prod-cluster",
value: {
kind: "Endpoints"
apiVersion: "v1"
metadata: {
name: "rfs-sample-redis"
namespace: "default"
}
}
}
#Endpoints.value.subsets
policies:
- name: prod-topology
type: topology
properties:
clusters: ["prod-cluster"]
workflow:
- type: deploy
name: deploy-to-prod-cluster
properties:
policies: ["prod-topology"]
我们描述了类型为topology的应用策略“prod-topology”,topology策略是kubevela内置的策略,描述组件部署到哪些k8s集群,这里指定为只部署到“prod-cluster”集群。然后还需要将该策略配置给deploy-to-prod-cluster工作流步骤。
五、以NodePort暴露web服务给集群外部访问
OAM模型中,组件可以有运维特征,我们给组件配置水平自动扩缩容需要用到运维特征,给组件注入环境变量也可以用到运维特征。
现在,为了验证我们部署的web-demo-service服务能够正常访问,需要用到kubevela内置的expose特征,以NodePort类型暴露8080端口给k8s集群外部访问。
yamlapiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: web-demo-app
spec:
components:
- name: web-demo-service
type: webservice
dependsOn:
- sample-redis
properties:
image: xxx.xxx.xx/web-demo-service:1.0
ports:
- port: 8080
expose: true
redis:
endpoints:
- {} # 待部署后生成
inputs:
- from: redisoutput
parameterKey: properties.values.redis.endpoints
traits:
- type: expose
properties:
port: [8080]
type: NodePort
- name: sample-redis
type: redis-failover
properties:
replicas: 1
output:
- name: redisoutput
valueFrom: |
import "vela/op"
#Endpoints: op.#Read & {
value: {
kind: "Endpoints"
apiVersion: "v1"
metadata: {
name: "rfs-sample-redis"
namespace: "default"
}
}
}
#Endpoints.value.subsets
policies:
- name: prod-topology
type: topology
properties:
clusters: ["prod-cluster"]
workflow:
- type: deploy
name: deploy-to-prod-cluster
properties:
policies: ["prod-topology"]
总结
此web服务举例还不完整,还缺少申请基础设施的部分:要暴露给浏览器访问,首先还得要有个域名,然后还涉及到ALB(应用层负载均衡)等,因为过于复杂,特别是ALB,不好理解,所以案例把基础设施部分砍掉了。不过通过后面小节内容的学习,我们也能了解到如何通过OAM来描述申请基础设施。
另外,在此web案例中,我们将Redis作为中间件部署,实际上,我们也可以将Redis作为基础设施申请,例如使用阿里云的Redis服务,阿里云的Terraform Provider提供了Redis资源。