読者です 読者をやめる 読者になる 読者になる

uyamazakのブログ

仕事中の問題と解決メモ。PythonとGoogle Cloudがメイン。bizoceanで新規事業の企画と開発担当。 BigQueryを使ったビッグデータ収集・解析・リアルタイム処理プロジェクト進行中 https://github.com/uyamazak/oceanus

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から読み込む場合は下記のようにモジュールとして必要な変数を読み込みます

from common.settings import DATABASE_NAME

もちろん、DATABASE_NAMEのような設定はdefault_settings.py、other_settings.pyに宣言し、settings.pyには不要です。

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

my_settngs.py

DATABASE_NAME = "default-oceanus"
other_settngs.py
DATABASE_NAME = "other-oceanus"

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

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

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

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

環境変数名をOCEANUS_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が推奨されています。


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.0 ドキュメント

最終的には下記のような形になります。
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の教科書