Elixirアプリケーションのコンテナをできるだけダイエットした話

公開:2021/01/08
更新:2021/01/08
4 min読了の目安(約4200字TECH技術記事

概要

Elixirで書いたchatops用アプリケーション anagoをGKEで動かそうとdocker buildしたらかなり重めのRails並の容量になってしまったのでどこまでそぎ落とせるか検証した話です。
Qiitaに書いた記事の転載です

FROM elixir:latest

WORKDIR /app
ENV MIX_ENV=prod

RUN mix local.hex --force && \
  mix local.rebar --force

COPY mix.exs mix.exs
COPY mix.lock mix.lock
COPY config config
RUN mix deps.get --only prod && \
  mix deps.compile

COPY . .
CMD ["mix", "run", "--no-halt"]

最初の頃のDockerfileはこんな感じです。
このDockerfileでビルドしたアプリケーションのサイズは下記の通り...

docker images
##
REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
anago                                     latest          aa7e0779d912   2 seconds ago    1.27GB

画像もJSONもDBもない簡単なアプリケーションでのサイズが1.27GBと出てきたらさすがにダイエットを決意するよね...

ベースイメージの確認

Elixirのベースイメージサイズは下記の通り。

docker pull elixir
## docker images
REPOSITORY                                TAG             IMAGE ID       CREATED         SIZE
elixir                                    latest          83478b936eba   2 days ago      1.24GB

比較にGo言語のコンテナを取得してみると...

docker pull golang
### docker images
REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
golang                                    latest          5f9d35ce5cfe   3 weeks ago      839MB

やっぱりファイルサイズはビルドしたライブラリのコード含めても30MB程度。
だけどベースイメージがそもそも1.24GBもある...
比較に取ったGoのイメージと比べても300MB近くElixirのほうが重たい...

alpineにしてみる

イメージサイズのダイエットの入り口はベースイメージをalpineにすることだと思うのでベースイメージを変えてみる。

docker pull elixir:alpine
## docker images
REPOSITORY                                TAG             IMAGE ID       CREATED         SIZE
elixir                                    alpine          dd10458addd0   2 days ago       84.9MB

圧倒的シェイプ!!
これにアプリケーションの30MBを追加しても114MBに!

REPOSITORY                                TAG             IMAGE ID       CREATED              SIZE
anago                                     alpine          c8b8c08b7aef   About a minute ago   114MB

もともとのサイズと比較すると10%未満にダイエット成功!!
もうこれで良いんじゃない?

escript、動きます

Erlangにはアプリケーションを一つの実行ファイルに固めるescriptという仕組みがあります。
実行ファイルはErlangのランタイムがある環境でなら実行可能だったりします。
ということはErlang:alpineのイメージで動くんじゃない?
さっそくErlang:alpineのベースイメージを手に入れます。

docker pull erlang:alpine
## docker images

REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
erlang                                    alpine          daa6bbdd7458   2 weeks ago      69.4MB

ElixirはErlang上で動くためErlangのベースイメージのほうが軽量だったりします。
15MB程度ですが軽量ですね。
これで行きましょう!!

ということでDockerfileに mix escript buildを追加。
ついでにソースコードも要らないのでマルチステージビルドもやりましょう。

FROM elixir:alpine as build

WORKDIR /app
ENV MIX_ENV=prod

RUN mix local.hex --force && \
  mix local.rebar --force

COPY mix.exs mix.exs
COPY mix.lock mix.lock
COPY config config
RUN mix deps.get --only prod && \
  mix deps.compile

COPY . .

RUN mix escript.build

FROM erlang:alpine

WORKDIR /app
COPY --from=build /app/anago /app/anago

これでヨシ!
あらためで docker buildしてサイズを見てみると :eyes:

REPOSITORY                                TAG             IMAGE ID       CREATED          SIZE
anago                                     erlang_alpine   a01f810d11dc   2 seconds ago    71.4MB

71.4MBまで軽量化に成功!!!
ついに100MBの壁を突破しましたよ!!


閑話休題

このアプリケーションはもともと mix run --no-haltで動かすつもりだったのですがescriptにしたら即終了されてしまいます。
そういう時は timer.sleep(:infinity)と書いておくとずっと動いてたりします。

defmodule Anago.CLI do
  def main(_args) do
    :timer.sleep(:infinity)
  end
end

どこまで減らせる?

erlang:alpineだって最低限何らか一緒に入っているだろうとは思うのでalpineに apk add erlangしてどこまで減るのか気になってきました。
バニラalpineのサイズはわずかに5MBです。
試しにalpineにerlangだけインストールするDockerfileを作ってみます。

FROM alpine:latest
RUN apk add --no-cache erlang

このDockerfileをビルドすると結果は 63.6MBまで軽量化されます!(Erlangが58MBくらいの容量になるのでこれ以上のダイエットは無理)
ここまで減らしちゃうとちゃんと動くのかが不安になりますがここまで来たら目標もどこまでサイズを削ぎおとせるかにシフトしているのでアプリケーションを含めてビルドしてみます。

REPOSITORY                                TAG             IMAGE ID       CREATED             SIZE
anago                                     custom_alpine   fb1de28697a1   9 minutes ago       65.6MB

65.6MBまで来ました :tada:

あとがき

ちなみにこのイメージもちゃんと実行できました(すごい...)
Erlangのサイズの限界でおそらくこれ以上の軽量化はできないと思うんで軽量化の限界は63.6MB + escriptとなるようです。