Minimal Ubuntu の Dockerfile を詳しく調べてみた

Posted on

EC2 向けの Minimal Ubuntu を検証する機会があったので、ついでに Minimal Ubuntu の Docker イメージがどういう Dockerfile から作られているか調べてみました。 Docker Hub に登録されているオフィシャルのイメージは、すでに Minimal Ubuntu に変更されています。

今回調べてみてわかったのですが、Dockerfile の中では Minimal Ubuntu 向けに特別なことはしていません。

Ubuntu 18.04 の Dockerfile

今回は bionic-20180821 のタグで取得できる Ubuntu 18.04 の Dockerfile を調べました。 2018/08/29 時点の latest で参照される Docker イメージです。 Dockerfile は GitHub にあります。

コメントを除いたコードは次のとおりです。

FROM scratch
ADD ubuntu-bionic-core-cloudimg-amd64-root.tar.gz /

RUN set -xe \
    \
    && echo '#!/bin/sh' > /usr/sbin/policy-rc.d \
    && echo 'exit 101' >> /usr/sbin/policy-rc.d \
    && chmod +x /usr/sbin/policy-rc.d \
    \
    && dpkg-divert --local --rename --add /sbin/initctl \
    && cp -a /usr/sbin/policy-rc.d /sbin/initctl \
    && sed -i 's/^exit.*/exit 0/' /sbin/initctl \
    \
    && echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \
    \
    && echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean \
    && echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean \
    && echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean \
    \
    && echo 'Acquire::Languages "none";' > /etc/apt/apt.conf.d/docker-no-languages \
    \
    && echo 'Acquire::GzipIndexes "true"; Acquire::CompressionTypes::Order:: "gz";' > /etc/apt/apt.conf.d/docker-gzip-indexes \
    \
    && echo 'Apt::AutoRemove::SuggestsImportant "false";' > /etc/apt/apt.conf.d/docker-autoremove-suggests

RUN rm -rf /var/lib/apt/lists/*

RUN sed -i 's/^#\s*\(deb.*universe\)$/\1/g' /etc/apt/sources.list

RUN mkdir -p /run/systemd && echo 'docker' > /run/systemd/container

CMD ["/bin/bash"]

調べてみてわかったのですが、スタンダードな Ubuntu と Minimal Ubuntu の大きな違いはデフォルトでインストールされるパッケージの数です。その違いは 2 行目で ADD しているルートファイルシステムで実現されているので、実は RUN で実行しているスクリプトはこれまでと同じです。

ルートファイルシステムの tarball は Canonical 社が公開しています。

RUN コマンドの中身を理解する

4 つある RUN コマンドのうち、1 つ目の長い RUN コマンドから気になるところを理解します。コメントの URL にヒントがあるので参考にします。元になっているのは moby (Docker) リポジトリにある debootstrap というスクリプトです。

それでは順番に見ていきます(読みやすいように &&\ は削りました)。

echo '#!/bin/sh' > /usr/sbin/policy-rc.d
echo 'exit 101' >> /usr/sbin/policy-rc.d
chmod +x /usr/sbin/policy-rc.d

For most Docker users, "apt-get install" only happens during "docker build", where starting services doesn't work and often fails in humorous ways. This prevents those failures by stopping the services from attempting to start.

apt-get install でパッケージをインストールしたときに、自動的に init スクリプトが実行されないように抑制しているようです。終了ステータスの 101invoke-rc.d の man ページに載っていました。

Action not allowed. The requested action will not be performed because of runlevel or local policy constraints.

dpkg-divert --local --rename --add /sbin/initctl
cp -a /usr/sbin/policy-rc.d /sbin/initctl
sed -i 's/^exit.*/exit 0/' /sbin/initctl

こちらは upstart スクリプト向けの処理です。 /usr/sbin/policy-rc.d は存在しなかったのでファイルを作るだけでしたが、 /sbin/initctl はパッケージインストールで上書きされる可能性があるため dpkg-divert で上書きされないようにしています(パッケージがインストールされても該当のファイルは退避される)。 Ubuntu 16.04 から systemd が採用されているので保険的な意味合いだと思います。

echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup

For most Docker users, package installs happen during "docker build", which doesn't survive power loss and gets restarted clean afterwards anyhow, so this minor tweak gives us a nice speedup (much nicer on spinning disks, obviously).

パッケージのインストールを高速化するための設定です。具体的には dpkg のパッケージ展開後に fsync() を呼ばないことで高速化しているようです。

echo 'DPkg::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' > /etc/apt/apt.conf.d/docker-clean
echo 'APT::Update::Post-Invoke { "rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true"; };' >> /etc/apt/apt.conf.d/docker-clean
echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' >> /etc/apt/apt.conf.d/docker-clean

Since for most Docker users, package installs happen in "docker build" steps, they essentially become individual layers due to the way Docker handles layering, especially using CoW filesystems. What this means for us is that the caches that APT keeps end up just wasting space in those layers, making our layers unnecessarily large (especially since we'll normally never use these caches again and will instead just "docker build" again and make a brand new image).

パッケージのインストール後に *.deb*.bin ファイルを削除して Docker イメージが肥大化するのを防いでいます。

まとめ

オフィシャルの Docker イメージに関しては Dockerfile まで確認していませんでしたが、ちゃんと読んでみるとさまざまな工夫がなされていて勉強になりました。

OS のディストリビューションによって Dockerfile でどこまで手を加えるのかまったく違うので、見比べてみるとおもしろいです。ちなみに、Debian の Dockerfile はわずか 3 行でした :-)