Shin x Blog

PHPをメインにWebシステムを開発してます。Webシステム開発チームの技術サポートも行っています。

ベースイメージを共通化して docker-compose up を速くする

docker-compose で複数サービスを起動する際に時間を要するのが、Docker イメージのダウンロードと展開です。この時間を削減するために、ベースイメージを共通化する方法を試してみました。

本エントリでは、開発環境や CI 環境に docker-compose を利用することを想定しています。

改善前

ここでは、dokcer-compose up(pull) の時間を削減できるかを確認するだけなので、下記のように dynamodb, elasticmq, elasticsearch のみを docker-compose.yml に含めています。

version: "2.0"
services:
  dynamodb:
    image: amazon/dynamodb-local:1.12.0
    ports:
      - 8000:8000
  elasticmq:
    image: softwaremill/elasticmq:0.15.7
    ports:
      - 9324:9324
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200

この docker-compose.yml を CircleCI で起動すると、55s を要します。

f:id:shin1x1:20200427175553p:plain

問題点

上記の 3 イメージは別々のベースイメージからビルドされており、それぞれ別のレイヤを持ちます。つまり、docker-compose up 実行時には全てのレイヤをダウンロードし、さらに展開する必要があります。この処理に時間を要するので、削減します。

$ docker inspect -f '{{json .RootFS.Layers}}' amazon/dynamodb-local:1.12.0 | jq .
[
  "sha256:9a1290d6039b7d2456bc035d687e9753e85b78647095a2f43f882b2975b20a0b",
  "sha256:7ca537ddeb95cbd49208e26cbb64225b915c39cf7b94ba210fe3ed5a6da9339d",
  "sha256:7d832d77ed1b80c4d3ccb546cf8488df25e8b7a6ea7985e6f5655e2a6f43a3c8"
]
$ docker inspect -f '{{json .RootFS.Layers}}' softwaremill/elasticmq:0.15.7 | jq .
[
  "sha256:e2a8a00a83b20c88b81952f81e6cfc2e2dd5aa7f00a23b067e6342c70602a567",
  "sha256:15210a41d4ee66fef9f6917804a00d44f72517c8ed8feee1c97e08a35be52ad6",
  "sha256:392f356944ff70fa41f6dbf6e6d207beef060f0869e584edb58ca8cd343b341f",
  "sha256:a4e797bc3f155d8be8dcd6bb565fb41e07cc4febdba63efdf7a8dcb6ac10444e",
  "sha256:c6465287316203e0732c16b7a1834a28e1fd5a3916789f18f72dba74aea151cd",
  "sha256:7955da51da828051722b0bbdcba0307130e064f518cf341b4cf8caa748c8aede",
  "sha256:a4e7d1c2f08d0d8420a077c391372e7a366cbe3d2cc78f095950ebaba64ec9e1",
  "sha256:4861ed836e7651a543d3fbf2ee5de5f15acc40a881eecbfa3d31c41b8d2fbd5c",
  "sha256:68e8576c229676e395ba11c57c45d0068e8f67fd362ed693c5add5b941862eab",
  "sha256:a6a7b41a96061209ee4548cb012c0bffd2c39f26cad80ee47fb70a63f8fdbe09",
  "sha256:9e4f8dd8b1f3573419b6e7b41d44522c2a6a56298a19ddea3b94d2daf3fb137a",
  "sha256:c08329398ff9d7d02d65945c4e8a70f715e3fe102b2d0612a6195bdc3528ef44",
  "sha256:be3f840efe299226b292a3ec70b28faa31f9fe93609616b6d5ca5469a8e4915f"
]
$ docker inspect -f '{{json .RootFS.Layers}}' docker.elastic.co/elasticsearch/elasticsearch:7.6.2 | jq .
[
  "sha256:77b174a6a187b610e4699546bd973a8d1e77663796e3724318a2a4b24cb07ea0",
  "sha256:7712f32688d1bef330aa3b4fac2683cec1d7339335ce403483b329423debffb6",
  "sha256:1a090720e70c88e61053c89d3ff01932943b198644f4a7e86426e576b721d2e3",
  "sha256:0535424758bd9ab49a3d03af52a9fa5447b807198fadc35e9f80123a0929402e",
  "sha256:4d2f8f4a58623431cca0ee321bbee273b87070f9e381b3147e4293e83dc146d7",
  "sha256:77c5267605c2c7cf2426242d5df50af78931e66403e9d0d06f2b9fd7adc3adc9",
  "sha256:537370aeea86be07f79bbcd25464a1df23f347bb5313e0ff7ca15d6aad70e074"
]

