distrolessイメージを使って、ランタイムDockerイメージを作ってみる

# distrolessとは？

Googleが公開している、**ランタイム用**のDockerイメージです。

https://github.com/GoogleContainerTools/distroless

# なぜ使うか？

Dockerはプロダクション環境にも導入されるようになりました。

プロダクション環境では脆弱性対応などをする必要があります。
デプロイされるDockerイメージをアプリケーション実行に必要なものだけに制限することで、このような間接的な作業を減らすことが可能です。

`distroless`イメージはこのような要望に対応した、アプリケーションランタイムのみに特化したDockerイメージです。

実際にイメージに入っているのは、`gcr.io/distroless/base`の場合、

- glibc
- libssl
- openssl
- ca-certificates
- A /etc/passwd entry for a root user
- A /tmp directory

のみです。

nodejsイメージなどは、上記に追加で各ランタイム用のバイナリが含まれるようです。

# 使ってみる

## distrolessイメージを使ったランタイムイメージを作成する

https://github.com/GoogleContainerTools/distroless
のexampleを元にnodejsで試してみました。

以下のような `Dockerfile` が格納されています。

```Dockerfile
FROM node:8.9.1 AS build-env
ADD . /app
WORKDIR /app

FROM gcr.io/distroless/nodejs
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.js"]
```

ビルドステージ（最初3行）とそのアウトプットを `distroless`イメージにコピーするステージ（最後の4行）に分かれています。

※`distroless`イメージの話というよりマルチステージビルドの話になりますが、この記述の意図は、「アプリのビルド時だけに必要な依存パッケージは最終イメージに持ち込ませない」だと思います。

この`Dockerfile`を使って、
`distroless`イメージをベースとしたDockerイメージをビルドしてみます。

```bash
$ docker build . -t nodejs_distroless                                                                                  水  7/18 22:09:30 2018
Sending build context to Docker daemon  7.168kB
Step 1/7 : FROM node:8.9.1 AS build-env
8.9.1: Pulling from library/node
85b1f47fba49: Pull complete
ba6bd283713a: Pull complete
817c8cd48a09: Pull complete
47cc0ed96dc3: Pull complete
8888adcbd08b: Pull complete
6f2de60646b9: Pull complete
51fa8867e10f: Pull complete
3de546fb9d8f: Pull complete
Digest: sha256:552348163f074034ae75643c01e0ba301af936a898d778bb4fc16062917d0430
Status: Downloaded newer image for node:8.9.1
 ---> 1934b0b038d1
Step 2/7 : ADD . /app
 ---> edaf827024d3
Step 3/7 : WORKDIR /app
Removing intermediate container 1712ca1226a1
 ---> 26d537ccf985
Step 4/7 : FROM gcr.io/distroless/nodejs
latest: Pulling from distroless/nodejs
57752e7f9593: Pull complete
ba7c544469e5: Pull complete
fe1a8dd285c0: Pull complete
Digest: sha256:ed997b713028c133799c41c2401e5d77ba1973876e92c55d1d67b6a0b68e9c29
Status: Downloaded newer image for gcr.io/distroless/nodejs:latest
 ---> a853ae078c7b
Step 5/7 : COPY --from=build-env /app /app
 ---> b3c68eb49cc5
Step 6/7 : WORKDIR /app
Removing intermediate container 79aa5baa396d
 ---> b6f4f9fd66d3
Step 7/7 : CMD ["hello.js"]
 ---> Running in 4dfbc310729f
Removing intermediate container 4dfbc310729f
 ---> ccb158736e44
Successfully built ccb158736e44
Successfully tagged nodejs_distroless:latest
```

## 実際どうなのか

### イメージサイズ

`node` Dockerイメージを使ってビルドした結果と比較します。

| ベースのイメージ                 | イメージサイズ |
|:-----------------------------:|:-----------:|
|node:8.9.1                     | 676MB       |
|node:8.9.1-alpine              | 67.7MB      |
|gcr.io/distroless/nodejs:latest| 75.1MB      |

結果的に、alpineの方が容量が少なくなりましたが、
ほとんど同レベルとなっています。

### ログインしようとしてみる

```bash
$ docker run --rm -it --entrypoint /bin/bash nodejs_distroless 
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"/bin/bash\": stat /bin/bash: no such file or directory": unknown.
```

```bash
$ docker run --rm -it --entrypoint /bin/sh nodejs_distroless 
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown.
```

できませんでした。

# まとめ

最初タイトルを「最小のランタイム用」としようと思っていましたが、
nodejsはalpineよりもサイズが大きくなってしまいました笑
（Golangでやってみればよかった）

現状は以下のイメージが公開されています。

- gcr.io/distroless/base (Golangなどバイナリでコンパイルされたものの実行環境)
- gcr.io/distroless/java (openjdk8ベース)
- gcr.io/distroless/cc (Rust, D言語用)
- gcr.io/distroless/python2.7
- gcr.io/distroless/python3
- gcr.io/distroless/nodejs
- gcr.io/distroless/java/jetty
- gcr.io/distroless/dotnet


このうち、base以外はまだ、プロダクションレディのようです。
※java, ccについては、GitレポジトリのReadmeからはどちらかわかりませんでした。。