環境
$ lsb_release -d
Description: Ubuntu 18.04.2 LTS
$ elixir -v
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Elixir 1.8.1 (compiled with Erlang/OTP 20)
21.1 水漏れパイプを直す
アプリケーションファイルを更新する
E本では ppool.app
erlcount.app
の2つのアプリを持っているが、
私は erlcount.app
を作っていない。
辻褄合わせということで、テスト用に使った nagger.ex
をライブラリにしてしまおう。
$ mix new pool --umbrella
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs
Your umbrella project was created successfully.
Inside your project, you will find an apps/ directory
where you can create and host many apps:
cd pool
cd apps
mix new my_app
Commands like "mix compile" and "mix test" when executed
in the umbrella project root will automatically run
for each application in the apps/ directory.
$ cd pool
$ tree
.
├── README.md
├── apps
├── config
│ └── config.exs
└── mix.exs
2 directories, 3 files
apps
ディレクトリに PPool
Nagger
を作る。
$ cd apps
$ mix new p_pool --module PPool --sup
...
$ mix new nagger
...
$ tree
.
├── nagger
│ ├── README.md
│ ├── config
│ │ └── config.exs
│ ├── lib
│ │ └── nagger.ex
│ ├── mix.exs
│ └── test
│ ├── nagger_test.exs
│ └── test_helper.exs
└── p_pool
├── README.md
├── config
│ └── config.exs
├── lib
│ ├── p_pool
│ │ └── application.ex
│ └── p_pool.ex
├── mix.exs
└── test
├── p_pool_test.exs
└── test_helper.exs
9 directories, 13 files
そして、前回までのソースをコピーする。
$ ls -F ../../
p_pool/ pool/
$ cp -r ../../p_pool/lib/* p_pool/lib/
$ cp ../../p_pool/test/nagger.ex nagger/lib/
$ tree
.
├── nagger
│ ├── README.md
│ ├── config
│ │ └── config.exs
│ ├── lib
│ │ └── nagger.ex
│ ├── mix.exs
│ └── test
│ ├── nagger_test.exs
│ └── test_helper.exs
└── p_pool
├── README.md
├── config
│ └── config.exs
├── lib
│ ├── p_pool
│ │ ├── application.ex
│ │ ├── poolvisor.ex
│ │ ├── stash.ex
│ │ ├── supervisor.ex
│ │ └── workervisor.ex
│ └── p_pool.ex
├── mix.exs
└── test
├── p_pool_test.exs
└── test_helper.exs
9 directories, 17 files
アプリケーションをコンパイルする
$ cd .. # アンブレラ pool のルートに戻る
$ ls -F
README.md apps/ config/ mix.exs
$ iex -S mix
$ mix compile
でもいいね。
iex(1)> :application.which_applications()
[
{:p_pool, 'p_pool', '0.1.0'},
{:nagger, 'nagger', '0.1.0'},
{:logger, 'logger', '1.8.1'},
{:mix, 'mix', '1.8.1'},
{:iex, 'iex', '1.8.1'},
{:elixir, 'elixir', '1.8.1'},
{:compiler, 'ERTS CXC 138 10', '7.3.2'},
{:stdlib, 'ERTS CXC 138 10', '3.8.1'},
{:kernel, 'ERTS CXC 138 10', '6.3.1'}
]
iex(2)> PPool.start_pool(Nagger, :start_link, [])
{:ok, #PID<0.151.0>}
iex(3)> PPool.run(Nagger, [self(), :msg, 500, 3])
{:ok, #PID<0.155.0>}
iex(4)> Received down message.
flush()
{#PID<0.155.0>, :msg}
{#PID<0.155.0>, :msg}
{#PID<0.155.0>, :msg}
:ok
おお、いぇー。
PPool
Nagger
共に mix.exs
が今までとは少し違う。
defmodule PPool.MixProject do
use Mix.Project
def project do
[
app: :p_pool,
version: "0.1.0",
# E本の内容に合わせるならば以下を追加
description: "Run and enqueue different concurrent tasks",
# Mix によって追加されてる
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger],
mod: {PPool.Application, []},
registered: [PPool.Supervisor] # お忘れなく
]
end
defp deps do
[]
end
end
$ tree _build
_build
└── dev
├── consolidated
│ ├── Elixir.Collectable.beam
│ ├── Elixir.Enumerable.beam
│ ├── Elixir.IEx.Info.beam
│ ├── Elixir.Inspect.beam
│ ├── Elixir.List.Chars.beam
│ └── Elixir.String.Chars.beam
└── lib
├── nagger
│ └── ebin
│ ├── Elixir.Nagger.beam
│ └── nagger.app
└── p_pool
└── ebin
├── Elixir.PPool.Application.beam
├── Elixir.PPool.Poolvisor.beam
├── Elixir.PPool.Stash.beam
├── Elixir.PPool.Supervisor.beam
├── Elixir.PPool.Workervisor.beam
├── Elixir.PPool.beam
└── p_pool.app
7 directories, 15 files
なるほどね。(日経電子ry
※ アンブレラ内部の依存関係
今回の場合には関係のない話なのだが、
例えば PPool
が起動時に Nagger
に依存していたとする。
defmodule PPool.MixProject do
use Mix.Project
...
defp deps do
[{:nagger, in_umbrella: true}]
end
end
なるほどね。(にっry
※ Distillery でのリリース
Elixir では Distillery を使う。
defmodule Pool.MixProject do
use Mix.Project
def project do
[
apps_path: "apps",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
defp deps do
[
distillery: "~> 2.0" # 追加
]
end
end
$ mix do deps.get, deps.compile
...
$ mix release.init
An example config file has been placed in rel/config.exs, review it,
make edits as needed/desired, and then run `mix release` to build the release
rel/config.exs
が生成されたらしいので見てみよう。
# Import all plugins from `rel/plugins`
# They can then be used by adding `plugin MyPlugin` to
# either an environment, or release definition, where
# `MyPlugin` is the name of the plugin module.
~w(rel plugins *.exs)
|> Path.join()
|> Path.wildcard()
|> Enum.map(&Code.eval_file(&1))
use Mix.Releases.Config,
# This sets the default release built by `mix release`
default_release: :default,
# This sets the default environment used by `mix release`
default_environment: Mix.env()
# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/config/distillery.html
# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile
environment :dev do
# If you are running Phoenix, you should make sure that
# server: true is set and the code reloader is disabled,
# even in dev mode.
# It is recommended that you build with MIX_ENV=prod and pass
# the --env flag to Distillery explicitly if you want to use
# dev mode.
set dev_mode: true
set include_erts: false
set cookie: :"Qt.(TQmtgz<olih:/ihSj$V2{`NFP&m3~CL>?eyRz9}Au1T|m|{dF|mrCeC&9`r<"
end
environment :prod do
set include_erts: true
set include_src: false
set cookie: :",Xf:!buFeY`>~kG1yw)kHjD2AYRqF61lBiu,XFB)mD^{TgbdWeL8sugf&hONdAx5"
set vm_args: "rel/vm.args"
end
# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default
release :pool do
set version: "0.1.0"
set applications: [
:runtime_tools,
nagger: :permanent,
p_pool: :permanent
]
end
アンブレラ・プロジェクトだと問題あるかもと思ったけど、いけてるみたい。
この設定だと本番環境でリリースした場合に Erlang ランタイムが含まれそうだね。
それでは開発版のリリースといこう。
$ mix release
==> Assembling release..
==> Building release pool:0.1.0 using environment dev
==> You have set dev_mode to true, skipping archival phase
Release successfully built!
To start the release you have built, you can use one of the following tasks:
# start a shell, like 'iex -S mix'
> _build/dev/rel/pool/bin/pool console
# start in the foreground, like 'mix run --no-halt'
> _build/dev/rel/pool/bin/pool foreground
# start in the background, must be stopped with the 'stop' command
> _build/dev/rel/pool/bin/pool start
If you started a release elsewhere, and wish to connect to it:
# connects a local shell to the running node
> _build/dev/rel/pool/bin/pool remote_console
# connects directly to the running node's console
> _build/dev/rel/pool/bin/pool attach
For a complete listing of commands and their use:
> _build/dev/rel/pool/bin/pool help
iex -S mix
ライクに使うなら console
だよって書いてあるね。
$ _build/dev/rel/pool/bin/pool console
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(pool@127.0.0.1)1> PPool.start_pool(Nagger, :start_link, [])
{:ok, #PID<0.790.0>}
iex(pool@127.0.0.1)2> PPool.run(Nagger, [self(), make_ref(), 500, 3])
{:ok, #PID<0.794.0>}
iex(pool@127.0.0.1)3> Received down message.
flush()
{#PID<0.794.0>, #Reference<0.1485725171.1652031495.47565>}
{#PID<0.794.0>, #Reference<0.1485725171.1652031495.47565>}
{#PID<0.794.0>, #Reference<0.1485725171.1652031495.47565>}
:ok
おお、やったぁ。
デプロイ
本番環境でリリース。
$ MIX_ENV=prod mix release
...
$ ll _build/prod/rel/pool/releases/0.1.0/合計 12892
drwxrwxr-x 5 feelinguy feelinguy 4096 5月 10 04:11 ./
drwxrwxr-x 3 feelinguy feelinguy 4096 5月 10 04:11 ../
drwxrwxr-x 2 feelinguy feelinguy 4096 5月 10 04:11 commands/
-rw-rw-r-- 1 feelinguy feelinguy 34974 5月 10 04:11 config.boot
-rw-rw-r-- 1 feelinguy feelinguy 47582 5月 10 04:11 config.script
drwxrwxr-x 10 feelinguy feelinguy 4096 5月 10 04:11 hooks/
drwxrwxr-x 4 feelinguy feelinguy 4096 5月 10 04:11 libexec/
-rw-rw-r-- 1 feelinguy feelinguy 34910 5月 10 04:11 no_dot_erlang.boot
-rw-rw-r-- 1 feelinguy feelinguy 47518 5月 10 04:11 no_dot_erlang.script
-rw-rw-r-- 1 feelinguy feelinguy 35616 5月 10 04:11 pool.boot
-rwxrwxrwx 1 feelinguy feelinguy 5768 5月 10 04:11 pool.ps1*
-rw-rw-r-- 1 feelinguy feelinguy 448 5月 10 04:11 pool.rel
-rw-rw-r-- 1 feelinguy feelinguy 48166 5月 10 04:11 pool.script
-rwxrwxrwx 1 feelinguy feelinguy 4195 5月 10 04:11 pool.sh*
-rw-rw-r-- 1 feelinguy feelinguy 12719279 5月 10 04:11 pool.tar.gz # これだよー
-rw-rw-r-- 1 feelinguy feelinguy 35616 5月 10 04:11 start.boot
-rw-rw-r-- 1 feelinguy feelinguy 48166 5月 10 04:11 start.script
-rw-rw-r-- 1 feelinguy feelinguy 34910 5月 10 04:11 start_clean.boot
-rw-rw-r-- 1 feelinguy feelinguy 47518 5月 10 04:11 start_clean.script
-rw-rw-r-- 1 feelinguy feelinguy 97 5月 10 04:11 sys.config
-rw-rw-r-- 1 feelinguy feelinguy 849 5月 10 04:11 vm.args
一番大きなサイズの tarball がある、12MBぐらい。
この pool.tar.gz
を本番環境に展開すればデプロイ完了。
$ mkdir ~/deploy
$ cp _build/prod/rel/pool/releases/0.1.0/pool.tar.gz ~/deploy/
$ cd ~/deploy
$ tar -zxf pool.tar.gz
$ ls -F
bin/ erts-10.3.4/ lib/ pool.tar.gz releases/
$ cd bin
$ ./pool start
$ ./pool ping
pong
$ ./pool remote_console
...
# iex セッション Ctrl+C
...
$ ./pool stop
ok
できたねぇ。
例えばこれを別アーキテクチャの環境に持っていっても動かない。
今時なんかだと Docker とかでやるんでしょうね。覚えること多すぎ。
おわりに
次の第22章はやりません、たぶん。
ホット・コード・ロードの話になるんだけど、これまたきつい。
これについては、書籍 プログラミング Elixir の第18章が参考になる。
ちょっと古いので Distillery ではなく EXRM というのを使ってるんだけど、
どっちのツールも開発者は同じなのでなんとかなる。
英語も読めなければ、メンターもいない私。がんばったよ。