環境変数について改めて考える

こんばんは!やがさきです。

今回は改めて環境変数について考えてみます。みなさん、環境変数使ってますよね?どんな場面で使ってますか?そりゃ、名前の通り環境ごとに変えないといけない値を入れてますよね。でも、それ、本当にそれは環境固有の情報ですか?なんでもかんでも環境変数にしちゃってませんか?1実行環境に環境変数が100個超えちゃってたりしますか?そして、その環境変数ってどうやって管理してますか?また、パスワードやクレデンシャルなどのシークレット情報も環境変数で管理してますか?

このあたりを改めて考えてみたいと思います!

環境変数にすべき値

では、まずどんな値を環境変数にすべきかを改めて考えてみます。名前の通り、環境によって変わる値を環境変数にすべきなのは当然かと思うのですが、これをそのまま地でいくと、だいたいすごい数の環境変数になっちゃいます。そうすると、環境変数をメンテしたり設定したりするだけでひと仕事になりますし、どの環境変数をどうするとどんな挙動になるのかを理解するのが非常に困難になり、環境変数職人みたいな謎のスキルを持った人が爆誕したりします。これはかなりしんどい状況でありつつ、あるあるなんじゃないかなと思います。では、どのような値を環境変数にしないということが考えられるでしょうか?

環境ごとに変わるかもしれないけど基本的にはまず変わらない値

こういった物はコードに書いちゃって良いのではないか?と考えることもできます。

特に昨今では、Infrastructure as Code(IaC) や CI/CD が発達していることもあり、本番環境において環境変数などのパラメータだけを変更することは少なくなってきているかと思います。パラメータシートをもとに、手動でパラメータの設定をダブルチェックをしながらやったりするのは、ほんとやめましょう。それは危険なので、環境変数とともにデプロイ、もしくは再プロビジョニングをするという方向になってきているのではないでしょうか?そのため、”環境ごとに変わるけれども、基本的にはまず変わらない値” はコードに埋め込んでしまうほうが効率的なのではないか?と考えられるようになってきているかと思います。コードに書くと言っても、さすがに思いっきりインラインで書いちゃうと変更もつらすぎるので、定数にはすべきかと思います。

もし開発環境だけはテストやモックのために書き換えたい値がある場合は、適切なデフォルト値を設定するようにしておき、開発環境だけはその値を環境変数で上書きできるようにする、というのも良いのではないでしょうか?デフォルト値は設定されているが、それがダミーのような値や開発環境用の値というのはよくあるのではないでしょうか?我々は開発環境で動くアプリケーションを作っているのではなく、本番環境で適切に動くアプリケーションを作っているので、デフォルト値を本番環境で動作するように適切に設定するというのが良いのではないでしょうか?

環境変数の値の管理方法

上記の流れで環境変数を必要最小限にしたとしてもまだ環境変数はゼロにはできないケースが多いかと思います。では、環境変数の値はどのように管理するのが良いでしょうか?たとえば AWS の Amazon ECS を使うという場合を考えてみると

  • コンテナの定義に環境変数を直接書く
  • コンテナの定義に環境変数をどのパラメータストア・シークレットマネージャから取ってくるかを書く
  • .env などの環境変数ファイルをコンテナに同梱してアプリケーションから読み込む
  • フィーチャーフラグなど外部に持たせる
  • そもそも環境変数を使わない

などの方法があります。これらを IaC, CI/CD を使っているという前提をおいてもう少しブレークダウンしてみましょう。

コンテナの定義に環境変数を直接書く

ECSのタスク定義に直接環境変数名と値を書くパターンです。インフラのIaCでタスク定義ファイルを管理するという前提であれば、これはバージョン管理もされますし、良い選択かと思います。

コンテナの定義に環境変数をどのパラメータストア・シークレットマネージャから取ってくるかを書く

