Elixir
環境設定
Phoenix

ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる

(この記事は、「Elixir or Phoenix Advent Calendar 2017」の19日目です)

昨日は、@piacere さんの「Excelから関数型言語マスター4回目:Webに外部APIデータ表示」でした


はじめに

Elixirで実際にプロダクト開発した経験からサンプルコードを交えて解説する本連載

今回もDB周りではありますが、configに関するお話です。

今までの連載で実装したサンプルでは、DBのアクセス情報はPostgreSQLのデフォルトをそのまま使っていましたが、本番環境ではユーザー/パスワードなどの接続情報はセキュリティ観点から何かしらの秘匿をする必要があります。

※どういった秘匿方法をとるかは、システムに求められる要件や、本番環境の構成、保守チームの運用ルールによるところがあるので一概には言えません。

今回はPostgreSQLへの接続設定を例に、環境変数に設定することでGitなどのリポジトリに残らない設定方法を紹介します。

※開発者と運用者が分離された体制において、秘匿情報をコード管理上から排除することで開発者に対して秘匿する目的の場合に有効

本連載の記事はこちら

|> ElixirでSI開発入門 #1 Ectoで悲観的ロック

|> ElixirでSI開発入門 #2 Ectoで楽観的ロック

|> ElixirでSI開発入門 #3 主キーが"id "じゃない既存DBへの接続

|> ElixirでSI開発入門 #4 本番パスワードを環境変数に持たせる

:stars::stars::stars::stars::stars: お知らせ :stars::stars::stars::stars::stars:

「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します

私も本連載で出してないSI開発ネタを出す予定ですので、

Elixirでプロダクト開発最前線に興味ある方はぜひぜひご応募ください!

image.png


System.get_env()で環境変数を取得する

Phoenixの configは、それ自体がMix.Configを実装したElixirプログラムですので、内部でモジュールを呼び出すことが可能です。

System.get_env()を使えば、設定を実行環境の環境変数から取得することができます。


実装の前提

今回は以下の実装を例に考えます


  • PhoenixでUserモデルを実装する

  • DBはデフォルトのPostgreSQLを選択する

  • 本番環境設定ではDB設定情報を環境変数から取得する

また、以下の環境で実装しました


  • Elixir v1.6.1

  • Phoenix v1.3.2

  • Ecto v2.2.10

  • PostgreSQL v10.2


開発手順 


プロジェクト〜モデルの作成

PhoenixプロジェクトとDBを作成

> mix phx.new config_env_var --no-brunch

> cd config_env_var
> mix ecto.create

モデルを作成

> mix phx.gen.html Accounts User users name mail

ルーティングを追加


lib/config_env_var_web/router.ex

defmodule ConfigEnvVarWeb.Router do

use ConfigEnvVarWeb, :router

〜 中略 〜

scope "/", ConfigEnvVarWeb do
pipe_through :browser # Use the default browser stack

get "/", PageController, :index
resources "/users", UserController # <-- ルーティングを追加
end

〜 中略 〜

end



configの内容を確認する

Phoenixの設定ファイルを改めて見てみると環境ごとの設定は{環境名}.exsというネーミングの別のファイルを参照している。


config/config.exs

# This file is responsible for configuring your application

# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

〜 中略 〜

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs" # <-- 環境固有の設定は{環境名}.exsファイルに記載されている


開発環境の場合、DBの接続情報もそのまま定義されている。


config/dev.exs

use Mix.Config

〜 中略 〜

# Configure your database
config :config_env_var, ConfigEnvVar.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres", # <-- テスト系では秘匿しないで良い
password: "postgres", # <-- テスト系では秘匿しないで良い
database: "config_env_var_dev",
hostname: "localhost",
pool_size: 10


一方、本番環境の設定では、さらにprod.secret.exsという別の設定ファイルを参照するようになっている。


config/prod.exs

use Mix.Config

〜 中略 〜

# Finally import the config/prod.secret.exs
# which should be versioned separately.
import_config "prod.secret.exs" # <-- 環境固有となる設定の大部分はprod.secret.exsに記載するようになっている


prod.secret.exsは.gitignoreファイルにあらかじめ定義されているので、バージョン管理の対象とならない(ここがみそ)


.gitignore


〜 中略 〜

/config/*.secret.exs


ただし、この仕組みに則ると、サーバー毎にprod.secret.exsを git以外の経路で配布、管理する必要がある。

で、あればもうファイルじゃなくてもいいのではないかと・・・


本番環境設定を変更する

prod.exsを修正して、DBの接続情報を環境変数から取得するようにする


config/prod.exs

use Mix.Config

〜 中略 〜

# エンドポイントの設定は今回対象外なのでdevのものを移植して動くようにする

#config :config_env_var, ConfigEnvVarWeb.Endpoint,
# load_from_system_env: true,
# url: [host: "example.com", port: 80],
# cache_static_manifest: "priv/static/cache_manifest.json"
config :config_env_var, ConfigEnvVarWeb.Endpoint,
http: [port: 4000],
debug_errors: true,
code_reloader: false, # <-- デフォルトのmix.exsではphoenix_live_reloadがonly: :devなのでOFFにする
check_origin: false,
watchers: []

〜 中略 〜

# Finally import the config/prod.secret.exs
# which should be versioned separately.
# import_config "prod.secret.exs" <--コメントアウトする

# 以下を追記
config :config_env_var, ConfigEnvVar.Repo,
adapter: Ecto.Adapters.Postgres,
username: System.get_env("EX_DB_USER"), # <--秘匿情報を環境変数から取得
password: System.get_env("EX_DB_PASS"), # <--秘匿情報を環境変数から取得
database: System.get_env("EX_DB_NAME"), # <--秘匿情報を環境変数から取得
hostname: System.get_env("EX_DB_HOST"), # <--秘匿情報を環境変数から取得
pool_size: 10



本番環境設定で動かしてみる

環境変数を設定して確認する

> export EX_DB_USER=prod_user

> export EX_DB_PASS=prodpass
> export EX_DB_NAME=config_env_var_prod
> export EX_DB_HOST=localhost
> env | grep EX_
EX_DB_USER=prod_user
EX_DB_PASS=prodpass
EX_DB_NAME=config_env_var_prod
EX_DB_HOST=localhost

Phoenixサーバーを本番設定で起動する。

> MIX_ENV=prod mix ecto.create

> MIX_ENV=prod mix ecto.migrate
> MIX_ENV=prod mix phx.server

以下のURLにアクセスし、適当にユーザーを登録してみる。

http://localhost:4000/users

本番用のDBにアクセスして登録結果を確認してみる。

> psql -h localhost -d config_env_var_prod -U postgres

config_env_var_prod=# select * from users ;
id | name | mail | inserted_at | updated_at
----+--------+------------------------------+----------------------------+---------------------------
1 | 土ロー | tcr.yoshimura@karabiner.tech | 2018-05-11 10:12:19.152122 | 2018-05-11 10:12:19.15584
(1 row)


まとめ


  • 環境変数を取得するにはSystem.get_env()

  • 本番設定でMixコマンドを実行するときには先頭にMIX_ENV=prodをつける

  • 秘匿の方法や必要なレベルはプロジェクトによって異なるので注意

いかがだったでしょうか。

これで本番リリースへ向けて一歩前進したのではないでしょうか?

明日は、@twinbee さんの「Elixirから簡単にRustが呼べるRustler#1 準備編」です。