見出し画像

【初学】docker-composeでの環境変数・秘密情報受け渡し

概要

golangアプリコンテナとDB(MySQL)コンテナをdocker-composeで立ち上げる構築の学習中。Compose周りで初級本で見なかったような点や考察
(悩み)について書きたい。

調べもの

▼Compose file

docker-compose.yamlについてちょっと調べてみた。

The Compose file is a YAML file defining version (DEPRECATED), services (REQUIRED), networks, volumes, configs and secrets.
The default path for a Compose file is compose.yaml (preferred) or compose.yml in working directory. Compose implementations SHOULD also support docker-compose.yaml and docker-compose.yml for backward compatibility. If both files exist, Compose implementations MUST prefer canonical compose.yaml one.

Composeファイルは、version(DEPRECATED)、 services(REQUIRED)、networks、volumes、configs、secretsを定義 するYAMLファイルです。
Composeファイルのデフォルトパスは compose.yaml(推奨)または作業ディレクトリ内の compose.yml です。Composeの実装は、下位互換性のため docker-compose.yaml や docker-compose.yml もサポートする必要があります。両方のファイルが存在する場合、Composeの実装は正規の  compose.yaml を優先する必要があります。

[compose-spec] compose-file

Docker Compose V2から compose.yaml が推奨ファイル名となり、またversion 指定は推奨されなくなっていくもよう。

▼Version

トップレベルのversionプロパティは、下位互換性の仕様によって定義されていますが、情報を提供するだけです。

Composeの実装では、このバージョンを使用して、Composeファイルを検証するための正確なスキーマを選択するべきではありませんが、設計時の最新のスキーマを優先します。

[compose-spec] version-top-level-element

互換性のため一応定義出来るようにしてるとあり、先の概要でも「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指定、の優先順で、存在しなければ右のものを参照していく感じだろうか。

environment と env_file による設定ファイルの両方にて変数が指定されると、環境変数の値はまずは environment キーが優先して取得され、次に設定ファイルから取得することになり、その次に Dockerfile の ENV エントリとなります。

Dockerfile ファイル内の ARG や ENV は、 environment や env_file による Docker Compose の設定がある場合は評価されません。

シェル内にて設定される値は、 .env ファイル内のものよりも優先されます。

.env ファイル

上の状況で↓のようにシェル変数定義すればそちらが優先され、「'webapp:v2.0'」を指定したことになると思われる。

$ export TAG=v2.0


secrets

docker-compose仕様で秘密情報受け渡し構文があるもよう。

シークレットは、セキュリティを考慮せずに公開してはならない機密データの構成データの特定のフレーバーです。シークレットは、コンテナにマウントされたファイルとしてサービスで利用できるようになりますが、機密データを提供するためのプラットフォーム固有のリソースは、Compose仕様内の明確な概念と定義に値するほど具体的です。

[compose-spec] The Compose application model
services:
  frontend:
    image: awesome/webapp
    secrets:
      - server-certificate
secrets:
  server-certificate:
    file: ./server.cert

コンテナ内の「/run/secrets/<secret_name>」で読み取り専用としてマウントされます。ソース名と宛先マウントポイントは両方ともシークレット名に設定されます。

ファイルをマウントとあるが、Twelve-Factor Appでは「設定ファイルが誤ってリポジトリにチェックインされやすいこと」を問題視し、環境変数で渡すべきだとしている。

設定に対するもう1つのアプローチは、バージョン管理システムにチェックインされない設定ファイルを使う方法である。例として、Railsにおけるconfig/database.ymlがある。この方法は、リポジトリにチェックインされる定数を使うことに比べると非常に大きな進歩であるが、まだ弱点がある。
設定ファイルが誤ってリポジトリにチェックインされやすいことと、設定ファイルが異なる場所に異なるフォーマットで散乱し、すべての設定を一つの場所で見たり管理したりすることが難しくなりがちであることである。

[Twelve-Factor App] III. 設定

一応その点を考慮して以下のようにしたらどうかと考えた。
・「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指定を上書きする。お手軽に環境変数渡しする。

この記事が気に入ったらサポートをしてみませんか?