0%

elixir release package 启动失败调查记录

事件经过

有一天,同事在部署的时候得到了一个莫名奇妙的错误:

1
Runtime terminating during boot ({'cannot get bootfile','xxx/bin/start.boot'})

最后他发现问题出在文件env.sh中:

1
2
export RELEASE_DISTRIBUTION=name
export RELEASE_NODE="release_name@$(hostname -i)"

问题的原因是:系统升级之后hostname -i的结果与预期不一致造成的。

系统升级之前

1
2
hostname -i
192.168.1.2

系统升级之后

1
2
hostname -i
fe80::4350:4350:4350:fb17 192.168.1.2

最后造成 RELEASE_NODE 的值为
"release_name@fe80::4350:4350:4350:fb17 192.168.1.2"

深入调查

这个错误是怎么触发告警 {'cannot get bootfile','xxx/bin/start.boot'} 的?
抱着这个问题,我进行了深入的调查

首先看最外层的执行文件 bin/release_name:

1
2
3
4
5
6
7
8
9
10
11
12
13
start () {
export_release_sys_config
REL_EXEC="$1"
shift
exec "$REL_VSN_DIR/$REL_EXEC" \
--cookie "$RELEASE_COOKIE" \
$(release_distribution "$RELEASE_NODE") \
--erl "-mode $RELEASE_MODE" \
--erl-config "$RELEASE_SYS_CONFIG" \
--boot "$REL_VSN_DIR/$RELEASE_BOOT_SCRIPT" \
--boot-var RELEASE_LIB "$RELEASE_ROOT/lib" \
--vm-args "$RELEASE_VM_ARGS" "$@"
}

扩展变量之后

1
xxx/releases/0.1.0/elixir --cookie xxx --name release_name@fe80::4350:4350:4350:fb17 192.168.1.2 --erl -mode embedded --erl-config releases/0.1.0/sys --boot releases/0.1.0/start --boot-var RELEASE_LIB rel/release_name/lib --vm-args rel/release_name/releases/0.1.0/vm.args --no-halt

非预期的参数,多了一个 ip

1
--name release_name@fe80::4350:4350:4350:fb17 192.168.1.2

后续参数中也发现 指定了 bootfile

1
--boot releases/0.1.0/start

那为什么还会 抛 cannot get bootfile 异常 呢?

继续调查,运行 elixir --help:

1
Usage: elixir [options] [.exs file] [data]

这里发现 elixir 第一个梯队的视为参数,其余视为 filedata,

所以 -name xxx@xxx 之后的都不被视为参数
继续深挖一下 elixir 的启动文件,发现通过一个变量,可以输出 erlang 的启动命令 ELIXIR_CLI_DRY_RUN=1 bin/release_name start

1
releases/0.1.0/../../erts-14.1/bin/erl -noshell -s elixir start_cli -elixir ansi_enabled true -setcookie xxx -name release_name@fe80::4350:4350:4350:fb17 -extra 192.168.1.2 --erl -elixir config_provider_reboot_mode embedded --erl-config rel/release_name/tmp/release_name-0.1.0-20240131182044-1f61.runtime --boot rel/release_name/releases/0.1.0/start --boot-var RELEASE_LIB rel/release_name/lib --vm-args rel/release_name/releases/0.1.0/vm.args --no-halt

看看正常的输出:

1
releases/0.1.0/../../erts-14.1/bin/erl -noshell -s elixir start_cli -elixir ansi_enabled true -elixir config_provider_reboot_mode embedded -setcookie xxx -name release_name@192.168.1.2 -config rel/release_name/tmp/release_name-0.1.0-20240131182242-6338.runtime -boot releases/0.1.0/start -boot_var RELEASE_LIB rel/release_name/lib -args_file rel/release_name/releases/0.1.0/vm.args -extra --no-halt

对比发现, -name 之后开始,参数的顺序不一样, -extra 参数被前置了,翻看 erl 文档
Plain arguments are not interpreted in any way. They are also stored by the init process and can be retrieved by calling init:get_plain_arguments/0. Plain arguments can occur before the first flag, or after a – flag. Also, the -extra flag causes everything that follows to become plain arguments.

-extra 参数后面的值都会被视为额外参数,所以怪不得启动 erl 找不到 bootfile,结案。

总结

这次的问题虽然是升级造成,前后版本命令的输出不一致导致的,但是归根结底是因为在错误的地方多了一个空格,多了一个参数,造成了程序的错误解读。
这次调查收获,后续遇到类似的启动失败情况,可以设置 ELIXIR_CLI_DRY_RUN=1 环境变量开启命令调试,这样有助于快速发现问题。

后话:这算一个 bug 吗?

我的答案是不算!
对于 elixirerl 这两个应用来说,文档已经定义好了适用范围,程序也是按定义进行了正确的执行,只是在这个问题上与使用者预期不一致而已;
对于 发布包的 bin 执行文件 来说,这也不算一个 bug,总不可能因为有这种问题,去对每一个变量进行防御式编程吧。

梦想基金
feng19 微信

微信

feng19 支付宝

支付宝

欢迎关注我的其它发布渠道