Skip to content

描述文件 Spec

注意:此文档适用于edition3.0.0的YAML文件。 如果您使用的是edition不为3.0.0的YAML文件,请参考旧版YAML规范

在非cli模式下(Yaml 模式 Cli 模式对比),进行应用的操作、组件的使用,需要按照 Serverless Devs 的规范,提供相对应的资源/行为描述文件,且该文件还需要符合以下条件:

  • 拓展名可以是.yaml.yml
  • 格式必须符合Yaml规范

👉 对于需要通过描述文件进行环境隔离的项目,建议将文件命名为 s-${ENV}.yamls-${ENV}.yml 格式。 例如:s-prod.yaml

在默认情况下,Serverless Devs 开发者工具会默认该描述文件的名称为s.yamls.yml,且s.yaml的优先级大于s.yml, 即在一个 Serverless 应用下,同时出现s.yamls.yml时,系统会优先识别和使用s.yaml

当然,开发者也可以通过-t, --template [templatePath]进行指定,例如,在某应用在生产环境下的描述文件名为s-prod.yml,则可以在执行相关命令时,增加参数-t s-prod.yml或者--template s-prod.yml

描述文件格式/规范

关于 Serverless Devs 所支持的资源/行为描述文件基本格式为:

edition: 3.0.0 # 命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: hello-world-app # 项目名称
access: default # 秘钥别名

vars: # [全局变量,提供给各个项目使用]
  Key: Value

validation: true # 是否开启资源属性值校验

actions: globalActions # 自定义全局的执行逻辑

resources: # 可以包括多个业务模块
  ProjectName: # 业务模块
    actions: projectActions # 自定义执行逻辑
    component: componentName # 组件名称
    props: componentProps # 组件的属性值

例如,一个相对完整的 Yaml 案例可以是:

edition: 3.0.0 # 命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: hello-world-app # 项目名称
access: default # 秘钥别名

vars: # [全局变量,提供给各个业务模块使用]
  logo: https://image.aliyun.com/xxxx.png

validation: true # 开启资源属性值校验

actions: # 自定义全局的执行逻辑
  pre-deploy: # 项目deploy执行之前执行
    - run: npm install # 要运行的命令行
      path: ./src # 命令行运行的路径
  success-deploy: # 项目deploy执行成功之后执行
    - plugin: dingding-robot # 要使用的插件
      allow_failure: true # true/false 允许失败条件
      args: # 插件的参数
        key: value 
  fail-deploy: # 项目deploy执行失败之后执行
    - plugin: dingding-robot # 要使用的插件
      allow_failure: # 允许失败条件
        command: # 允许失败的执行command
          - deploy
        exit_code: # 允许失败的退出码
          - 100
          - 101
      args: # 插件的参数
        key: value 
  complete-deploy: # 项目deploy执行完成之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 

resources:
  nextjs_portal: #  项目名称
    component: fc3  # 组件名称
    actions: # 自定义执行逻辑
      pre-deploy: # 在deploy之前运行
        - run: npm install  # 要运行的命令行
          path: ./nextjs_portal # 命令行运行的路径
      success-deploy: # 在deploy之后运行
        - component: fc3 invoke  # 要运行的组件,格式为【component: 组件名 命令 参数】
          allow_failure: true # true/false 允许失败条件
    props: #  组件的属性值
      region: ${vars.region}
      functionName: nextjs_portal
      runtime: nodejs14
      code: ./nextjs_portal
      handler: index.handler
      memorySize: 128
      timeout: 30

  assets:
    component: static
    props:
      cache-control: "public, max-age=604800, immutable"
      www: "./public"

  express_blog:
    component: express
    props:
      app: ./express-blog
      url: ${vars.domain}
    actions:
      pre-deploy:
        - run: npm run build
          path: ./express-blog

  gateway:
    component: serverless-gateway # 路由组件:HTTP URL和服务之间的映射规则
    props:
      routes:
        - route: /~assets
          value: ${resources.assets.output.url}
        - route: /
          value: ${resources.nextjs_portal.output.url}
          index: index.html
        - route: /~portal
          value: ${resources.nextjs_portal.output.url}
          inex: index.html
        - route: /~blog
          value: ${resources.express_blog.output.url}

元数据

