0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker と docker-composeのTips

Last updated at Posted at 2024-09-03

この記事は、Dockerとdocker-composeの概要とTipsについて、今さらながらまとめたものである。

1. Dockerとdocker-composeの概要

1.1. Docker

ホストマシンのLinuxカーネルを利用して、ホストマシンとは別のLinuxディストリビューションで仮想環境を起動できるソフトウェアで、正式には「Dockerエンジン」。
Docker上で動作する各仮想環境は「コンテナ」と呼ばれる。

より詳細には、こちらを参照。

1.1.1. 使い方

Dockerfileというファイルに、コンテナの設定(各ソフトウェアのインストールなど)を記述する。

例. とあるGoアプリケーションのDockerfile

# ベースイメージ
FROM golang:1.21.1

# モックモジュールのインストール
RUN go install go.uber.org/mock/mockgen@latest

# /appディレクトリの作成&移動
WORKDIR /app

# プロジェクトのコピー
COPY . .

# 必要なGoモジュールをダウンロード
RUN go mod download

# Goアプリケーション実行ファイルをビルドしてルートディレクトリに配置
RUN GOOS=linux GOARCH=amd64 go build -o /sample_app ./cmd/sample_app/*.go

# Goアプリケーションの実行
CMD ["/sample_app"]

dockerコマンドで、Dockerfileを元にイメージを作成したり、イメージをDockerレジストリへpush・pullしたり、イメージからコンテナを起動・停止したりできる。

以下の各 docker コマンドの動作のイメージを示す。

イメージのビルドとDockerレジストリに対する操作
DockerとDockerCompose-Dockerレジストリの操作 (1).jpg

Docker イメージ操作関連のコマンド

  • docker build
  • docker login
  • docker push
  • docker pull

コンテナのライフサイクル
DockerとDockerCompose-コンテナの状態遷移 (1).jpg

Docker コンテナ操作関連のコマンド

  • docker create
  • docker start
  • docker run
  • docker pause
  • docker unpause
  • docker start
  • docker stop
  • docker rm

図中のステータスについては、後ほどdocker psコマンドと関連付けて詳細に説明する。

Docker Compose

「ローカル開発環境の構築」向けのコンテナオーケストレーションツールのデファクトスタンダード。
Docker Composeを利用する上で、各コンテナの元になるDockerfileやイメージは別途用意する必要がある。

使い方

docker-compose.ymlというファイルに、以下のようなコンテナオーケストレーションの設定を記述する。

  • コンテナを作成する際の元になるDockerfile or イメージ
  • コンテナを作成・起動する際のdockerコマンドのオプションに相当するもの
  • コンテナの起動順序、起動条件

例. とあるサンプルアプリのローカル開発環境を構築するためのdocker-compose.yml

docker-compose.yml
services:
  # Spanner エミュレータ
  spanner-emulator:
    image: gcr.io/cloud-spanner-emulator/emulator:1.5.22
    ports:
      - "9010:9010"
      - "9020:9020"

  # Spanner エミュレータ起動後に流すスクリプト
  # 指定したプロジェクトID, インスタンスID, データベースIDのデータベースを作成する
  spanner-emulator-init-script:
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:alpine
    platform: linux/amd64
    command: >
      bash -c 'gcloud config configurations create emulator || gcloud config configurations activate emulator &&
              gcloud config set auth/disable_credentials true &&
              gcloud config set project ${GCP_PROJECT_ID} &&
              gcloud config set api_endpoint_overrides/spanner ${SPANNER_EMULATOR_URL}/ --quiet&&
              gcloud spanner instances create ${SPANNER_INSTANCE_ID} --config=emulator-config --description="Test Instance" --nodes=1 &&
              gcloud spanner databases create ${SPANNER_DATABASE_ID} --instance=${SPANNER_INSTANCE_ID}'
    depends_on:
      - spanner-emulator

  # Spanner エミュレータのヘルスチェック用コンテナ
  spanner-emulator-healthcheck:
    image: curlimages/curl
    depends_on:
      - spanner-emulator
    platform: linux/amd64
    entrypoint: tail -f /dev/null
    healthcheck:
      test: curl -f ${SPANNER_EMULATOR_URL}/v1/projects/${GCP_PROJECT_ID}/instances/${SPANNER_INSTANCE_ID}/databases/${SPANNER_DATABASE_ID}
      interval: 5s
      timeout: 10s
      retries: 10
      start_period: 10s

  # GoアプリケーションのAPIサーバ
  api:
    build:
      context: ../../
      dockerfile: ./build/packages/docker/Dockerfile.sample_app
    ports:
      - "8080:8080"
    env_file:
      - .env
    tty: true
    volumes:
      - type: bind
        source: ../../
        target: /app/
    depends_on:
      spanner-emulator-healthcheck:
        condition: service_healthy

  # Goアプリケーションのヘルスチェック用コンテナ
  api-healthcheck:
    image: fullstorydev/grpcurl:latest-alpine
    depends_on:
      - api
    platform: linux/amd64
    entrypoint: tail -f /dev/null
    healthcheck:
      test: |
        grpcurl -plaintext -d '{"name": "sample"}' ${API_HOST} api.SampleService.CreateSample
      interval: 5s
      timeout: 10s
      retries: 10
      start_period: 10s

  # 全てのサービスの起動が完了したことの確認用
  all-service-up:
    image: alpine
    depends_on:
      api-healthcheck:
        condition: service_healthy
    entrypoint: echo "all services up"

docker-compose.ymlを用意した上で、docker-composeコマンドを実行することで、主に以下の操作を行う。

  • 各コンテナのイメージの作成(docker-compose build)
  • 依存関係を考慮した各コンテナの作成・起動(docker-compose up)
  • 各コンテナの削除(docker-compose down)

2. Tips

2.1. 起動しているコンテナ内で任意のコマンドを実行する

docker exec -it `docker ps -qf name=<コンテナ名(の一部)>` <コマンド>

注意点
docker ps -qf name=<コンテナ名(の一部)>で、コンテナが複数ヒットする場合は、コマンドを実行するコンテナが不確実になってしまうため注意。
不要なコンテナをあらかじめ停止または削除しておくと、docker psの表示対象外となるため多少避けられる。

2.1.1. 起動しているコンテナ内へログインする

上記の<コマンド>を シェルを起動するコマンド(/bin/bashとか)にする。

2.2. dockerイメージから元になったDockerfileの内容を知る

docker history <イメージ> --no-trunc --format '{{ json .CreatedBy }}'| tail -r

例として、この記事内の とあるGoアプリケーションのDockerfile のDockerfileを元に作成したイメージに対して、上記コマンドを実行してみると以下のように出力された。

"/bin/sh -c #(nop) ADD file:7a0adbde6e967e2bcaafa69f04fabdec993025645c8d0d51acc991a31b404eed in / "
"/bin/sh -c #(nop)  CMD [\"bash\"]"
"/bin/sh -c set -eux;  apt-get update;  apt-get install -y --no-install-recommends   ca-certificates   curl   gnupg   netbase   sq   wget  ;  rm -rf /var/lib/apt/lists/*"
"/bin/sh -c apt-get update && apt-get install -y --no-install-recommends   git   mercurial   openssh-client   subversion     procps  && rm -rf /var/lib/apt/lists/*"
"/bin/sh -c set -eux;  apt-get update;  apt-get install -y --no-install-recommends   g++   gcc   libc6-dev   make   pkg-config  ;  rm -rf /var/lib/apt/lists/*"
"/bin/sh -c #(nop)  ENV PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
"/bin/sh -c #(nop)  ENV GOLANG_VERSION=1.21.1"
"/bin/sh -c set -eux;  arch=\"$(dpkg --print-architecture)\"; arch=\"${arch##*-}\";  url=;  case \"$arch\" in   'amd64')    url='https://dl.google.com/go/go1.21.1.linux-amd64.tar.gz';    sha256='b3075ae1ce5dab85f89bc7905d1632de23ca196bd8336afd93fa97434cfa55ae';    ;;   'armel')    export GOARCH='arm' GOARM='5' GOOS='linux';    ;;   'armhf')    url='https://dl.google.com/go/go1.21.1.linux-armv6l.tar.gz';    sha256='f3716a43f59ae69999841d6007b42c9e286e8d8ce470656fb3e70d7be2d7ca85';    ;;   'arm64')    url='https://dl.google.com/go/go1.21.1.linux-arm64.tar.gz';    sha256='7da1a3936a928fd0b2602ed4f3ef535b8cd1990f1503b8d3e1acc0fa0759c967';    ;;   'i386')    url='https://dl.google.com/go/go1.21.1.linux-386.tar.gz';    sha256='b93850666cdadbd696a986cf7b03111fe99db8c34a9aaa113d7c96d0081e1901';    ;;   'mips64el')    url='https://dl.google.com/go/go1.21.1.linux-mips64le.tar.gz';    sha256='3aa007a41f533b50eae2491bbd29926ada09357367a8aa05e7e50ec50c78acf9';    ;;   'ppc64el')    url='https://dl.google.com/go/go1.21.1.linux-ppc64le.tar.gz';    sha256='eddf018206f8a5589bda75252b72716d26611efebabdca5d0083ec15e9e41ab7';    ;;   'riscv64')    url='https://dl.google.com/go/go1.21.1.linux-riscv64.tar.gz';    sha256='fac64ed26e003f49f1d77f6d2c4cf951422aecbce12232d9ec1bf4585fc44ee1';    ;;   's390x')    url='https://dl.google.com/go/go1.21.1.linux-s390x.tar.gz';    sha256='a83b3e8eb4dbf76294e773055eb51397510ff4d612a247bad9903560267bba6d';    ;;   *) echo >&2 \"error: unsupported architecture '$arch' (likely packaging update needed)\"; exit 1 ;;  esac;  build=;  if [ -z \"$url\" ]; then   build=1;   url='https://dl.google.com/go/go1.21.1.src.tar.gz';   sha256='bfa36bf75e9a1e9cbbdb9abcf9d1707e479bd3a07880a8ae3564caee5711cb99';   echo >&2;   echo >&2 \"warning: current architecture ($arch) does not have a compatible Go binary release; will be building from source\";   echo >&2;  fi;   wget -O go.tgz.asc \"$url.asc\";  wget -O go.tgz \"$url\" --progress=dot:giga;  echo \"$sha256 *go.tgz\" | sha256sum -c -;   GNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME;  gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 'EB4C 1BFD 4F04 2F6D DDCC  EC91 7721 F63B D38B 4796';  gpg --batch --keyserver keyserver.ubuntu.com --recv-keys '2F52 8D36 D67B 69ED F998  D857 78BD 6547 3CB3 BD13';  gpg --batch --verify go.tgz.asc go.tgz;  gpgconf --kill all;  rm -rf \"$GNUPGHOME\" go.tgz.asc;   tar -C /usr/local -xzf go.tgz;  rm go.tgz;   if [ -n \"$build\" ]; then   savedAptMark=\"$(apt-mark showmanual)\";   apt-get update;   apt-get install -y --no-install-recommends golang-go;     export GOCACHE='/tmp/gocache';     (    cd /usr/local/go/src;    export GOROOT_BOOTSTRAP=\"$(go env GOROOT)\" GOHOSTOS=\"$GOOS\" GOHOSTARCH=\"$GOARCH\";    ./make.bash;   );     apt-mark auto '.*' > /dev/null;   apt-mark manual $savedAptMark > /dev/null;   apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false;   rm -rf /var/lib/apt/lists/*;     rm -rf    /usr/local/go/pkg/*/cmd    /usr/local/go/pkg/bootstrap    /usr/local/go/pkg/obj    /usr/local/go/pkg/tool/*/api    /usr/local/go/pkg/tool/*/go_bootstrap    /usr/local/go/src/cmd/dist/dist    \"$GOCACHE\"   ;  fi;   go version"
"/bin/sh -c #(nop)  ENV GOTOOLCHAIN=local"
"/bin/sh -c #(nop)  ENV GOPATH=/go"
"/bin/sh -c #(nop)  ENV PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
"/bin/sh -c mkdir -p \"$GOPATH/src\" \"$GOPATH/bin\" && chmod -R 1777 \"$GOPATH\""
"/bin/sh -c #(nop) WORKDIR /go"
"RUN /bin/sh -c go install go.uber.org/mock/mockgen@latest # buildkit"
"WORKDIR /app"
"COPY . . # buildkit"
"RUN /bin/sh -c go mod download # buildkit"
"RUN /bin/sh -c GOOS=linux GOARCH=amd64 go build -o /sample_app ./cmd/sample_app/*.go # buildkit"
"CMD [\"/sample_app\"]"

