[Docker] Docker Composeを学ぼう

[Docker] Docker Composeを学ぼう

2022年7月21日

お兄さんたち、私だけ不便を感じてる?

二回目のお兄さんたちです。実際、不便なことが多いからです。

Dockerの用途

Dockerはどんな時に使用するといったのか?

Dockerの核心技術であるコンテナを使用したいときでしょう。

つまり、再利用のためです。

前に学んだDockerfileは、コンテナを作成するのに必要な多くの部分を自動化しました。

しかし、Dockerfileにも短所があります。

以前の投稿でも扱ったように、ホストOSの設定を自動化することには制限があるという部分でした。

例えば、ホストOSのポートフォワーディングや、ボリュームのパスをマウントすることまではできていませんでした。

結局、多くの開発者はDockerfileを作成し、

buildを通じて作成されたコンテナを相互に接続し、

volumeをマウントする必要がありました。

Docker Compose

img.png

Docker Composeは上記の煩わしさを解決します。

docker-compose.ymlで定義できる内容にはvolumeportenvの他にコンテナを定義する様々な作業を実行できるようにしています。

また、単一コンテナだけでなく、多重コンテナを定義し、コンテナ間の関係を設定できるようにも支援しています

Docker公式ドキュメントではComposeをプラットフォームに依存しないコンテナベースのアプリケーションとして紹介しています。

.yml, .yaml

Docker Composeについて知る前に知っておかなければならないことがあります。

img2

先にdocker-compose.ymlに多重コンテナに下すコマンドを設定できることを強調しました。

では、.ymlファイルとは何なのか?

.yml.yaml拡張子がついているファイルはYet Another Markup Languageという一種のマークアップ言語の一形態です。

まるでPythonのようにコロンとインデントを利用して階層を構成し、特定のオブジェクト、設定値などを簡単に記述することができます。

直感的に理解するために以下の例を見てみましょう。

car:
  name: "bus"
  color: "red"
  door: 4
  capability:
    max_weight: "4T"
    human: 10
  customers:
    - name: "Alice"
      age: 14
    - name: "Ban"
      age: 16
    - name: "Yang"
      age: 26

この形式の.yamlは以下のようなJSON形式でも表現できます。

{
  "car": {
    "name": "bus",
    "color": "red",
    "door": 4,
    "capability": {
      "max_weight": "4T"
    }
  }
}

このように.ymlは階層的な形態の資料、既存のJSONXMLなどのフォーマットを使ってデータを表現する際に利用しやすいファイル形式です。

docker-compose.yml

では、docker-compose.ymlを作成し、私たちが使用するコンテナを明示してみましょう。

services

まず、定義する各コンテナに関する仕様はserviceという単位で定義されます。

.ymlフォーマットを借りて説明するとこのような形式になります。

services:
  container_1:
    ...
  container_2:
    ...

image

取得するイメージを仕様する部分です。

様々な方法で欲しいイメージを取得することができます。

# イメージ名
image: redis
# イメージ名:タグ名
image: redis:5
# イメージ名@sha256: IMAGE ID
image: redis@sha256:0ed5d5928d4737458944eb604cc8509e245c3e19d02ad83935398bc4b991aac7
# リポジトリ名/イメージ名
image: library/redis
# レジストリ名/リポジトリ名/イメージ名
image: docker.io/library/redis
# 個人レジストリアドレス:ポート番号/イメージ名
image: my_private.registry:5000/redis

build

Dockerfileを通じてコンテナを生成したい時に使用します。

services:
  frontend:
    image: awesome/webapp
    build: ./webapp # 下位段階を指定しなければcontextの役割

  backend:
    image: awesome/database
    build:
      context: backend # Dockerfileを探索するパス
      dockerfile: ../backend.Dockerfile # ファイル名がDockerfileでない場合はファイル名を明示

  custom:
    build: ~/custom # ホームディレクトリを示す"~"も使えます。
  1. awesome/webappイメージはComposeファイルの上位フォルダ内の./webapp下位ディレクトリをdockerビルドコンテキストとして使用してビルドされます。 このフォルダ内にDockerfileが見つからない場合、エラーが発生します。
  2. awesome/databaseイメージはComposeファイルの上位フォルダ内の./backend下位ディレクトリを使ってビルドされます。 backend.Dockerfileファイルはビルド段階を定義するために使用され、このファイル(backend.Dockerfile)はコンテキストパス(./backend)を基準に定義されています。 つまり、このサンプルの..の場合、Composeファイルの上位フォルダとして解釈されるので、backend.Dockerfile兄弟Dockerfileも検索対象にされます。
  3. customサービスはユーザーのHOMEディレクトリのcustomディレクトリをcontextとして使用してビルドされます。

imagebuildが同時にある場合

imageは明示されたイメージを基にコンテナを生成することであり、buildDockerfileを使ってコンテナを生成することだとおっしゃいました。

しかし、DockerfileでもFROMキーワードを通じてイメージを取得できるため、その属性が衝突しないか不安になるかもしれません。

Docker公式ドキュメントでもこれについてのセクションを見つけることができますが、私たちが恐れる通りその部分はどのイメージが使われるのか保証できないと明記しています。

