Dockerfileを工夫して、Dockerイメージを軽量化する
最近、Dockerを使い始めました。まずはDockerfileをゴリゴリ書いていたんですが、書いているうちに軽量化のテクニックを知っているかどうかで最終的にできるDockerイメージのサイズが劇的に変わることがわかったので、ここにそのテクニックをまとめてみようと思います。
ちなみに、Dockerイメージのサイズが小さくなると以下のようなメリットがあります。
- ホストのディスク容量を圧迫しない
- イメージの配布が効率的に行える
今回のお題
今回は人気サンドボックスゲーム Minecraft向けのオープンソースサーバーであるSpigotのDockerイメージを作成します。
記事中ではDockerイメージの作成で一般的に使えるテクニックについて解説するため、Minecraftに全く興味がない方も安心してご覧ください。
Spigotのビルド方法は以下を参考にしています。
注意:Spigotは権利関係の事情により、ビルド済み実行ファイルを配布することができません。(詳しい経緯は以下が参考になるかと思います。)Dockerイメージでの配布も同様に問題になると考えられるため、Docker Hub等で公開しません。
DMCA - Bukkit Spigot Japan Wiki
まずは思いついたままに作る
とりあえず、一番単純なDockerfileを書いてみたら、どれぐらいのイメージサイズになるのか確認してみます。
以下のDockerfileをビルドすると、イメージサイズは1.24GBになりました。かなり大きいですね。
FROM openjdk:8-jdk
ENV SPIGOT_VER 1.12.2
WORKDIR /minecraft
RUN wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar
RUN java -jar BuildTools.jar –rev $SPIGOT_VER
WORKDIR /data
RUN echo "eula=true" > ./eula.txt
EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot-${SPIGOT_VER}.jar nogui
軽量イメージを使用する
openjdk:8-jdk
イメージはDebianを基に作られていますが、Javaを実行するだけならDebianに付随する多くのソフトウェアは必要ありません。そこで、最小限の機能を備えた軽量OSを基に作られたイメージを利用します。この用途にはAlpine Linuxが使われることが多く、openjdkのAlpine Linux版イメージもDocker Hubで公開されているため、今回はこれを使ってみます。
Alpine Linuxにはgit等も入っていないため、必要であれば随時インストールする必要があります。Alpine Linuxでは主にapkというパッケージマネージャを利用してソフトウェアをインストールします。
以下のようにAlpine Linux版のopenjdkイメージを利用してDockerfileを書き直し、ビルドしたところ、イメージサイズは734MBになりました。
FROM openjdk:8-jdk-alpine
ENV SPIGOT_VER 1.12.2
WORKDIR /minecraft
RUN apk --no-cache add git
RUN wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar
RUN java -jar BuildTools.jar –rev $SPIGOT_VER
WORKDIR /data
RUN echo "eula=true" > ./eula.txt
EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot-${SPIGOT_VER}.jar nogui
RUNをまとめる
docker build
ではRUN, COPY, ADDを実行するたびにイメージのレイヤーを作成し、キャッシュ等に利用しています。つまり、これらの文が増えるほどイメージ内にレイヤーが作られるため、イメージサイズが大きくなってしまいます。各コマンドを &&
で繋げて1つのRUN文内に押し込めることで、余計なレイヤーが作られるのを防ぐことができます。
...というはずだったんだすが、この方法を使って書き直したDockerfileをビルドしてみたところ、イメージサイズは先程の方法と同じ734MBでした。おそらく、もっとビルド手順が多い場合にある程度効果が出てくると思われます。
FROM openjdk:8-jdk-alpine
ENV SPIGOT_VER 1.12.2
WORKDIR /minecraft
RUN apk --no-cache add git && \
wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar && \
java -jar BuildTools.jar –rev $SPIGOT_VER && \
mkdir /data && \
echo "eula=true" > /data/eula.txt
WORKDIR /data
EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot-${SPIGOT_VER}.jar nogui
マルチステージビルドを使う
今回のようにJavaソフトウェアのビルドを行う場合、ビルド時にはJDKが必要ですが、実行イメージにはJREだけ入っていれば十分です。つまり、ビルドの成果物だけをJREが入ったコンテナにコピーすればいいわけですが、これはDocker 17.05から追加されたマルチステージビルドを使うことで簡単に実現することができます。
ちなみに、マルチステージビルドを使えばビルド用のコンテナでどれだけレイヤーが作られても最終的な結果には影響がないため、先程のRUNをまとめるテクニックは使う必要がありません。
マルチステージビルドを使って書き直したDockerfileをビルドしてみたところ、イメージサイズは126MBになりました。マルチステージビルドすごい!!!
FROM openjdk:8-jdk-alpine AS build-env
ENV SPIGOT_VER 1.12.2
WORKDIR /build
RUN apk --no-cache add git
RUN wget "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar" -O BuildTools.jar
RUN java -jar BuildTools.jar –rev $SPIGOT_VER
RUN mkdir minecraft
RUN cp spigot-${SPIGOT_VER}.jar minecraft/spigot.jar
RUN mkdir data
RUN echo "eula=true" > data/eula.txt
FROM openjdk:8-jre-alpine
WORKDIR /data
COPY --from=build-env /build/minecraft /minecraft
COPY --from=build-env /build/data /data
EXPOSE 25565
ENTRYPOINT java -jar /minecraft/spigot.jar nogui
まとめ
最終的にDockerイメージのサイズを1.24GBから126MBと大幅に小さくすることができました。同じような機能を持つDockerイメージでも、ちょっとした工夫によって劇的に軽量化できるので、ぜひ皆さん参考にしてみてください。