2.3. ローカルに存在する全てのイメージを一覧表示する

docker image ls -a  

2.4. ローカルに存在する全てのコンテナを一覧表示する

docker ps -a

実行すると、以下のような形で表示される。

CONTAINER ID   IMAGE                                                    COMMAND                   CREATED      STATUS                  PORTS                                            NAMES
3790a90b5c14   alpine                                                   "echo 'all services …"   6 days ago   Exited (0) 6 days ago                                                    sample_app-all-service-up-1
6cc2aa94fcb1   sample_app-api                                           "/sample_app"             6 days ago   Up 6 days               0.0.0.0:8080->8080/tcp                           sample_app-api-1
964e0e1cb230   gcr.io/google.com/cloudsdktool/google-cloud-cli:alpine   "bash -c 'gcloud con…"   6 days ago   Exited (0) 6 days ago                                                    sample_app-spanner-emulator-init-script-1
91f6234133e0   gcr.io/cloud-spanner-emulator/emulator:1.5.22            "./gateway_main --ho…"   6 days ago   Up 6 days               0.0.0.0:9010->9010/tcp, 0.0.0.0:9020->9020/tcp   sample_app-spanner-emulator-1

2.4.1. docker psで一覧表示されるコンテナのSTATUSについて

docker psや後述のdocker-compose psで表示されるSTATUSは、本記事で示したコンテナのライフサイクルの図に登場する状態と対応付けると、以下のようになる。

