升级脚本的编写

升级脚本包括了 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.ambotpkg-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 对象