DooD(Docker outside of Docker)で、Docker Composeで一部コンテナを逐次アップデートする

こんにちは、ピリカ開発チームの九鬼です。

Docker Composeで複数コンテナからなるサービスを動かしている最中に、一部コンテナをアップデートしたいことがあります*1

そこで、本記事ではその方法を紹介します(もし、さらに良い方法がありましたらぜひ伺いたいです)。

前提

例えば下図のサービス構成で、service_1~3を動的に更新することを考えます。

この構成では、apiコンテナがコンテナ外部とのインタフェースになっています。コンテナ更新用のAPIにリクエストがあると、core_controllコンテナを介してservice_*用imageコンテナを都度ビルド、新規コンテナをアタッチする想定です。

<network>     api             internal
        |--------------|  |---------------|

                                 <=> [service_1]
     [api] <=>  [core_controll]  <=> [service_2]
                                 <=> [service_3]

新たにコンテナをビルドをするにはDockerデーモンにアクセスする必要があるのですが、デフォルト設定ではコンテナ内からアクセスできません。

そこで、DooD(Docker outside of Docker)を使用します*2。DooDの説明にいては割愛します。

環境構築

ファイル構成を準備する

以下の通りファイルを配置します*3

some_service/
- docker-compose.yml
- api/
  - Dockerfile
  - ...(APIサーバ用コード)...
- core_controll/
  - Dockerfile
  - docker-compose.yml
  - ...(core_controll用コード)...

service1/
- Dockerfile
- ...(service1用コード)...

service2/
- Dockerfile
- ...(service1用コード)...

service3/
- Dockerfile
- ...(service1用コード)...

この構成において、some_service直下でdocker-compose upを実行することによりapiコンテナ、core_controllコンテナを作成するようにしています。

DooDを使えるようにする

some_service/docker-compose.ymlについて、volumesで/var/run/docker.sock:/var/run/docker.sockと指定します。

この設定がホスト環境のDockerのsockをcore_controllコンテナに共有する設定になります。これにより、コンテナ側からホスト側のDockerを操作できるようになります。

some_service/docker-compose.yml

version: '3.4'

services:
  vision:
    container_name: "api"
    build:
      context: ./api
      dockerfile: Dockerfile
    restart: always
    tty: true
    ports:
      - "8000:8000"
    networks:
      - api

  core_controll:
    container_name: "core_controll"
    build:
      context: ./core_controll
      dockerfile: Dockerfile
    depends_on:
      - service1
      - service2
      - service3
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - api
      - internal

networks:
  api:
  internal:

コンテナ内部でdocker-composeコマンドを使えるようにする

core_controllコンテナ内でdocker-composeコマンドを使えるようインストールに加えます。

(Alpine Linux環境のためapkを使っていますが、ベースOSに応じてyumやapt-get等に読み替えてください)

FROM node:14-alpine

RUN apk add --update docker-compose

コンテナ内部でservice_*用コードを取得、ビルドする機構を作る

コード上で、以下2処理を記載します。

  • service1~3のコードをsome_service/下にフェッチします(サンプルコードではシェルスクリプトによりコピーしているので、割愛しています)。
  • サブプロセスでdocker-compose up -dを実行します。pythonの場合、subprocess.run、Node.jsの場合child_process.execなどが該当します。

service1~3構成用Docker Compose設定を記載する

some_service/core_controll/docker-compose.ymlにservice1~3の構成を記載します。

version: '3.4'

services:
  service1:
    container_name: "service1"
    build:
      context: ./service2
      dockerfile: Dockerfile
    networks:
      - internal

  service2:
    container_name: "service2"
    build:
      context: ./service2
      dockerfile: Dockerfile
    networks:
      - internal

  service3:
    container_name: "service3"
    build:
      context: ./service3
      dockerfile: Dockerfile
    networks:
      - internal

networks:
  internal:
    external:
      name: some_service_internal

このうち、networksでinternalネットワークと、some_service_internalネットワークを接続しています。このsome_service_internalネットワークは、some_service/docker-compose.ymlで設定したinternalネットワークに対応します。

以上で設定は完了です。

service1~3コンテナを動的に更新する

※ 具体的な実装例、および動作をテストしてみたい場合はサンプルコードをご覧ください。

(事前処理) some_service/下でdocker-compose up -dを実行し、apiコンテナとcore_controllコンテナを立ち上げておきます。

コンテナ更新用のAPIを呼び出し、core_controll下でdocker-composeコマンドによるビルドを行います。ビルドが成功していれば、service1~3コンテナが自動的にアタッチされます。

これによりサービス全体を動かしつつ、service1~3を動的に更新することができます。

サンプルコード

以下URLを参照ください。

https://github.com/pirika-inc/docker_compose_hot_container_update_sample

以上になります。ご覧いただきありがとうございます!

*1:例えば深層学習において、あるメンバーはTensorFlowで解析するサービス、あるメンバーはPyTorchで解析するサービスを作る、という構成が挙げられます

*2:他にもDocker inside of Dockerもありますが、コンテナにprivileged権限を与える必要があります。セキュリティ観点上、--cap-dropや--deviceで権限を細かく制御する必要があり、公式にも推奨されません

*3:実運用では、service_*をgitのリモートリポジトリなどから持ってくることになります。通信インタフェースが一致している限り、別機能を持ったサービスを持ってくることも可能です