GAミント至上主義

Web Monomaniacal Developer.

Docker + Pythonで設定ファイルを環境変数によって切り替える

oceanusではPythonで書いたアプリケーションをDockerで動かしています。

Dockerを使ったアプリケーションでは、コンテナごとに変わる可能性がある変数は、DockerfileでENVを使って、環境変数にセットして、プログラム側でそれを読み込んで使えます。

しかし、Pythonで書かれた設定ファイルで、dict型だったり入れ子になったリストやタプル、CPUのコア数な動的に変更されるような変数は、環境変数での管理は難しくなります。


そこで、どの設定ファイル読み込むか、ENVで切り替えられるように作る必要があります。

もう少し詳しく言うと設定ファイルを、取得した環境変数を使って動的に読み込んで、さらに内容を展開する必要があります。

Djangoのsettings.pyを起動時に指定できるのも同じでしょうか(未確認)。

共通に読み込む設定ファイルを作る

当たり前ですが設定ファイルは複数のアプリケーション、モジュールから呼び出されます。

もし、設定ファイルを変更する度にモジュール名の変更が必要になってしまったり、動的に切り替える処理をそれぞれに書いてしまうとDRYではありません。


そこで、一つ不変な設定ファイルを作り、アプリケーションは常にそれを読み込めばいい形にします。

例として、下記のように、settings.pyを読み込む不変なファイルとし、読み込む選択肢は例として、my_settngs.py、other_settngs.pyとします。

- app
  - app.py
  - common
   - settings.py
   - my_settngs.py
   - other_settngs.py

app.pyから読み込む場合は下記のようにモジュールとして必要な変数(DATABASE_NAME)を読み込みます

from common.settings import DATABASE_NAME

もちろん、DATABASE_NAMEのような設定はdefault_settings.py、other_settings.pyに宣言し、settings.pyには無し、もしくはデフォルト値を書いておきます。

例として下記のような設定です。

my_settngs.py

DATABASE_NAME = "my-oceanus"

other_settngs.py

DATABASE_NAME = "other-oceanus"

settings.pyから動的に別ファイルを読み込む

まず読み込むモジュール名を環境変数からstr型として取得するようにします。

モジュール名なので、ディレクトリは.で区切り、ファイル名の.pyは不要です。

importが慣れない人は、まずコマンドラインなどで正しく読めるかを試したほうがいいかもしれません。

環境変数名をOCEANUS_SETTINGSとし、これにモジュールパス(例:common.default_settings)を入れます。

Dockerfileでは下記のように書いておき

ENV OCEANUS_SETTINGS common.default_settings

runする時に、-eをつかって

docker run -e OCEANUS_SETTINGS=common.other_settings

pythonで同名の変数に代入。

デフォルトが必要な場合はgetの第2引数でセットしておきます。

次にその文字列を使って動的にimportします。

動的にimportを行うには、__import__()でも可能ですが、importlib.import_moduleが推奨されています。

31.5. importlib — import の実装 — Python 3.6.1 ドキュメント

from os import environ

OCEANUS_SETTINGS = environ.get("OCEANUS_SETTINGS", "common.default_settings")

import_module(OCEANUS_SETTINGS)

これだけはまだdefault_settingsを読み込んだだけで、中身の展開はできていないので

from common.settings import DATABASE_NAME

という形では使えません。

読み込んだ設定ファイルの中身を展開する

そして、その読み込んだファイルにある変数などをすべて展開します。

ここはどうやるかしばらくハマりましたが、下記のページを参考にglobals()を使って、現在のモジュール(common.settings)のグローバル変数に追加することで、うまくいきました。

31.5. importlib — import の実装 — Python 3.6.1 ドキュメント

最終的には下記のような形になります。
settings.py

from os import environ
from importlib import import_module

OCEANUS_SETTINGS = environ.get("OCEANUS_SETTINGS", "common.default_settings")

globals().update(import_module(OCEANUS_SETTINGS).__dict__)

これで、環境変数OCEANUS_SETTINGSを変更して、

from common.settings import DATABASE_NAME

のように読み込めば、common.default_settingsや、common.other_settingsの設定ファイルを読みこむことができます。

この動的読み込み&展開は設定ファイルに限らずいろんな所で応用がききそうです。

実践力を身につける Pythonの教科書

実践力を身につける Pythonの教科書