Dockerコンテナイメージのダイエット - Laravel編

2017-12-13
PHP
Docker
Laravel

これはPHPアドベントカレンダー2017 10日目の記事です。

あまりの寒さに、毎日背中にホッカイロを貼り付けてます。hanhan1978です。

コンテナでの開発を続けていると、最初は開発出来たこと自体が嬉しくて、次はDockerfileの中身を最適化してビルド順を最適化してなるべくビルド時間が短くなるように工夫していきます。

ただ、工夫はDockerイメージのレイヤを複雑に重ねてしまうので、コンテナイメージ自体は大きくなってしまいます。今回はPHPのフレームワークであるLaravelのアプリケーションを例にとって、メンテナンス可能でかつ、コンテナイメージを小さくする方法について紹介します。

結果から

気の早い人のために、まず結果をのせておきます。

概要 イメージサイズ Dockerfile
1. 普通のLaravelコンテナ 300MB v0.0.1
2. dockerignore対応 352MB v0.0.2
3. マルチステージビルド 121MB v0.0.3
4. マルチステージビルド - Nginxベース 63.8MB v0.0.4

2は増えてるじゃん!という気もしますが、最終的に63.8MBまで小さくすることが出来ました。詳細については、下の方を読んで見て下さい。

この記事で目指すこと

本ポストでは、メンテナンサビリティを考慮して、PHP及び、Nginxの公式コンテナイメージをベースにしつつ、極力小さいコンテナイメージを作る方法について解説します。

世の中には、専用のベースイメージを作って、とにかく小さいPHPコンテナを作ろうとしている方々もいます。それはそれで素晴らしい取組とは思うのですが、アプリケーションエンジニアであれば、ベースイメージよりもアプリケーションの構築に力を注ぎたい所です。

※PHPのミニマムイメージを作っている方のリポジトリ
GitHub - nubs/docker-php-minimal: A collection of Dockerfiles for different versions of a very barebones PHP.

なお、コンテナのサイズの大小がどうやって決まるのかについては、下記の記事が優しく解説しています。

コンテナの中身を見てみよう

ベースイメージのサイズ

まず、今回使う公式PHPイメージがベースにしているAlpine Linuxのコンテナイメージサイズを計ります。

1
2
3
$ docker images alpine:3.6
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine 3.6 e2cd449cde75 7 days ago 3.97MB

3.97MBでした。Alpine Linuxは必要最低限の機能のみを収めたコンテナイメージですので、ここがコンテナイメージの最小値と考えてよいと思います。

次に、PHPコンテナ開発のベースイメージとして、php:7.2-fpm-alpineのサイズを確認します。

1
2
3
$ docker images php:7.2-fpm-alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
php 7.2-fpm-alpine ed78d639c8b0 7 days ago 76.6MB

76.6MBです。Alpine Linuxのサイズを下回るのは大変そうですが、今回の目標としてとりあえず80MB程度を目安にします。

1. 普通のLaravelコンテナ

初期状態のDockerfile - v0.0.1

リンク先のDockerfileは、特に何の考慮もなく 7.2-fpm-alpineのイメージをベースにして、必要なライブラリをどんどんインストールした状態です。

普通にDockerでPHPアプリを開発し始めた場合、こんなDockerfileになるんじゃないかなと思います。

イメージサイズ

1
2
3
$ docker images hanhan1978/docker-laravel55-skelton:0.0.1
REPOSITORY TAG IMAGE ID CREATED SIZE
hanhan1978/docker-laravel55-skelton 0.0.1 2bbd8059a0c8 9 minutes ago 300MB

見ての通りで、300MBもあります。地球に優しくないサイズです。CD1枚分に到達しようかという勢い。さっそくダイエットしていきましょう。

2. dockerignoreの設定

Dockerfile - v0.0.2

必ずしもダイエットにつながるとは限らないのですが、.dockerignoreを設定します。

これにより、ローカルに持っている余分なディレクトリをビルド時にコンテナ側に送信しなくなるので、少し省スペース化が期待できます。また、開発でしか必要としないライブラリとかもついでに削りましょう。

こんな内容で.dockerignoreファイルを追加します。