在该格式中:

参数名 代表含义
edition 命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name 项目名称
access 秘钥别名,可以使用通过config命令配置的密钥信息,以及通过环境变量设置密钥
validation (3.0.5及后版本)是否开启资源属性值校验。设置为true时,会使用组件提供的schema校验模板中定义的资源属性值,默认为false
extend 所继承的模板
template 可被继承的模板
flow 操作顺序
vars 全局变量,提供给各个业务模块使用,是一个Key-Value的形式
actions 自定义全局的执行逻辑
resources 项目所包含的业务模块,是一个Key-Value的形式

template

关于 template 参数: 可被继承的模板,主要为key-object形式,例如;

template: 
  template1: 
    region: cn-hangzhou
    runtime: python3
    vpcConfig: vpc-1
  template2: 
    region: cn-beijing
    runtime: nodejs14
    vpcConfig: vpc-2

此时在 resource 中即成当前模板,可以进行重写操作,例如:

resources:
  resource1:
    component: fc       # 组件名称
    extend: 
      name: template1   # 继承template中的指定key对应的结构,与props内容进行合并
      ignore:           # 忽略的属性
         - vpcConfig
    props:
      region: cn-shanghai
      cpu: 1
      memorySize: 128
  resource2:
    component: fc        # 组件名称
    extend: 
      name: template1    # 继承template中的指定key对应的结构,与props内容进行合并
    props:
      region: cn-hongkong
      cpu: 1
      memorySize: 128
  resource3:
    component: fc        # 组件名称
    extend: 
       name: template2   # 继承template中的指定key对应的结构,与props内容进行合并
  resource4:
    component: fc        # 组件名称
    props:
      region: cn-hongkong
      cpu: 1
      memorySize: 128
完成渲染后,该部分的结果:

  • resource1:继承了template1,同时删除了vpcConfig参数,在template1基础上配置了region、cpu以及memorySize;
  • resource2:继承了template1,在template1基础上配置了region、cpu以及memorySize;
  • resource3:继承了template2;
  • Resource4:没有做任何继承,配置了region、cpu以及memorySize;

渲染结果:

resources:
  resource1:
    component: fc # 组件名称
    props:
      region: cn-shanghai
      runtime: python3
      cpu: 1
      memorySize: 128
  resource2:
    component: fc # 组件名称
    props:
      region: cn-hongkong
      runtime: python3
      vpcConfig: vpc-1
      cpu: 1
      memorySize: 128
  resource3:
    component: fc # 组件名称
    props:
      region: cn-hongkong
      cpu: 1
      memorySize: 128
  resource4:
    component: fc # 组件名称
    props:
      region: cn-hongkong
      cpu: 1
      memorySize: 128

resources

关于resources中Value参数:

参数名 代表含义
component 组件名称
extend 所继承的模板
actions 自定义执行逻辑
props 组件的属性值

component可以指定使用组件的版本,写法为:<组件名称>@<组件版本>,版本可选值可参考组件开发文档

变量赋值

Serverless Application模型对应的Yaml文件支持多种变量格式:

  • 获取当前机器中的环境变量:${env('环境变量')},例如 ${env('secretId')}, ${env('secretId', '默认值')}
  • 获取外部文档的变量:${file('路径')},例如 ${file('./path')}
  • 获取全局变量:${vars.*}
  • 获取Json字符串内容的变量:${json('json字符串')},例如 ${json(file('./a.json'))}
  • 获取路径的变量:${path('路径')},例如 ${path('../')}
  • 获取其他业务模块的变量:${resources.project_name.props.*}
  • 获取业务模块的结果变量:${resources.project_name.output.*}
  • 获取当前配置的config变量:${config('AccountID')}, 本质是获取 s config get中的变量值
  • 获取当前模块的信息:${this.xx}
  • 使用{{if}}语法实现条件判断

使用${env('')}获取环境变量

以下面的Yaml为例:

resources:
  next_demo:
    component: v3test
    props: # 组件的属性值
      region: cn-hangzhou
      function:
        functionName: "next-start-hello"
        runtime: ${env('runtime', 'nodejs16')}
        code: ./code

next_demo中,${env('runtime')}将尝试获取当前计算机中runtime环境变量的值,如果获取不到,将使用默认值nodejs16

