【初学】docker-composeでの環境変数・秘密情報受け渡し
概要
golangアプリコンテナとDB(MySQL)コンテナをdocker-composeで立ち上げる構築の学習中。Compose周りで初級本で見なかったような点や考察
(悩み)について書きたい。
調べもの
▼Compose file
docker-compose.yamlについてちょっと調べてみた。
Docker Compose V2から compose.yaml が推奨ファイル名となり、またversion 指定は推奨されなくなっていくもよう。
▼Version
互換性のため一応定義出来るようにしてるとあり、先の概要でも「DEPRECATED」の記述から今後使われなくなっていくもよう。
ただ「version: "3.9"」などの記述を削除するとGoLand上でyamlファイル内の入力補完が効かなくなる・・。と思ったが、「compose.yml」の場合は効かなくなるが従来の「docker-compose.yml」の場合は補完が効いた。移行中の不具合なんだろうか。
▼環境変数と秘密情報
Qiitaの記事「Docker Composeの環境変数ではなくsecretsで秘密情報を扱う」で、環境変数で渡すことのリスクが書いてあり秘密情報をどう渡すか考えたくなった。
▼envファイル指定
docker-compose仕様でenvファイルを作成して指定することで変数を渡すことが出来るとある。下記は「'webapp:v1.5'」を指定したことになる。
$ cat .env
TAG=v1.5
services:
web:
image: "webapp:${TAG}"
また環境変数渡しの方法はenvironment指定やDockerfile内での指定など幾つかあり、それらの優先度について記載がある。
シェル変数>docker-compose指定>Dockerfile指定、の優先順で、存在しなければ右のものを参照していく感じだろうか。
上の状況で↓のようにシェル変数定義すればそちらが優先され、「'webapp:v2.0'」を指定したことになると思われる。
$ export TAG=v2.0
▼secrets
docker-compose仕様で秘密情報受け渡し構文があるもよう。
services:
frontend:
image: awesome/webapp
secrets:
- server-certificate
secrets:
server-certificate:
file: ./server.cert
ファイルをマウントとあるが、Twelve-Factor Appでは「設定ファイルが誤ってリポジトリにチェックインされやすいこと」を問題視し、環境変数で渡すべきだとしている。
一応その点を考慮して以下のようにしたらどうかと考えた。
・「real」ディレクトリと「dummy」ディレクトリを作成
・「real」へは実運用用の秘密情報ファイルを入れ、secretsで参照する
・「dummy」へは同名ファイルだが空テキストにしたものを入れる
・「real」以下を .gitignore で無視する
services:
db:
~~~ 割愛 ~~~
container_name: test_db
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_USER: testuser
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_DATABASE: awesome_database
secrets:
- db_password
- db_root_password
~~~ 割愛 ~~~
secrets:
db_password:
file: ./infrastructure/secrets/real/db_password.txt
db_root_password:
file: ./infrastructure/secrets/real/db_root_password.txt
# .gitignoreに追加。ディレクトリだけgit管理下に置く
secrets/real/*
!secrets/real/.gitkeep
「dummy」に同名の空テキストを配置する理由は、このアプリケーションに必要な秘密情報が何かを示すため(「〜.sample」などのファイルを配置する運用と同じ)。
疑問
▼アプリでどう利用する?
データベースなど「〜_FILE」でファイル指定で渡せる場合はいいが、アプリで使う場合などは別途読み込むプログラムを実装する必要があることに気がついた。
せっかくyaml設定ファイル読み込みの標準ライブラリを利用していたが、特定の変数(環境変数ではなくsecretsで渡したい機密情報)は利用できなくなってしまう。この変数はyaml設定ファイルから、この変数は「/run/secrets/」以下の各シークレット名のファイルから、と場合分けする必要が出てきてしまう。本当にそんなことしてるんだろうかと結構ググったが全然見つからず、ただしこんな実装が必要になるのかなぁと思ったものが作られていたりするので恐らく必要なんだろう(rubyでもFile.Open()しているのを見かけた)。
▼本番でどう利用する?
しかもAWS ECSなどで運用する場合、そもそもCompose使わないわけで(→「Docker Compose と Amazon ECS を利用したソフトウェアデリバリの自動化」とあり、composeとECSが連携出来るようになっているもよう)、Composeのsecrets機能で特定ディレクトリに配置した特定のファイルから秘密情報を取得して利用することを前提とした環境は開発でしか使えないんじゃないかと思った。
▼secretsはコンテナ内に平文置いているだけ?
こちらの記事で secrets について扱っていて(docker docsからのリンク先)、作成されたものは暗号化されている。しかし docker-compose の secrets で作成されたもの(「/run/secrets/」以下にマウントされたファイル)を見にいくと平文だった。
$ docker exec -it test_db bash
/# cd /run/secrets/
/run/secrets# cat db_password
testpass
別に暗号化しているわけでなくただ指定したファイルをコンテナの指定した場所に配置しているだけで、Dockerfileのイメージビルド時に「COPY」コマンドで go.mod をコピーしたりプロジェクトを「/usr/src/app」にコピーしたりするのと同じようなことを docker-compose 側から行えるようにしているということ・・?
あとがき
最終的に「Docker で環境変数をホストからコンテナに渡す方法」に、Dockerイメージに焼き付けるのはダメだけど環境変数に渡そうがファイルで渡そうがどちらもセキュリティ的に変わらない、というのを見て、12factorに立ち戻り環境変数でいいか、となった(未対応)。
以下のような感じになるのだろうか。
▼Dockerfile
・秘密情報以外はENVで直接もしくは環境変数で設定する
・秘密情報は空のENVで明示する
→ 何を渡す必要があるか知らせるため
▼compose.yaml(docker-compose.yaml)
・秘密情報以外はenvironmentで直接もしくは環境変数で設定する
・秘密情報は こちらの記事 で紹介されているシェル環境変数渡しやenv_file指定、またdirenvなどを用いて渡す
→ environment指定でDockerfile指定を上書きする。お手軽に環境変数渡しする。