升级脚本的编写
升级脚本包括了 Stage 和 Step 两种主要步骤
一个 Stage 由多个 Step 构成,Stage 的主要意义在于可以利用决策脚本(Decision Script)来跳过一整个 Stage
先看一个大概
因为内容是穿插的,所以这里先不解释,只是给个 Demo
stage('sql 更新', steps=on('.master', [
extract("prs-light.sql", "/data/.upgrade-resources-4.6.3-4.6.4/"),
command("mysql -uroot -p'xxx' < /data/.upgrade-resources-4.6.3-4.6.4/prs-light.sql"),
]))
stage('auth 更新', steps=[
on('.master', [
extract("tophant-auth.sql", "/data/.upgrade-resources-4.6.3-4.6.4/"),
command("mysql -uroot -p'xxx' < /data/.upgrade-resources-4.6.3-4.6.4/tophant-auth.sql"),
]),
command('ambot-service restart auth-server', on='+auth-server')
])
dynamic_script('decision-cluster.ash')
stage('集群升级大数据', condition=decision('cluster'), steps=[
update('com.tophant.prs.nta-flink-task-nohive'),
])
stage('单机升级大数据', condition=condition_not(decision('cluster')), steps=[
command("ln -fs /opt/nta-light/nta-flink-task/nta-flink-task.jar /dolphinscheduler/root/resources/", on="+dolphinscheduler")
])
stage('restart dolphinscheduler', steps=on('+dolphinscheduler', [
command('ambot-service restart dolphinscheduler'),
sleep(150),
]))
Stage 声明
声明一个 Stage 就是 stage(name, steps=...),其中 name 是 Stage 的名字(字符串类型),steps 是一个 Step 或一组 Step
stage 还可以接受一个 condition 的参数,只有对应的 Condition 为 true 整个 stage 才会执行;如果没有 condition 则一定会执行
动态脚本
通常而言,一个升级包的所有行为均在编译时决定,这可以让 ambot 有能力去完成一些自动检查,但实际的环境可能较为复杂,因此引入了动态脚本的能力来解决这一问题。
动态脚本有能力利用一些特定函数来访问和设定一些 ambot update 内部的变量,动态脚本的执行与普通脚本执行遵从同样的断点重执行逻辑。
在 v1.3.10 或更低版本的 ambot-update 中,该项名为「决策脚本」,函数名为
decision_script
新的动态脚本dynamic_script完全兼容原有的decision_script,且增加了更多功能
在 v1.3.11 起,dynamic_script 脚本支持 data 参数,可以直接将脚本内容写入而无需另行创建脚本文件
编写
动态脚本就是标准的 ambot script,并可用集群功能。目前 Dynamic Script 只能在执行升级的机器上(即 master)上执行。
支持在其他机器上执行已经在计划中
编写决策脚本只需在 BUILD 同级目录下创建一个名为 [name].apy 的文件,并写入脚本内容
场景:决策
动态脚本可以用于决策后面的 stage 是否要执行。在一个动态脚本中可以使用 store_decision(name, result) 函数来记录自己的决策,其中 name 称为决策名,类型为 str,result 为决策结果,必须为 True 或 False。一个动态脚本可以输出零个、一个或多个决策,对于同一个决策名调用多次 store_decision 则以最后一次调用为准。
一个示例:
stat = run("cat `ls -alt /data/.ambot-stats/install-start-init-at-*.json | head -n 1 | awk '{print $9}'` | jq .params.global_params.deploy_mode")
assert(stat)
store_decision('cluster', b'CLUSTER' in stat.stdout)
场景:自定义目标服务器
动态脚本可以设定自己的「虚拟 hostname」,用于在复杂情况下实现一些自定义的逻辑操作。在一个动态脚本中可以使用 store_resolved(name, hosts) 函数来记录虚拟 host 映射,其中 name 为变量名、hosts 为一个或一组真实 hostname/ip(类型可以是 str 或 str list,一般利用 core.resolve + 若干自定义逻辑实现)。
一个示例:
all = set(core.resolve('all'))
master = set(core.resolve('.master'))
store_resolved('not-master', all^master)
# 后面的 Step 可以用 on='{not-master}' 来指代所有非 master 的机器
如需使用
on('{xxx}', action)而非action(on='{xxx}')的语法,请将 ambot-packager 升级到 v1.6.0-beta.6 以上版本
使用
在编写好动态脚本后, 在两个 stage 之间可以插入 dynamic_script('[name].apy') 的代码,其中 [name].apy 是上一步所编写的动态脚本文件的名称
如果动态脚本输出了决策,则在该动态脚本声明后的 stage 支持在 stage 声明中传递 condition=decision('something') 的参数,其中 something 是决策名,即动态脚本中调用 store_decision 传递的第一个参数(决策名),这个名字与动态脚本文件名无关(注:这个决策必须是存在的,没有默认值,如果传入了一个不曾定义过的 decision 则会报错)。
decision('something') 实际会返回一个 Condition 对象,可用于下面所述的 Condition 布尔逻辑运算中。
如果动态脚本输出了自定义目标服务器,则可以在后续所有的 on 中使用 {customName} 的形式来指代上面创建的映射。
Condition
Condition 分为脚本定义 Decision 与逻辑 Condition 两类
脚本定义 Decision 即上面所述的利用 dynamic_script 声明并执行、利用 decision 来使用的 Condition。
- decision(”woddecision_name”) 当指定 decision 为 true 时返回 true,false 时返回 false,如果 decision 不存在会返回异常
逻辑 Condition 主要是用于布尔运算的,即实现需要同时满足多个 Condition、满足任意一个 Condition、不满足某个 Condition 的语义。
- condition_and(condition...) 当所有参数 condition 都为 true 时方为 true
- condition_or(condition...) 当任一参数 condition 为 true 时为 true
- condition_not(condition) 返回相反的 condition
on
on 实际上是 Step 的一种,只是因为 on 的特殊性进行了文档独立。另外,首次阅读的话可能会无法看懂本节意义,可以考虑和 Step 一同看、相互参照阅读
Step 的 on 参数
绝大多数 Step 都拥有着 on 参数用于指定相应步骤在哪个机器上执行(默认为遵从上下文,如果没有上下文则为全部)
on 参数接收的值是执行目标(字符串或字符串数组),与集群命令执行 中的 on 类似,但适用的是严格模式
实质上,所有的 on 参数都会被转换为一个独立的上下文
on 指令
on 指令用于创建一个执行上下文,所有被 on 「包裹」的指令都可以认为给了一个 on 参数(不支持的除外、在特定步骤专门指定的除外)
on 指令接收两个或更多参数。
第一个参数为执行目标,接收一个字符串或一个列表(语法请参见上面的 on 参数);
第二个及后面的参数为执行命令,接收若干个要在执行目标上执行的命令,其中参数也可以传递多个命令所组成的列表,其会被依次拍平展开。
例如如下命令
on(..., [install(...), update(...)], remove(...), [install(...)])
等同于
on(...,
install(...),
update(...),
remove(...),
install(...),
)
Step
Step 是升级体系的核心,描述着「可以做什么」
install - 安装 Package
用于在集群中指定的机器上安装 Package
函数原型:install(pkg_id, allow_dirty=False, allow_no_tag=False, start=True, enable=True, on=None)
- pkg_id: Package ID,需要在升级脚本目录中存在
pkg-id.ambot或pkg-id.ambotup文件 - on:指定执行目标,默认来源于上下文或全部
- 其余参数请参见 安装
建议 install 始终指定执行目标(利用上下文或 on),否则会在集群中的所有机器上安装
update - 升级 Package
用于在集群中指定的机器上升级 Package
函数原型:update(pkg_id, mode='update', allow_dirty=False, allow_no_tag=False, skip_version_verify=False, start_new=True, enable_new=True, start=None, not_start=None, enable=None, not_enable=None, on=None)
- pkg_id: Package ID,需要在升级脚本目录中存在
pkg-id.ambotup文件 - on:指定执行目标,默认来源于上下文或全部
- mode: 升级模式 + update:在已经存在的机器上升级,在不存在的机器上跳过,如果整个集群均不存在该 package 则报错 + install: 在已经存在的机器上升级、在不存在的机器上安装
- skip_version_verify: 是否跳过 Simple Update 中的来源版本号检测功能
- 其余参数请参见安装
在 mode=update 时执行目标一般可以直接为 all,因为该模式下仅会在 on 限定的范围内已经安装了低版本的机器上执行,另外如果存在着执行目标外的机器上有存在着这个 package 则会报错
skip_version_verify 从 v1.3.12 开始支持
remove - 卸载 Package
因副作用不可预料,暂不提供卸载的功能
extract - 解压上传
用于将一个资源文件或目录附加至升级包,并在升级过程中释放到指定机器上
函数原型:extract(local, remote, on=None)
- local 是本地文件或目录的名称,需要存在于升级目录下
- remote 是要上传的目标地址,必须为绝对路径。如果以
/结尾则代表着上传到 remote 对应的目录下、文件名与 local 相同
command - 执行命令
用于在目标机器上执行一个程序
函数原型:command(cmd, bash=True, timeout=0, on=None)
- cmd: 要执行的命令,可以为字符串或列表,即
"ls -al"或["ls", "-al"] - bash: 是否在 Bash 环境下执行,默认为 True;管道等高级功能需要依赖于 Bash
- timeout:超时时间,默认 0 代表 1min 超时,可选 -1 代表用不超时、数字代表单位为 s 的超时时间,或传递 Duration 对象进行细微控制
script - 执行脚本
用于在目标机器上执行一个脚本,脚本目前支持 Shell 或 AmbotScript,利用文件后缀名区分(.sh 用 Bash、.apy 用 ambot-script-executor)
函数原型:script(script_name, timeout=0, on=None)
- script_name: 脚本名称,例如
myscript.apy,相应脚本文件应当存在于升级目录下 - timeout:超时时间,默认 0 代表 1min 超时,可选 -1 代表用不超时、数字代表单位为 s 的超时时间,或传递 Duration 对象进行细微控制
script 指令可以接收一个 data 参数,接收一个字符串,代表了脚本文件的内容。当指定了 data 参数时打包时将不会去真实查找 script_name 所对应的文件,但 script_name 依然需要存在且格式正确,其会被用来展示和决策执行引擎。
data 参数从 v1.3.11 开始支持
uninstall_classic_service - 卸载 classic service
用于卸载一个已经不再使用的 classic service (即不来源于 package 的 service)
函数原型:uninstall_classic_service(service_name, on=None)
如果 service 本就不存在,或 service 来源于 package,会静默忽略,因此可以安全的直接使用该指令而无需预先判断 service 的状态
sleep - 休眠等待
用于让整个程序暂停执行一段时间
函数原型:sleep(t)
- t 可以为数字(代表时间 s)或一个 Duration 对象