使用${file('')}获取外部文档内容

以下面的Yaml为例:

resources:
  framework: 
    component: fc 
    actions:
      pre-deploy:
        - plugin: website-fc
    props:
      service: ${file('./file.txt')}

若此时file.txt的内容为:

this is file fun test

则解析后结果为:

resources:
  framework: 
    component: fc 
    actions:
      pre-deploy:
        - plugin: website-fc
    props:
      service: this is file fun test

使用${vars.*}获取全局变量

以下面的Yaml为例:

vars: # 全局变量
  region: cn-hangzhou
  service:
    name: website
    description: Serverless Devs Website Service
    internetAccess: true
resources:
  framework: # 业务名称/模块名称
    component: fc3 # 组件名称
    props: # 组件的属性值
      region: ${vars.region}

framework中,${vars.region}将获取vars下的region参数,因此渲染结果为:

vars: # 全局变量
  region: cn-hangzhou
  service:
    name: website
    description: Serverless Devs Website Service
    internetAccess: true
resources:
  framework: # 业务名称/模块名称
    component: fc3 # 组件名称
    props: # 组件的属性值
      region: cn-hangzhou

使用${json('')}获取Json字符串内容

以下面的Yaml为例:

resources:
  framework: # 业务名称/模块名称
    component: fc3test # 组件名称
    props: # 组件的属性值
      region: cn-hangzhou
      function:
        name: vuepress
        description: ${json(file("./a.json"))}
        runtime: nodejs12

若其中a.json的内容为:

{
  "info": "this is a fun test"
}

则解析时,会将a.json中的内容加在description之下。渲染结果为:

resources:
  framework: # 业务名称/模块名称
    component: fc3test # 组件名称
    props: # 组件的属性值
      region: cn-hangzhou
      function:
        name: vuepress
        description: 
          info: this is a fun test
        runtime: nodejs12

使用${path('')}获取路径

以下面的Yaml为例:

vars: # 全局变量
  region: cn-hangzhou
  service:
    name: website
    description: Serverless Devs Website Service
    internetAccess: true
resources:
  framework: # 业务名称/模块名称
    component: ${path('./fc.js')} # 组件名称

framework中,${path('./fc.js')}将尝试获取fc.js文件的绝对路径。若路径为/Users/XXX/XXX/fc.js,则渲染结果为:

vars: # 全局变量
  region: cn-hangzhou
  service:
    name: website
    description: Serverless Devs Website Service
    internetAccess: true
resources:
  framework: # 业务名称/模块名称
    component: /Users/XXX/XXX/fc.js # 组件名称

使用${resources.project_name.props.*}获取其他业务模块的变量

以下面的Yaml为例:

vars: # 全局变量
  service:
    name: website-wof2
    description: Serverless Devs Website Service
    internetAccess: true

resources:
  framework: # 业务名称/模块名称
    component: fc3test # 组件名称
    props: # 组件的属性值
      region: cn-hangzhou
      service: ${vars.service}
      function:
        name: vuepress
        description: Serverless Devs Website vuepress Function
        codeUri: ./code/docs/.vuepress/dist
        runtime: nodejs12
        environmentVariables:
          region: cn-hangzhou
          functionName: ${resources.next_function.props.function.name}
  next_function: 
    component: fc3test
    props:
      region: cn-hangzhou
      service: ${vars.service} # 应用整体的服务配置
      function:
        name: next-function-example
        description: Serverless Devs Website vuepress Function
        codeUri: ./next-code
        runtime: nodejs12

framework中,${resources.next_function.props.function.name}会获取next_function中的function属性中的name值。因此,渲染结果为:

vars: # 全局变量
  service:
    name: website-wof2
    description: Serverless Devs Website Service
    internetAccess: true

resources:
  framework: # 业务名称/模块名称
    component: fc3test # 组件名称
    props: # 组件的属性值
    ...
      functionName: next-function-example
    ...

使用${resources.project_name.output.*}获取业务模块的结果变量

以下面的Yaml为例:

vars: # 全局变量
  region: cn-hangzhou
  service:
    name: website-wof2
    description: Serverless Devs Website Service
    internetAccess: true