1
2
laravel/vendor
laravel/node_modules

composerのライブラリ群と、Laravel Mix用のnode_modulesを除外対象にしています。

イメージサイズ

1
2
3
$ docker images hanhan1978/docker-laravel55-skelton:0.0.2
REPOSITORY TAG IMAGE ID CREATED SIZE
hanhan1978/docker-laravel55-skelton 0.0.2 f0205d6f7bd6 36 hours ago 352MB

ふ、増えてやがる…

予想外に成長してしまいました。これ結構あるあるで、ローカルのファイル群のが実は容量が小さかったり、インストールの段階でネットワーク不調でcomposerがソース落とし直したりされたりで、結構変動します。

.dockerignoreを追加すること自体は、アプリケーションとして正しいことではあると思うので、しょうがないので突き進みます。

3. マルチステージビルド

Use multi-stage builds | Docker Documentation

Dockerでは、version17.05以降でマルチステージビルドが使えます。要するにビルドで使うコンテナと、最終的な実行可能ファイルだけを詰め込んだコンテナを分けて使うことが出来ます。

まずLaravelアプリケーションの最終実行イメージにおいて、node_modulesは完全に邪魔です。必要なのは、npm run productionで出力される Javascriptやcssのファイルだけです。

そこで、マルチステージビルドを利用して、node系のモジュール群をコンテナから追い出してしまいます。

Dockerfile - v0.0.3

中身を見てもらうとわかりますが、ビルドが二段階に別れています。一つ目は公式npmコンテナをベースイメージにして、Laravel Mixを実行しています。

抜粋 npmの部分

1
2
3
4
5
6
7
8
9
10
11
FROM node:9.2

COPY laravel /var/laravel

WORKDIR /var/laravel

RUN npm install \
&& npm rebuild node-sass \
&& npm run production

RUN rm -rf ./node_modules

抜粋 次のコンテナで0番目のコンテナからファイルをコピー

1
COPY --from=0 /var/laravel /var/www/laravel

この書き方をすることで、Laravel Mix実行後のアプリケーションディレクトリをコピーしてきます。--from=0と書いてありますが、別名をつけることも可能です。詳しくは公式ドキュメントをどうぞ。

イメージサイズ

1
2
3
$ docker images hanhan1978/docker-laravel55-skelton:0.0.3
REPOSITORY TAG IMAGE ID CREATED SIZE
hanhan1978/docker-laravel55-skelton 0.0.3 d4952dce941a About a minute ago 121MB

ああ、もうこれで終わりでいいんじゃないかというくらい小さくなりました。

121MBですから、当初の1/3くらいです。流石マルチステージビルドです。最高!

しかし、まだ何かモニョリます。100MBを下回りたいというのが一つと、HTTPサーバとしてNginxをインストールしているのですが、phpのベースイメージ内にNginxをインストールするのはなんか嫌だなと。

そこで、マルチステージをもう一つ推し進めて、必要最低限のPHPの実行ファイル群だけを公式Nginxコンテナにコピーすることで、もう一つイメージを小さく出来ないか試します。

4. マルチステージビルド - Nginxベース

Dockerfile - v0.0.4

1
2
3
$ docker images nginx:1.13-alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx 1.13-alpine 22f5726c6dc0 9 days ago 15.5MB

nginxのベースイメージは15.5MB

ここにPHPの必要最低限の実行ファイルをコピーします。
ついでと言っては何ですが、vendor配下のtest系のスクリプトも全部削除しました。本番で必要な物以外は全て消しましょう。

イメージサイズ

1
2
3
$ docker images hanhan1978/docker-laravel55-skelton:0.0.4
REPOSITORY TAG IMAGE ID CREATED SIZE
hanhan1978/docker-laravel55-skelton 0.0.4 635393221d40 About a minute ago 63.8MB

最終的に63.8MBまで減らすことが出来ました。PHPの最低限の実行ファイルのみなので、後はアプリケーションのコードを減らす以外に無いかな…

最初の1/6くらいにはなりましたので、及第点というところでしょうか。

コンテナイメージが重いよ〜とお困りの方は、是非マルチステージビルドを有効活用して、コンテナのダイエットをされたらと思います。