ECSのタスク定義に直接環境変数名とそれに対応する値をどこから取ってくるかを書くパターンです。これは、パラメータストアやシークレットマネージャの値をどのように管理するかによって有益かどうかが決まってくるかと思います。IaCやCI/CDを伴わずに直接CLIやコンソールからパラメータストアの値をいじるユースケースが強くある場合はこれを選ぶのもありかもしれません。しかし、パラメータストアの値を結局IaCで管理しているのであれば、ただの2度手間になっているかもしれません。新しい環境変数を反映させるためにコンテナを再起動する手間もかかります。場合によって「コンテナの定義に環境変数を直接書く」の方が効率もセキュリティも良い可能性があります。

.env などの環境変数ファイルをコンテナに同梱してアプリケーションから読み込む

これは、CI/CDなどでコンテナをビルドするタイミングで .env などのファイルを同梱してしまうパターンです。 .env もコードリポジトリ内で環境ごとに分けて管理しているのであれば、ビルド時に確実に同梱されますし、ローカル環境、開発環境、本番環境での環境変数の扱いをなるべく共通にするという意味でも有益かと思います。ローカル環境だと、 .env が正義!みたいな状況だけども、本番環境ではクラウドから渡される環境変数が正義!みたいなことがよくあるかと思います。せっかくコンテナを使っているのに、それこそ環境差異による不具合の原因になったりしませんか?ということで、この方法も有益な場面が多くあるかと思います。

フィーチャーフラグなど外部に持たせる

フィーチャーフラグ・フィーチャートグルと言われる外部のサービスに値を持たせるという選択肢もあります。AWS においては、 AWS AppConfig が提供されています。また、 LaunchDarkly のように SaaS として提供されている場合もあります。アプリケーションにこれらのフィーチャーフラグを使うようにライブラリを組み込むことによって、動的に外部パラメータを読んで使うことができます。読み込みのオーバーヘッドやキャッシュ機構なども考慮する必要が出てきますが、ライブラリがよしなにやってくれる可能性が高いので安心して使えます。別途外部のサービスを使うという部分が懸念になる場合もありますが、フィーチャーフラグは環境変数用と以外にも有益です

参考: https://martinfowler.com/articles/feature-toggles.html

そもそも環境変数を使わない

いままでは、いわゆるバッドプラクティス・アンチパターンと言われていた、「コードに環境情報を埋め込む」という方法も、IaCやCI/CDが出てきてからは状況が変わっているかと思います。CI/CD無しで動作が変わることを本番環境にするのはNGですので、本番環境の環境変数だけを変更するという場面は少なくなってきているかと思います。そのため、思い切って環境変数を使わずにコードで判断する、という方法ももはや悪くはないのではないでしょうか?

シークレット情報の管理

しかし、環境変数にはデーターベースへの接続情報など、秘匿にしないといけないものも含まれていたりします。これはさすがにアプリケーションと同じコードリポジトリで管理するのは危険な場面が多いはずです。そのため、

  • 結局シークレットマネージャなど秘匿情報格納専用のサービスを使い、手動で設定する
  • 本番用に権限を絞った CI/CD リポジトリを用意してそちらに格納し、ビルド時に動的に環境変数に埋め込む
  • 本番用に権限を絞った CI/CD リポジトリを用意してそちらに格納し、ビルド時に動的にコードを変更してコードに埋め込む
  • 本番用の権限を絞った IaC リポジトリを用意してそちらに格納し、本番プロビジョニング時にパラメータストアやシークレットマネージャに格納する

などの検討が環境変数と合わせて必要になります。セキュリティ的にどれが良いのかというのは組織のポリシーなどによって決まってくるかと思いますが、結局誰かがどこかで値の管理をしないといけないので、丸見えの方法を取らない限りは、どこに保管するかよりも管理・運用方法の方が重要になってくるため、できるだけ人が関わらないプロセスを選ぶ方がセキュアになるかと思います。

おわりに

いままでは、

環境で変わりそうな値は環境変数にしましょう!
環境変数はパラメータストアなどに入れてコードと分離して変更しやすくしましょう!
秘匿情報は完全に分離して保存しておきましょう!

というような事をなんとなく慣習に従って行っていたこともあるかと思いますが、そもそもビルド、デプロイ、リリースのプロセスが大きく変わっているため、ちゃんと新しい方法に沿って環境変数の管理も考えないとな〜っていうのを自戒も兼ねて記しておきます。

こちらからは以上です!