resources:
  framework: # 业务名称/模块名称
    component: fc3test # 组件名称
    props: # 组件的属性值
      region: ${vars.region}
      service: ${vars.service}
      function:
        name: vuepress
        description: Serverless Devs Website vuepress Function
        codeUri: ./code/docs/.vuepress/dist
        timeout: 30
        memorySize: 512
        runtime: nodejs12
        environmentVariables:
          hello: ${resources.next_function.output.hello}
  next_function: # 第二个函数的案例,仅供参考
    component: fc3test
    props:
      region: ${vars.region}
      service: ${vars.service} # 应用整体的服务配置
      function:
        name: next-function-example
        description: Serverless Devs Website vuepress Function

framework中,${resources.next_function.output.hello}会等待next_function运行完后,获取输出的hello值。若next_function的输出的hello值为hello world,则渲染结果为:

vars: # 全局变量
  region: cn-hangzhou
  service:
    name: website-wof2
    description: Serverless Devs Website Service
    internetAccess: true
resources:
  framework: # 业务名称/模块名称
    component: fc3test
    props: # 组件的属性值
    ...
      hello: hello world
    ...

使用${config('')}获取当前配置的config变量

以下面的Yaml为例:

props: # 组件的属性值
  region: cn-hangzhou
  function:
    ...
    environmentVariables:
      AccountID: ${config('AccountID')}
      ...

props中,${config('AccountID')}将尝试获取在s config中配置的AccountID的值。若AccountID的值为123456789012,则渲染结果为:

props: # 组件的属性值
  region: cn-hangzhou
  function:
   ...
    environmentVariables:
      AccountID: 123456789012

使用${this.xx}获取当前模块的信息

以下面的Yaml为例:

edition: 3.0.0
name: NextProject
access: default-access

resources:
  nextjs_portal:
    component: component
    actions:
      pre-deploy:
        - run: s invoke ${this.props.url}
          path: ./backend_src
    props:
      code: ./frontend_src
      url: url

nextjs_portal中:

  • 使用${this.name}将解析为nextjs_portal
  • 使用${this.props.code}将解析为 ./frontend_src
  • 使用${this.access}将解析为default-access

使用{{if}}语法实现条件判断

Yaml文件支持键值对级别的条件判断语法,您能够在Yaml文件中的属性值中使用art-template{{if}}语法进行条件判断。以下面的Yaml为例:

resources:
  nextjs_portal:
    component: component
    props:
      code: ./frontend_src
      url: url
      runtime: ${var.runtime}
      layers: 
        - acs:xxx/versions/{{if that.props.runtime === 'custom'}}1{{else}}2{{/if}}

此时就能实现根据runtime的值来改变层版本,确保runtime变更的情况下不出现兼容性问题。当runtime的值为custom时,层版本为1,否则为2。当runtimecustom时,渲染结果为:

layers: 
  - acs:xxx/versions/1

具体的语法请参考art-template语法文档

注意:

  1. art-template包含标准语法和原始语法,均可在Yaml中使用。
  2. {{}}包裹的语句内,使用其他模版语法无需用${}包裹。
  3. {{}}包裹的语句内使用this语法时,需将this写成that

特殊变量

在Serverless-Devs中有些特殊变量有特定的用途,开发者没有特殊的需求,避免使用特殊变量 - ${aliyun-cli} 作用在access的值中,从获取aliyun cli的默认的profile,并且生效。

执行aliyun configure list可以查看当前生效的profile

执行顺序

如果一个Serverless Project 模型对应的 Yaml 文件中有多个的服务,系统会默认分析部署顺序,该部署顺序分为两个方面:

  • 是否已经制定flow流程
  • 按照指定的流程进行部署,没在流程中的不进行额外的操作·
  • 没有指定flow流程
  • 分析项目中的依赖关系
  • 有依赖关系的按照依赖关系从前到后部署,无依赖关系的按Yaml配置的从上到下部署

指定 flow

flow表示执行流程或顺序,主要是key-list形式组成,例如:

flow:
  deploy: # 支持正则
    - [project_a]
    - [project_b, project_c]

表示的是,在进行deploy操作时先部署project_a,然后同时(并行)部署project_b, project_c;

这里的key也支持正则,比如

flow:
  ${regex('.')}: # 支持正则
    - [project_a]
    - [project_b, project_c]