ただし、ユーザーの別途の指示がない限り、まずimageで定義したイメージを取得し、次にレジストリでイメージを見つけられない場合、Dockerfileから取得したイメージをビルドすると明記しています。

depends-on

複数のサービスをdocker-compose.ymlに定義するとき、特定のサービスが起動されてからそのサービスが起動される必要がある場合は、depends_on: サービス名を通じて指定できます。

services:
  seviceA:
    ...
    depends_on: serviceB
    
  serviceB:
    ...

entrypoint

Dockerfileで扱ったENTRYPOINTと同様の役割をします。

つまり、Dockerコンテナが実行する際の初期コマンドを定義する部分です。

前の章で扱ったように、そのコマンドは一度しか宣言できないので、もしbuildオプションを通じてDockerfileでもENTRYPOINTを記載している場合はどちらか一方(または両方)を削除しなければなりません。

多くの事例で主に初期にどんなシェルを使うのかに関する定義のような部分を使用するようです。

entrypoint: /code/entrypoint.sh

labels

DockerfileLABEL部分で調べたように、コンテナにメタデータを追加できる項目です。

environment

コンテナで使用する環境変数を定義する際に使用します。

二つの方式を使用できます。

  1. Map方式
environment:
  RACK_ENV: development
  SHOW: "true"
  USER_INPUT: "hello"
  1. Array方式
environment:
  - RACK_ENV=development
  - SHOW=true
  - USER_INPUT=hello

env_file

環境変数を直接定義するのではなくファイルで定義する場合はこのオプションを使用します。

env_file: some_env.env
# some_env.env
RACK_ENV=development
VAR="quoted"

この方式を使うと、環境変数をファイルの形式で管理できるため、管理がより容易になるという利点があります。

command

DockerfileCMD部分でも調べたように、そのサービスを担当するコンテナに下すコマンドを定義する部分です。

command: ls -al

筆者はバックエンド開発者なので、バックエンド開発者の視点から両方のコマンド(Dockerfile:CMD, docker-compose.yml:command)の違いを説明してみます。

DockerfileCMDは主にpip installgradle buildのようにサーバーを構成する基本要素をインストールするコマンドとして主に使用されます。

言ってみれば、コンテナを起動できる形態を作るということです (しかし、必ず従う必要はありません。)

docker-compose.ymlcommandは主にサーバーをデプロイするコマンド(例: ... run serve)のようにサービスを起動するコマンドとして主に使用されます。

volumes

volumesはユーザーが作成したDocker Volumeや、Host PathとContainer Pathを接続することを定義したり、ボリューム自体を宣言するのに使用されます。

volumeの仕様

volumesの下位エレメントを定義する方式は簡略構文と一般構文の二つが混在します。

一般的には簡略構文を使用しますが、場合やユーザーの嗜好に応じて一般構文も使用できます。

  1. 一般構文要素
ElementDescription
typeDocker Volumeを使用する(volume)のか、Host OS pathとバインドする(bind)のかを定義します。
sourceVolume nameやID値、またはバインドするHost OSのPathを指定します。
targetContainerのPathを入力します。
read_only読み取り専用に指定した場合は設定します。
    ...
    volumes:
      - type: volume
        source: db-data
        target: /data
    ...
  1. 簡略構文

VOLUME:CONTAINER_PATH:ACCESS_MODE形式で指定します。

ACCESS_MODEはデフォルトでrwに設定されます。

services:
  service_1:
    image: some/image
    # Docker VolumeとContainer Pathを接続する
    volumes:
      - db-data:/etc/data

  service_2:
    image: some/image
    # ホストOSのPathとContainer Pathを接続する
    volumes:
      - .:/var/lib/backup/data
      - 
  service_3:
    image: some/image
    # ホストOSのPathとContainer Pathを接続し、Container Pathを読み取り専用に設定する
    volumes:
      - .:/var/lib/backup/data:ro

Volumeを定義する

volumesを通してVolumeを定義したい場合はvolumesdocker-compose.ymlの最上位エレメントに置けばいいです。

# Docker Volume定義
volumes:
  db-data:

ports

Docker CLIのruncreateで使用される-pオプションと同じように機能します。 つまりHost OSのPortとContainer PathのPortをマッピングします。

    ...
    ports:
      - "8000:8000"
    ...

このように設定すると、Host OSの8000番ポートに入る全ての要求はContainerの8000番ポートに代わるように伝達されます。

Docker Compose CLI

さて、コンテナを作成することだけが残っています。

docker-compose upコマンドを使用すると、命令したディレクトリでdocker-compose.ymlファイルを見つけて自動的に実行します。

新しく出たDocker Compose V2ではdocker composeキーワードを使用して実行することを明記しているので、V2を使用している場合はこの点を考慮してください。

-dオプションを付けると、そのサービスがバックグラウンドモードで実行されます。(一般的に多く使用されます。)

-f [COMPOSE_FILE_NAME]オプションを通じてdocker-compose.yml以外の特定ファイルを通じてDocker Composeを実行することができます。

docker-compose start [SERVICE_NAME]を使用すると、services内の特定のサービスを実行することができます。

反対にdocker-compose stop [SERVICE_NAME]コマンドは特定のサービスを停止するコマンドです。

参考文献