Docker上でElixirのPhoenixとPostgreSQLを使ってみた
Phoenixの勉強をしたくて、手始めにDBからデータをとってきてテーブルで表示するというのをやってみたのでそれの備忘録兼Docker用いた日本語の記事が少ないような気がしたので、記事を投稿しようと思いました。
思ったより長めになってしまったので忙しい人は、こちらからソースコードを各自cloneしてください。
1. Dockerfileとdocker-compose.yml
とりあえず、今回のファイル群の中身です。説明も後にしますが、興味ない人はコピペだけでもいいと思います。
-
Dockerfile
FROM elixir WORKDIR /workspace RUN apt-get -y update &&\ mix local.hex --force && \ mix archive.install hex phx_new --force
-
docker-compose.yml
version: '3' services: PostgreSQL: image: postgres container_name: postgres ports: - 5432:5432 environment: POSTGRES_USER: root POSTGRES_PASSWORD: root POSTGRES_INITDB_ARGS: "--encoding=UTF-8" hostname: postgres restart: always user: root elixir: build: . ports: - 4000:4000 container_name: elixir volumes: - .:/workspace depends_on: - PostgreSQL command: /bin/bash tty: true stdin_open: true
まずはDockerfileから解説していきます。
-
Dockerfile
Dockerfileに関してはいたってシンプルです。
From命令でelixirイメージをpullして使用します。
WORKDIR命令では、Docker上でカレントディレクトリとして使用したいディレクトリを指定しています。デフォルトで指定したディレクトリが存在しない場合、新規に作成してくれます。
次に、RUN命令ですがとりあえず今後何かしらのコマンドを使用するかもしれないので
apt-get -y update
だけは実行しています。他に何かしら入れたい場合は、以下のように書けばいいでしょう。RUN apt-get -y update && apt-get install -y \ curl \ zip && \ mix local.hex --force && \ mix archive.install hex phx_new --force
\
は改行を表すもので可読性を高めるために用いられます。&&
はコマンドの区切りに用いられます。さて次に、
mix local.hex --force
とmix archive.install hex phx_new --force
に関してですが、これはPhoenixをインストールするために記述しており、またオプションの--force
つけています。Dockerの性質上、インストール時のyes/noを実行中に入力できないためこのオプションを付けて実行しています。 -
docker-compose.yml
docker-composeはDBを用いるため少し複雑です。まずこのdocker-composeは
version: '3'
とあるようにバージョンが3です。他の記事と合わせてこの記事をみるとき、片方がバージョン2を用いていた場合挙動がおかしくなったりエラーが発生する可能性があるので注意してください。さてservicesでは複数のコンテナを管理することが可能です。今回の場合ではelixirのコンテナとpostgresのコンテナを管理しています。ではそれぞれのコンテナについてみていきます。
-
PostgreSQL
PostgreSQLというのはサービス名です。コンテナの名前ではないことに注意してください。コンテナの名前は
container_name
に示されている通りpostgres
です。image
では使用するイメージを設定します。今回の場合ではpostgresです。container_name
は先ほど説明した通りこのサービスのコンテナ名です。ports
では通信するポートを指定します。文法としてはports: - ホスト側(自分のPC本体)のポート番号:コンテナ側のポート番号 - 以下例 - 4000:4000 - 8080:3000
となります。
environment
ではpostgresの環境設定を行います。今回の場合では、user名とpassword、文字コードの指定をしています。hostname
ではその名の通りホスト名を設定しています。restart
では何らかの問題でこのコンテナが落ちた場合に再接続させるためにalways
を設定します。always
を設定することにより、明示的にdocker stop コンテナ名
とされない限り、終了ステータスに関係なく常に再起動が行われます。user
ではuserを設定します。 -
elixir
ports
とcontainer_name
に関しては先ほど説明したので省きます。build
ではimage
とは違いDockerfileを用いてコンテナを作成したいときに用います。build: .
の時はdokcer-composeファイルと同階層にDockerfileを作成する必要がありますが以下のようにすればdocker-composeファイルから見た相対パスでDockerfileを指定できます。build: context:./hoge
volumes
の働きはホスト環境のディレクトリとコンテナのディレクトリを紐づけて共有するといったものである。文法は以下のような感じ。volumes: - ホストのディレクトリの相対パス若しくは絶対パス(ほとんどの場合相対パス):コンテナのディレクトリの絶対パス - 以下例 - ./hoge:/workspace - ./foo/run.sh:/woorkspace/run.sh
ディレクトリを共有するときは共有したいコンテナの場所の親ディレクトリを、
ファイルを共有したいときは共有したいコンテナの場所に同じ名前のファイルを指定するといった感じで書きます。
depends_on
の働きは、サービスの起動順序を決めます。ここではPostgreSQLサービスが立ち上がったのちにelixirサービスが立ち上がります。command
ではコンテナ起動時に、指定されたコマンドを実行します。tty
とstdin_open
はコンテナを永続化(起動しっぱなし)にするために設定します。
-
2. PhoenixとPostgreSQLを用いた簡単なwebアプリケーション
2.1 Phoenixプロジェクトの作成
やっと本題です。サブタイルにはWebアプリケーションと言っていますが、実際はDBに格納されたデータを只表示するだけなので、アプリケーション性は皆無です。
では早速、コマンドプロントを開いて以下のコマンドを実行してください。
cd /docker-composeの入ったディレクトリ
docker-compose up -d --build
docker attach elixir
docker-compose up -d --build
実行後は色々な処理が発生するので、それが終わるのを待ってから、docker attach elixir
を実行してください。
この時点でelixirコンテナ内にログインできているのを確認してから以下のコマンドを実行してください。
mix phx.new postgres_sample --no-webpack
cd postgres_sample
途中でyes/noを聞いてくるのですべてyを選択してください。
postgres_sampleディレクトリに移動していることを確認したのちに、ディレクトリの階層が以下のようになっていることを確認してください。
root@c77dbed21542:/workspace/postgres_sample# ls -al
total 18
drwxrwxrwx 2 root root 4096 Aug 21 05:30 .
drwxrwxrwx 2 root root 4096 Aug 21 05:31 ..
-rwxr-xr-x 1 root root 159 Aug 21 05:30 .formatter.exs
-rwxr-xr-x 1 root root 739 Aug 21 05:30 .gitignore
-rwxr-xr-x 1 root root 672 Aug 21 05:30 README.md
drwxrwxrwx 2 root root 0 Aug 21 05:30 config
drwxrwxrwx 2 root root 0 Aug 21 05:30 lib
-rwxr-xr-x 1 root root 1671 Aug 21 05:30 mix.exs
drwxrwxrwx 2 root root 0 Aug 21 05:30 priv
drwxrwxrwx 2 root root 0 Aug 21 05:30 test
確認出来たら、config/dev.exsをエディタで開いてください。
config :postgres_sample, PostgresSample.Repo,
username: "postgres",
password: "postgres",
database: "postgres_sample_dev",
hostname: "localhost",
show_sensitive_data_on_connection_error: true,
pool_size: 10
初期ではこのようになっているのでこれを以下のように変えます。
config :postgres_sample, PostgresSample.Repo,
username: "root",
password: "root",
database: "postgres_sample_dev",
hostname: "postgres",
show_sensitive_data_on_connection_error: true,
pool_size: 10
変更出来たら、コマンドプロントに戻って、以下を実行してください。
mix ecto.create
iex -S mix phx.server
もし、mix ecto.create
実行時にエラーが出た場合は、mix deps.get
コマンドを実行してください。
さて、iex -S mix phx.server
を実行したら、localhost:4000に接続してください。
すると、以下のような画面が表示されると思います。
これが表示されていればとりあえず、Phoenixが最低限動いていることが確認できます。
2.2 PostgreSQLのDBの構築
次は、PostgreSQLでDBを構築します。
まずはコマンドプロントで今、elixirが動いているので新しいウィンドウでコマンドプロントを立ち上げるか、ctrl+p,q
(ctrlを押しながらp,qの順番に押す)を実行すればコンテナを止めることなくホストの環境に戻ることができます。
ではホストの環境で、docker exec -it postgres psql -U root
と実行してください。
すると以下のように表示されるかと思います。
psql (11.5 (Debian 11.5-1.pgdg90+1))
Type "help" for help.
root=#
まずは、今現在どのようなデータベースが存在するかを確認するために、\l
と入力してください。すると以下のように表示されるはずです。
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
---------------------+-------+----------+------------+------------+-------------------
postgres | root | UTF8 | en_US.utf8 | en_US.utf8 |
postgres_sample_dev | root | UTF8 | en_US.utf8 | en_US.utf8 |
root | root | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | root | UTF8 | en_US.utf8 | en_US.utf8 | =c/root +
| | | | | root=CTc/root
template1 | root | UTF8 | en_US.utf8 | en_US.utf8 | =c/root +
| | | | | root=CTc/root
(5 rows)
次に早速データベースを構築していきます。まずは以下を実行してください。
create database elixir;
\l
コマンドでしっかりと作成されていることを確認します。
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
---------------------+-------+----------+------------+------------+-------------------
elixir | root | UTF8 | en_US.utf8 | en_US.utf8 |
postgres | root | UTF8 | en_US.utf8 | en_US.utf8 |
postgres_sample_dev | root | UTF8 | en_US.utf8 | en_US.utf8 |
root | root | UTF8 | en_US.utf8 | en_US.utf8 |
template0 | root | UTF8 | en_US.utf8 | en_US.utf8 | =c/root +
| | | | | root=CTc/root
template1 | root | UTF8 | en_US.utf8 | en_US.utf8 | =c/root +
| | | | | root=CTc/root
(6 rows)
一番上にelixirとあるのが確認できます。
次に\c elixir
で、先ほど作ったelixirデータベースに接続します。
接続すると先ほどまで、root=#
となっていたところが、elixir=#
となっていることが確認できます。
確認出来たら、テーブルの作成に移ります。今回は主キーなどの設定は行わず、ごくごく簡単なテーブルを作成します。
以下のコマンドでテーブルを作りましょう。
create table fruits_datas(id integer, name varchar(255), value integer, region varchar(255));
果物を管理するテーブルを今回は作成しました。それぞれのinteger
やvarchar
などの意味は各自で確認してください。
では、テーブルが正しく作成されているか\z
と\d fruits_datas
で確認します。前者はテーブル自体の存在確認で、後者はテーブルの定義を確認できます。
結果として、以下のようになると思います。
elixir=# \z
Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+--------------+-------+-------------------+-------------------+----------
public | fruits_datas | table | | |
(1 row)
elixir=# \d fruits_datas
Table "public.fruits_datas"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | integer | | |
name | character varying(255) | | |
value | integer | | |
region | character varying(255) | | |
こうなっていれば正しくテーブルは作成されています。
では、実際にこのテーブルに対してデータを与えていきます。以下のコマンドを実行して下さい。
insert into fruits_datas values(1, 'みかん', 80, '日本'), (2, 'りんご', 120, 'アメリカ'), (3, 'バナナ', 150, 'インド'), (2, 'りんご', 90, '中国'), (3, 'バナナ', 120, 'ブラジル');
これが正しく、実行されていれば以下のようになるはずです。
elixir=# select * from fruits_datas;
id | name | value | region
----+--------+-------+----------
1 | みかん | 80 | 日本
2 | りんご | 120 | アメリカ
3 | バナナ | 150 | インド
2 | りんご | 90 | 中国
3 | バナナ | 120 | ブラジル
(5 rows)
このようになっていれば今回のdbでの作業は終わりなので\q
を実行するかctrl+p,q
してください。
2.3 PhoenixとDBの紐づけ
次は、dbとPhoenixを紐づけていきます。とりあえず再度docker attach elixir
でコンテナにログインし、postgres_sample
ディレクトリに移動しておいてください。
と言っても、先ほどctrl+p,q
で終了した人は、attachしてもなかなかコマンドプロントが表示されないと思いますが、Enterキーなどを打ってもらうと
nil
iex(2)>
のように表示されると思うので、ctrl+c
を2回押してもらうと、作業可能状態になります。
では、postgres_sampleディレクトリ下に/lib/util/db.exを作成しましょう。
そして、db.exを以下のように編集します。
PostgresSample.Repoとなっているところはプロジェクト作成時のmix phx.new postgres_sample --no-webpack
のpostgres_sampleの部分に合わせてください。わからなければ、postgres_sample\config\config.exsやpostgres_sample\config\dev.exsにそれっぽいのが書いているのでそれをコピペしましょう。
defmodule Db do
def query( sql ) when sql != "" do
{ :ok, result } = Ecto.Adapters.SQL.query( PostgresSample.Repo, sql, [] )
result
end
def columns_rows( result ) do
result
|> rows
|> Enum.map( fn row -> Enum.into( List.zip( [ columns( result ), row ] ), %{} ) end )
end
def rows( %{ rows: rows } = _result ), do: rows
def columns( %{ columns: columns } = _result ), do: columns
end
次に、postgres_sample以下にlib\postgres_sample_web\templates\page\index.html.eexを作成して以下のように編集します。
<%
result = Db.query( "select * from fruits_datas" )
data = result |> Db.columns_rows
%>
<table border="1">
<tr>
<th>ID</th>
<th>名前</th>
<th>価格</th>
<th>生産国</th>
</tr>
<%= for record <- data do %>
<tr>
<td><%= record[ "id" ] %></td>
<td><%= record[ "name" ] %></td>
<td><%= record[ "value" ] %></td>
<td><%= record[ "region" ] %></td>
</tr>
<% end %>
</table>
次に、config/dev.exsのdatabaseの部分をpostgres_sample_dev
からelixir
に変更します。
config :postgres_sample, PostgresSample.Repo,
username: "root",
password: "root",
database: "elixir",
hostname: "postgres",
show_sensitive_data_on_connection_error: true,
pool_size: 10
では、動くかどうか確認しましょう。
iex -S mix phx.server
を実行してください。
実行したらlocalhost:4000に接続してください。
すると成功していれば以下のような画面が表示されるはずです。
さきほどDBで作成したデータがきちんと表示されていますね。
以上で基本的なことは終了です。お疲れさまでした。
終わりに
これから、自分もこの知識を生かし次のステップに進もうと思います。またある程度知識がつけば記事にしたいと思っています。長々とありがとうございました。