本质上是将regex接收的参数value执行 new RegExp('value').test('当前执行的指令'), 比如: new RegExp('.').test('deploy'), 如果匹配成功,则按照指定的flow进行操作,如果匹配不成功,则按照系统分析出的顺序进行操作。

如果用户指定了flow, 按照指定的流程进行部署,没在流程中的不进行额外的操作·

未指定 flow

  • 被依赖的 resource 优先部署;
  • 从上到下的顺序,按顺序进行部署;

例如,某资源描述 Yaml 可以缩写成:

edition: 3.0.0 #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack #  项目名称
access: xxx-account1 #  秘钥别名

resources:
   nextjs-portal: #  服务名称
    component: vue-component # 组件名称
    props: #  组件的属性值
      src: ./frontend_src
      url: url

   assets:
    component: static
    props:
      www: './public'

  gateway:
    component: serverless-gateway # 路由组件:HTTP URL和服务之间的映射规则
    props:
      routes:
        - url: ${assets.output.url}

此时,可先进行依赖关系分析,服务nextjs-portalassets没有额外依赖,服务gateway通过魔法变量${assets.output.url}依赖了assets服务;此时部署顺序则为:
nextjs-portalassets按照上下顺序部署, 之后 gateway 拿到 assets 服务的返回结果再进行部署 即:nextjs-portal->assets->gateway

Yaml 继承

通过关键字extend, 可以解决多个Yaml配置冗余的问题。

典型场景

比如使用Serverless Devs部署一个函数计算FC应用的时候,预发环境的和正式环境除了name不一致。其他配置完全一致。Yaml配置如下

├── code
├── s.yaml
├── s.prod.yaml
└── s.pre.yaml

其中:

  • s.yaml为默认配置
edition: 3.0.0
access: "default"
resources:
  fc-deploy-test:
    component: fc3
    props:
      region: cn-hangzhou
      nasConfig: Auto
      name: hello-function
      description: "Serverless Devs Function"
      codeUri: "./"
      runtime: nodejs12
      timeout: 60
  • s.pre.yaml配置如下
extend: s.yaml
resources:
  fc-deploy-test:
    props:
      name: fc-function-pre
  • s.pro.yaml配置如下
extend: s.yaml
resources:
  fc-deploy-test:
    props:
      name: fc-function-pro

显示的声明 extend关键字,获得继承能力

  • 最终生效的配置

通过指定yaml配置s deploy -t s.pro.yaml生效

edition: 3.0.0
access: "default"
resources:
  fc-deploy-test:
    component: fc3
    props:
      region: cn-hangzhou
      nasConfig: Auto
      name: fc-function-pro
      description: "Serverless Devs Function"
      codeUri: "./"
      runtime: nodejs12
      timeout: 60

合并规则

配置的合并使用extend2 模块进行深度拷贝。 但是考虑到yaml的配置层级比较深,比如上面的示例,我们在预发环境需要覆盖resource名称,需要严格按照层级关系进行编写,相对繁琐。

resources:
  fc-deploy-test:
    props:
      name: fc-service-pro

数组合并

数据在做合并的时候,直接覆盖,而不是合并操作

const a = {
  arr: [1, 2],
};
const b = {
  arr: [3],
};
extend(true, a, b);
// => { arr: [ 3 ] }

最佳实践

Yaml继承一般用作环境划分,比如预发环境为s.pre.yaml,线上环境为s.pro.yaml,部署时候通过指定对应部署模版s deploy -t s.pro.yaml配置。

行为描述

全局Action

全局Action的基本格式是:

actions: # 自定义全局的执行逻辑
  pre-命令: # 项目在命令执行之前执行
    - run: npm install # 要运行的命令行
      path: ./src # 命令行运行的路径
  success-命令: # 项目在命令执行成功之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  fail-命令: # 项目在命令执行失败之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  complete-命令: # 项目在命令执行完成之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 

例如:

actions: # 自定义全局的执行逻辑
  pre-deploy: # 项目deploy执行之前执行
    - run: npm install # 要运行的命令行
      path: ./src # 命令行运行的路径
  success-deploy: # 项目deploy执行成功之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  fail-deploy: # 项目deploy执行失败之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  complete-deploy: # 项目deploy执行完成之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 

