MCL(minos Configuration Language)

Syntax

配置文件格式是yaml,需要先符合yaml的语法。

Layout

整个配置必须是一个mapping(python里的dict)。
下面是个例子:

extends: scribe/common/scribe-common.yaml
service: scribe
name: dptst-example
external:
  hdfs: hdfs/hdfs-dptst-example.yaml
packages:
  main:
    artifact: scribe
    version: 1.0-mdh1.0-SNAPSHOT
    revision: 47008ee78bb030052cee3de4cf75a1e730fd44ac
    timestamp: 20140321-123009
  hadoop:
    __attr__:
      extends: external.hdfs.packages.main
configurations:
  scribed.conf:
    generator: 'inline'
    content: |
      <store>
      type=file
      fs_type=hdfs
      file_path=hdfs://${external.hdfs.name}/user/scribe
      rotate_on_reopen=yes
      create_symlink=no
      base_filename=thisisoverwritten
      max_size=1000000000 #1G
      add_newlines=0
      rotate_period=daily
      rotate_hour=0
      </store>
jobs:
  scribe:
    ports:
      base: 12500
    hosts:
      - 192.168.0.1
      - 192.168.0.2
    execute:
      envs:
        LD_LIBRARY_PATH: '${task.dirs.packages.hadoop}/hadoop/lib/native:${task.envs["LD_LIBRARY_PATH"]}'
        CLASSPATH:
          - '${task.envs["CLASSPATH"]'
          - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
        LIBHDFS_OPTS: '-Djava.security.krb5.conf=${task.dirs.packages.hadoop}/hadoop/etc/hadoop/krb5.conf'
      command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'

配置中的任何一项(主要关注它的value)都可以靠它的path来引用,下面是一些例子,注意配置项不一定是最底层的scalar,任何一个层级的value都可以是配置项(嵌套的):

name
external.hdfs
packages
packages.main
jobs.scribe
jobs.scribe.ports
jobs.scribe['ports']
jobs.scribe.hosts
jobs.scribe.hosts[0]

引用mapping某个key的value有两种语法:.['key'],比如:jobs.scribe.portsjobs.scribe['ports']
引用sequence的某项是[index],比如:jobs.scribe.hosts[0]
配置项是下面要讲的变量替换表达式中的最常用变量。

external

external必须放在配置文件的顶级配置项里,表示对另外一个配置文件的应用,且不是extends关系。
external下可以有多个key,用来引用多个配置文件。

Variable Substitution

${expr}表示变量替换,可以使用在配置文件里的任何key和value。expr是任意的python的表达式(符合eval()函数的要求),表达式可以直接用变量的形式引用一个配置项,比如:${jobs.scribe.ports}${task.dirs.package}${external.hdfs.name},也可以是更复杂的表达式,比如:${jobs.scribe.ports.base+1}expr被求值后的结果用来做变量替换。

使用在有语义的key,或者__attr__等magic variable中时,由于实现上要先解析语义,再进行变量替换,所以语法上虽然合法,但实际上会丢失原始的语义,变成普通的配置项,一般不是期望的结果。

Magic Variables

有时候配置项需要一些特殊的设置,用来指导配置项的解释,但又不想成为配置项value的一部分(比如后面要解释的extends),所以引入了Magic Variable,目前支持__attr____val__

