ツール実行のための docker compose
docker compose で terraform や sops みたいなツールを実行する際にはこうしてるよという記事。
背景
開発プロジェクトを新しく始める際には、開発するアプリケーションを実行するための各種ソフトウェア、例えばコンパイラなどの実行環境をセットアップします。
同時に、開発プロジェクトでは、それらのソフトウェアの他にも、terraform、sops、gcloud、awscli など、様々なソフトウェアも実行する必要があります。この辺をここではざっくりとツールと呼ぶことにします。
私はこういったツールを実行するための環境として docker compose を愛用しています。というのも以下のメリットを得られるためです。
- ドキュメントを簡略化できる。
- プロジェクトへの新規参加者向けのドキュメントに各ツールのインストール方法を案内する必要がなく、Docker と docker compose の実行環境を要件とするだけで良い。
- OS への依存を無くせる(もしくは減らせる)。
- プロジェクトメンバーの OS 環境が Windows、MacOS、Linux のどれでも、ほぼ共通のドキュメントにできる。
- 実際には Linux についてはバインドマウント時のファイルオーナーの問題が他の OS よりもややこしくはなる。
- ソフトウェアのバージョンを揃えられる。
- プロジェクトメンバーで利用するツールのバージョンを揃えられる。
- 特に、複数のプロジェクトで異なるバージョンのツールを運用でき、ツールのバージョン違いによる余計なトラブルを回避できる。
そんなわけで、docker compose をツールの実行に使用しているときのノウハウをまとめました。自分用コピペメモでもあります。
全部入りテンプレ
後述の「ツール実行に便利な compose 記法」の内容を全部盛り込んだ compose ファイル。
これをコピペして必要な部分だけを残して使う…という想定。
なお適宜削って使う前提なので、このまま使ってもあまり有用ではないです。
version: '3'
services:
yourtool:
image: ...
profiles: [dummy]
network_mode: bridge
environment:
- http_proxy
- https_proxy
- no_proxy
- CLOUDSDK_CORE_PROJECT=exampleproject
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_SESSION_TOKEN
- AWS_DEFAULT_REGION
- AWS_REGION
volumes:
- .:/workspace
- /workspace/node_modules
- ${CLOUDSDK_CONFIG:-${APPDATA:-${HOME:-}/.config}/gcloud}:/root/.config/gcloud
working_dir: /workspace
command:
- /bin/sh
- -c
- |
set -ex
...
ツール実行に便利な compose 記法
アプリケーション実行環境との同居 (profiles の利用)
services:
yourtool:
profiles: [dummy]
docker-compose.yaml
を開発中の Web アプリケーションの起動用に使用した場合、ツール実行用のサービスも追記すると docker compose up
のたびにいちいちツールが起動してしまう…という不便があります。
docker compose には profiles という機能が提供されており(docker-compose v1.28.0 以降)、 docker compose up
のときに起動するサービスの選択ができます: Using profiles with Compose | Docker Documentation。
上記の設定ファイルのように profiles: [dummy]
と書くと、docker compose up
の際にこのサービスは起動対象になりません。このサービスも起動するためには docker compose --profile dummy up
と指定する必要がありますが、フツーは dummy
なんて指定しないよね、ということでこの指定を行っています。
ツールを実行する際には以下のように docker compose run
を使用します:
docker compose run --rm yourtool ...
なお、 profiles: [dummy]
は以下のようにも書けます。上記の例ではコピペしやすいようになるたけ短い記述を採用しています。
profiles: ["dummy"]
profiles:
- dummy
ネットワークを作成しない
services:
yourtool:
network_mode: bridge
docker compose は通常、プロジェクトごとの専用のネットワークを作成し、起動するコンテナをそのネットワークに接続します: Networking in Compose | Docker Documentation
ただ、ツールの実行にしか使用しない docker-compose.yaml
でいちいちネットワークを作成するのはいかにも無駄ですし、大量にネットワークを作成すると Docker が「空いているセグメントがなくなった」と言ってネットワークの作成に失敗するようになります。そうなると docker compose down
で docker compose のプロジェクトを削除するか、 docker network rm
でもう使っていないネットワークを削除しないと、新しい docker compose のプロジェクトを起動できなくなります。
フツーに docker run
でコンテナを起動したときにはそんなことにはならず、それは docker run
で起動したコンテナは特別な指示がない場合は、デフォルトで用意されている bridge
ネットワークに接続されるようになっているためです。
ツール実行用のサービスもそうなっていると大変都合が良いです。
ということで、前述の例の通り、 network_mode: bridge
を指定するとこれを達成できます。
なお、この指定を行った際は、compose のプロジェクト内でのサービス名を指定したサービス間の通信ができなくなることに注意してください。他のサービスとの通信が必要なツールの場合は、この指定は避けます。
HTTPプロキシ配下でも利用できるようにする
services:
yourtool:
environment:
- http_proxy
- https_proxy
- no_proxy
企業によってはインターネットとの通信にHTTPプロキシを使用しているケースがあることでしょう。
そういう環境では、起動したツールがインターネットと通信できるようにするためには、コンテナに対してプロキシ設定が必要です。具体的には、(多くの場合)環境変数 http_proxy
、https_proxy
、no_proxy
を設定します。
上記の設定例がその指定となります。
- http_proxy=${http_proxy}
でも似たような意味になりますが、変数名だけを指定すると「ホストマシンで環境変数 http_proxy が設定されている場合にだけ、コンテナにも同じ名前で同じ値の環境変数を設定する」という意味になり、より適切な設定になります。
ちなみにこの変数名だけ指定する手法は docker run
でも利用でき、また、 Dockerfile の ARGS
についても同様に利用可能です:
- docker run | Docker Documentation#Set environment variables (-e, --env, --env-file)
- docker build | Docker Documentation#Set build-time variables (--build-arg)
- Compose file version 3 reference | Docker Documentation#environment
- Compose file version 3 reference | Docker Documentation#args
なお注意事項として、Java アプリケーションについては環境変数 http_proxy
、https_proxy
ではプロキシの設定ができません。JVM の起動オプションでプロキシの指定が必要です: Networking Properties (Java SE 20 & JDK 20)
プロジェクトのルートディレクトリをバインドマウントする
services:
yourtool:
volumes:
- .:/workspace
working_dir: /workspace
上記のように指定すると、以下の動作になります:
- プロジェクトのルートディレクトリ (
docker-compose.yaml
が置かれているディレクトリ) がコンテナ内の/workspace
ディレクトリにバインドマウントされる。 -
/workspace
ディレクトリがコンテナのカレントディレクトリになる。
基本的に docker compose を実行するときのディレクトリは docker-compose.yaml
が置いてあるディレクトリになるため、ホストのカレントディレクトリと、コンテナから見えるカレントディレクトリが対応づくという結果になります。
具体的には、例えばこんなふうにホスト側であるファイルを相対パスで指定して引数に渡すと、コンテナ側からもそのファイルにアクセスでき、全体として違和感のない動作になります:
docker compose run --rm sops -d ./secrets/environment.yaml
データをボリュームで永続化する
version: '3'
services:
yourtool:
volumes:
- /workspace/node_modules
volumes
でコンテナ内のディレクトリだけを指定すると、dockerボリュームが作成されそのディレクトリにボリュームマウントされます (Compose file version 3 reference | Docker Documentation#volumes/Short syntax)。
この際の強力な動作として、ボリュームの作成時、コンテナイメージの該当ディレクトリにファイルがある場合、それらのファイルがボリュームにコピーされます。つまりコンテナイメージのディレクトリを初期状態として、その後のディレクトリへの変更を維持できるような動作になります (Volumes | Docker Documentation#Populate a volume using a container)。
ほとんどの場合はコンテナイメージに含まれるディレクトリを更新したくなることはないですし、変更の維持もバインドマウントで足りるのであまり使う頻度が少ない指定ですが、バインドマウントすると極端に動作が遅くなるソフトウェア (npm
など) などで便利に利用できる場合があります。
スクリプトを実行する
services:
yourtool:
command:
- /bin/sh
- -c
- |
set -ex
npm install
npm run something
ツールで複数のコマンドを実行したい、という場合はシェルコマンド (sh
やら bash
やら) に、 -c
オプションで複数行のスクリプトを引き渡すことができます。ある程度複雑になったら素直にスクリプトファイルを作ってバインドマウントして実行するようにしたほうが良いですが、スクリプトファイルを1つ増やすほどじゃないなーみたいなときに便利です。
yaml では |
から始めると複数行のテキストを設定できます: YAML Ain’t Markup Language (YAML) revision 1.2.2#8.1.2. Literal Style
Google Cloud/ホストの認証情報を利用する
services:
yourtool:
volumes:
- ${CLOUDSDK_CONFIG:-${APPDATA:-${HOME:-}/.config}/gcloud}:/root/.config/gcloud
Google Cloud CLI や、 Cloud クライアントライブラリ を使ったソフトウェアのコンテナを実行する際は、認証が必要です。
認証は gcloud auth login
や gcloud auth application-default login
コマンドで行いますが、これによって作成された認証情報はユーザー構成ディレクトリに保存されています (gcloud CLI の構成を管理する)。そのディレクトリをコンテナにバインドマウントすることで、ホストで実行した gcloud auth login
や gcloud auth application-default login
の情報をコンテナから利用することができます。
ここでは変数のデフォルト値で更に変数を参照することで、Windows、MacOS、Linux いずれのホストマシンでもいい感じに動作するように設定しています (docker compose v2 は ${VAR1:-${VAR2}} が使える)。
何らかの事情で docker compose のバージョンを上げられない場合(特に v1 を使用しないといけない場合)は、デフォルト値内で更に変数を使用することができないため、以下のような設定にし、プロジェクト参加者に CLOUDSDK_CONFIG
環境変数を設定することを要求します:
services:
yourtool:
volumes:
# ホストマシンで以下のように CLOUDSDK_CONFIG を定義する:
# Windows: %APPDATA%\gcloud
# Linux/Mac: $HOME/.config/gcloud
- ${CLOUDSDK_CONFIG:-}:/root/.config/gcloud
なお、コンテナで利用するユーザーが root ではない場合は、マウント先のディレクトリを適宜変更する必要があります。
コンテナ側でも CLOUDSDK_CONFIG
を設定すれば実行ユーザーへの依存を無くせそうですが、 CLOUDSDK_CONFIG
は Google Cloud CLI だけで利用可能な変数で、Cloud クライアントライブラリでは一般的にサポートされた環境変数ではないため、Google Cloud を利用するソフトウェア一般では利用できません (参考: googleapis/google-cloud-go: ApplicationDefaultCredentials don't work in the Cloud Shell #288)。
代わりに GOOGLE_APPLICATION_CREDENTIALS を指定する方法もあります (アプリケーションのデフォルト認証情報の仕組み)。ただしこの方法は gcloud auth application-default login
の認証にしか利用できない点に注意が必要です。gcloud auth login
の認証情報は上記ファイルには保存されないため、 Google Cloud CLI のコンテナではこの方法は認証情報を参照できません:
volumes:
- ${CLOUDSDK_CONFIG:-${APPDATA:-${HOME:-}/.config}/gcloud}:/gcloud_config/gcloud
environment:
- GOOGLE_APPLICATION_CREDENTIALS=/gcloud_config/application_default_credentials.json
Google Cloud/便利な環境変数
services:
yourtool:
environment:
- CLOUDSDK_CORE_PROJECT=exampleproject
開発プロジェクトによって使用するGoogle Cloudプロジェクトが異なる、ということはよくあります。
gcloud config set project
での設定はグローバルに保存されてしまうため、あるプロジェクトの手順書でこの実行を指定すると、他の開発プロジェクトにまで影響を及ぼしてしまいます。
gcloud CLI のプロパティの管理 の通りこの設定は環境変数で上書きできるため、開発プロジェクトで使用するGoogle Cloudプロジェクトが決まっている場合は、環境変数の指定で固定にしておくのがオススメです。
AWS/aws-vaultと併用する
services:
yourtool:
environment:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_SESSION_TOKEN
- AWS_DEFAULT_REGION
- AWS_REGION
AWS の認証情報の管理には aws-vault を使用しています。
aws-vault exec myaccount -- docker compose run --rm ...
とやるためには、 aws-vault
が設定してくれる環境変数を引き渡す必要があるため、上記の通り設定が必要です。
AWS_SESSION_TOKEN
と AWS_DEFAULT_REGION
を忘れがちです。
AWS/ホストの認証情報を利用する
services:
yourtool:
environment:
- AWS_PROFILE
- AWS_DEFAULT_REGION
- AWS_REGION
volumes:
- ${HOME:-${USERPROFILE:-}}/.aws:/root/.aws
前項の通り aws-vault
を使用しているのですが、先に紹介した Google Cloud での例と同様に、ホストに設定した ~/.aws
を参照させる設定も可能です: Location of the shared config and credentials files - AWS SDKs and Tools
プロファイルを切り替える際は、 AWS_PROFILE
環境変数で引き渡します。
AWS_PROFILE=myaccount docker compose run --rm terraform plan
よく使うコンテナイメージ
ツールとしては以下のコンテナをよく使うよというリンク集。ちょくちょく場所がわからなくなる:
- hashicorp/terraform
-
mozilla/sops
-
alpine
イメージを使用する (v3.7.3-alpine
など)。そうでないと極端にイメージサイズが大きい。
-
- jq
- 適切なイメージリポジトリが存在しない (2023年8月現在)
- 現在 rc バージョンしか存在しないが、 https://github.com/jqlang/jq/pkgs/container/jq に期待
- mikefarah/yq
- gcr.io/google.com/cloudsdktool/google-cloud-cli
- public.ecr.aws/aws-cli/aws-cli
Discussion