圧倒的軽量化!? slimイメージを使ってみる

# はじめに
こんにちは。[@canon1ky](https://twitter.com/canon1ky)です。
本記事は[Docker Advent Calendar 2019](https://qiita.com/advent-calendar/2019/docker)、７日目の記事です。

最近Dockerを使っていて、**当たり前だけどやっていなかった圧倒的軽量化** を行って幸せになれたのでこの記事を書かせていただきます。
Dockerを使うすべての人が少しでも「やったぜ！！！」ってなってくれると嬉しいです。

知っている人は知っているかと思いますが、知らない人は是非見ていってください！

# 軽量化やってますか？
Dockerコンテナの軽量化やってますか？？
既に「当たり前でしょ！！軽量化は基本中の基本！！」という人もいれば、「とりあえず使ってはいたけどあまり軽量化は意識したことなかったな〜」という人もいるでしょう。

私は「軽量化をしていたけど、そこまでガツンと軽量にはできていなかった(涙目)」民でした。

# コンテナイメージサイズが大きいことによる弊害
主に下記のような現象が発生します。

* イメージのビルド時間が長い
* イメージをDocker Registoryにプッシュする時間が長い
* イメージをPullする時間が長い

それらが起因して、下記の弊害が起こります。

* トライアンドエラーに時間がかかり、生産性が低下
* ビルド時間、CI時間の増大
* オートスケールでコンテナがサービスインされるまでの時間が長くなる
* Kubernetesクラスタを構成するNodeディスクの消費

# 軽量化のアプローチ
私が主にやっていたのは下記のアプローチです。

## 1. RUN命令をまとめる
おそらく基本中の基本でしょう。
例えば下記の、Node.jsでGulpを使うコンテナを構築するDockerfile。

```Dockerfile
FROM node:10.17.0

# setup gulp
RUN npm install -g gulp@3.9.1
RUN npm install gulp-load-plugins 
RUN npm install gulp-plumber
RUN npm install gulp-sass
RUN npm install gulp-pleeease 
RUN npm install gulp-uglify
RUN npm install gulp-rename
RUN npm init -y
```

非常に多くのRUNコマンドが並んでいます。
RUN命令が走る度にイメージレイヤーが生成されてしまうので、ちょっと重くなります。

この例だと下記のように、RUNコマンドのレイヤーを1つにまとめられます。

```Dockerfile
FROM node:10.17.0

# setup gulp
# レイヤーが1つになる
RUN npm install -g gulp@3.9.1 \
    && npm install gulp-load-plugins \
    && npm install gulp-plumber \
    && npm install gulp-sass \
    && npm install gulp-pleeease \
    && npm install gulp-uglify \
    && npm install gulp-rename \
    && npm init -y
```
なお、さらに下記のようにできると良さそうです。

```Dockerfile
FROM node:10.17.0

# setup gulp
# npm installの後のパッケージを羅列
RUN npm install -g gulp@3.9.1 \
    && npm install gulp-load-plugins \
    gulp-plumber \
    gulp-sass \
    gulp-pleeease \
    gulp-uglify \
    gulp-rename \
    && npm init -y
```

このように、複数のRUNコマンドを「連続で実行する1つのコマンド」として扱うことで、**イメージレイヤーをまとめて軽量化**できます。

※なお当方は、Node.js初心者であるためnpmに関する使い方で気になるところがあれば優しく指摘していただけると助かります（）

## 2. いらないものを消す
例えば下記の、Webサーバー用PythonイメージをビルドするDockerfile。

```Dockerfile
FROM python:3.7.5
ENV PYTHONUNBUFFERED 1

# ↓ここに注目
RUN apt-get update && \
    apt-get install -y default-libmysqlclient-dev && \
    mkdir /code

WORKDIR /code
COPY . ./
RUN pip install --upgrade pip \
    && pip install -r requirements.txt
```

1つ目のRUNコマンドでmysqlclientのDebianパッケージを`apt-get`でインストールしています。
この場合、**インストール時に使ったaptキャッシュ（ゴミファイル）**が残ってしまうので、下記のようにキャッシュを削除できます。

```Dockerfile
FROM python:3.7.5
ENV PYTHONUNBUFFERED 1

RUN apt-get update && \
    apt-get install -y default-libmysqlclient-dev && \
    apt-get clean && \                # ここと
    rm -rf /var/lib/opt/lists/* && \  # ここでaptキャッシュを消している
    mkdir /code 

WORKDIR /code
COPY . ./
RUN pip install --upgrade pip \
    && pip install -r requirements.txt
```

こんな感じで、キャッシュなどの不要なファイルを消すことで軽量化できます。

## しかし
軽くなっても数MB程度で、ほぼほぼ軽量化の実感は湧きませんでした。
先ほど挙げたNode.jsを使う下記のDockerfile。

```Dockerfile
FROM node:10.17.0

# setup gulp
RUN npm install -g gulp@3.9.1 \
    && npm install gulp-load-plugins \
    gulp-plumber \
    gulp-sass \
    gulp-pleeease \
    gulp-uglify \
    gulp-rename \
    && npm init -y
```

このシンプルさを持っても、イメージサイズは980MB...

```shell
$ docker build . -t gulp-image-1
Sending build context to Docker daemon  2.048kB
...

$ docker images
REPOSITORY     TAG      IMAGE ID      CREATED         SIZE
gulp-image-1   latest   ee9ad2ee2e6b  2 minutes ago   980MB
node           10.17.0  d5680e53a228  9 days ago      903MB
...
```

こんなので1GB近くも使ってるなんてやってられるか！！！！
うーん、もっと軽くできないか...

# 改善していく
## よく見てみると
ん...？ちょっと待てよ？？？
さっきのimage一覧をもう一度。

```shell
$ docker build . -t gulp-image-1
Sending build context to Docker daemon  2.048kB
...

$ docker images
REPOSITORY     TAG      IMAGE ID      CREATED         SIZE
gulp-image-1   latest   ee9ad2ee2e6b  2 minutes ago   980MB
node           10.17.0  d5680e53a228  9 days ago      903MB # ←これ
...
```

そもそも**ベースにしているnodeイメージがデカくね？？？**
もっと軽いNode.jsイメージないのか？

## もっと軽いやつがあった
[DockerHub](https://hub.docker.com/_/node)を探してみる。
バージョンはNode.jsの10.17.0を使いたいのでそいつを...

おっ！！
![スクリーンショット 2019-12-02 23.50.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154121/28b2ceea-8619-5f80-c38e-f16e869d79ff.png)

イメージ名は`10.17.0-stretch-slim`。
[Nodeイメージのページ](https://hub.docker.com/_/node)の下の方にバッチリ説明がある。

> node:\<version>-slim
This image does not contain the common packages contained in the default tag and only contains the minimal packages needed to run node. Unless you are working in an environment where only the node image will be deployed and you have space constraints, we highly recommend using the default image of this repository.

「通常のnodeイメージに入っている一般的なパッケージは入っておらず、nodeを走らせるための最小限のパッケージのみが入っている。nodeイメージのみがデプロイされ、容量に制約がある環境で作業している場合を除き、このリポジトリのデフォルトイメージを使用することを強くお勧めします。」

つまるところ、**いらない物全部抜きで最小限の物で構築されているからこれを使った方が良いよ！**ということ。

## 試しにビルドしてみる
下記のように書き換える。

```Dockerfile
# stretch-slimイメージに変更
FROM node:10.17.0-stretch-slim

# setup gulp
RUN npm install -g gulp@3.9.1 \
    && npm install gulp-load-plugins \
    gulp-plumber \
    gulp-sass \
    gulp-pleeease \
    gulp-uglify \
    gulp-rename \
    && npm init -y
```

そしてビルド。

```shell
$ docker build . -t gulp-image-2
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM node:10.17.0-stretch-slim
...

$ docker images
REPOSITORY     TAG                   IMAGE ID      CREATED         SIZE
gulp-image-2   latest                77cd2cef489d  2 minutes ago   225MB # 新
gulp-image-1   latest                ee9ad2ee2e6b  24 minutes ago  980MB # 旧
node           10.17.0               d5680e53a228  9 days ago      903MB # 旧
node           10.17.0-stretch-slim  7a5e19754b85  5 weeks ago     148MB # 新
```

980MB→225MB!!!
イメージサイズが**4分の1以下**になりました。やったぜ。

slimイメージが軽いというか、デフォルトのNodeイメージって本当にいろんな物が入ってたんですね...

# まとめ
今回はslimイメージを使ったコンテナ軽量化の紹介をしました。
Node.jsの例の場合の使い分けとしてはいろいろあるかと思いますが、下記のような感じが良いのかなと思いました。

* 「とにかくDockerfileに細かいことを書かず、一通りツールが揃ったコンテナですぐにNode.jsを使いたい！！」みたいな場合は通常のNode.jsイメージ
* チーム開発や本番環境など、長い目線でコンテナを使用する場合はslimイメージ

今回はNode.jsを例にしましたが、もちろんPythonやPHPなどの他の言語用にもslimイメージは用意されているので、言語に合わせて使い分けてください。

それでは皆さん、来年度も良きDockerライフを！！