🗂

ツール実行のための docker compose

2023/08/20に公開

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_proxyhttps_proxyno_proxy を設定します。

上記の設定例がその指定となります。

- http_proxy=${http_proxy} でも似たような意味になりますが、変数名だけを指定すると「ホストマシンで環境変数 http_proxy が設定されている場合にだけ、コンテナにも同じ名前で同じ値の環境変数を設定する」という意味になり、より適切な設定になります。

ちなみにこの変数名だけ指定する手法は docker run でも利用でき、また、 Dockerfile の ARGS についても同様に利用可能です:

なお注意事項として、Java アプリケーションについては環境変数 http_proxyhttps_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 logingcloud auth application-default login コマンドで行いますが、これによって作成された認証情報はユーザー構成ディレクトリに保存されています (gcloud CLI の構成を管理する)。そのディレクトリをコンテナにバインドマウントすることで、ホストで実行した gcloud auth logingcloud 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_TOKENAWS_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

よく使うコンテナイメージ

ツールとしては以下のコンテナをよく使うよというリンク集。ちょくちょく場所がわからなくなる:

Discussion