DockerとDockerCompose-コンテナの状態遷移 (1).jpg

docker psのSTATUS 図中のステータス 説明
Created CREATED イメージからコンテナが作成された状態。コンテナのスペック(CPU, メモリなど)や、コンテナ起動時に内部で実行されるコマンドといったコンテナの設定が決まった状態。
Up RUNNING コンテナが正常に動作している状態。docker startを実行すると以下のようなフローでRUNNINGになる模様(実際に実験した結果より)。

1. コンテナにCPU, メモリ等が割り当てられる
2. 当該コンテナのイメージに元となるDockerfileに記述していたFROM, RUN, COPYが順に実行される(初回のCREATED->RUNNING時のみ)
3. バインドマウントが適用される
4. ENTRYPOINT, CMDが実行される
Up (paused) PAUSED コンテナが一時停止した状態。コンテナ内の全てのプロセスが一時停止したような(kill -STOPを実行したような)状態。
Exited (0) EXITED コンテナを正常にシャットダウンさせた状態。コンテナ動作中に生成・更新されたコンテナ内のファイルは残る。
Exited (0以外) EXITED コンテナを強制的にシャットダウンさせた状態。コンテナ動作中に生成・更新されたコンテナ内のファイルは残る。以下のようなケースでこのSTATUSになる。
- docker stopを実行したが、シャットダウンがタイムアウトになった
-docker stop実行中にCtrl + Cで強制終了させた
表示すらされない DELETED (or 一度もCREATEDになっていない) コンテナ自体がそもそも作成されたことがないか、コンテナが削除された状態。コンテナを削除するとコンテナ内に存在していたファイルも消し飛ぶ。

