Dotenvはproductionで使わないほうがよいのではという話
またRubyとかRailsの話になってる。本当はこんな話なんてしたくなくて、スクフェスの話でもしたい。凛ちゃんマジえんじぇー。
Webアプリケーションを書いていると、データベースのユーザ名やパスワード、接続先サーバのIPなどなど、アプリケーションコードとは関係がないけれどもリポジトリ内には含めたくない、動かす環境に合わせて変更する必要のある設定を扱う機会がしばしばある。
こういう設定は環境変数に設定すると便利だよっていう考えがあって、The Twelve Factorsで紹介されていたりする。
これを実現するには、例えば /etc/environment
や ~/.bash_profile
みたいなファイルに書けばいいんだけど、開発環境では1台のPC上で複数のアプリケーションを書く機会も少なくないはず。
そういう場合に、/etc/environment
のようなファイルに設定を書くと、プロジェクトをまたぐたびに設定を書き換えなくてはならず非常に面倒。
これを手軽に切り替えられるようにしようというのが dotenv というgemで、プロジェクトディレクトリに.env
ファイルを置いておくとアプリケーションロード時に環境変数を.env
ファイルに書いてある内容で上書きしてくれる、というもの。
こうなってくると便利だし、development
な環境だけではなくて production
な環境でも使いたいよね、ってなりそう。
「データベースのパスワードなどはリポジトリにコミットしたくないし、各サーバにdotenvを配置して、capistrano で link したら便利そう。preload_app: true で unicornをreloadすれば設定も変わって便利!」みたいに思うけど、実際にはそうはいかない。
reloadしたタイミングはdotenvは.env
ファイルを読み直さないので設定が読み込まれない。
適当にビューを作って、pp ENV
とでも書いて、unicornにSIGUSR2を送れば読み込まれないことが確認できる。
そういうことなので、普通に使うと.env
ファイルを変更しても unicorn を stop/start しなければ設定は再読み込みされず、そうするとダウンタイムが発生してしまう。
production
な環境でダウンタイムが発生するのはよくないので使えなさそう、ってなるけど unicorn には色々な hook があって、例えば、fork して 子プロセスがアプリケーションコードを読み込む前に、.env
の内容で環境変数を上書きしてしまえばこの問題を解決できるんじゃない?って思い始める。
dotenv には #overload
というメソッドがあって、ENV の内容を .env
の内容で上書きできるので、これを before_exec
に書いてみる。
before_exec do Dotenv.overload end
なるほど動きそう、便利っぽい。うん、いいね、って一瞬思いそうになるけどgithubのissueを眺めていると、こんなissueを見つける。
すごい雑にいうとこんな質問。
unicorn使ってるんだけどrestartせずにreloadで設定読み直せない?
それに対する回答がこれ。
ないし、実装の予定もないけど、アプリケーションコードで、
Dotenv.overload
を呼べばリロードできるよ。
なるほど。しかし・・・
dotenv が Ruby プロセスに環境変数をロードすることは、以前から設定されていた環境変数とdotenvによって読み込まれた変数を判別することを難しくします。 私が production 環境において dotenv の使用を提唱しない理由の一つです。 環境変数はサーバの /etc/environment や /etc/profile などのファイルに設定すればunicornをreloadするだけで動作します。
ふむ・・・。訳に自信がないけどこんなことが書いてある気がする。
確かにDotenv.overload
は、.env
ファイルを変更した場合、以前の設定値に対して上書きを行うだけ。
少し乱暴に言えば ENV.update new_env
をするのと似ています。
ここまで長々と書いたけど、自分の目で確かめたい人もいそうなので rails new
することにしました。
GitHub - t-cyrill/dotenv-example: Dotenv + Rails + unicorn example
Exampleも用意したので、動かしながら確認。そろそろ書くのが面倒になってきた。がんばりたい。
最初の.env
には AAAA_FOO="FOO"
とAAAA_BAR="BAR"
を書いてあるので、動かすとそれぞれの値が表示される、はず。
dotenv-example
はInitialで基礎部分を書いて、before_exec
の部分を次のコミットに書いておいたので、 .env
を適当に書き換えて、例えばAAAA_FOO="ZZZZZ"
とAAAA_BAR="XXXXX"
のような値を設定してREADMEに書いたようにunicorn
にSIGUSR2
を送ってあげると、値が書き換わることが確認できると思う。
これだけなら問題はなさそうだけど、ここで.env
ファイルに書かれているAAAA_FOO
を消してみる。
AAAA_BAR="BAR"
この状態でunicorn
にSIGUSR2
を送る。.env
ファイルはreloadされるが、AAAA_FOO
が残っていることが確認できると思う。
この動きを忘れるとproduction
環境では思わぬトラブルを起こすかもしれない。
だから dotenvをproduction
で使うのはやめよう、っていうのが上のissueに対する回答みたい。
確かに開発環境ではrails s
したサーバにSIGINTしてrails s
しなおすのはよく行われるだろうし、この問題にははまりづらい。
でも、unicorn
をproduction
で使うとまずはまるよね。
値を追加したり、書き換えるケースでは問題にはならないケースがほとんどだけど消すのはまずい。
そんなケースあるの?って思うかもしれないけどアプリケーションコード中にこんなコードがあるかもしれない。
if ENV['EXPERIMENTAL_ACTION_FLAG'] # something end
こういうのをレビューで防ぎきるのは難しそうだし、危険が潜んでいるなら避けていきたい。
でも、/etc/environment
を使えばこの問題は解決できるのだろうか、はて?
ところでこんなに色々書いたところで、ふと昔に似たような話をみたような、と思って探してみたらこんなのも見つけた。
- Overload environment variables by ecbypi · Pull Request #61 · bkeepers/dotenv · GitHub
- DotEnv + Unicorn zero downtime = fail · Issue #57 · bkeepers/dotenv · GitHub
Dotenvをproduction
で使われることは本当に望まれていないみたい・・・。
Dotenvは開発時のサポートに作られたものなので production
環境で使うのは避けたいですね。
続きの話を書きました (2016/12/22)
最後に
凛ちゃんマジえんじぇーで始まったこのエントリーですが、にこまきが好きです。Railsのコードより にこまき をお待ちしております。
クオリティの低いツイート流していきたい
— シリル@バンドリを信じるアカウント (@d_cyrill1129) March 24, 2015
適度に透明になっていきたい
— シリル@バンドリを信じるアカウント (@d_cyrill1129) March 24, 2015