CLOVER🍀

That was when it all began.

Docker Composeで、コンテナの起動時に初期処理をしたい

これは、なにをしたくて書いたもの?

Docker Composeを使って起動するコンテナの初期処理をするには、どうしたらいいのかな?ということで。

ちょっと考えてみました。

環境

今回の環境は、こちら。

$ docker version
Client: Docker Engine - Community
 Version:           23.0.1
 API version:       1.42
 Go version:        go1.19.5
 Git commit:        a5ee5b1
 Built:             Thu Feb  9 19:47:01 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.1
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.5
  Git commit:       bc3805a
  Built:            Thu Feb  9 19:47:01 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.18
  GitCommit:        2456e983eb9e37e47538f59ea18f2043c9a73640
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0


$ docker compose version
Docker Compose version v2.16.0

コンテナの初期処理をしたい

Dockerfileでビルドする時になんとかすればいいのでは、という話もありますが、すでに作成済みのコンテナイメージを使いたい場合、
その他いろんな理由でコンテナの初期処理を行いたい場合があるような気がします。

今回はDocker Composeの範囲でなんとかするように考えてみます。

Compose specification | Docker Documentation

Docker Composeの構成ファイルには、初期処理用の定義などはないですからね。ではどうしましょうか?という話です。

お題として、MinIOのDockerイメージを使ってみましょう。

minio/minio

entrypointを再定義する

最初に思いつくのは、entrypointを定義することですね。

Compose file specification / Services top-level element / entrypoint

たとえば、MinIOのDockerイメージに含まれるmcコマンドを使えるようにgunzipする初期処理を行ってみましょう。

compose.yaml

services:
  minio:
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    ports:
      - 9000:9000
      - 9001:9001
    environment:
      MINIO_ROOT_USER: myminioadmin
      MINIO_ROOT_PASSWORD: myminioadmin
    entrypoint: >-
      bash -c 'gunzip /opt/bin/mc.gz \
               && chmod a+x /opt/bin/mc \
               && /usr/bin/docker-entrypoint.sh minio server /data --console-address ":9001" '

こんな感じで、entrypointを再定義します。

    entrypoint: >-
      bash -c 'gunzip /opt/bin/mc.gz \
               && chmod a+x /opt/bin/mc \
               && /usr/bin/docker-entrypoint.sh minio server /data --console-address ":9001" '

これで、コンテナの主となるプロセス(今回はMinIOサーバー)の起動前に処理を追加したりできます。

初期処理を行う前の状態は、こうでした。

$ docker compose exec -it minio ls -l /opt/bin
total 106684
-rw-r--r-- 1 root root  9246981 Feb 17 19:11 mc.gz
-rwxr-xr-x 1 root root 99983360 Feb 17 19:11 minio
-rw-r--r-- 1 root root      329 Feb 17 19:11 minio.minisig
-rw-r--r-- 1 root root      100 Feb 17 19:11 minio.sha256sum

先述のcompose.yamlの内容で起動すると

$ docker compose up

mc.gzファイルが展開できていることが確認できます。

$ docker compose exec -it minio ls -l /opt/bin
total 122784
-rwxr-xr-x 1 root root 25735168 Feb 17 19:11 mc
-rwxr-xr-x 1 root root 99983360 Feb 17 19:11 minio
-rw-r--r-- 1 root root      329 Feb 17 19:11 minio.minisig
-rw-r--r-- 1 root root      100 Feb 17 19:11 minio.sha256sum

この方法の欠点は、カスタマイズ元のDockerイメージの起動コマンドがわかっていないと差し替えができないこと、適用できるのが
コンテナ内で主となるプロセスの起動前にできる内容に限られること、ですね。

使用するコンテナイメージの内部にちょっと踏み込んでいる感じがします。

今回のMinIOのコンテナイメージだとcommandで起動引数を追加できるようになっているはずなのですが、それも潰してしまって
いますし。

初期処理用のコンテナを使う

次の方法としては、初期処理用のコンテナを追加することを考えてみたいと思います。

たとえば今回のMinIOを使った例だと、MinIOサーバーが起動した後にバケットの作成を行うといった感じですね。

compose.yaml

services:
  minio:
    image: minio/minio:RELEASE.2023-02-17T17-52-43Z
    ports:
      - 9000:9000
      - 9001:9001
    environment:
      MINIO_ROOT_USER: myminioadmin
      MINIO_ROOT_PASSWORD: myminioadmin
    command: server /data --console-address ":9001"
  minio-setup:
    image: minio/mc:RELEASE.2023-02-16T19-20-11Z
    restart: "no"
    depends_on:
      - minio
    entrypoint: []
    command: >-
      bash -c 'sleep 3 \
                      && mc alias set myminio http://minio:9000 myminioadmin myminioadmin \
                      && mc mb myminio/my-bucket \
                      && echo "minio setup complete"'

初期処理をしたら、終了するコンテナを追加しています。初期処理が目的なので、MinIOクライアントのコンテナイメージを使っています。

minio/mc

depends_onで、対象のコンテナの後に起動するようにして

    depends_on:
      - minio

再起動も行わないようにします。これはデフォルトですが。

    restart: "no"

あとは、entrypointの内容を書き換えて、commandで初期処理を実行。

    entrypoint: []
    command: >-
      bash -c 'sleep 3 \
                      && mc alias set myminio http://minio:9000 myminioadmin myminioadmin \
                      && mc mb myminio/my-bucket \
                      && echo "minio setup complete"'

entrypointにそのまま書いてしまってもいいと思いますけど。

    entrypoint: >-
      bash -c 'sleep 3 \
               && mc alias set myminio http://minio:9000 myminioadmin myminioadmin \
               && mc mb myminio/my-bucket \
               && echo "minio setup complete"'

sleepを入れているのは、depends_onはあくまでコンテナの起動順をコントロールするだけなので、MinIOサーバーが立ち上がるのを
待つようにするためです。

これで起動すると

$ docker compose up

バケットが作成できていることが確認できます。

$ aws s3 ls --endpoint-url http://localhost:9000
2023-02-19 03:27:01 my-bucket

初期処理用のコンテナの様子は、こんな感じですね。

xxxxx-minio-setup-1  | Added `myminio` successfully.
xxxxx-minio-setup-1  | Bucket created successfully `myminio/my-bucket`.
xxxxx-minio-setup-1  | minio setup complete
xxxxx-minio-setup-1 exited with code 0

処理が終わったら終了しています。

この方法だと初期処理を行いたい対象のコンテナ自体はそのままでよいのですが、初期処理のためだけのコンテナ定義ができてしまうことと、
初期処理を適用したい対象のコンテナを--scaleで複数インタンスにしてしまうとまた事情が変わってしまうことだと思います。
複数のインスタンスがあっても、クラスタリングなどで解決できればいいんですけどね。

まとめ

Docker Composeを使って、コンテナの起動時に初期処理を行う方法を考えてみました。

だいぶムリヤリな感じはしますが、Docker Composeだけでなんとかしようとする場合はこんな感じではないでしょうか。