なお、-aを付けない場合、ステータスがEXITEDなコンテナは一覧表示されない。

2.5. Docker利用によるディスク容量の逼迫へ対処する

Dockerを使っていると簡単に数十GB容量食ってしまうため、定期的に不要なファイルを削除したくなる。

# Dockerで使われてるディスク容量を調査
# イメージ, コンテナ, ローカルボリューム, ビルドキャッシュそれぞれの使用量が表示される
docker system df
 
# ビルドキャッシュを削除
docker builder prune -f
 
# 全てのコンテナを停止(ステータスExitedに)
docker stop $(docker ps -q)
 
# Exitedになってる全てのコンテナを削除
docker container prune -f
 
# 存在する(ステータスがCreated でも Running でも Exited でも Pausedでもない)どのコンテナとも紐づかないイメージを全て削除
docker image prune -f
 
# 存在するどのコンテナとも紐づかないボリュームを全て削除
docker volume rm $(docker volume ls -qf dangling=true)

2.6. コンテナを起動しっぱなしにする

DockerfileCMDENTRYPOINTに、実行しても終了しないプロセスを起動するコマンドを記述する。
例. tail -f /dev/null

2.7. ホスト側のディレクトリ、ファイルをコンテナへ引き渡しつつのコンテナ初回起動処理をDockerfileに書きたい。

ビルド時にビルドコンテキストを指定 + DockerfileCOPYで実現できる。

COPY <ホスト側のパス> <コンテナ側のパス>

ホスト側のパス
ビルドコンテキストで指定したディレクトリを基準とする相対パスで記述する。

コンテナ側のパス
コンテナ側のパスは、絶対パスもしくはWORKDIRを基準とする相対パスで記述する。

COPYは、以下のような挙動をしてくれる。

  • イメージのビルド時に、ホスト側に存在する<ホスト側のパス>以下のディレクトリ・ファイルをイメージ上へコピー
  • コンテナの初回起動時に、コンテナ内の<コンテナ側のパス>直下へコピー

ちなみに、ビルドコンテキストに含まれないホスト側のディレクトリ・ファイルは、COPYコマンドで触ることすらできないので注意。
例.

xxx/
  yyy/
    aaa.go
zzz/
  bbb.go

