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.ports
和jobs.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
。下面是个例子,表示当前配置项(execute
)extends 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表达方式:this
, up
, top
, auto
this
表示 包含当前上下文的最内层mapping,注意必须是 mapping。
上面这个例子${ports}
所在上下文的this
是jobs.scribe.execute
。
这个例子:
jobs:
magic: ${foo}
scribe:
ports:
base: 12500
hosts:
- ${bar}
${foo}
所在上下文的this
是jobs
,${bar}
所在上下文的this
是jobs.scribe
。
up
是指this
的上一层mapping,可以连续使用,比如up.up
。top
是指最顶层的mapping,即整个配置文件对应的mapping。
this
,up
和top
都是关键词。
上面三种作用域引用是确定性的,另外一种作用域auto
是不确定的。auto
是缺省的引用规则(如果没有使用this
,up
或top
的话),不需要显示指定,也没有定义auto
这个关键词。
auto
引用的规则是一个变量对当前和所有内含mapping是可见的。这个例子里:
jobs:
scribe:
ports:
base: 12500
execute:
command: 'scribed -p ${ports}'
ports
,up.ports
,scribe.ports
,jobs.scribe.ports
,top.jobs.scribe.ports
都会解析到同一个配置项。
this.ports
,up.up.ports
,top.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
即配置项级别的extends
,extends基本规则里的几个例子都是配置项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的from
和as
都是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配置项,比如id
,host
和一些和runtime相关的配置项。(TODO:明确定义runtime配置)
引入task
这个关键词,表示当前正在部署的task specific配置项。例如部署scribe任务到第二个task时,task
等价于jobs.scribe.tasks[1]
,task.id
是1
, 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:12500
,192.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可以嵌套。