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