share facebook facebook twitter menu hatena pocket slack

2019.04.22 MON

Dockerイメージの軽量化

田中 祐介

WRITTEN BY 田中 祐介

久しぶりに東京都リーグのサッカーに参戦して、3日間筋肉痛の streampack の Tana です。

概要

アプリケーションのコンテナ対応を行う際にこのような経験される方が多いかと思います。

Dockerイメージサイズが大きくなると、Docker Hub や Amazon ECR などのプライベートレポジトリに
push/pull する際に時間がかかり、サービス反映に時間がかかったり待たされるのでストレスになります。
よって、少しでもサイズを小さく軽量化しセキュリティリスクを軽減するための一つ方法です。

軽量化するには、

がキーポイントになるかと思ってます。

例えば、オフィシャル版と Alpine と比較すると 1/10 ぐらいサイズが異なります。

nodeの場合

  • node:8 -> 670MB
  • node:8-alpine -> 65MB

rubyの場合

  • ruby:2.4 -> 684MB
  • ruby:2.4-alpine -> 61.7MB

Rails のための Dockerfile 例

では、 Dockerfile をステップバイステップで見ていきましょう。

最初に、Ruby on Rails の場合は、Gem install によるパッケージなどのインストールが必要なので、
それに伴って必要なパッケージを apk を使ってインストールします。
今回のケースでは yarn を使っているとし、すでに node がインストールされている ruby-2.6 を指定してます。 必要に応じて gem install で必要なパッケージを追加してください。例えば、mysql-dev などなど。

ファーストステージ(Builder)

FROM ruby:2.5.0-alpine as Builder

RUN apk add --update --no-cache \
    build-base \
    openssl \
    git

次は、 bundle install です。
-j4 は同時に4つダウンロード&インストール実施してくれるので、少しでも時間を短縮したい場合に便利です。 また、終わった後に不要な cache, コンパイル用ファイルは削除します

RUN gem install --no-document bundler
RUN bundle install -j4 --retry 3 \
      && rm -rf /usr/local/bundle/cache/*.gem \
      && find /usr/local/bundle/gems/ -name "*.c" -delete \
      && find /usr/local/bundle/gems/ -name "*.o" -delete

あとは、yarn install などを実施して、ファーストステップとしては終了です。
rails assets::precompile などは時間を要するので、Dockerfile では指定せず、別タスクとして事前に実施することをオススメします。

# Install yarn packages
COPY package.json yarn.lock /app/
RUN yarn install

# Add the Rails app
ADD . /app

ファイナルステージ

ここでは、実際にアプリケーションを動かすのに必要なバージョンを From にて指定します。
また、ここでは必要最低限のパッケージしかインストールしないのが特徴です。
余計なパッケージを入れると、サイズが大きくなり、またセキュリティリスクも高まります。

# -----ファイナルステージ -----
FROM ruby:2.4.1-alpine

RUN apk add --update --no-cache \
    tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

ここでは仮で tester というユーザーを作成してます。
アプリを動かすのには、root 権限は不要なためです。

# Add user
RUN addgroup -g 1000 -S tester \
 && adduser -u 1000 -S tester -G tester

ファーストステージにてインストールした gem ライブラリや Railsソースコードをコピーします。
権限も chown で書き換えることを忘れずに。

COPY --from=Builder /usr/local/bundle/ /usr/local/bundle/
COPY --from=Builder --chown=tester:tester /app /app

最後は Rails(pumaサーバ)を起動させて終了です。

USER tester
WORKDIR /app

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

アプリケーションにとっては不要なものは入っていないので、Rails console などは動きませんのでその点注意が必要です。

おまけ

Golang の場合だともっとシンプルですね。

# --- ビルドステージ ---
FROM golang:alpine AS Builder
RUN apk update && apk add ca-certificates git

WORKDIF /app
ADD . /app

# mod を使った場合のパッケージダウンロード
COPY ./go.mod ./go.sum ./
RUN go mod download
RUN cd /app && go build -o goapp

# --- ファイナルステージ ---
FROM alpine
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*

WORKDIR /app
COPY --from=Builder /app/goapp /app
ENTRYPOINT ./goapp

結論

以前は 1Gほどなってたイメージサイズが 400MB ぐらいになり、ECR からの pull/push が以前より早くなり快適になりました。Golang の場合だと、20MB 程度なのでさらに快適でした。シンプル・軽量・かつ早い・・よりコンテナに向いているのが理解できますね。

Multi-stage builders を学習するに当たってこちらの YouTube がとてもイメージしやすく参考になりました。
https://www.youtube.com/watch?v=wGz_cbtCiEA

では、良いコンテナライフを。

元記事はこちら

Dockerイメージの軽量化

田中 祐介

田中 祐介

動画おじさん

cloudpack

cloudpackは、Amazon EC2やAmazon S3をはじめとするAWSの各種プロダクトを利用する際の、導入・設計から運用保守を含んだフルマネージドのサービスを提供し、バックアップや24時間365日の監視/障害対応、技術的な問い合わせに対するサポートなどを行っております。
AWS上のインフラ構築およびAWSを活用したシステム開発など、案件のご相談はcloudpack.jpよりご連絡ください。