改善案

今回起動するサービスはいずれも JVM で実行するものなので、OS と JVM 環境を含む同一のイメージをベースに利用し、その上に個別に必要な内容を含めるようにします。

ベースイメージ

ベースイメージは、OS と JVM 環境を含む adoptopenjdk:8-jre-hotspot イメージを利用します。

https://hub.docker.com/_/adoptopenjdk

各サービスイメージ

各サービスでは、ベースイメージを FROM で指定し、必要な内容を追加します。

  • dynamodb - Dockerfile
FROM adoptopenjdk:8-jre-hotspot

RUN mkdir /opt/dynamodb \
    && cd /opt/dynamodb \
    && curl -O https://s3.ap-northeast-1.amazonaws.com/dynamodb-local-tokyo/dynamodb_local_latest.tar.gz \
    && tar zxvf dynamodb_local_latest.tar.gz \
    && rm dynamodb_local_latest.tar.gz

WORKDIR /opt/dynamodb
ENTRYPOINT ["java"]

CMD ["-Djava.library.path=./DynamoDBLocal_lib",  "-jar", "DynamoDBLocal.jar", "-inMemory"]
  • elasticmq - Dockerfile
FROM adoptopenjdk:8-jre-hotspot

RUN mkdir /opt/elasticmq \
    && cd /opt/elasticmq \
    && curl -o elasticmq-server.jar https://s3-eu-west-1.amazonaws.com/softwaremill-public/elasticmq-server-0.15.7.jar

ADD custom.conf /opt/elasticmq/custom.conf

WORKDIR /opt/elasticmq
ENTRYPOINT ["java"]

CMD ["-Dconfig.file=custom.conf",  "-jar", "elasticmq-server.jar"]

elasticmq - custom.conf

include classpath("application.conf")
  • elasticsearch - Dockerfile
FROM adoptopenjdk:8-jre-hotspot

RUN mkdir -p /opt \
    && cd /opt \
    && curl -L -o elasticsearch.tar.gz https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2-linux-x86_64.tar.gz \
    && tar zxvf elasticsearch.tar.gz \
    && rm -f elasticsearch.tar.gz \
    && mv elasticsearch-7.6.2 elasticsearch \
    && chown -R nobody elasticsearch

USER nobody

WORKDIR /opt/elasticsearch
ENTRYPOINT ["./bin/elasticsearch"]

CMD []

それぞれのイメージは docker build でビルドして、Docker Hub 等の Docker Registry に push しておきます。push したイメージのサンプルは下記です。

docker-compose.yml の変更

docker-compose.yml ではビルドしたイメージを利用するように変更します。

version: "2.0"
services:
  dynamodb:
    image: shin1x1/test-dynamodb
    ports:
      - 8000:8000
  elasticmq:
    image: shin1x1/test-elasticmq
    ports:
      - 9324:9324
  elasticsearch:
    image: shin1x1/test-elasticsearch
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200

結果

この改善策にて、docker-compose up を実行すると、25s に短縮できました。つまり、30s 短縮できました。

f:id:shin1x1:20200427175630p:plain

ビルドしたイメージのレイヤを確認すると、3 つのイメージで上から 6 つのレイヤ(b7f7d2967507ba709dbd1dd0426a5b0cdbe1ff936c131f8958c8d0f910eea19e から 105be441f2caa7d1b332f89e227cabd6652dc9257174735562c9e76c93d1632b)が一致していることが分かります。

