# リリース

> Source: https://www.ymotongpoo.com/works/adopting-erlang-ja/production/releases/


[OTPを俯瞰する](../../development/otp_high_level/)では、アプリケーションがどのようにリリースへと組み合わされるかを大まかに見ました。そして[開発](../../development/)の各章では、OTPアプリケーションを実際に作りテストしてきました。ここまでで、Erlangのシステムが他の多くの言語と違うセットアップになっていること、たとえば起動時に呼び出される`main()`関数のような単一のエントリポイントを持たないことを説明してきました。とはいえ、最終的にあなたのコードを動かすErlangノードを起動するという行為は他の言語やランタイムとさほど変わらず、Dockerコンテナとして他のコンテナと同じように動かすことができ、コンテナを起動するだけで動き出す点では特に変わったものには見えなくなります。しかし、使いやすいシェルスクリプトの付いたリリース一式や、すぐ動かせるDockerイメージができあがるまでの道のりは、最初は戸惑うことが多く、Erlangは自分たちの環境で動かすには違いすぎる、あるいは難しすぎるという印象を与えてしまいがちです。

この章ではリリースがどのように構築されるかについて低レベルの詳細にも触れますが、主な目的は[service_discovery](https://adoptingerlang.org/gh/servicediscovery)プロジェクトを使って、`rebar3`でローカル開発向けのリリースを構築し、続いて本番向けのリリースを構築し、最後に`rebar3`が提供するツールでリリースを設定・操作する方法を示すことです。並行して動くアプリケーション群を起動するという仕組みがもたらす柔軟性は、運用を秘教的にする代償を伴わなくてもよいのだということを示していきます。

## 泥臭い部分

オペレーティングシステムとの対比に戻ると、Erlangのリリースを起動することは、OSのブートシーケンスに続いてinitシステムが各種サービスを起動する流れとよく似ています。Erlangノードは[ブートファイル](http://erlang.org/doc/man/script.html)に書かれた命令を実行することで起動します。この命令はモジュールをロードし、アプリケーションを起動します。Erlangには、必要なアプリケーション一覧からブートスクリプトを生成する関数が用意されており、その一覧は拡張子`.rel`のリリースリソースファイルと、各アプリケーションが持つ`.app`アプリケーションリソースファイルに定義されます。アプリケーションリソースファイルは、ブートスクリプトがロードすべきモジュールと各アプリケーションの依存関係を定義しており、それによってブートスクリプトは正しい順序でアプリケーションを起動できます。あるリリースで使うアプリケーションだけがブートスクリプトとともにひとまとめにされ、ターゲットへコピー・インストールできる状態になったものを**ターゲットシステム**と呼びます。

Erlang/OTPの初期には、リリースリソースファイルからブートスクリプトを生成する手段は`systools`とその関数群しかありませんでした。当時、リリースの取り扱いは手作業であり、利用者は自分たちでツールを組み立てるしかありませんでした。その後登場した`reltool`はErlang/OTPに同梱されるリリース管理ツールで、リリース作成を容易にすることを狙ったものであり、GUIまで備えていました。ただし、ターゲットシステムの作成とインストールについては、`sasl`アプリケーションに含まれるサンプルモジュール`sasl/examples/src/target_system.erl`以外にまとまった手段が提供されたことは一度もありません。

リリースの構築は多くの利用者にとって謎めいた難物であり続けました。relxは、リリースの作成と管理を、もう誰も避けて通りたい重荷だとは感じないほど単純にすることを目指して作られました。これは、最小限の設定だけで使い始められることと、生成されるリリースにランタイム管理のためのツールを同梱することによって実現されています。Rebar3が作られたとき、そのリリース構築機能を提供するためにrelxが組み込まれました。

構築とパッケージングに加えて、`relx`にはリリースを起動し、実行中のリリースと対話するためのシェルスクリプトも付属します。リリースを実行するだけなら`erl`(これ自体、Erlangのインストール先の`bin/`ディレクトリにある`start.script`から構築されたブートスクリプトを実行するものです)だけでも十分ですが、relxが提供するスクリプトは、設定ファイルを指すための適切な引数の設定、稼働中のノードへのリモートコンソールの接続、稼働中のノードでの関数実行など、より多くのことを面倒みてくれます。

以降の節では、[service_discovery](https://adoptingerlang.org/gh/servicediscovery)プロジェクトを題材にリリース構築を掘り下げていきます。焦点はツールの実運用での使い方であり、リリースの構築・実行にまつわる低レベルの詳細ではありません。

## 開発用リリースの構築

[service_discovery](https://adoptingerlang.org/gh/servicediscovery)でまず見るべきは、`rebar.config`の`relx`セクションです。

```erlang
{relx, [{release, {service_discovery, {git, long}},
         [service_discovery_postgres,
          service_discovery,
          service_discovery_http,
          service_discovery_grpc,
          recon]},

        {sys_config, "./config/dev_sys.config"},
        {vm_args, "./config/dev_vm.args"},

        {dev_mode, true},
        {include_erts, false},

        {extended_start_script, true},

        {overlay, [{copy, "apps/service_discovery_postgres/priv/migrations/*", "sql/"}]}]}.
```

`relx`設定リストの`release`タプルは、リリースの名前・バージョン・そのリリースに含めるアプリケーションを定義します。ここでのバージョンは`{git, long}`であり、これは現在のコミットのgit shaのフルレファレンスをリリースのバージョンとして使うよう`relx`に指示しています。リリースが起動するアプリケーションには、Postgresストレージバックエンド、サービスとDNS設定への主要なインターフェースとなるアプリケーション、HTTPとgRPCのフロントエンド、本番ノードの調査に便利なツールである`recon`が含まれます。

ここでアプリケーションを並べる順序は重要です。ブートスクリプトを構築する際には、各アプリケーションの依存関係に基づいた安定ソートが行われて起動順が決まります。しかし、依存関係の順序だけでは決定しきれない場合は、リスト内で定義された順序が保たれます。`service_discovery_postgres`、`service_discovery_http`、`service_discovery_grpc`はいずれも`service_discovery`に依存しているため、`service_discovery`が最初に起動します。次に起動するのは`service_discovery_postgres`で、これはリストの中で先に書かれているためです。HTTPとgRPCのサービスが使えるようになる前にストレージバックエンドを利用可能にしておく必要があるため、この順序は重要です。

`rebar3 release`は、`rebar.config`の`relx`セクションとRebar3のプロジェクト構造にもとづいた設定で`relx`を実行し、`relx`がリリース構築に必要なアプリケーションを見つけられるようにします。

```shell
$ rebar3 release
===> Verifying dependencies...
===> Compiling service_discovery_storage
===> Compiling service_discovery
===> Compiling service_discovery_http
===> Compiling service_discovery_grpc
===> Compiling service_discovery_postgres
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
          /app/src/_build/default/lib
          /app/src/apps
          /root/.cache/erls/otps/OTP-22.1/dist/lib/erlang/lib
          /app/src/_build/default/rel
===> Resolved service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70
===> Dev mode enabled, release will be symlinked
===> release successfully created!
```

出力の中で最初に現れる`relx`のステップは「Resolving OTP Applications」であり、続いてそれが検索するディレクトリの一覧が表示されます。`relx`は、リリース設定にある各アプリケーション(この場合`service_discovery_postgres`、`service_discovery`、`service_discovery_http`、`service_discovery_grpc`、`recon`)についてビルド済みアプリケーションのディレクトリを見つけ、さらにそのアプリケーションの`.app`ファイルに記載された各アプリケーションについても同じことを行います。

> `relx`の設定リストのアプリケーション一覧に`sasl`が含まれていない点に注意してください。Rebar3のテンプレートでは標準でこの一覧に含まれています。これは、一部のリリース操作に`sasl`が必要になるためです。`sasl`アプリケーションにはリリースのアップグレードとダウングレードの機能を提供する`release_handler`が含まれます。ここではライブアップグレードではなく置き換えを前提としたコンテナの構築に焦点を当てているため、`sasl`を含める必要はありません。

このリリースは`{dev_mode, true}`で構築されているため、リリースの`lib`ディレクトリには各アプリケーションをコピーする代わりにシンボリックリンクが作られます。

```shell
$ ls -l _build/default/rel/service_discovery/lib
lrwxrwxrwx ... service_discovery-c9e1c80 -> .../_build/default/lib/service_discovery
```

これはランタイム設定ファイルの`dev_sys.config`と`dev_vm.args`についても同様です。

```shell
$ ls -l _build/default/rel/service_discovery/releases/c9e1c805d57a78d9eb18af1124962960abe38e70
lrwxrwxrwx [...] sys.config -> [...]/config/dev_sys.config
lrwxrwxrwx [...] vm.args -> [...]/config/dev_vm.args
```

これにより、ローカルでのテスト用にリリースを実行する際のフィードバックループが速くなります。単にリリースを停止・再起動するだけでbeamファイルや設定への変更が反映され、`rebar3 release`を再実行する必要はありません。

> Windowsでは`relx`の`dev_mode`は必ずしも機能せず、代わりにコピーへフォールバックします。

開発用リリースのファイルシステムツリー全体は次のようになります。

```shell
_build/default/rel/service_discovery/
├── bin/
│   ├── install_upgrade.escript
│   ├── nodetool
│   ├── no_dot_erlang.boot
│   ├── service_discovery
│   ├── service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70
│   └── start_clean.boot
├── lib/
│   ├── acceptor_pool-1.0.0 -> /app/src/_build/default/lib/acceptor_pool
│   ├── base32-0.1.0 -> /app/src/_build/default/lib/base32
│   ├── chatterbox-0.9.1 -> /app/src/_build/default/lib/chatterbox
│   ├── dns-0.1.0 -> /app/src/_build/default/lib/dns
│   ├── elli-3.2.0 -> /app/src/_build/default/lib/elli
│   ├── erldns-1.0.0 -> /app/src/_build/default/lib/erldns
│   ├── gproc-0.8.0 -> /app/src/_build/default/lib/gproc
│   ├── grpcbox-0.11.0 -> /app/src/_build/default/lib/grpcbox
│   ├── hpack-0.2.3 -> /app/src/_build/default/lib/hpack
│   ├── iso8601-1.3.1 -> /app/src/_build/default/lib/iso8601
│   ├── jsx-2.10.0 -> /app/src/_build/default/lib/jsx
│   ├── recon-2.4.0 -> /app/src/_build/default/lib/recon
│   ├── service_discovery-c9e1c80 -> /app/src/_build/default/lib/service_discovery
│   ├── service_discovery_grpc-c9e1c80 -> /app/src/_build/default/lib/service_discovery_grpc
│   ├── service_discovery_http-c9e1c80 -> /app/src/_build/default/lib/service_discovery_http
│   └── service_discovery_storage-c9e1c80 -> /app/src/_build/default/lib/service_discovery_storage
├── releases/
│   ├── c9e1c805d57a78d9eb18af1124962960abe38e70
│   │   ├── no_dot_erlang.boot
│   │   ├── service_discovery.boot
│   │   ├── service_discovery.rel
│   │   ├── service_discovery.script
│   │   ├── start_clean.boot
│   │   ├── sys.config.src -> /app/src/config/sys.config.src
│   │   └── vm.args.src -> /app/src/config/vm.args.src
│   ├── RELEASES
│   └── start_erl.data
└── sql
    ├── V1__Create_services_endpoints_table.sql
    └── V2__Add_updated_at_trigger.sql
```

最後の`sql`ディレクトリは、`overlay`(`{copy, "apps/service_discovery_postgres/priv/migrations/*", "sql/"}`)によって作られます。`overlay`は、アプリケーションやブートファイルという通常のリリース構造の外側にあるファイルを`relx`に伝える仕組みで、ディレクトリの作成、`mustache`による簡易的なテンプレート処理、ファイルのコピーをサポートします。ここでは`service_discovery_postgres`アプリケーションのマイグレーションをリリースのトップレベルディレクトリ`sql`にコピーするだけでよいのです。これらのマイグレーションはリリースに含まれるアプリケーションの`priv`にあるため元々含まれてはいるのですが、後述するように同時に有効なリリースは1つだけにする想定なので、既知のトップレベルディレクトリにまとめておくことで後で見るマイグレーションスクリプトを単純化できます。

> OTPのリリース構造は同じリリースの複数バージョンを持てるようになっています。それらは同じ`lib`ディレクトリを共有しつつ、アプリケーションごとに異なるバージョン、さらには異なる`erts`を持つこともできます。だからこそ、バージョン番号のディレクトリを内包する`releases`ディレクトリと`RELEASES`ファイルが存在します。すべてのバージョンのリリースで共有されるディレクトリにSQLファイルを置くのは、こうした環境では問題になりえます。しかし、ここではコンテナの中で他から独立して動く自己完結型のリリースを構築することに焦点を当てているため、常にバージョンは1つだけだと仮定して構いません。

このリリースはPostgresストレージバックエンドを使っているため、リリースを起動する前にデータベースが稼働してアクセス可能になっている必要があります。プロジェクトのトップレベルでDocker Composeを実行すると、データベースが立ち上がりマイグレーションが実行されます。

```shell
$ docker-compose up
```

> リリースからErlangシェルを得るために、すべてを起動する必要はありません。Rebar3で構築されたすべてのリリースには`start_clean`というブートスクリプトが付属しており、これは`kernel`と`stdlib`アプリケーションだけを起動します。これは`console_clean`コマンドで実行でき、デバッグ用途で便利です。

開発用リリースを対話的なErlangシェルとして起動するには、`console`コマンドで拡張起動スクリプトを実行します。

```shell
$ _build/default/rel/service_discovery/bin/service_discovery console
Erlang/OTP 22 [erts-10.5] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:30] [hipe]

(service_discovery@localhost)1>
```

`service_discovery`が動いている状態で、`curl`を使ってHTTPインターフェースにアクセスできます。以下のコマンドはサービス`service1`を作成し、一覧表示することで作成されたことを確認し、IP`127.0.0.3`でこのサービスにエンドポイントを登録し、ポート8000に名前付きポート`http`を追加します。

```shell
$ curl -v -XPUT http://localhost:3000/service \
    -d '{"name": "service1", "attributes": {"attr-1": "value-1"}}'
$ curl -v -XGET http://localhost:3000/services
[{"attributes":{"attr-1":"value-1"},"name":"service1"}]
$ curl -v -XPUT http://localhost:3000/service/service1/register \
    -d '{"ip": "127.0.0.3", "port": 8000, "port_name": "http", "tags": []}'
$ curl -v -XPUT http://localhost:3000/service/service1/ports \
    -d '{"http": {"protocol": "tcp", "port": 8000}}'
```

`service_discovery`のDNSサーバはポート8053で動いています。`dig`を使うと、`service1`が正しいIPで登録済みのエンドポイントになっていること、そしてサービス(SRV)のDNSクエリがそのサービスのポートとDNS名を返すことが確認できます。

```shell
$ dig -p8053 @127.0.0.1 A service1
;; ANSWER SECTION:
service1.		3600	IN	A	127.0.0.3
$ dig -p8053 @127.0.0.1 SRV _http._tcp.service1.svc.cluster.local
;; ANSWER SECTION:
_http._tcp.service1.svc.cluster.local. 3600 IN SRV 1 1 8000 service1.svc.cluster.local.
```

## 本番用リリースの構築

本番にデプロイするリリースの準備には、ローカル開発で使うのとは異なるオプションが必要になります。Rebar3のプロファイルを使うと`relx`の設定を上書き・追加できます。このプロファイルは一般に`prod`という名前にします。

```erlang
{profiles, [{prod, [{relx, [{sys_config_src, "./config/sys.config.src"},
                            {vm_args_src, "./config/vm.args.src"},
                            {dev_mode, false},
                            {include_erts, true},
                            {include_src, false},
                            {debug_info, strip}]}]
            }]}.
```

`prod`プロファイルでは2つの設定値を上書きしています。`dev_mode`は`false`に設定され、すべての内容がリリースディレクトリにコピーされるようになります。別のマシンから`_build`ディレクトリへのシンボリックリンクは使えないし、本番用リリースはプロジェクトの不変なスナップショットであるべきだからです。`include_erts`は、リリースが依存するErlangランタイムとErlang/OTPアプリケーションをリリースディレクトリにコピーし、ブートスクリプトがこのコピーされたランタイムを指すよう設定します。

追加された項目は、`include_src`を`false`に、`debug_info`を`strip`に設定することと、設定ファイルの`_src`版を使うことです。本番でリリースを実行する際にソースコードは不要なので、最終的なリリースから容量を削減するために`include_src`を`false`にします。デバッガや`xref`、`cover`のようなツールが使うデバッグ情報も、`debug_info`を`strip`に設定してbeamファイルから取り除くことでさらに容量を節約します。リリースの中でこれらのツールが使われることはなく、明示的に含めない限り利用すらできません。

`sys_config_src`と`vm_args_src`は、デフォルトプロファイルにあった`sys_config`と`vm_args`のエントリより優先されます。この2つについては、この後の節で詳しく述べます。本番用リリースを構築する際には警告が表示されますが、これは意図的に行っていることなので無視して構いません。

本番プロファイルを有効にして構築すると、成果物はプロファイルディレクトリ`_build/prod/`に書き出されます。

```shell
$ rebar3 as prod release
===> Verifying dependencies...
===> Compiling service_discovery_storage
===> Compiling service_discovery
===> Compiling service_discovery_http
===> Compiling service_discovery_grpc
===> Compiling service_discovery_postgres
===> Starting relx build process ...
===> Resolving OTP Applications from directories:
          /app/src/_build/prod/lib
          /app/src/apps
          /root/.cache/erls/otps/OTP-22.1/dist/lib/erlang/lib
===> Resolved service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70
===> Both vm_args_src and vm_args are set, vm_args will be ignored
===> Both sys_config_src and sys_config are set, sys_config will be ignored
===> Including Erts from /root/.cache/erls/otps/OTP-22.1/dist/lib/erlang
===> release successfully created!
```

新しい`prod`プロファイルのリリースディレクトリのツリーを見ると、次のようになっています。

```sh
_build/prod/rel/service_discovery
├── bin
│   ├── install_upgrade.escript
│   ├── nodetool
│   ├── no_dot_erlang.boot
│   ├── service_discovery
│   ├── service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70
│   └── start_clean.boot
├── erts-10.5
│   ├── bin
│   ├── doc
│   ├── include
│   ├── lib
│   └── man
├── lib
│   ├── acceptor_pool-1.0.0
│   ├── asn1-5.0.9
│   ├── base32-0.1.0
│   ├── chatterbox-0.9.1
│   ├── crypto-4.6
│   ├── dns-0.1.0
│   ├── elli-3.2.0
│   ├── erldns-1.0.0
│   ├── gproc-0.8.0
│   ├── grpcbox-0.11.0
│   ├── hpack-0.2.3
│   ├── inets-7.0.8
│   ├── iso8601-1.3.1
│   ├── jsx-2.10.0
│   ├── kernel-6.5
│   ├── mnesia-4.16
│   ├── public_key-1.6.7
│   ├── recon-2.4.0
│   ├── service_discovery-c9e1c80
│   ├── service_discovery_grpc-c9e1c80
│   ├── service_discovery_http-c9e1c80
│   ├── service_discovery_storage-c9e1c80
│   ├── ssl-9.3.1
│   └── stdlib-3.10
├── releases
│   ├── c9e1c805d57a78d9eb18af1124962960abe38e70
│   │   ├── no_dot_erlang.boot
│   │   ├── service_discovery.boot
│   │   ├── service_discovery.rel
│   │   ├── service_discovery.script
│   │   ├── start_clean.boot
│   │   ├── sys.config.src
│   │   └── vm.args.src
│   ├── RELEASES
│   └── start_erl.data
└── sql
    ├── V1__Create_services_endpoints_table.sql
    └── V2__Add_updated_at_trigger.sql
```

`lib`以下にシンボリックリンクはなく、`stdlib-3.10`のようなOTPアプリケーションはコピーとして含まれています。ツリーの最上位には`erts-10.5`があり、これにはErlangランタイム`bin/beam.smp`のほか、リリースの実行・操作に必要な`erlexec`、`erl`、`escript`といった実行ファイルが含まれます。

リリースのターゲットシステムを構築するには、`prod`プロファイルで`tar`コマンドを実行します。

```shell
$ rebar3 as prod tar
```

これで、任意の互換ホストにコピーして展開・実行できるtarball `_build/prod/rel/service_discovery/service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70.tar.gz`ができあがります。ただし、それを行う前に、リリース起動時に`sys.config.src`と`vm.args.src`をどう設定すればよいかを知っておく必要があります。

## ランタイムの設定

`service_discovery`プロジェクトには、本番用リリースに含まれる`config/`ディレクトリ配下に2つの設定ファイル、`vm.args.src`と`sys.config.src`があります。これらのファイルは、実行時に環境変数をもとに埋め込まれるテンプレートとして機能します。リリースの実行には2段階の設定が関わってくるため、ファイルは2つに分かれています。1つ目は、Erlang仮想マシン自体の設定です。これらの値はVMが起動する前に設定されている必要があるため、稼働中のVMがなければ読み込みも解釈もできない`sys.config`のようなErlangタームファイルの一部にすることはできません。代わりに、VMの引数は`erl`(リリースの起動に使われるコマンド)に直接渡されます。これを簡単にするため、`erl`コマンドにはコマンドライン引数をプレーンテキストファイルから読み込める`-args_file`という引数があり、このファイルは一般に`vm.args`という名前にされます。

2段階目は、リリースを構成するErlangアプリケーションの設定です。これは`-config`引数を通じて`erl`に渡されるファイルによって行われます。このファイルは2要素タプルのリストであり、1つ目の要素は環境を設定するアプリケーションの名前、2つ目の要素は環境に設定するキーと値のペアのリストです。

もちろん静的なファイルにはかなりの制約があり、OS環境変数を通じて設定できるようにしたいという要望はよくあることです。環境変数による設定に柔軟性とサポートを提供するため、Rebar3を使った際に生成されるリリース起動スクリプトは、`${FOO}`という形式の変数を現在の環境にある値で置き換えることができます。これはファイルの拡張子が`.src`で終わっている場合に自動的に行われます。

`relx`の設定では`vm_args_src`と`sys_config_src`を使ってこれらのファイルを含め、テンプレートであることを伝えます。これは、リリース構築の際に`sys.config`が正しいErlangタームのリストであることを検証しようとしないようにするために必要です。たとえば`{port, ${PORT}}`はErlangタームとして正しくありません。

```erlang
{relx, [...
        {sys_config_src, "config/sys.config.src"},
        {vm_args_src, "config/vm.args.src"},
        ...
       ]}.
```

DockerとKubernetesの章で、なぜ`sbwt`を設定する必要があるのかを説明しますが、ここでは`vm.args.src`でどう書くかだけを見ておきます。

```shell
+sbwt ${SBWT}
```

`sys.config.src`では`logger`のレベルも変数にします。こうすることで、たとえばデプロイ済みのサービスを調査する際に`debug`や`info`レベルのロギングを有効にして詳細を得られるようになります。

```erlang
{kernel, [{logger_level, ${LOGGER_LEVEL}}]}
```

こうなると、リリースを実行する際にはこれらの変数を設定しなければならず、設定しなければリリースは起動に失敗します。

```shell
$ DB_HOST=localhost LOGGER_LEVEL=debug SBWT=none \
  _build/prod/rel/service_discovery/bin/service_discovery console
Erlang/OTP 22 [erts-10.5] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:30] [hipe]

(service_discovery@localhost)1>
```

必要な環境変数を設定せずに実行した際に出るエラーは分かりにくいことがあります。現状、設定ファイルで使われているすべての環境変数が設定されているかどうかを検証し、不足しているものを表示するような仕組みはありません。代わりに、不足している変数は空文字列に置き換えられます。もし変数が`"${DB_HOST}"`のように文字列の中で使われていれば、アプリケーションは起動はするもののデータベースに接続できなくなります。`${LOGGER_LEVEL}`のように、変数が欠けることでパースできない`sys.config`ができてしまう場合には、構文エラーになります。

```shell
$ DB_HOST=localhost SBWT=none \
  _build/prod/rel/service_discovery/bin/service_discovery console
{"could not start kernel pid",application_controller,"error in config file \"/app/src/_build/prod/rel/service_discovery/releases/71e109d8f34ef5e5ccfcd666e0d9e544836044f1/sys.config\" (48): syntax error before: ','"}
could not start kernel pid (application_controller) (error in config file "/home/tristan/Devel/service_discovery/_build/prod/rel/service_discovery/releases/71e109d8f34ef5e5ccfcd666e0d9e544836044f1/sys

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

`vm.args`の値が欠けている場合には、フラグに渡された引数についてのエラーになることが多いです。`+sbwt ${SBWT}`の場合、次の行を`+sbwt`の引数として使おうとしてしまい、この例では`+C multi_time_warp`が`+C`の値として不正だというブートエラーになります。

```shell
$ DB_HOST=localhost LOGGER_LEVEL=debug \
  _build/prod/rel/service_discovery/bin/service_discovery console
bad scheduler busy wait threshold: +C
Usage: service_discovery [flags] [ -- [init_args] ]
The flags are:
...
```

変数が不足した際の失敗すべてがこれほど単純とは限らず、非常に奇妙なクラッシュにつながることもあれば、起動時にはクラッシュせず、その値に依存するアプリケーション内で失敗することもあります。この点は今後のRebar3のバージョンで、起動前に不足している環境変数の一覧を出して失敗させるチェックを入れることで改善したいと考えています。しかし今のところ、奇妙な挙動や理解できないクラッシュに遭遇したら、まず環境変数を確認するのがよいでしょう。

## この先に向けて

次の章で行うことのイメージをつかむために、`_build/prod/rel/service_discovery/service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70.tar.gz`を`/tmp/service_discovery`にコピーし、展開してノードを起動してみましょう。

```shell
$ export LOGGER_LEVEL=debug
$ export SBWT=none
$ export DB_HOST=localhost
$ mkdir /tmp/service_discovery
$ cp _build/prod/rel/service_discovery/service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70.tar.gz /tmp/service_discovery
$ cd /tmp/service_discovery
$ tar -xvf service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70.tar.gz && rm service_discovery-c9e1c805d57a78d9eb18af1124962960abe38e70.tar.gz
...
$ ls
bin  erts-10.5  lib  releases
$ bin/service_discovery console
...
(service_discovery@localhost)1>
```

`console`で実行すると対話的なErlangシェルが得られます。対話シェルなしでリリースを実行するには`foreground`を使います。これは、次の章でDockerコンテナの中でリリースを実行する際のやり方でもあります。リリースを`foreground`または`console`で実行した状態のまま、別のターミナルを開いて同じスクリプトに引数`remote_console`を渡してみましょう。

```shell
$ export LOGGER_LEVEL=debug
$ export SBWT=none
$ export DB_HOST=localhost
$ bin/service_discovery remote_console
...
(service_discovery@localhost)1>
```

`remote_console`は[erl](http://erlang.org/doc/man/erl.html#flags)の`-remsh`フラグを使って対話シェルを稼働中のリリースに接続します。このコマンドがどのErlangノードに接続すべきかは、そのノードが最初に起動されたときと同じ`vm.args`由来の設定、すなわちノード名とcookieの設定にもとづいて判断されます。つまり、ノード起動時に`vm.args`を埋めるために使った環境変数は、リモートコンソールを接続する際にも同じ値を設定しておく必要があります。