__attr__用来设置配置的属性,比如extends。下面是个例子,表示当前配置项(executeextends external.java.execute

execute:
  __attr__:
    extends: external.java.execute
  jvm_args:
    'verbose:' 'gc'
    'Xms': '2048m'

__attr__必须是一个mapping。

除了mapping,yaml的value类型还有scalar和sequence。scalar和sequence上使用__attr__的时候,就需要把原来的值放到__val__这个特殊变量里(因为这时候value必须是个mapping了)。下面是几个例子:
scalar例子:

name: "<must be a scalar string>"

name:
  __attr__:
    required: true
  __val__: "<must be a scalar string>"

sequence例子:

hosts:
  - 192.168.0.1
  - 192.168.0.2

hosts:
  __attr__:
    magic: true
  __val__:
    - 192.168.0.1
    - 192.168.0.2

scalar (sequence item) 例子:

hosts:
  - 192.168.0.1
  - 192.168.0.2

hosts:
  - __attr__:
      magic: true
    __val__: 192.168.0.1
  - 192.168.0.2

当然mapping value也可以用__val__来表示值,虽然并无必要。

Scope

下面这个例子中,${ports}是对ports这个配置项变量值的引用:

jobs:
  scribe:
    ports:
      base: 12500
    execute:
      command: 'scribed -p ${ports.base}'

由于配置项是嵌套的,同样的名字(即key)可能在多个层级上存在,需要有明确的规则来解析到确定的配置项上(即它的path),所以需要引入scope的概念。
有四种基本的scope表达方式:thisuptopauto

this表示 包含当前上下文的最内层mapping,注意必须是 mapping
上面这个例子${ports}所在上下文的thisjobs.scribe.execute

这个例子:

jobs:
  magic: ${foo}
  scribe:
    ports:
      base: 12500
    hosts:
      - ${bar}

${foo}所在上下文的thisjobs${bar}所在上下文的thisjobs.scribe

up是指this的上一层mapping,可以连续使用,比如up.uptop是指最顶层的mapping,即整个配置文件对应的mapping。
thisuptop都是关键词。

上面三种作用域引用是确定性的,另外一种作用域auto是不确定的。auto是缺省的引用规则(如果没有使用thisuptop的话),不需要显示指定,也没有定义auto这个关键词。
auto引用的规则是一个变量对当前和所有内含mapping是可见的。这个例子里:

jobs:
  scribe:
    ports:
      base: 12500
    execute:
      command: 'scribed -p ${ports}'

portsup.portsscribe.portsjobs.scribe.portstop.jobs.scribe.ports都会解析到同一个配置项。
this.portsup.up.portstop.ports就无法被正确解析。

如果有两个同名的变量,在作用域上有重叠(必然是一个包含另外一个),内层定义的变量会覆盖(其实是extends,详见auto extends)外层定义的变量。比如这个例子:

jobs:
  ports:
    base: 12500
  scribe:
    ports:
      base: 22500
    execute:
      command: 'scribed -p ${ports.base}'

ports.base引用的是jobs.scribe.ports.base。更详细的描述见隐式extends

还有一种扩展的scope表达方式是task,详见task描述

extends

extends类似于java的extends,用来定义(配置项的)继承关系。有几点约束:
1. 只能extends另外一个配置项,即单继承
2. 不能extends自己
3. 不能extends自己内含的配置项,即不能继承inner class
4. 不能extends包含自己的配置项,即不能继承outer class
5. 两者的类型必须兼容(相同,或者其中一个为空)

违反上面约束的属于语法错误,会报错。

extends基本规则

scalar的extends,如果derived配置项有值(非null)就用derived配置项的值(无变化),如果无值就使用base配置项的值:

common:
  port: 12500
server:
  port:
    __attr__:
      extends: common.port

server.port的值等于common.port,即12500

common:
  port: 12500
server:
  port:
    __attr__:
      extends: common.port
    __val__: 22500

server.port的值等于22500

sequence的extends在base配置项的值后面append derived配置项定义的值:

common:
  hosts:
    - 192.168.0.1
    - 192.168.0.2
server:
  hosts:
    __attr__:
      extends: common.hosts
    __val__:
      - 192.168.0.3
      - 192.168.0.4

server.hosts的值是['192.168.0.1', '192.168.0.2', '192.168.0.3', '192.168.0.4']

mapping的extends类似于python dict的update,具体规则如下:
1. 如果一个key,只在base或者derived配置项里定义了,最后的value就是被定义的那个value
2. 如果一个key,在base或者derived配置项里都定义了,最后的value就是 extends 的结果
下面是个例子:

common:
  execute:
    envs:
      CLASSPATH:
        - '${task.envs["CLASSPATH"]'
        - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
server:
  execute:
    __attr__:
      extends: common.execute
    envs:
      CLASSPATH:
        - ':${task.dirs.packages.hadoop}/hadoop/lib/'
    extra_args:
      - 'start'

server.execute的值是:

  execute:
    envs:
      CLASSPATH:
        - '${task.envs["CLASSPATH"]'
        - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
        - ':${task.dirs.packages.hadoop}/hadoop/lib/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
    extra_args:
      - 'start'

mapping的extends隐含了它内含配置项的extends,如果内含的配置项有显示的extends语句,会覆盖隐含的extends:

common:
  execute:
    envs:
      CLASSPATH:
        - '${task.envs["CLASSPATH"]'
        - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
  scala_envs:
    CLASSPATH:
      - '${task.envs["CLASSPATH"]'
      - ':${task.dirs.packages.hadoop}/scala/'
server:
  execute:
    __attr__:
      extends: common.execute
    envs:
      __attr__:
        extends: common.scala_envs
    extra_args:
      - 'start'

server.execute的值是:

  execute:
    envs:
      CLASSPATH:
        - '${task.envs["CLASSPATH"]'
        - ':${task.dirs.packages.hadoop}/scala/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
    extra_args:
      - 'start'

还有一个特殊用法是extends null,表示不继承任何base配置项。它的目的是为了阻断隐含的extends,比如:

common:
  execute:
    envs:
      CLASSPATH:
        - '${task.envs["CLASSPATH"]'
        - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
server:
  execute:
    __attr__:
      extends: common.execute
    envs:
      CLASSPATH:
        __attr__:
          extends: null
        __val__: 
          - '${task.dirs.packages.hadoop}/hadoop/lib/'
    extra_args:
      - 'start'

server.execute的值是:

  execute:
    envs:
      CLASSPATH:
        - '${task.dirs.packages.hadoop}/hadoop/lib/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
    extra_args:
      - 'start'

另外在extends中,__attr__ magic variable也被继承,但只继承一层。如果下面的属性有嵌套,不会递归继承。
上面的例子中都省略了。

文件extends

即文件顶级的extends,例如:

extends: scribe/common/scribe-common.yaml

表示继承另外一个文件所表示的配置项。由于文件对应的整个配置项必须是一个mapping,所以这儿的extends也就按mapping的extends来解释。这个extends指令必须放在配置文件的顶级配置项里。

配置项extends

即配置项级别的extendsextends基本规则里的几个例子都是配置项extends,这儿不再重复举例。

配置项extends对base配置项的path有个约束,必须是从top(见Scope)开始,而且top是隐含的。

auto extends

这是一种特殊的隐含extends。

如果有两个同名的变量,在作用域上有重叠,内层定义的变量会隐含extends外层定义的变量。比如这个例子:

execute:
  envs:
    CLASSPATH:
      - '${task.envs["CLASSPATH"]'
      - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
  command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
server:
  execute:
    envs:
      CLASSPATH:
        - ':${task.dirs.packages.hadoop}/hadoop/lib/'
    extra_args:
      - 'start'

server.execute的值是:

  execute:
    envs:
      CLASSPATH:
        - '${task.envs["CLASSPATH"]'
        - ':${task.dirs.packages.hadoop}/hadoop/etc/hadoop/'
        - ':${task.dirs.packages.hadoop}/hadoop/lib/'
    command: '${task.dirs.package}/scribed -p ${ports.base} -c ${task.dirs.conf}/scribed.conf'
    extra_args:
      - 'start'

这和extends基本规则里mapping的extends第一个例子的效果是一样的,除了server.execute不需要再显示extends common.execute

这个规则有个约束,两个配置项的值类型必须是兼容的。如果类型不兼容,属于合法的语法,但不再有这种隐含的extends。如果想阻断这种隐含的extends,可以用extends null这个语法。

extends解析顺序

extends的解析顺序是先做文件extends,然后是配置项extends和auto extends。配置项extends和auto extends同时做,如果有显式的配置项extends就不再做隐式的auto extends。

Imports and Functions

变量替换的${expr}表达式,可以是任意的python的表达式(符合eval()函数的要求)。表达式除了引用和操作配置项变量,也需要调用系统或者自定义的函数。

Functions

functions配置里放自定义的函数,这个指令必须放在配置文件的顶级配置项里。下面是个例子:

functions:
  java_generate_properties:
    args: 'props'
    body: |
      if not props: return ""
      return ' '.join(map(lambda x: '-D%s=%s' % x, props.iteritems()))
execute:
  command: 'java ${java_generate_properties(properties)} ${args}'
  properties: null
  args: ""

args是函数参数列表,格式和python函数一样(要去掉括号),可以有多个参数,缺省值,*args,和**kwargs
body是函数体,语法同python函数。

functions也受文件extends和配置项extends的作用,extends的情况下,derived配置可以重定义同名的函数。
函数的定义是发生在extends解析完成之后和变量求值替换之前。

自定义函数的作用域是顶级,使用时不需要functions.前缀,因此需要注意函数名不能和其它顶级配置相同。如有相同会报错。
如果需要引用external引入的配置文件里定义的行数,则需要external.<module>.的前缀,下面是个例子:

jre.yaml:

functions:
  java_generate_properties:
    args: 'props'
    body: |
      if not props: return ""
      return ' '.join(map(lambda x: '-D%s=%s' % x, props.iteritems()))

service.yaml:

external:
  jre: jre.yaml
execute:
  command: 'java ${external.jre.java_generate_properties(properties)} ${args}'
  properties: null
  args: ""

Imports

使用系统函数,即任何在python模块,而不是在yaml里定义的函数,需要先在imports配置里声明引入:

imports:
  - external_utils
  - from: os
    import: path
    as: path_utils
execute:
  command: 'java ${external_utils.java_generate_properties(properties)} ${args}'
  properties: null
  args: ""

imports是个sequence,每项是一个string,或者一个mapping:

from: <package>
import: <module>
as: <as_name>

mapping的fromas都是optional的。
如果是个scalar string,等价于import: <module>

类似的,import的模块作用域是顶级,也需要注意模块名不能和其它顶级配置相同。如有相同会报错。
引用external引入的配置文件里的模块也用类似的方式。

注意built-in函数(也就是__builtin__模块)不需要import。

Task

task描述

部署最终是以task为单位的,所以最后的配置生成都是task级别上进行。有些配置项,在不同的task上值不一样,比如host,或者一些runtime的值。所以需要引入task specific配置项。以这个为例:

jobs:
  scribe:
    ports:
      base: 12500
    hosts:
      - 192.168.0.1
      - 192.168.0.2

引入一个jobs.scribe.tasks的特殊配置项,它的类型是sequence,jobs.scribe.tasks[0]jobs.scribe.tasks[1]表示了scribe任务的两个task(即实例)的task specific配置项,比如idhost和一些和runtime相关的配置项。(TODO:明确定义runtime配置)

引入task这个关键词,表示当前正在部署的task specific配置项。例如部署scribe任务到第二个task时,task等价于jobs.scribe.tasks[1]task.id1, task.host是192.168.0.1

资源描述

任务可以描述自己的资源需求,我们先假定同一任务多个task的资源需求是一样的,所以这是个job级别的配置。下面是个例子:

jobs:
  scribe:
    resource:
      ram: 4096m
      cpu: 8
      hdd: 100g
    ports:
      base: 12500
    hosts:
      - 192.168.0.1
      - 192.168.0.2

多端口

单个task可以需要bind多个端口,我们假定这些端口是连续的。下面是个例子:

jobs:
  scribe:
    ports:
      base: 12500
      amount: 5
    hosts:
      - 192.168.0.1
      - 192.168.0.2

实际每个task可以bind 12500~12504这5个端口。

单机多实例

有可能需要在单个机器上部署同一个任务的多个task,由于多个task不能共用相同的端口,所以在语法上要有所区分。基本语法是host变成host:port的形式,表示这个task的ports.base变成这个指定的端口。另外有几种扩展语法。下面是个例子:

jobs:
  scribe:
    ports:
      base: 12500
      amount: 5
    hosts:
      - 192.168.0.1:12500
      - 192.168.0.1:12510
      - 192.168.0.2
      - 192.168.0.3:12500
      - 192.168.0.3:12520
      - 192.168.0.4/3

192.168.0.1:12500192.168.0.1:12510是基本语法,192.168.0.2等价于192.168.0.2:${ports.base}192.168.0.4/3等价于:

- 192.168.0.4:${ports.base + ports.amount * 0}
- 192.168.0.4:${ports.base + ports.amount * 1}
- 192.168.0.4:${ports.base + ports.amount * 2}

注意最终解析出的多个实例,host:port不能重复,否则会报错。

实例异构

有时候同一个任务的多个task有异构的需求,比如资源,甚至命令行。最直接的办法可以把它们放到不同的job里去,共享的配置用extends来实现。

另外一个方法是用groups这个配置项,下面是个例子:

jobs:
  scribe:
    resource: foo
    ports: foo
    execute: foo
    groups:
      - hosts:
          - 192.168.0.1:12500
          - 192.168.0.1:12510
      - resource: bar
        ports: bar
        hosts:
          - 192.168.0.2
      - resource: baz
        ports: baz
        hosts:
          - 192.168.0.3:12500
          - 192.168.0.3:12520
          - 192.168.0.4/3

这个例子里定义了3个group,第一个group的resource/ports是从job.scribe.resource/job.scribe.ports隐含extends而来的,另外两个group重新定义了resource/ports。这三个group的execute都是从job.scribe.execute隐含extends而来的。

注意任务里定义了groups后,就不能再有hosts了。另外groups可以嵌套。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值