当Serverless Devs开发者工具执行相关的命令时,项目执行相关的命令之前,会执行全局的pre-命令操作,项目执行成功之后,会执行全局的success-命令操作,项目执行失败之后,会执行全局的fail-命令操作, 项目执行完成之后,会执行全局的complete-命令操作。

以下面的Yaml为例:

edition: 3.0.0        #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack       #  项目名称
access: default       #  秘钥别名

actions: # 自定义全局的执行逻辑
  pre-deploy: # 项目deploy执行之前执行
    - run: npm install # 要运行的命令行
      path: ./src # 命令行运行的路径
  success-deploy: # 项目deploy执行成功之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  fail-deploy: # 项目deploy执行失败之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  complete-deploy: # 项目deploy执行完成之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 

resources:
  nextjs_portal: #  业务模块
    component: vue-component  # 组件名称
    props: #  组件的属性值
      src: ./frontend_src
      url: url

当开发者在当前应用下执行了deploy命令,系统将会按照以下顺序进行操作:

  1. 执行全局的pre-deploy命令:在./src目录下执行npm install
  2. 调用组件vue-componentdeploy方法,并将props和项目的基本信息传入到组件vue-componentdeploy方法中
  3. 如果第2步骤执行成功则执行全局的success-deploy操作,执行失败则执行全局的fail-deploy操作,不管成功还是失败,只要执行完成后一定执行全局的complete-deploy操作。

关于actions中的runplugin的定位和区别:

  • run,需要指定执行目录,仅仅是一个hook的能力,可以认为就是单纯的执行命令(即调用系统的命令);
  • plugin,是一种轻量化的插件,每个插件通常情况下只会支持一个能力;

注意:全局Action中仅支持runplugin

局部Action

在Serverless Application模型对应的Yaml文件中,可以针对业务模块提供对应的行为操作,其基本格式是:

actions: # 自定义执行逻辑
  pre-命令: # 在命令之前运行
    - run: command  # 要运行的操作
      path: ./path # 运行操作的路径
    - component: pgo  # 要运行的组件,格式为【component: 组件名 命令 参数】
    - plugin: website-fc  # 要使用的插件
      args: # 插件的参数
        key: value 
  success-命令: # 在命令执行成功之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
    - component: pgo  # 要运行的组件,格式为【component: 组件名 命令 参数】
  fail-deploy: # 在命令执行失败之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 
  complete-deploy: # 在命令执行完成之后执行
    - plugin: dingding-robot # 要使用的插件
      args: # 插件的参数
        key: value 

例如:

edition: 3.0.0        #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack       #  项目名称
access: default       #  秘钥别名
resources:
  nextjs_portal: #  业务模块
    actions: # 自定义全局的执行逻辑
      pre-deploy: # 在deploy之前运行
        - run: npm install  # 要运行的命令行
          path: ./backend_src # 命令行运行的路径
        - component: fc build --use-docker  # 要运行的命令行
      success-deploy: # 在deploy成功之后运行
        - plugin: fc-warm
          args:
            corn: '********'
      fail-deploy: # 在deploy执行失败之后执行
        - plugin: dingding-robot # 要使用的插件
          args: # 插件的参数
            key: value 
      complete-deploy: # 在deploy执行完成之后执行
        - plugin: dingding-robot # 要使用的插件
          args: # 插件的参数
            key: value 
    component: vue-component  # 组件名称
    props: #  组件的属性值
      src: ./frontend_src
      url: url

当开发者在当前应用下执行了deploy命令,系统将会按照以下顺序进行操作:

  1. ./backend_src目录下执行npm install
  2. 在对项目nextjs_portal,使用fc组件的build方法,入参为--use-docker(即在docker环境下,对项目nextjs_portal进行构建)
  3. 调用组件vue-componentdeploy方法,并将props和项目的基本信息传入到组件vue-componentdeploy方法中
  4. 如果第3步骤执行成功则执行success-deploy操作,将部署的输出结果等信息,传递给插件fc-warm,并将{"corn": "********"}作为参数传入,执行失败则执行fail-deploy操作,不管成功还是失败,只要执行完成后一定执行complete-deploy操作。

