环境准备
系统要求
ambot-packager 要求在 Linux 系统上使用,可用于 ARM 或 Intel/AMD 机器。
尽管提供了适用于 macOS 的安装包,但仅应用于测试,正式发布时请勿使用
安装 ambot-packager
如果是一个已经安装好 ambot-service 的机器,可执行 ambot-service install-self --install-packager 补充安装 ambot-packager
如果是一个全新的机器(并未安装过 ambot-service),可直接将 ambot-service 程序重命名或软链接为名称为 ambot-packager 的程序且加入至系统 PATH 中使用
查看 ambot-packager 版本
ambot-packager 会不时更新,某些新功能需要足够新的 ambot-packager 的版本才可使用。因此请在使用前预先确认所使用的 packager 版本
可以利用如下命令看到 ambot-packager 的版本
ambot-packager -v
创建 Package
Package 的创建强依赖于 git,因此要求必须拥有一个 git 仓库才会进行对应的打包
注:以下均需要使用 v1.4.0 版本的 ambot-packager,低版本可能存在一些问题
创建可以被打包的 Repo
如果是一个新的项目,需要存在 **/.ambot/BUILD 文件才会被 ambot-packager 认为是一个可以被打包的项目
如果存在
BUILD-xxx则代表使用了进阶的 Build Policy 功能
BUILD 脚本的编写请阅读 Build Script 相关内容
一个 Repo 中可以包含若干个 Package,ambot-packager 程序会自动递归寻找,需要注意的是,多个 package 的 id 不能相同
基于 Package 打包
基于一个 repo 进行打包使用的是 ambot-packager new 命令
$ ambot-packager new -h
创建一个全新的 ambot 软件包
Usage:
ambot-packager new <repo> [flags]
Flags:
--allow-invalid-tag
-b, --branch string
--build-policy strings
-h, --help help for new
-t, --to string
--unsafe-allow-dirty
一个典型的使用方法是 ambot-packager new REPO -t /path/to/somewhere
该命令执行完毕后会在 /path/to/somewhere 生成 [pkg-id].ambot 的文件,pkg-id 部分为对应的 package id,为了防止命名冲突该文件名不支持自定义
Repo 地址
Repo 地址支持一个本地的绝对路径或 SSH URL,前者用于本地测试用,后者通常给 CI 使用
在使用本地的绝对路径时应当注意目标必须是一个 git 目录并且 checkout 到了目标分支(此时 -b 参数是没有意义的)
在使用 SSH URL 时应当注意在 Repo 设置中 (Settings → Repository → Deploy Keys → Privately accessible deploy keys)中启用 AutoDeployKey-PRS key(19:0a:33:6a:bb:01:ee:ee:04:77:14:66:a3:d4:69:80),ambot-packager 会自动 clone 相应 repo 并 checkout 到指定分支(分支由 -b 参数指定,默认为 HEAD)
参数
unsafe-allow-dirty
该参数只会在本地路径中出现,如果本地 repo 的 Worktree 中存在任何尚未提交的内容(即 git status -s 输出不为空),则认为当前是 dirty 状态,所打出的 package 为 Dirty Package。
因为 Dirty Package 无法被追溯代码(相关改动并未提交到仓库),因此默认情况下是禁止使用 Dirty 的。如果你确有需要,可以使用明确指定 --unsafe-allow-dirty 来允许 Dirty Package 被打包(但不推荐),另外,Dirty Package 默认情况下也是禁止安装的,具体可参考安装文档的相关章节。
allow-invalid-tag
如果使用的版本号不遵从语义化版本号的相关规范,会导致 package 打包失败。如果是仅自己测试,可以打开 --allow-invalid-tag 选项,这时会自动创建一个类似 0.0.0-xxx 的版本号。
`
安装 Package
一个以 .ambot 后缀的文件即为一个 Ambot Package 的安装包,安装它只需要很简单的赋予可执行权限并执行即可
$ ./xxx.ambot -h
Usage:
ambot-install [flags]
Flags:
-T, --allow-no-tag allow install no-tag package
-c, --clean clean package (not execute)
-h, --help help for ambot-install
-s, --just-show just show info (not install)
--just-show-tag just show tag (not install)
--just-show-version just show version (not install)
-E, --not-enable do not enable service after install
-S, --not-start do not start service after install
--unsafe-allow-dirty (unsafe) allow install dirty package
-y, --yes auto confirm for all choices
基础使用
一般而言,在正常的过程中,直接 ./xxx.ambot 即可安装,无需额外指定任何其他命令行参数
对于自动化的过程,由于默认情况下会展示出安装信息并确认,因此需要指定 -y 命令行参数来跳过这个确认步骤
仅展示
有的时候可能只是想获得这个 package 的元信息,这时可以使用 -s 或 --just-show-tag --just-show-version 参数,其中 -s 是给人看的,后两个是给自动化工具获取信息使用的
Service 控制
默认情况下,Ambot Package 的服务会在安装完成后自动启用(enable)和启动(start),如果想要修改这个行为,可以使用 --not-enable --not-start 参数
非标准 Package
默认情况下只允许安装基于 Tag 打出来的 Package,如果想要安装不是 Tag 的可以使用 --allow-no-tag 参数,如果想要安装 Dirty Package 则需要使用 --unsafe-allow-dirty 参数
简单升级包
简单升级包用于将同一个 Package 从低版本升级到高版本
创建简单升级包
$ ambot-packager convert-update -h
将一个 ambot 软件包转换为升级包
Usage:
ambot-packager convert-update <install-package-path> [flags]
Flags:
-h, --help help for convert-update
--source-allow strings
--source-expr string
使用 ambot-packager convert-update /path/to/xxx.ambot 即可在 .ambot 同目录下生成 xxx.ambotup 简单升级包
必须限制只有从哪个版本到哪个版本才可以用这个升级包升级,支持两种方式
source-allow
可以罗列若干个来源版本,可以将这个 flag 传递多次或是多个用逗号分隔。版本号没有前缀v
--source-allow 1.0.0代表仅允许从 v1.0.0 升级--source-allow 1.0.0,1.1.0和--source-allow 1.0.0 --source-allow 1.1.0代表仅允许从 v1.0.0 或 v1.1.0 升级
source-allow 是支持指定一个更高版本的,即允许降级
source-expr
用于指定一组满足条件的版本,可以从所有满足该表达式的版本进行升级
具体语法请参考 语义化版本号 - 约束
*不支持先行版本号- 指定多个表达式时,满足任一即可
- source-expr 不可用于降级
利用简易安装包升级
$ ./xxx.ambotup -h
Usage:
ambot-update-simple [flags]
Flags:
-T, --allow-no-tag allow install no-tag package
--unsafe-allow-dirty (unsafe) allow install dirty package
--start strings services should start after update
--enable strings services should enable after update
--not-start strings services should not start after update
--not-enable strings services should not enable after update
-S, --not-start-new do not start new service after update
-E, --not-enable-new do not enable new service after update
-y, --yes auto confirm for all choices
-s, --just-show just show info (not install)
--as-installer 将升级包作为安装包使用
--author Author standard 'Name <name@example.com>' format
-h, --help help for ambot-update-simple
与安装包类似,简单升级包是以 .ambotup 为后缀,赋予可执行权限后可以直接执行
手动使用
手动使用时,直接执行 ./xxx.ambotup 即可,会交互式的引导你输入一系列的内容
自动化使用
如果想要在自动化中使用(非交互式),请遵循如下要求
- 始终传递
-y来跳过确认 - 始终传递
--author参数,例如--author 'ci@tophant.com' - 始终对服务指定
--start--not-start、--enable--not-enable
默认情况下,升级会保持原有服务的启用/启动状态不同,并将新的服务的启用/启动状态设定为开 可以通过
--start--not-start--not-start-new--enable--not-enable--not-enable-new来修改这个默认行为 自动化中始终建议明确的设定这个行为
allow-no-tag, unsafe-allow-dirty
与 安装 里面的一致,这里的 unsafe-allow-dirty 仅用于安装 Dirty Package
如果本身相关 Package 处于 Update Dirty 的状态,则必须手动 revert,具体可参考 术语:Dirty
利用简易升级包安装
某些情况可能不想升级,而是想直接安装。因为升级包中存储了全量的安装包,因此这是可行的,仅需指定 --as-installer 参数即可。
--as-installer 是一个特殊参数,会完全改变程序的行为,使用 --as-installer 时其他参数遵从安装包安装的参数,而不是升级的参数
$ ./xxx.ambotup --as-installer -h
ambot-install [flags]
Flags:
-T, --allow-no-tag allow install no-tag package
-c, --clean clean package (not execute)
-h, --help help for ambot-install
-s, --just-show just show info (not install)
--just-show-tag just show tag (not install)
--just-show-version just show version (not install)
-E, --not-enable do not enable service after install
-S, --not-start do not start service after install
--unsafe-allow-dirty (unsafe) allow install dirty package
-y, --yes auto confirm for all choices
例如,常见的错误是,在自动化中错误的传递了 --author
具体文档请参考 安装
修改简单升级包的可用来源版本
某些情形下,可能出现打了升级包,现场发现来源版本不对,需要修改的场景,做法是将它「恢复」成安装包,再重打升级包。
ambot-packager convert-update-back ./xxx.ambotup
ambot-packager convert-update ./xxx.ambot --source-expr '1.2.3'
升级系统
与 简单升级包 不同,升级包是用来完成整个集群升级的,并可执行一些进阶操作
如果需要想要创建升级包,请创建一个目录,并在目录下放置 BUILD 升级脚本,资源放置于其中。
创建升级包
$ ambot-packager new-update /path/to/update
其中,/path/to/update 是升级包的路径。运行完会生成 /path/to/update.ambotupdate 文件,这个是一个升级包。
为了方便,该升级包提供了 Amobot Bundle 的辅助创建脚本,有需要可以复制粘贴执行。
目前 Ambot Bundle 在一些系统下可能有异常行为,因此可以考虑先不做使用
执行升级包
Ambot Bundle
直接在 master 机器上执行即可
如果发现运行过程中 ambot-service 的安装被异常跳过导致出现问题,请手动执行
/tmp/ambot-service uc进行安装
执行 Ambot Bundle 会自动完成升级 ambot-service、解压 ambotupdate 和执行升级的流程
如果中途出现错误,解决后可以直接重新执行 Ambot Bundle,也可以直接执行 ambot-update /data/updates/[update-name] ([update-name] 与上面的 /path/to/update 中的 update 一致)
如果没有 ambot-update 程序,请执行
ln -s /usr/bin/ambot-service /usr/bin/ambot-update
Ambot Update File
拿到一个 xxx.ambotupdate 文件,请将其传到 master 上,并依次执行以下命令
# 解压升级包(期间会执行自动的验证)
ambot-util package unpack xxx.ambotupdate -t /data/updates/xxx
# 安装
ambot-update /data/updates/xxx
异常恢复
Ambot Update 有着全自动的异常恢复机制,一般如果是命令执行失败可以直接通过重新执行 ambot-update /data/updates/xxx 来二次执行(会自动跳过已经执行过的)
如果需要修改 Decision Script 执行结果、重新执行某一步、修改步骤等,请阅读 Ambot Update Insider
升级脚本的编写
升级脚本包括了 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 对象
Update Insider
本文将暴露一些 Ambot Update 的设计机制,用于在有特殊需要时进行一定处理
文件结构
标准的 Ambot Bundle .sh 文件在执行后会将相关资源解压至 /data/updates/[name]-[random] 目录下,其中 name 为更新包名称,random 为随机字符串。对于一个特定的 Ambot Bundle 而言,其 name 和 random 是固定的。
该目录下,我们需要关注的主要是 meta.json just-for-view.json 和若干以 UUID 命名的文件。
meta.json 和 just-for-view.json 存储的是元信息,二者的区别在于对 meta.json 的修改会在升级流程中被应用,而 just-for-view.json 只能用于查看,对它的修改不会导致执行过程的变化。just-for-view.json 相比于 meta.json 增加了一些用于快速定位的信息,因此对于大多数场景而言只需要去关注 just-for-view.json 中的信息即可。
meta.json 和 just-for-view.json 的一个最主要的区别就是各种 ID 的存储格式不同。Ambot Bundle 广泛使用了 UUID 作为唯一标识,包括 StageID、ActionID、FileID 都是 UUID 的格式。在 meta.json 中的 ID 使用的是内部格式存储,而在 just-for-view.json 中则使用了通常的字符串格式进行展示,方便搜索(执行过程中输出的日志均为字符串格式)。两种格式之间转换可以使用 ambot-util uuid 工具进行
ambot-util uuid encode bf0c032c-312c-4b39-89df-fd3764a68c78
# vwwDLDEsSzmJ3/03ZKaMeA==
ambot-util uuid decode vwwDLDEsSzmJ3/03ZKaMeA==
# bf0c032c-312c-4b39-89df-fd3764a68c78
修改嵌入文件
这里的嵌入文件包括远程脚本和要自定义的资源,但不包括嵌入的安装包和升级包
利用 ActionID 在 just-for-view.json 中搜索找到相应的 Action,可以看到一个 Ref Object,key 包括 id uuid name hash 等,即下面的样子
{
"id":"0",
"dataOffset":"0",
"dataLen":"6874743",
"uuid":"1e564420-7b24-4b4e-9843-cbc1ba6563f0",
"name":"xxx.apy",
"hash":"X7WaEUOkh6bg395LavjlxrLiJL+Ebw6JFyW86+t9uZo=",
"isDir":false
}
我们利用这里的 uuid 的值找到和 just-for-view.json 同级目录下以这个 UUID 命名的文件(无扩展名),对其内容进行修改即可。对内容的修改会直接应用,无需修改元数据。
对于嵌入目录,这里的文件是一个 gzip tar 压缩包,可以进行修改
目前嵌入目录仅用于 extract 命令,因此如需修改完全可以考虑进入到上传后的目标修改,在一台机器上修改完后利用ambot-run -t xx @distribute /path/to/modified/file即可一键分发修改后的文件
修改指令参数
指令参数的修改比较繁琐,需要进入 meta.json 进行修改。首先利用 ActionID 的 encode 版本(ambot-util uuid encode xxx-xxx)找到 Action,然后进行修改
对于元信息的修改需要特别注意,建议在修改前进行提前备份,否则可能会导致无法恢复的全局数据损坏。对元数据的修改建议仅在开发人员的指导下进行。
缓存机制
所有的执行过程会在 /data/.ambot/ambot-packages/upgrade-steps/[Ambot Bundle UUID] 下记录缓存信息来实现自动跳过已经执行的过程的逻辑。Ambot Bundle UUID 为 just-for-view.json 中最上面的 fileId 字段。
跳过或重复执行指定步骤
如果一个 Action 被执行,会在缓存目录下的 stages/{StageID}/{ActionID} 目录创建 done 文件。因此,如果想要跳过一个步骤的执行只需创建 done 文件、如需重新执行某一步骤只需删除 done 文件。
小技巧:快速进入 Action 目录
如果只知道 ActionID,可以简单的利用 bash 的通配来搜索目录,即直接执行 cd /data/.ambot/ambot-packages/upgrade-steps/**/{ActionID} ,Bash 会替你找到相关步骤。因为 ActionID 使用的是 UUID 天然保证了不会重复,因此不用担心跳转到了错误的目录下。
注意:递归 Action 重复执行
如果是想重复执行某一步骤,如果该步骤是嵌套的,而父 Action 也已经执行成功,则你只是单纯删除这个 Action 目录下的 done 文件是无效的,你需要删除该路径下所有的 done 文件。目前想要找到整条路径只有利用 just-for-view.json 肉眼寻找或是查看执行日志(首次执行会打印完整的 Action 路径)
重新执行决策脚本 / 修改决策信息
所有的动态决策决策脚本都只会执行一次,其执行后会在缓存目录下生成 dynamic/ScriptID 文件,内容为决策信息。可以删除这一文件来让决策脚本重新执行或修改该文件的内容来修改决策信息。
升级过程出现已经安装过高版本的情形
⚠️ 请先行确认为何会出现安装过高版本,强行降级到升级包的版本可能出现未知问题
方法一:跳过版本检查
报错的步骤会打印出一个 UUID(多个 Step 取最后出现的),使用 ambot-util uuid encode <uuid> 获取内部 ID
在 meta.json 中搜索获取到的内部 ID,可以找到 skipVersionVerify,将它的值改为 False
该功能仅在 ambot-update v1.3.12 或更高版本中有效
方法二:手动安装旧版本
利用报错的包名搜索 just-for-view.json 文件,寻找到包名第二行跟随了一个 ref 键的位置,记录 ref.uuid(例如 1e564420-7b24-4b4e-9843-cbc1ba6563f0)

在 just-for-view.json 的同级目录下,找到这个文件,复制出来并添加 .ambotup 扩展名,赋予其可执行权限
cp 1e564420-7b24-4b4e-9843-cbc1ba6563f0 xxx.ambotup
chmod +x xxx.ambotup
然后利用标准的手动升级添加环境变量进行忽略版本强制升级
Ambot Bundle
Ambot Bundle 是下一代采用的打包体系,依然在开发中,相关文档可能不全或过时
意义
Ambot Bundle 支持自动完成如下行为
- 附加最新版本的 ambot-service 并在集群中自动升级
- 将内嵌文件进行完整性验证并解压
- 解压后执行一个自定义脚本
Ambot Bundle 的第一个应用为集群升级,在该功能发布并稳定后,升级只需要提供一个单文件 Ambot Bundle 而无需提供一个 zip 并指导解压等
可变参数
内置 ambot-service
- 是否内置
- 是否自动安装
内置 tar 包
- 是否内置
- 默认解压路径
- 解压后执行脚本
生成方式
$ ambot-packager new-bundle -h
Usage:
ambot-packager new-bundle [flags]
Flags:
--auto-execute Auto execute command when executing the bundle (only if --with and --command is set) (default true)
--auto-extract Auto extract archive when executing the bundle (only if --with is set) (default true)
--auto-install Auto install ambot-service when executing the bundle (only if --with-installer is set) (default true)
--command string Command to execute after extracting archive (only if --with is set)
--default-prefix string Default prefix for extracting archive (only if --with is set)
-h, --help help for new-bundle
-n, --name string name for the bundle
-t, --save-to string Save the bundle to (default: $(pwd)/name.sh)
--with string Embed archive to the bundle
--with-installer Embed current version of ambot-service to the bundle (default true)
运行方式
生成的 Ambot Bundle 就是标准的 .sh 文件,可以在任何机器上(不依赖过往 ambot-service)直接执行
语义化版本号
定义
Ambot Package 遵从标准的语义化版本号规范,即版本号应当是 版本号格式:主版本号.功能版本号.小版本号[-先行版本号]+修订号 的格式(注意前置 v 的符号是 tag 的要求,而不是版本号的要求)。
根据语义化版本号的规范,版本号必须为 vX.Y.Z 的形式,然而因历史原因,PRS 产品使用过诸如 v1.2、v1.2.3.4 等非标准的三位样式,ambot package 做了一定的兼容。这个兼容未来会被删除,请新的
package 停止使用。
版本号比较
- 非同一产品,无法比较
- 主版本号大的,版本较高
- 功能版本号大的,版本较高
- 小版本号大的,版本较高
- 先行版本号
- 无先行版本号的,版本较高
- 均无先行版本号的【属于 bug】,发布日期较晚版本较高
- 发布日期同一天的,修订号高的版本较高
- 修订号相同的,同样高【属于 bug】
- 发布日期同一天的,修订号高的版本较高
- 均无先行版本号的【属于 bug】,发布日期较晚版本较高
- 先行版本号比较
- 先行版本号相同的,发布日期较晚版本较高
- 发布日期同一天的,修订号高的版本较高
- 修订号相同的,同样高
- 发布日期同一天的,修订号高的版本较高
- 先行版本号相同的,发布日期较晚版本较高
- 无先行版本号的,版本较高
约束
目前版本约束不适用于先行版本号
Ambot Package 的 Semver 约束支持如下语法
基本比较
=: equal (aliased to no operator)!=: not equal- 无通配符情况下执行全文匹配
- 有通配符情况
- 如果约束为 pre-release,版本为 release,返回 true
- 如果约束为 release,版本为 pre-release,返回不可用
- 通配符前的部分不能相等
>: greater than<: less than>=: greater than or equal to<=: less than or equal to
范围比较
1.2 - 1.4.5which is equivalent to>= 1.2, <= 1.4.52.3.4 - 4.5which is equivalent to>= 2.3.4 <= 4.5
通配符
1.2.xis equivalent to>= 1.2.0 < 1.3.0>= 1.2.xis equivalent to>= 1.2.0<= 2.xis equivalent to<= 3*is equivalent to>= 0.0.0
版本约束
~1.2.3is equivalent to>= 1.2.3 < 1.3.0~1is equivalent to>= 1, < 2~2.3is equivalent to>= 2.3 < 2.4~1.2.xis equivalent to>= 1.2.0 < 1.3.0~1.xis equivalent to>= 1 < 2
主版本约束
^1.2.3is equivalent to>= 1.2.3, < 2.0.0^1.2.xis equivalent to>= 1.2.0, < 2.0.0^2.3is equivalent to>= 2.3, < 3^2.xis equivalent to>= 2.0.0, < 3^0.2.3is equivalent to>=0.2.3 <0.3.0^0.2is equivalent to>=0.2.0 <0.3.0^0.0.3is equivalent to>=0.0.3 <0.0.4^0.0is equivalent to>=0.0.0 <0.1.0^0is equivalent to>=0.0.0 <1.0.0
术语:Dirty/Changed
ambot package 体系中广泛使用了 Dirty 这一概念,但在不同的场景下其语义不尽相同。
Dirty Package
如果在 ambot-version 中查看到 tag 是 <no tag> D ,其中的 D 就代表 dirty,使用 -a 或 --show-dirty 更能看到一个独立的 Dirty 列,同时 Version 列也会包括 -dirty 后缀,BUILD 脚本中也有 dirty 这一预定义变量。
以上的 dirty 是一个相同的含义,其含义是在打包时工作路径存在未提交内容(包括 untracked, unstaged, staged),因此 dirty package 的 commit 是无意义的,因为实际执行代码可能已经在编译前被修改。
dirty package 的存在是危险的,其意味着相关版本管理均将无效。同时 dirty package 无法被升级(除非特殊为 dirty 进行打包)。因此,ambot-packager 默认将拒绝打包 dirty 环境,同时 ambot-install 也将拒绝安装 dirty package
(Update) Dirty 状态
在正在确定的升级流程中,提到了「如果是 dirty 状态则禁止升级」,我们在本段中将这一 dirty 称为 update dirty。
可能为 Update Dirty 的内容目前有三个:Software Mount、Config Mount、Service。
对于 Mount 而言(包括 Software Mount 和 Update Mount),Update Dirty 状态等同于 Update Changed(详情请参考下面的 Changed 状态)。
对于 Service 而言,Update Changed 状态会导致 Update Dirty(详情请参考下面的 Changed 状态),另外,如果使用过 ambot-service edit 并进行了实质修改则也为 Update Dirty。
你可以使用 ambot-git why-dirty -p [pkg-id] 来查看具体什么导致了 Dirty。
解决 Update Dirty 的唯一办法是使用 ambot-git revert 命令,具体请参考文档或使用 ambot-git revert -h 查看帮助(这个命令可能失败,因此只允许手工执行、不可放入自动化)。
(Update) Changed 状态
一个 Changed 状态会始终导致 Dirty 状态
如果你在 ambot-version 中查看到一行为黄色、package 名称后有 * 或是使用 -a 或 -c 参数时出现的 Changed 列不为空即证明这个 Package 处于 Changed 状态。
Changed 状态意味着所展示的目录(安装路径或配置路径)存在未经标准流程的修改,例如手动编辑过配置文件、替换过程序等,或是修改了服务配置但并未使用 ambot-service edit 。
注: Config Changed 状态可能是异常情况也可能是因为 vars 无法覆盖所有的情形导致的。如果一个 Config 文件的变更完全可以利用 vars 修改那么这是一个异常,相关修改人员应当复原这一修改并依据标准流程重新修改。而如果一个 config 文件初期并未被标记为依靠模板渲染或是模板变量未覆盖所期望的修改,则这不是一个异常,这种情况相关人员在先行修改后应当告知部署团队在下一个版本中将之稳定为模板,并在相关版本升级后将这一修改使用模板完成。
你可以使用 ambot-git why-changed -p [pkg-id] 来查看具体什么导致了 Changed。
解决 Update Dirty 的唯一办法是使用 ambot-git restore 命令,具体请参考文档或使用 ambot-git restore -h 查看帮助(这个命令可能失败,因此只允许手工执行、不可放入自动化)。
BUILD 脚本
BUILD 脚本基于 ambot-script,请预先阅读相关文档。BUILD 脚本的运行环境在打包机器上,因此无法使用集群功能。
- 环境信息
- 预定义变量
- 内置函数
- package(id: string)
- check_version(string) → bool
- version(string)
- require_vars(vars: dict<string, string>)
- add_service(service: str, name?: str, vars?: Dict[str, str])
- add_mount(mountpoint: string, file_root: string)
- add_config(mountpoint: string, file_root: string)
- add_script(script: string, name: string = '', auto_run: bool = false)
环境信息
工作目录
build 脚本默认运行目录为 .ambot 目录的父目录
即,一个位于 /opt/something/.ambot/BUILD 的脚本的执行工作目录为 /opt/something,其值保证为绝对路径、且不以 / 结尾。
典型构成
一个典型的 BUILD 脚本包括如下内容
- 声明自己的 id
- 添加需要包含的目录(不可修改的 Software、可利用 vars 渲染的 Config)
- 添加需要包含的 Service
ID 规范
package identifier (即可 pkg-id) 为域名反写,请遵从以下规范
- 只能含有字母、数字、中横线
- 公司内部项目以 com.tophant. 开头
- 特定项目使用 com.tophant.[项目名]. 开头,例如
com.tophant.prs.nta-api - 公共项目使用 com.tophant.common. 开头,任何未来可能扩展用途的项目均应当使用 common
- 特定项目使用 com.tophant.[项目名]. 开头,例如
- 第三方项目以相应项目的官方标识符或官网地址反写开头,例如
org.elasticsearch.es - 多部分以
.分隔,应当至少包括三个部分,但不要超过五个部分- 例如不要使用
org.elasticsearch - 多个部分用于表示层级关系,例如
com.tophant.common.matrix.mh可以直接认为是 com.tophant.common.matrix 的子项目
- 例如不要使用
- identifier 最后一个部分为 name,其应当是尽量唯一的,否则在机器上进行筛选等会出问题
- 因此 name 不能为一个通用概念,而必须可以直接看出这个 package 对应的内容
- identifier 一旦确定无法修改,相关升级流程等均依靠此决定,因此不要增加
configservice等后缀,因为后期可能会添加其他内容,界时相关名称会对用户导致误导- 例如初期 matrix 只包括 config,但不要使用 com.tophant.common.matrix-config 名称,而只使用 com.tophant.common.matrix
- 在初期只包括 config 时,可以指定 display 参数(默认与 name 保持一致),display 参数应当严格设定为
name (comment),即原始名称 + 空格 + 括号内写注释,例如上述例子的 identifier 为 com.tophant.common.matrix,其 name 自动定为 matrix,因此指定 display 时应当为matrix (config)
所有 identifier 确定后无法修改并且不能产生冲突,不当的 display 可能会使用户产生误解,因此如果需要使用和 identifier 不同的也应当慎重考虑
预定义变量
tag, describe_tag, commit, dirty
打包项目的 git 版本信息,可用于计算版本使用
- tag: string, 最近一次的 tag
- describe_tag: string, 当前 tag 的描述信息,即
git describe --tags的执行结果 - commit: string, 最近一次提交的 commit id
- dirty: bool, 最近一次提交后是否还有修改
git_describe
基于上述与定义变量计算而来,逻辑如下
def get_git_describe():
prefix = describe_tag
if prefix == "": # 从来没有过 tag
prefix = commit[:8]
suffix = ""
if dirty: # 在最后一次后仍然有提交
suffix = "-dirty"
return prefix + suffix
assert(get_git_describe() == git_describe)
内置函数
package(id: string)
声明 package 信息,必须为 BUILD 脚本的第一条指令。
check_version(string) → bool
检查给定的 version 是否合法(符合版本号规范,不以 v 开头)
version(string)
默认情况下的版本号会基于 tag, describe_ tag, commit, dirty 信息进行综合计算得到,但前提是得到的 describe_tag 是一个标准的 semver 版本信息。
该函数用于提供一个版本号来覆盖默认行为。如果提供的版本号不合法会直接中断脚本执行,可以使用 check_version 来检测 version 是否合法。
对于非标 tag 可以使用 version 指令来指定版本号,当然必须注意的是因 BUILD 脚本被设计为不经常修改,因此 version 所传递的内容必须是利用预定义变量计算出来的,以下是默认行为,可以基于其进行修改
# 在脚本执行前已经预定义好了所需变量
# * tag = v1.0.0
# * describe_tag = v1.0.0-3-abcdef
# * commit = 012345678...abcdef
# * dirty = False
# * git_describe = v1.0.0-3-abcdef
def get_version():
describe = git_describe
if describe.startswith("test_"): # 测试 tag 特殊逻辑
describe = describe[len("test_"):] + "-test"
if not (describe.startswith("v") or describe.startswith("V")):
# 非版本号
return "0.0.0-" + describe
if check_version(describe[1:]):
return describe[1:]
else:
return "0.0.0-" + describe
package(...)
version(get_version())
# ... 其他逻辑
require_vars(vars: dict<string, string>)
声明这个包安装时依赖的变量列表及类型,如果安装时对应的变量不存在或类型不合法,则会拒绝安装
vars 参数是一个 dict,其 key 为变量名称,value 为变量的类型约束,如果在安装时变量不存在或类型不匹配则会拒绝安装。
变量的名称应当遵守如下约定
- package 特定配置应当以
package_name.var_name命名,例如 es 的 hostname 应当命名为es.hostname - 公有配置应当先行在自动化相关项目中记录并讨论审核确定再使用
- 配置的层级用
.分隔,任何一层的命名均为以字母开头后接若干字母、数字、下划线、中划线
任何配置名称和类型均应当在对应自动化项目中记录
变量的类型目前仅支持
- string
- number
- bool
- array
- object
对于 array 不支持具体检查底层类型,未来可能会支持在 install 脚本中进行检查的能力
add_service(service: str, name?: str, vars?: Dict[str, str])
增加一个 service,service 参数传递的是 service 文件的名称,例如指定了 xx 则会依次搜索 .ambot/services/xx.service, .ambot/xx.service, .ambot/../services/xx.service, .ambot/../xx.service, ,这一搜索路径与工作目录无关。service 参数也可以指定一个相对路径,会基于当前工作目录进行搜索,使用相对路径时必须为全路径,即不能省略末尾的 .service 。
在 add_service 时,会在匹配到的 .service 文件的同级目录下查找同名但后缀为 .env 的文件,即如果添加了 a.service 则会在该文件的同级查找 a.env,如果找到则将此环境变量文件一同打包。环境变量文件可以使用模板变量,会自动渲染。
如果指定了 name 参数,则会 service 对应的文件、安装名则为 name 指定的
如果指定了 vars 参数,那么 ENV 会进行模板渲染(服务文件不会,因此如需使用请在 Service 中引用环境变量), vars 参数是一个 kv 对象,指示了渲染 ENV 需要什么变量、变量的类型是什么(key 为变量名、value 为类型)
如需了解怎么编写 Service 文件请参考 写给开发看的 Service 文件编写指南
add_mount(mountpoint: string, file_root: string)
增加软件包挂载,mountpoint 是未来在目标机器上进行挂载的路径,必须是绝对路径,file_root 是要打包的目录,应当是基于当前工作目录的相对路径
mountpoint 应当遵循一定的规则
- 任何两个包中不能重复
- 有一定的可预测性,例如 mount 放到 /opt/[name] 下而 config 放到 /data/[name]/config 下等,可以参考 建议的目录结构
mountpoint 必须保证不重复
add_config(mountpoint: string, file_root: string)
与 add_mount 类似,但是挂载配置文件使用的,并支持模板渲染功能。
add_config() 执行会返回一个 ConfigMutator 对象,其包括一个成员方法 render(file: str, ..., vars: Dict[str, str])
render 的使用方式类似 render("file1", "file2", { "xxx.yyy": "number" })
- file 指定了 filename 的相对于 config mount root 的路径,可以传递多个
- vars 则是一个 kv 对象,指示了这个模板渲染需要什么变量、变量的类型是什么(key 为变量名、value 为类型)
add_script(script: string, name: string = '', auto_run: bool = false)
该函数在 v1.6.0-beta.4 或更高版本可用
增加一个 script,script 参数传递的是 script 文件的名称,例如指定了 xx.apy 则会依次搜索 .ambot/scripts/xx.apy, .ambot/xx.apy, .ambot/../scripts/xx.apy, .ambot/../xx.apy, ,这一搜索路径与工作目录无关。script 参数也可以指定一个相对路径,会基于当前工作目录进行搜索。
script 的扩展名必须为 .apy 或 .ash 或 .py 或 .sh,使用其他扩展名会在运行期报错。
如果省略 name 参数,则自动从 script 中提取 name(xx.apy -> xx) ,name 参数(无论是手动指定的还是自动生成的)不可与 package name 相同。
建议的目录结构
ambot 并未强制以下内容,这仅仅是建议,或者说,约定?
建议在使用 Ambot Package 体系后将组件的目录进行如下规范
- 程序路径:/opt/
/... - 配置路径:/etc/prs/
/... - 数据路径:/data/
/... - 日志路径:/data/log/
/... - 临时路径:/data/tmp/
/...
写给开发看的 Service 文件编写指南
新版的 ambot-service 改变了 service 文件的分发模式,之前的 service 是由 ambot deploy 项目维护的,现改为由开发维护,因此开发应当懂得一定的 service 文件概念与编写方式。
位置和名称约定
ambot 会自动扫描项目文件夹内的 .ambot 目录(详见扫描机制,这里应当有一个超链接),如果其中有一个文件夹为 services ,并且其中有一个或多个以 .service 结尾的文件,将被视为启用服务。
根据相关文档,这一文件名(包括 .service 后缀)不能超过 256 字符,并且只能由 ASCII 字母、数字、冒号、中横线、下划线、点和反斜杠组成,ambot-service 推荐只包括字母、数字和中横线。在整个部署项目中,service 名称不应有重复,可进入 Ambot 自动化部署项目管理平台(这个平台未来一定会有的,到时候这里会放个链接)查询名称是否被占用,如果名称被占用则在部署时安装 service 会报错。
编写简述
一个最最最最最基本的 service 只要包括
[Service]
ExecStart = /usr/bin/echo 123
即满足以下条件
- 拥有 Service 段
- Service 段下有 ExecStart 配置
- ExecStart 配置是要运行的命令,命令必须是绝对路径
但一个良好的 service 应当包括如下内容
[Unit]
Description = Test
Wants = ...
After = ...
[Service]
ExecStart = /usr/bin/echo 123
[Install]
Unit 段
名称 Description
虽然 Description 的含义是「描述」,但根据官方文档,其实它的作用是名称
A human readable name for the unit. This is used by systemd (and other UIs) as the label for the unit, so this string should identify the unit rather than describe it, despite the name. "Apache2 Web Server" is a good example. Bad examples are "high-performance light-weight HTTP server" (too generic) or "Apache2" (too specific and meaningless for people who do not know Apache). systemd will use this string as a noun in status messages ("Starting description...", "Started description.", "Reached target description.", "Failed to start description."), so it should be capitalized, and should not be a full sentence or a phrase with a continuous verb. Bad examples include "exiting the container" or "updating the database once per day.".
一个例子是,比如 Apache Service 的 Description 应当被设定为「Apache2 Web Server」这一名称而非「high-performance light-weight HTTP server」这一描述
启动顺序 Before / After
这一配置可以出现多次或是利用空格分隔,即下面两种表述是相同的
// 出现多次
Before = a.service
Before = b.service
// 出现一次
Before = a.service b.service
其用于定义服务启动的先后顺序,默认情况下多个 service 都是同步启动的,而配置了 Before / After 后则只有在一个 service 启动完成后才会启动另一个 service。
需要注意的是 Before 和 After 描述的是「启动顺序」而非「依赖关系」,如果两个服务设定了启动顺序的先后关系,先启动的服务是否启动成功不会影响到后续服务的启动。
除启动顺序外,其还影响关机时的停止顺序,顺序和启动顺序相反。
依赖关系 Wants / Requires / Requisite / BindsTo / PartOf
这些配置用于描述各个服务间的依赖关系,用法和启动顺序配置 Before / After 类似。需要注意的是,这里描述的「依赖关系」并不会为其自动配置「启动顺序」,所以依赖关系配置一般会和相应的启动顺序共同配置。
Wants
Wants 是一个最「弱」的依赖,如果启动服务时其 want 的服务没有启动则会将之拉起(如果没配置 Later 那么会是同步拉起,后面都是这样,不再重复表述了),但它不会关心拉起的服务的状态。
Requires
Requires 属于强依赖,除会和 Wants 一样在启动时拉起依赖服务外,如果指定了 Later,那么会根据拉起的服务状态进行判断,如果拉起的服务 fail 了那么它也会 fail。
当服务启动以后,如果对其依赖服务执行了 stop(这里就和是否指定 later 没关系了),那么当前服务也会被 stop 掉。
但这个 Require 并不是一定保证依赖服务执行的,如果依赖服务指定了条件检查失败或是依赖服务自行退出了那么并不会使当前服务退出。如果想实现需要使用 BindsTo
Requisite
与 Requires 类型,不过如果依赖服务没有被启动 Requires 会启动它而 Requisite 不会而是会直接失败。因此在启动阶段想要保证它能启动必须指定 After
BindsTo
- 如 Requisite,如果依赖服务未启动那么会直接失败而不会拉起
- 依赖服务因任何原因进入到 inactive 状态(自行退出或失败)均会导致当前服务也退出
PartOf
启动时不会检查也不会拉起依赖服务,但对依赖服务执行 restart 或 stop 会同样作用于当前服务
Service
ExecStart
ExecStart 是一个服务的「入口」,一般情况是必须存在的,用于指定这个 service 在 start 时所执行的命令。
其格式是「命令路径 + 命令参数」,实际上就类似于在 bash 中运行软件所需要写的命令,但需要注意的是 systemd 不会理解 PATH(文档中写的是对于非绝对路径会在 systemd-path search-binaries 输出的目录中搜索,但是实际测试并没有搜索这些路径,文档中也建议了始终使用绝对路径,因此可以直接假定必须使用绝对路径),因此要运行的程序必须写全路径。例如想要简单的「打印」出 123,应当这样写
[Service]
ExecStart = /usr/bin/echo 123
而不能简单的写作
[Service]
ExecStart = echo 123
因为 systemd 不能理解 PATH,无法找到 echo 程序的路径
另外需要说明的是,尽管 ExecStart 格式和 bash 中运行的差不多,但实际上有所区别
- 不支持重定向、管道等
- 对于环境变量,支持
$VAR与${VAR}两种,前者会被进行 shell 转义,而后者会被原样插入至相应位置
ExecStart 支持在命令前加上前缀来实现一些「特殊效果」(事实上,后面说到的 ExecStartPre, ExecStartPost, ExecReload, ExecStop 和 ExecStopPost 均支持),常见特殊前缀有
-:程序以非 0 返回值退出也不会将程序标记为 fail+:程序不会受到任何权限限制
Type
Type 指定了 Service 的类别,一般用 simple 或 notify。simple 代表着一个启动就成功的 service,而 notify 则需要程序适配 sdnotify 接口来向 systemd 主动汇报状态
ExecStop / ExecRestart / ExecReload
与 ExecStart 类似,但用于控制 停止 / 重启 /reload 的行为
- 默认停止就是 kill
- 默认重启就是先 ExecStop 再 ExecStart
- 默认不支持 reload
Restart
控制程序在 Stop(或 Fail)后是否重启
- no:(默认)不重启
- on-success:成功退出时重启
- on-failure
- on-abnormal
- on-watchdog
- on-abort
- always

Install
如果服务想要支持 Enable 则需要指定 Install 段
Install 而言通常只需要指定如下行
[Install]
WantedBy = multi-user.target
在 CentOS 7 上,这行必须存在,否则无法安装;在 CentOS 8 上,Ambot Service 会自动注入安装指令
将 /etc/ambot/services 作为 systemd 搜索 目录
仅为可行性试验,并未真实应用
编辑 /etc/default/grub 文件
将 GRUB_CMDLINE_LINUX 行末尾添加如下内容(注意不要丢失末尾的冒号)
SYSTEMD_UNIT_PATH=/etc/ambot/services:
执行 grub2-mkconfig -o /etc/grub2.cfg 后重启
这之后无需再将 /etc/ambot/services 下的内容软链至 /lib ,也无需将 Drop-In Conf 放置于 /etc 下