$ docker inspect -f '{{json .RootFS.Layers}}' shin1x1/test-dynamodb | jq .
[
  "sha256:b7f7d2967507ba709dbd1dd0426a5b0cdbe1ff936c131f8958c8d0f910eea19e", <---
  "sha256:a6ebef4a95c345c844c2bf43ffda8e36dd6e053887dd6e283ad616dcc2376be6", <---
  "sha256:838a37a24627f72df512926fc846dd97c93781cf145690516e23335cc0c27794", <---
  "sha256:28ba7458d04b8551ff45d2e17dc2abb768bf6ed1a46bb262f26a24d21d8d7233", <---
  "sha256:55c91231ac46fdd63c3cf84b88b11f8a04c1870482dcff033029a601bc50e1ab", <---
  "sha256:105be441f2caa7d1b332f89e227cabd6652dc9257174735562c9e76c93d1632b", <---
  "sha256:ecf2bae03f590766d77542cc1aa36603c4af4a61dc88786cbfe8974d05ca98eb"
]
$ docker inspect -f '{{json .RootFS.Layers}}' shin1x1/test-elasticmq | jq .
[
  "sha256:b7f7d2967507ba709dbd1dd0426a5b0cdbe1ff936c131f8958c8d0f910eea19e", <---
  "sha256:a6ebef4a95c345c844c2bf43ffda8e36dd6e053887dd6e283ad616dcc2376be6", <---
  "sha256:838a37a24627f72df512926fc846dd97c93781cf145690516e23335cc0c27794", <---
  "sha256:28ba7458d04b8551ff45d2e17dc2abb768bf6ed1a46bb262f26a24d21d8d7233", <---
  "sha256:55c91231ac46fdd63c3cf84b88b11f8a04c1870482dcff033029a601bc50e1ab", <---
  "sha256:105be441f2caa7d1b332f89e227cabd6652dc9257174735562c9e76c93d1632b", <---
  "sha256:3d77653fa44c3b902d9c8d107493b5277f59b88b152a904174f9324c2489d40c",
  "sha256:b090447c7a50d5fe331abc7aef4fd6d81a2722342408444a98ca2d5e82cc21af"
]
$ docker inspect -f '{{json .RootFS.Layers}}' shin1x1/test-elasticsearch | jq .
[
  "sha256:b7f7d2967507ba709dbd1dd0426a5b0cdbe1ff936c131f8958c8d0f910eea19e", <---
  "sha256:a6ebef4a95c345c844c2bf43ffda8e36dd6e053887dd6e283ad616dcc2376be6", <---
  "sha256:838a37a24627f72df512926fc846dd97c93781cf145690516e23335cc0c27794", <---
  "sha256:28ba7458d04b8551ff45d2e17dc2abb768bf6ed1a46bb262f26a24d21d8d7233", <---
  "sha256:55c91231ac46fdd63c3cf84b88b11f8a04c1870482dcff033029a601bc50e1ab", <---
  "sha256:105be441f2caa7d1b332f89e227cabd6652dc9257174735562c9e76c93d1632b", <---
  "sha256:136ecb5b062dfe72bade3a1467843f092f76c8cbec1c7b3ca8d84b34264e5848"
]

イメージサイズの変化

3 イメージサイズは、改善前と改善後で以下のようになりました。

  • 改善前: 1,923MB(3イメージ合算。)
  • 改善後: 758MB(3イメージ合算。重複分は除外。)
  • 差分: -1,165MB

差分のうち、512MB がベースイメージを共通化して削減した分です(206MB*2)。残りの 653MB は、イメージを自作し、内容を必要最低限なものに絞ったことで削減できました。これは特に、dynamodb と elasticmq で顕著で、両イメージではそれぞれ 383MB、283MB をカットしています。

直接的にベースイメージの共通化だけが削減の要因ではありませんが、イメージサイズを減らすことで docker-compose up の起動時間が短縮できることがあらためて実感できました。

改善前

Service Image Size
dynamodb amazon/dynamodb-local 611 MB
elasticmq softwaremill/elasticmq 521 MB
elasticsearch docker.elastic.co/elasticsearch/elasticsearch 791 MB
1923 MB

改善後

Service Image Size 改善前との差分
dynamodb shin1x1/test-dynamodb 228 MB -383MB
elasticmq shin1x1/test-elasticmq 238 MB -283MB
elasticsearch shin1x1/test-elasticsearc 704 MB -87MB
重複分を削除 adoptopenjdk:8-jre-hotspot(206MB) * 2 -512 MB
Total 758 MB -1165MB

さいごに

30s 短縮の効果をどう見るかは状況によるでしょう。

改善前、改善後にはそれぞれ pros/cons があり、改善前は実行時間はかかりますが、公開されているイメージをそのまま利用するので構築は容易です。さらにイメージのメンテナンスも不要です。一方、改善後は実行時間はかかりますが、イメージの構築、メンテナンスが必要になります。

どちらを選ぶかは状況によりますが、改善できる方法を知っておくのは大切ですね。