关于actions中的runcomponentplugin的定位和区别:

  • run,需要指定执行目录,仅仅是一个hook的能力,可以认为就是单纯的执行命令(即调用系统的命令);
  • component,使用格式是组件名 命令 参数,将会把当前项目所使用的密钥信息、属性信息等一并传给指定的组件方法;
  • plugin,是一种轻量化的插件,每个插件通常情况下只会支持一个能力,与component最大的不同是,他可以修改属性。例如用户配置了props中的某个k-v为:codeUri: ./code
    • 在使用component之后,当前信息(codeUri: ./code),会继续成为项目执行的参数,不会变更;
    • 在使用plugin之后,当前信息(codeUri: ./code),可能会发生变更,并将变更后的内容作为项目执行的参数;

关于三者的具体的例子:

场景1:

edition: 3.0.0        #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack       #  项目名称

resources:
    nextjs_portal: #  业务模块
    component: test-component  # 组件名称
    props: #  组件的属性值
        src: ./frontend_src
        url: url 
用户在执行s deploy -a mytest后,系统会将密钥mytest,以及props的参数({"src": "./frontend_src", "url": "url"})传递给组件test-componentdeploy方法;

场景2:

edition: 3.0.0        #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack       #  项目名称

resources:
    nextjs_portal: #  业务模块
    component: test-component  # 组件名称
    actions: # 自定义执行逻辑
        pre-deploy: # 在deploy之前运行
        - run: s build
            path: ./
    props: #  组件的属性值
        src: ./frontend_src
        url: url 

用户在执行s deploy -a mytest后,系统会:

  • ./目录下执行s build,此时-a mytest参数并不会直接传递给s build方法,可以认为纯粹的执行某个命令,无相关状态的继承和关联;
  • 将密钥mytest,以及props的参数({"src": "./frontend_src", "url": "url"})传递给组件test-componentdeploy方法;

场景3:

edition: 3.0.0        #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack       #  项目名称

resources:
    nextjs_portal: #  业务模块
    component: test-component  # 组件名称
    actions: # 自定义执行逻辑
        pre-deploy: # 在deploy之前运行
        - component: fc build
    props: #  组件的属性值
        src: ./frontend_src
        url: url 

用户在执行s deploy -a mytest后,系统会:

  • 将密钥mytest,以及props的参数({"src": "./frontend_src", "url": "url"})传递给组件fcbuild方法;
  • 将密钥mytest,以及props的参数({"src": "./frontend_src", "url": "url"})传递给组件test-componentdeploy方法

场景4:

edition: 3.0.0        #  命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: FullStack       #  项目名称

resources:
    nextjs_portal: #  业务模块
    component: test-component  # 组件名称
    actions: # 自定义执行逻辑
        pre-deploy: # 在deploy之前运行
        - plugin: qbuild
            args:
            key: value
    props: #  组件的属性值
        src: ./frontend_src
        url: url 

用户在执行s deploy -a mytest后,系统会:

  • 将密钥mytest,以及props的参数({"src": "./frontend_src", "url": "url"}),plugin的参数({"key": "value"})传递给插件qbuild,此时插件qbuild进行相关的业务处理,处理完成:
    • 如果返回信息对props进行了修改,那么会将密钥mytest以及修改后的props的传递给组件test-componentdeploy方法;
    • 如果返回信息未对props进行了修改,那么会将密钥mytest以及原始的props的传递给组件test-componentdeploy方法;

在一个项目下,如何一键部署整个项目?又或者如何只部署应用中的某个业务模块?可以参考自定义命令使用指南

Action通配符

工具会识别魔法变量regex里的内容来正则匹配当前的执行方法。比如全局的pre-${regex(.)}表示项目执行任何方法之前都会执行pre的动作

本质上是将regex接收的参数value执行 new RegExp('value').test('当前执行的指令'), 比如: new RegExp('.').test('deploy')

actions: 
  pre-${regex('.')}: # 执行任何方法之前都会执行
    - run: npm install # 要运行的命令行
      path: ./src # 命令行运行的路径

Yaml 模式 Cli 模式对比

Serverless Devs 开发者工具从根本上提供了两种使用方法。

  • Yaml模式:需要依赖资源描述文档进行操作的模式
  • Cli模式:可以在任何目录下直接执行,而不需要依赖资源描述文档;

