Rubyistのための環境変数徹底ガイド――仕組みの理解からRailsでの実践管理まで
Webアプリを開発環境と本番環境の両方で効果的に管理したいなら、環境変数への深い理解は欠かせません。
実は、これは昔から当たり前だったわけではありません。数年前まで、Railsアプリを環境変数で設定する開発者はほとんどいませんでした。しかし、Herokuの登場がその常識を大きく変えたのです。
Herokuは開発者に「12-Factor App(12ファクター・アプリ)」という設計手法を広めました。このマニフェストには、デプロイしやすいアプリを作るためのベストプラクティスが数多くまとめられており、中でも環境変数に関するセクションは特に大きな影響力を持っています。
12ファクター・アプリは設定(config)を環境変数に格納します(env vars や env と略されることもあります)。環境変数はコードを一切変更せずにデプロイごとに簡単に切り替えられます。設定ファイルと違い、誤ってリポジトリにコミットされてしまう心配もほとんどありません。さらに、独自の設定ファイルやJavaのシステムプロパティといった他の設定メカニズムと異なり、言語やOSに依存しない標準的な仕組みです。
現在、環境変数を活用するRubyistはかつてないほど増えています。しかし残念ながら、その多くは「カーゴカルト(呪文的な模倣)」的に使っているのが実情です。仕組みを本当の意味で理解しないまま使っているのです。
この記事では、環境変数が実際にどのように動作するのか、そしておそらくそれ以上に重要な「どのように動作しないのか」を解説します。さらに、Railsアプリで環境変数を管理する代表的な方法もいくつかご紹介します。それでは始めましょう!
NOTE: 環境変数のセキュリティ対策については、こちらの関連記事も併せてご覧ください。
各プロセスは独自の環境変数セットを持つ
サーバー上で実行するすべてのプログラムには、少なくとも1つのプロセスが存在します。そして、そのプロセスはそれぞれ独自の環境変数セットを受け取ります。一度起動してしまうと、そのプロセスの外部からは環境変数を変更できません。
初心者が陥りやすい誤解の一つが、「環境変数はサーバー全体で共有されている」と思い込むことです。Herokuのようなサービスは、環境変数の設定がディスク上の設定ファイル編集と同じ感覚でできるように見えるため、なおさらそう錯覚しがちです。しかし、環境変数は設定ファイルとはまったく別物なのです。
サーバー上で実行するすべてのプログラムは、起動された瞬間に専用の環境変数セットを受け取ります。
各プロセスは独自の環境を持つ
環境変数はプロセスと運命を共にする
環境変数を設定したのに、マシンを再起動したら消えていた――そんな経験はありませんか? 環境変数はプロセスに属しているため、プロセスが終了すると同時に環境変数も消えてしまいます。
これは簡単に確認できます。1つ目のIRBセッションで環境変数を設定し、そのセッションを閉じた後、2つ目のIRBセッションで同じ変数にアクセスしてみてください。
プロセスが終了すると、その環境変数は失われる
サーバーの再起動時やシェルを終了したときに環境変数が失われるのも、まったく同じ原理によるものです。セッションをまたいで環境変数を保持したい場合は、.bashrc のような設定ファイルに保存しておく必要があります。
プロセスは親から環境変数を受け継ぐ
すべてのプロセスには親が存在します。すべてのプログラムは、何らかの別のプログラムによって起動される必要があるからです。
bashシェルからvimを起動すれば、vimの親はそのシェルです。RailsアプリがImageMagickの identify コマンドで画像を判別するなら、identify プログラムの親はあなたのRailsアプリになります。
子プロセスは親から環境変数を受け継ぐ
以下の例では、IRBプロセス内で $MARCO という環境変数を設定しています。続いてバッククォートを使ってシェルを呼び出し、その変数の値を echo で表示しています。
IRBはここで生成したシェルの親プロセスにあたるため、シェルは $MARCO 環境変数のコピーを受け取るのです。
Rubyで設定した環境変数は子プロセスに引き継がれる
親は子に渡す環境変数をカスタマイズできる
デフォルトでは、子プロセスは親が持つすべての環境変数のコピーを受け取ります。しかし、親側にはこの挙動を制御する手段があります。
コマンドラインからは env コマンドを利用できます。また、bashには親自身には影響を与えず、子プロセスだけに環境変数を設定できる特別な構文が用意されています。
envコマンドを使えば、親に設定せずに子プロセスだけへ環境変数を渡せる
Rubyからシェルコマンドを実行する場合も、ENVハッシュを汚すことなく子プロセスへカスタム環境変数を渡せます。system メソッドで次のような構文を使います:
Rubyのsystemメソッドへカスタム環境変数を渡す方法
子プロセスは親の環境変数を変更できない
子プロセスが受け取るのは親の環境変数のコピーだけです。そのため、子プロセス側で行った変更が親に反映されることはありません。
環境変数は「参照渡し」ではなく「値渡し」
次の例では、バッククォート構文でシェルを呼び出し、環境変数を設定しようとしています。子プロセス内では変数は設定されますが、その新しい値が親へさかのぼることはありません。
子プロセスは親の環境変数を変更できない
実行中のプロセス間で環境の変更は同期されない
以下の例では、2つのIRBを並行して実行しています。片方のIRBセッションの環境に変数を追加しても、もう片方のIRBセッションには一切影響しません。
あるプロセスに環境変数を追加しても、他のプロセスには反映されない
シェルは環境変数システムのUIにすぎない
環境変数システム本体はOSカーネルの一部です。つまり、シェルには環境変数に対する特別な権限など存在しません。シェルもまた、あなたが実行する他のすべてのプログラムと同じルールに従わなければならないのです。
環境変数とシェル変数は別物である
最大級の誤解の原因は、シェルが独自の「ローカル」シェル変数システムを備えていることにあります。ローカル変数の記述構文は環境変数とほぼ同じため、初心者はこの2つを混同しがちです。
しかし決定的な違いとして、ローカル変数は子プロセスにコピーされません。
環境変数とシェル変数は同じではない
具体例を見てみましょう。まず MARCO という名前のローカルシェル変数を設定します。これはローカル変数なので、どの子プロセスにもコピーされません。その結果、Ruby経由で値を表示しようとしても失敗します。
次に、export コマンドを使ってローカル変数を環境変数へ変換します。すると、このシェルが今後生成するすべての子プロセスに値がコピーされるようになり、Rubyからも参照できるようになります。
ローカル変数は子プロセスから利用できない。exportで環境変数へ変換できる
実践:環境変数の管理
ここまでの知識は、現場ではどのように活きるのでしょうか? 具体的な例を見てみましょう。
1台のサーバー上で2つのRailsアプリが稼働しているとします。例外監視にはHoneybadgerを利用していますが、ここで一つの問題に直面しました。
HoneybadgerのAPIキーを $HONEYBADGER_API_KEY 環境変数に保存したいところですが、2つのアプリはそれぞれ異なるAPIキーを持っています。
「1つの環境変数に2つの異なる値を持たせるには、どうすればいいのか?」
ここまで読み進めた皆さんなら、もう答えは分かるはずです。環境変数はプロセスごとに独立しているため、2つのRailsアプリが別々のプロセスで動いている限り、それぞれが独自の $HONEYBADGER_API_KEY の値を持つことに何の矛盾もありません。
あとは設定方法の問題だけです。幸い、これを簡単に実現してくれるgemがいくつか存在します。
Figaro
Figaro gemをRailsアプリにインストールすると、config/application.yml に記述した値が、アプリ起動時にRubyのENVハッシュへ自動的に読み込まれます。
インストールはとてもシンプルです:
# Gemfile
gem "figaro"
あとは application.yml に設定項目を追加していくだけです。重要な注意点として、このファイルは必ず .gitignore に追加してください。機密情報を誤ってコミットしてしまうのを防ぐためです。
# config/application.yml
HONEYBADGER_API_KEY: 12345
Dotenv
dotenv gemはFigaroと非常によく似ていますが、YAMLではなく .env ファイルから環境変数を読み込む点が異なります。
gemをインストールします:
# Gemfile
gem 'dotenv-rails'
続いて設定値を .env に記述します。GitHubへ誤って公開しないよう、このファイルも必ずgit ignoreの対象に含めてください。
HONEYBADGER_API_KEY=12345
あとはRubyのENVハッシュから値へアクセスできます:
ENV["HONEYBADGER_API_KEY"]
さらに、事前に定義した環境変数のセットを使ってシェルコマンドを実行することも可能です:
dotenv ./my_script.sh
Secrets.ymlはどうなのか?
残念ながら、Secrets.ymlは確かに便利な機能ですが、環境変数を設定するものではありません。そのため、Figaroやdotenvといったgemの代替手段にはなり得ない点に注意しましょう。
素のLinuxコマンドで管理する
gemに頼らず、基本的なLinuxのコマンドだけでアプリごとに独立した環境変数セットを管理することも可能です。定番のアプローチの一つは、サーバー上の各アプリをそれぞれ異なるユーザーアカウントで実行するようにすることです。そうすれば、ユーザーごとの .bashrc を使って、アプリ固有の設定値を安全に分離して保存できます。
-
環境変数を守る:APIキーやシークレットのためのセキュリティ対策ガイド
前回の記事「The Rubyists Guide To Environment Variables」では、環境変数システムの仕組みと、よくある誤解について解説しました。しかし、ある読者から「セキュリティについての言及が少ない」とのご指摘をいただきました。 現在では、秘密のAPIキーなどの重要な情報を環境変数に保存するのが一般的になっています。だからこそ、環境変数が持つセキュリティ上のリスクを正しく理解することが重要です。今回はそのポイントを見ていきましょう。 最悪のシナリオ ハッカーがroot権限、あるいはWebアプリケーションの所有ユーザーとしてサーバーへのアクセスに成功したと想像してくださ
-
Rubyで環境変数を使う方法|ENVオブジェクトの操作から設定・Rails credentialsまで徹底解説
環境変数とは、次のような「キーと値」のペアとして表されるデータのことです。 KEY=VALUE この変数は、パソコン上で動くすべてのプログラム間で設定情報(コンフィグ)を共有するために使われています。 だからこそ、環境変数がどのような仕組みで動作し、Rubyプログラムから特殊な ENV オブジェクトを使ってどうアクセスするのかを学んでおくことが大切なのです。 環境変数の活用例 デフォルトエディターの設定 Rubyにgemの保存場所を伝える(GEM_PATH / GEM_HOME) APIキーをGitリポジトリにコミットすることなくアプリケーションへ渡す OSがバイナリファイル(Windo