如何让 elixir 节点在启动时自动连接?

How to get elixir nodes to connect automatically on startup?

背景

我正在尝试在几个 elixir 节点之间设置集群。我的理解是我可以通过修改发布vm.args来设置它。我正在使用 Distillery 构建版本并遵循此处的文档:https://hexdocs.pm/distillery/config/runtime.html.

我的rel/vm.args文件如下:

-name <%= release_name %>@${HOSTNAME}
-setcookie <%= release.profile.cookie %>
-smp auto
-kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155
-kernel sync_nodes_mandatory '[${SYNC_NODES_MANDATORY}]'

我有一个构建服务器 运行ning Ubuntu 18.04 和两个网络服务器 运行ning Ubuntu 18.04。我正在构建服务器上构建版本,将存档复制到网络服务器,然后取消存档并在那里启动它。

在服务器上,两个 vm.args 文件被计算为:

-name hifyre_platform@10.10.10.100
-setcookie wefijow89236wj289*PFJ#(*98j3fj()#J()#niof2jio
-smp auto
-kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155
-kernel sync_nodes_mandatory '["\'my_app@10.10.10.100\'","\'my_app@10.10.10.200\'"]'

-name hifyre_platform@10.10.10.200
-setcookie wefijow89236wj289*PFJ#(*98j3fj()#J()#niof2jio
-smp auto
-kernel inet_dist_listen_min 9100 inet_dist_listen_max 9155
-kernel sync_nodes_mandatory '["\'my_app@10.10.10.100\'","\'my_app@10.10.10.200\'"]'

发行版 运行 通过 systemd 使用以下配置:

[Unit]
Description=My App
After=network.target

[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/opt/app
ExecStart=/opt/app/bin/my_app foreground
Restart=on-failure
RestartSec=5
Environment=PORT=8080
Environment=LANG=en_US.UTF-8
Environment=REPLACE_OS_VARS=true
Environment=HOSTNAME=10.10.10.100
SyslogIdentifier=my_app
RemainAfterExit=no

[Install]
WantedBy=multi-user.target

问题

发布在两台服务器上都开始正常,但是当我打开远程控制台并 运行 Node.list() 结果是一个空列表,除非我手动连接两个节点。

如果我手动 运行 Node.connect(:"my_app@10.10.10.200") 我会在每个节点上 运行 宁 Node.list() 时看到另一个节点,但这不会在启动时自动发生。

vm.args 文件最终使用 -args_file 参数传递给 Erlang。我去看了 the documentation for -args_file,发现它实际上没有很好的文档记录。原来vm.args就像洋葱一样,层次很多,文档好像都在源码里面

让我们从我们想要结束的地方开始吧。我们希望 sync_nodes_mandatory 是一个原子列表,我们需要用 Erlang 语法来编写它。如果我们使用短节点名称,例如my_app@myhost,我们可以不用引用原子,但是带点的原子需要用单引号引用:

['my_app@10.10.10.100','my_app@10.10.10.200']

我们希望这是 the function build_args_from_string in erlexec.c 的输出。这个函数有四个规则:

  • 反斜杠字符转义任何一个字符
  • 双引号转义所有字符(包括反斜杠)直到下一个双引号
  • 单引号转义所有字符(包括反斜杠)直到下一个单引号
  • 一个space字符标记一个参数的结束

既然我们想将单引号传递给解析器,我们有两种选择。我们可以转义单引号:

[\'my_app@10.10.10.100\',\'my_app@10.10.10.200\']

或者我们可以用双引号将单引号引起来:

["'my_app@10.10.10.100','my_app@10.10.10.200'"]

(事实上,双引号的数量和位置并不重要,只要每次出现的单引号都在一对双引号内即可。这只是一种可能的实现方式.)

BUT 如果我们选择用反斜杠转义单引号,我们会遇到另一层! The function read_args_file 是在将 vm.args 文件传递​​给 build_args_from_string 之前实际从磁盘读取文件的函数,它首先强加了自己的规则!即:

  • 反斜杠字符转义任何一个字符
  • 一个#字符忽略所有字符直到下一个换行符
  • 任何白色space字符都被单个space替换,除非用反斜杠
  • 转义

所以如果我们在 vm.args 中写 [\'my_app@10.10.10.100\',\'my_app@10.10.10.200\']read_args_file 会吃掉反斜杠,而 build_args_from_string 会吃掉单引号,留下无效的术语和错误:

$ iex --erl '-args_file /tmp/vm.args'
2019-04-25 17:00:02.966277 application_controller: ~ts: ~ts~n
    ["syntax error before: ","'.'"]
    "[my_app@10.10.10.100,my_app@10.10.10.200]"
{"could not start kernel pid",application_controller,"{bad_environment_value,\"[my_app@10.10.10.100,my_app@10.10.10.200]\"}"}
could not start kernel pid (application_controller) ({bad_environment_value,"[my_app@10.10.10.100,my_app@10.10.10.200]"})

Crash dump is being written to: erl_crash.dump...done

所以我们可以使用双反斜杠:

-kernel sync_nodes_mandatory [\'my_app@10.10.10.100\',\'my_app@10.10.10.200\']

或者只用双引号(这次是不同但同样有效的变体):

-kernel sync_nodes_mandatory "['my_app@10.10.10.100','my_app@10.10.10.200']"

the documentation for the kernel application 所述,您还需要将 sync_nodes_timeout 设置为以毫秒为单位的时间或 infinity:

Specifies the time (in milliseconds) that this node waits for the mandatory and optional nodes to start. If this parameter is undefined, no node synchronization is performed.

添加如下内容:

-kernel sync_nodes_timeout 10000

这是一个替代解决方案。我在调查这个问题的时候发现的。

创建包含以下内容的文件 ./priv/sync.config

[{kernel, [
  {sync_nodes_mandatory, ['my_app@10.10.10.200', 'my_app@10.10.10.200']},
  {sync_nodes_timeout, 15000}
]}].

将此行添加到 vm.args:

-config <%= :code.priv_dir(release_name) %>/sync

构建版本并在连接控制台的情况下在 15 秒内(配置文件的超时值)启动两个节点。执行Node.list()验证。

现在您可以考虑在构建版本时生成此配置文件。