这两者的核心区别是:

  1. 如果想要使用 Yaml 模式,在当前目录下,必须要有s.yaml/s.yml文件,或通过-t/--template指定的资源部描述文件;
  2. 如果想要试用 Cli 模式,则必须是 s cli 组件名 方法 参数的格式进行,此时不需要 Yaml 文件;

举一个非常简单的例子,如果有一个应用的资源描述文件s.yaml如下:

name: myApp
edition: 3.0.0
access: "myaccess"

resources:
  website-starter:
    component: website
    props:
      bucket: testbucket
  backend-starter:
    component: demo
    props:
      service:
        name: serviceName
      function:
        name: functionName
      region: cn-hangzhou

此时,可以执行s deploy进行myApp应用部署,如果执行s backend-starter deploy则可以进行myApp应用下的backend-starter项目/服务部署。

此时,部署过程中,所需要的相关参数,可以通过该 Yaml 文件进行读取。

但是,在某些情况下,并不方便直接使用 Serverless Devs 规范的 Yaml 文件(例如,将线上资源同步到本地),此时可以选择纯命令行形式,即s cli模式。

s cli 模式下,由于不会读取 Yaml 等资源描述文件,所以很多参数都需要自行填写,这时的填写方法有两种:

  • 通过 s cli 天然支持的 -p/--prop 参数,进行相关 Yaml 参数的赋值,例如上述案例的s backend-starter deploy,此时可以改写成:
s cli demo -p "{\"service\":{\"name\":\"serviceName\"},\"function\":{\"name\":\"functionName\"},\"region\":\"cn-hangzhou\"}"
  • 通过 demo 组件本身所支持的一些参数,例如通过s cli demo -h,可以得到帮助信息,部分内容如下:
    --region [region]               [C-Required] Specify the fc region, value: cn-hangzhou/cn-beijing/cn-beijing/cn-hangzhou/cn-shanghai/cn-qingdao/cn-zhangjiakou/cn-huhehaote/cn-shenzhen/cn-chengdu/cn-hongkong/ap-southeast-1/ap-southeast-2/ap-southeast-3/ap-southeast-5/ap-northeast-1/eu-central-1/eu-west-1/us-west-1/us-east-1/ap-south-1  
    --service-name [serviceName]    [C-Required] Specify the fc service name  
    --function-name [functionName]  [Optional] Specify the fc function name   

此时,就可与通过下面的命令实现上述功能:

s cli demo --region cn-hangzhou --service-name serviceName --function-name functionName

特点对比

模式 使用方法 优势 劣势 适用场景
Yaml模式 在具有符合Serverless Devs规范,且存在资源/行为描述的Yaml文件的应用目录下,执行组件对应的命令,即可直接使用,例如s deploys servicename build 可以一键部署一个完整的应用(例如,某个应用中规定了多个Service,可以通过该命令一键部署);同时,通过资源/行为描述文档,可以更佳简单,清晰的对应用进行描述; 需要学习Yaml的规范,且在某些时候与一些自动化流程进行结合,会比较复杂; 部署、运维等操作,尤其是批量操作时更为合适;
纯Cli模式 在任何目录下,通过子命令cli进行触发,同样适用全部组件,例如s cli deploy -p "{/"function/": /"function-name/"}"s cli fc3 sync --region cn-hangzhou --function-name test -a default 相对来说可以更加简单,快速上手工具,并且可以非常简单的与自动化流程进行结合,降低了Yaml格式/规范的学习难度 对于一些复杂项目而言,需要在命令行中写过多的参数,出错的概率会比较高; 更适合项目的管理,源自化操作

设计思路

❓ 为什么要同时存在 Yaml 模式和 Cli 模式? 💬 因为在长期的实践过程中,我们发现通过 Yaml 进行资源描述会相对来说更简单和方便,例如 K8S 等也都是通过 Yaml 进行资源描述的;但是,在某些情况下,Yaml 文件也可能成为一种负担,例如想要查看某个服务下的函数列表,查看某个地区下的服务列表,因为这样一个简单的事情要额外的去完成一个 Yaml 文件,就显得过于臃肿,所以,在 Serverless Devs 项目中,同时保留了两种使用方法。