事件经过
有一天,同事在部署的时候得到了一个莫名奇妙的错误:
1 | Runtime terminating during boot ({'cannot get bootfile','xxx/bin/start.boot'}) |
最后他发现问题出在文件env.sh
中:
1 | export RELEASE_DISTRIBUTION=name |
问题的原因是:系统升级之后hostname -i
的结果与预期不一致造成的。
系统升级之前
1 | hostname -i |
系统升级之后
1 | hostname -i |
最后造成 RELEASE_NODE
的值为"release_name@fe80::4350:4350:4350:fb17 192.168.1.2"
深入调查
这个错误是怎么触发告警 {'cannot get bootfile','xxx/bin/start.boot'}
的?
抱着这个问题,我进行了深入的调查
首先看最外层的执行文件 bin/release_name
:
1 | start () { |
扩展变量之后
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 第一个梯队的视为参数,其余视为 file
和 data
,
所以 -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 吗?
我的答案是不算!
对于 elixir
和 erl
这两个应用来说,文档已经定义好了适用范围,程序也是按定义进行了正确的执行,只是在这个问题上与使用者预期不一致而已;
对于 发布包的 bin
执行文件 来说,这也不算一个 bug,总不可能因为有这种问题,去对每一个变量进行防御式编程吧。