ビルドコンテキストのパスとしてxxx/を指定してビルドした場合、COPY ../zzz/bbb.go .` といった記述はできない。

2.7.1. ビルドコンテキスト

docker build実行時に、Dockerエンジン側へ一時的に引き渡すディレクトリ・ファイル。
Dockerfile内のCOPYで使われる。

docker buildコマンドでのビルド時の指定方法

docker build -f <Dockerfileのパス> <ビルドコンテキストのパス>

-fがない場合は、カレントディレクトリのDockerfileを使ってビルドされ、ビルドコンテキストもカレントディレクトリとみなされる。

docker-compose buildコマンドでのビルド時の指定方法
docker-compose.yml内のbuildパラメータに付随するcontextパラメータ(必須)で、相対パスで指定する。
相対パスの基準は「docker-compose.ymlファイルが存在するディレクトリ」になる。
docker buildコマンドとは異なり、docker-compose buildでは、デフォルトのパスが適用されることはなく、明示的に指定する必要がある。

2.8. 軽量なベースイメージを使う

以前は軽量ベースイメージといえばalpine一択な印象だったが、最近ではdistroless, slimといったものも出てきているらしい(要調査)。

2.9. マルチステージビルド

1つのDockerfile内に以下のような設定を記述できる。

最初のステージで
- 使いたいパッケージが入ったサイズが大きめのベースイメージを指定
- COPYでホスト側から多めにディレクトリ・ファイルコピー
- 実行ファイルをビルド

次のステージで
- 最低限のパッケージを含む軽量ベースイメージを指定
- COPYで前ステージのイメージから本番利用で必要になる実行ファイルのみコピー

どのステージまでビルドするのかは、docker build--targetオプションや、docker-compose.ymlでのbuild.targetで指定できる。
これにより、以下のようなことができる。

  • 前のステージではデバッグツールなどを盛り込んだ重めのイメージを作成(開発環境向き)
  • 後のステージでは実行ファイルとこれを動作させるのに必要な最低限のパッケージを盛り込んだ軽量なイメージを作成(本番環境向き)

2.10. Dockerfile内で記述する各要素の実行タイミング

実験して挙動を確認した感じ、以下のようになっている模様。

  • FROM, RUN, COPYは、初回のコンテナ起動時(≒docker start実行時)のみ、その順序で実行される。
  • CMD, ENTRYPOINTは、毎回のコンテナ起動時に実行される。
  • バインドマウント(docker runコマンドのオプションとして指定できる)は、毎回の起動時にCMD, ENTRYPOINTの直前に実行される。

2.11. docker-composeコマンド関連のTips

2.11.1. docker-compose.ymlの検証

docker-compose -f <docker-compose.ymlのパス> -p <docker-composeのプロジェクト名> config

記述に問題があればその箇所をエラーで出力してくれる。
また、環境変数の適用状態も確認できる。

2.11.2. docker-compose up 各サービスのイメージをpullまたはビルドし、コンテナを作成・起動する

各サービスについて、デフォルトで以下の処理を一気通貫で実行してくれる。

  • (イメージがなければ)イメージのpull(docker pull相当)またはビルド(docker build相当)
  • (コンテナが存在していなければ)コンテナの作成(docker create相当)
  • コンテナの起動(docker start相当)
docker-compose -f <docker-compose.ymlのパス> -p <docker-composeのプロジェクト名> up -d

-f: docker-compose.ymlへのパスを指定。指定しない場合は、カレントディレクトリ直下のdocker-compose.yml が使われる
-p: プロジェクト名を指定。プロジェクト名は、各サービスのコンテナ名の一部としても使われる("<プロジェクト名>-<サービス名(docker-compose.yml内で指定)>-X")
-d: バックグラウンドでコンテナを実行。

注意
docker-compose upでのイメージのビルドについては、「ビルドキャッシュを使わない」のオプション指定ができないという制約がある。
ビルドキャッシュを使わないようにビルドしたい場合は、docker-compose buildを別途使うようにする。

所感: docker-compose up万能すぎる
docker startdocker createに相当するdocker-composeのコマンドも個別に用意されているが、どういった場面で使うのか今のところ謎・・・

2.11.3. docker-compose build 各サービスのイメージをビルドする

各サービスについて、docker build相当の処理をしてくれる。

docker-compose -f <docker-compose.ymlのパス> build --no-cache

-f: docker-compose.ymlへのパスを指定できる。指定しない場合、カレントディレクトリ直下のdocker-compose.ymlが使われる
--no-cache: キャッシュを利用せずにイメージをビルド。

2.11.3.1. 所感: ビルドキャッシュについて

イメージのビルドの高速化にこだわるのであれば、ビルドキャッシュを使うことを検討する必要がありそう。
ただし、ビルドキャッシュについては複雑な仕様があるようで、正確に把握していないと痛い目をみそう。

高速化にこだわる必要がないのであれば、ビルドキャッシュを使わないようにビルドするのが無難と考える。

2.11.4. docker-compose down 各サービスのコンテナを停止・削除する

各サービスのコンテナについて、docker stopdocker delete相当の処理を一気通貫で実行してくれる。

docker-compose -f <docker-compose.ymlのパス> -p <docker-composeのプロジェクト名> down

-f: docker-compose.ymlへのパスを指定。指定しない場合は、カレントディレクトリ直下のdocker-compose.yml が使われる
-p: プロジェクト名を指定。プロジェクト名は、各サービスのコンテナ名の一部としても使われる("<プロジェクト名>-<サービス名(docker-compose.yml内で指定)>-X")

注意
docker-compose upで指定した docker-compose.yml および プロジェクト名と一致しない場合は、うまく動作しない。

2.11.5. docker-compose ps 各サービスのコンテナを一覧表示する

各サービスのコンテナについて、docker ps相当の処理を実行。

docker-compose -f <docker-compose.ymlのパス> -p <docker-composeのプロジェクト名> ps

-f: docker-compose.ymlへのパスを指定。指定しない場合は、カレントディレクトリ直下のdocker-compose.yml が使われる
-p: プロジェクト名を指定。プロジェクト名は、各サービスのコンテナ名の一部としても使われる("<プロジェクト名>-<サービス名(docker-compose.yml内で指定)>-X")
-a: つけない場合、コンテナのステータスがEXITEDなものは一覧に表示されない。

注意
docker-compose upで指定した docker-compose.yml および プロジェクト名と一致しない場合は、うまく動作しない。

2.11.5.1. 各サービスのコンテナの一覧表示を見やすくする

結論としては、以下のコマンドを実行する。

# watch -n <更新間隔> 'docker-compose -f <ステータスを表示したいサービスが記述されているdocker-compose.ymlへのパス> -p <docker-compose up で指定するプロジェクト名> ps -a --format "table <表示したい項目>"'
# 具体例.
watch -n 0.05 'docker-compose -p sample_app -f deploy/docker-compose/docker-compose.yml ps -a --format "table {{.Service}}\t{{.Status}}"'

ステータスがEXITEDであるコンテナも表示対象にする
-aをつけない場合は、ステータスが CREATED, RUNNING, PAUSED なコンテナのみ一覧表示される。
-aをつけることで、EXITEDなものも対象になる。
※削除されているコンテナについてはそもそも表示されない
EXITEDなコンテナのみ表示対象外にしたい状況はあまり思いつかないため、とりあえずつけておくのが無難と考える

表示項目を制限する
デフォルトでは表示項目が多く、ターミナルで折り返しが発生してしまい非常に見づらい。
--formatオプションで表示項目を制限できる。例えば、
--format "table {{.Service}}\t{{.Status}}"
とすることで、サービス名とそのステータスのみを表示することができる。

ステータスをリアルタイムに表示し続ける
docker-compose.ymlDockerfileの設定をトライアンドエラーで調整する際、コンテナのステータスを確認したいことがある。都度docker-compose psを実行するのがめんどくさい。
「各コンテナがどんなステータス遷移をしているのかをリアルタイムに把握したい」ことも多い。
そんなときには、linuxコマンドwatchを併用する。

# インストール(Macの場合)
brew install watch
 
# リアルタイムに表示
watch -n <更新間隔(秒)> <docker-comopse psコマンド>

2.12. docker-compose.ymlのTips

2.12.1. 各サービスのコンテナの起動順序を制御する

2.12.1.1. depends_onhealthcheck

depends_onは、各コンテナの起動開始順序を制御できるだけであり、これだけだと前のコンテナの起動の開始がなされていれば、すぐに次のコンテナの起動が開始されてしまう。
しかし実際のところ、各コンテナの起動条件として、先に起動していてほしいコンテナについては、「そのコンテナがある状態になっていることまで確認した上で起動したい」はずである。 

docker-compose標準機能として、depends_onで指定したコンテナについて、追加のオプション"condition"で先に開始してほしいコンテナが満たすべきステータスを設定できる。
また、先に開始していてほしいコンテナ側のステータスを決定するための"healthcheck"という機能が用意されている。

depends_onhealthcheckを併用することで、詳細にコンテナの開始条件を制御できる。

サンプルコード

  • service2はservice1のステータスがhealthyになったらコンテナを開始できる。
  • service1のヘルスチェックの設定は以下の通り。
    • ヘルスチェックの成功条件は、「service1コンテナ内で、curlでlocalhostへのHTTPリクエストを投げるコマンドを実行する。ステータスコード200が返ってきたら成功、それ以外は失敗」とする。
    • ヘルスチェックは最大10回連続で失敗するまで実行する。
    • ヘルスチェックは5秒間隔で実行する。
    • ヘルスチェックコマンド実行後、10秒経過してもコマンドの実行が終わらなければ失敗扱いとする。コンテナを開始してから10秒の間は、ヘルスチェックコマンドが失敗しても失敗回数にカウントしない。この期間も成功は認める。
docker-compose.yml
services:
  service1:
    ...
    healthcheck:
      test: curl -f http://localhost
      interval: 5s
      timeout: 10s
      retries: 10
      start_period: 10s
  service2:
    ...
    depends_on:
      service1:
        condition: service_healthy

オプションの解説:

test
そのサービスのコンテナ内部で実行するヘルスチェック用コマンドを記述する。
成功時に0, 失敗時に0以外の終了コードを返すようになっていれば何でもOK。
この例で記載しているcurl の -fオプションは、ステータスコード200なら終了コードを0にして、それ以外は終了コード0以外とするもの。 -fをつけない場合はどんなステータスコードでも終了コード0になる。シェルの話になるが、直前に実行したコマンドの戻り値は"$?"で確認できる。

interval
ヘルスチェックの実行間隔

timeout
ヘルスチェック用コマンド実行をタイムアウトにするしきい値

retries
リトライ回数

start_period
ヘルスチェック失敗をカウントしない時間。コンテナを開始してからの時間で設定。

2.12.1.2. helthcheckの弱点

healthcheckは、指定したコマンドをステータスチェック対象のコンテナの内部で実行してステータスチェックするものであるため、distrolessをベースイメージとするような「チェックに必要なコマンドがインストールされないようなコンテナ」だとヘルスチェックに必要なコマンドが実行できず、チェックできないという問題がある。

回避策の例としては以下のようなものがある。

  • 外形監視用のコンテナを別途起動する(後述)
  • ヘルスチェック対象の軽量イメージをラップした独自イメージを頑張って作る
  • wait-for-it使う
  • dockerize使う

2.12.1.2. ヘルスチェックのために外形監視用のコンテナを用意する

個人的に作っているGoアプリケーションのローカル開発環境のdocker-compose.ymlで発生した問題を元に説明する。

元々の状況
Spannerエミュレータコンテナ(spanner-emulator)起動後、コンテナ外からSpannerエミュレータコンテナ内のある名前のデータベースに接続できる状態になってからGoアプリケーションコンテナ(api)を起動したい。
データベースに接続できる状態か?は、HTTPのAPIで確認できる。ゆえにspanner-emulatorサービスに対してhealthcheckを設定するのが正攻法。以下のようにhealthcheck, depends_onを設定したい。
しかし、spanenr-emulatorの元になっているイメージには、HTTPアクセスするためのcurlコマンドがインストールされていないため、正攻法でのチェックが不可能である。

docker-compose.yml
services:
  # Spanner エミュレータ
  spanner-emulator:
    image: gcr.io/cloud-spanner-emulator/emulator:1.5.22
    ports:
      - "9010:9010"
      - "9020:9020"
    healthcheck:
      test: curl -f ${LOCAL_HOST_URL}/v1/projects/${GCP_PROJECT_ID}/instances/${SPANNER_INSTANCE_ID}/databases/${SPANNER_DATABASE_ID} # curlがインストールされていないため実行不可能
      interval: 5s
      timeout: 10s
      retries: 10
      start_period: 10s 

  # Spanner エミュレータ起動後に流すスクリプト
  # 指定したプロジェクトID, インスタンスID, データベースIDのデータベースを作成する
  spanner-emulator-init-script:
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:alpine
    platform: linux/amd64
    command: >
      bash -c 'gcloud config configurations create emulator || gcloud config configurations activate emulator &&
              gcloud config set auth/disable_credentials true &&
              gcloud config set project ${GCP_PROJECT_ID} &&
              gcloud config set api_endpoint_overrides/spanner ${SPANNER_EMULATOR_URL}/ --quiet&&
              gcloud spanner instances create ${SPANNER_INSTANCE_ID} --config=emulator-config --description="Test Instance" --nodes=1 &&
              gcloud spanner databases create ${SPANNER_DATABASE_ID} --instance=${SPANNER_INSTANCE_ID}'
    depends_on:
      - spanner-emulator

  # GoアプリケーションのAPIサーバ
  api:
    build:
      context: ../../
      dockerfile: ./build/packages/docker/Dockerfile.sample_app
    ports:
      - "8080:8080"
    env_file:
      - .env
    tty: true
    volumes:
      - type: bind
        source: ../../
        target: /app/
    depends_on:
      spanner-emulator:
        condition: service_healthy

回避策

これを回避するために、spanner-emulatorを外形監視するためのspanner-emulator-healthcheckサービスを以下のように定義した。

  • curlを使えるイメージをベースとしてコンテナを起動
  • spanner-emulatorコンテナの起動をトリガーにコンテナを起動
  • コンテナが起動しっぱなしになるようtail -f /dev/nullをコンテナ起動時に実行するコマンドとして設定
  • spanenr-emulatorコンテナからステータスコード200が返って来たらspanner-emulator-healthcheckコンテナのヘルスチェック成功となるよう設定

そしてapiサービスでは、spanner-emulator-healthcheckサービスの状態がhealthyになったら起動という設定をしている。

docker-compose.yml
services:
  # Spanner エミュレータ
  spanner-emulator:
    image: gcr.io/cloud-spanner-emulator/emulator:1.5.22
    ports:
      - "9010:9010"
      - "9020:9020"

  # Spanner エミュレータ起動後に流すスクリプト
  # 指定したプロジェクトID, インスタンスID, データベースIDのデータベースを作成する
  spanner-emulator-init-script:
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:alpine
    platform: linux/amd64
    command: >
      bash -c 'gcloud config configurations create emulator || gcloud config configurations activate emulator &&
              gcloud config set auth/disable_credentials true &&
              gcloud config set project ${GCP_PROJECT_ID} &&
              gcloud config set api_endpoint_overrides/spanner ${SPANNER_EMULATOR_URL}/ --quiet&&
              gcloud spanner instances create ${SPANNER_INSTANCE_ID} --config=emulator-config --description="Test Instance" --nodes=1 &&
              gcloud spanner databases create ${SPANNER_DATABASE_ID} --instance=${SPANNER_INSTANCE_ID}'
    depends_on:
      - spanner-emulator

  # Spanner エミュレータのヘルスチェック用コンテナ
  spanner-emulator-healthcheck:
    image: curlimages/curl
    depends_on:
      - spanner-emulator
    platform: linux/amd64
    entrypoint: tail -f /dev/null
    healthcheck:
      test: curl -f ${SPANNER_EMULATOR_URL}/v1/projects/${GCP_PROJECT_ID}/instances/${SPANNER_INSTANCE_ID}/databases/${SPANNER_DATABASE_ID}
      interval: 5s
      timeout: 10s
      retries: 10
      start_period: 10s

  # GoアプリケーションのAPIサーバ
  api:
    build:
      context: ../../
      dockerfile: ./build/packages/docker/Dockerfile.sample_app
    ports:
      - "8080:8080"
    env_file:
      - .env
    tty: true
    volumes:
      - type: bind
        source: ../../
        target: /app/
    depends_on:
      spanner-emulator-healthcheck:
        condition: service_healthy

ヘルスチェック用コンテナの削除
普通にdocker-compose up するとヘルスチェック用のコンテナが起動しっぱなしになる。
ヘルスチェック用コンテナは起動判断のみ必要なものであるため、
docker-compose upを実行した後にdocker-compose rm -fsv spanner-emulator-healthcheckを実行するようなスクリプトを組んでおくとより便利。

2.12.2. 起動中のコンテナへログインできるようにする

docker-compose.ymlのデバッグのために、コンテナ内へログインして色々コマンド実行したいときに一時的に記述することがある。
ENTRYPOINTやCMDで、コンテナが起動しっぱなしになるようなプロセスを起動している前提で、tty: trueを記述する。

2.12.3. docker-compose.yml 内で展開する環境変数を設定し、使用する

docker-compose.ymlが存在するディレクトリに、環境変数を列挙した.envを配置する。
この環境変数はコンテナ内には渡されない。
docker-compose.yml上で${環境変数}を記述することで展開できる。

2.12.4. 各サービスのコンテナ向けの環境変数を設定する

環境変数を列挙したファイル(★)を作成し、docker-compose.ymlの各サービスのenv_fileパラメータで★のファイルを指定する。

2.12.4. DockerfileのCMDやENTRYPOINTをdocker-compose.ymlから上書きする

docker-compose.yml内でcommandentorypointを指定すると上書きできる。

2.12.5. ホスト側のディレクトリ・ファイルをコンテナ内と同期する

ローカル開発環境を起動しつつ、ホスト側でのソースコードの変更を即コンテナ内へも反映したい時に。

バインドマウントの設定で可能。以下のように記述する。

volumes:
  - type: bind
    source: ../../
    target: /app/

source: バインドしたいホスト側のディレクトリを相対パスで指定する。基準ディレクトリはdocker-compose.ymlが配置されているディレクトリ。
target: バインドされるコンテナ側のディレクトリを、コンテナ内の絶対パスで指定する。

コンテナ起動時のバインドマウントの適用タイミングは、先述の通り。
バインドマウント適用前に、コンテナ側のディレクトリ内部に存在していたファイル・ディレクトリは破棄されるような形になることに注意。 

参考

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?