RailsのDockerイメージを小さくしたい
- Docker imageを小さくする
- RailsのDockerイメージ
- 現状のDockerイメージ
- rbenvのinstallとrubyのinstallが無駄ではないか?
- ruby-alpineに変えてみる
- 結局何がサイズを大きくしてんの??
- Multi-Stage Buildsを使おう
- OSを変える
- いらなそうなファイルがないか探す
- とても大事なことに最後に気づく、、、
- まとめ
- ちなみに
Docker imageを小さくする
Dockerイメージは小さいほど良いです。これは自明です。
プログラムを動かすための必要最低限の実行ファイルがあれば良いのです。
RailsのDockerイメージ
そもそもバイナリだけで動くgoみたいな言語と違って、イメージが大きくなりがちなんですが
それでも多少は頑張りたいと思い試行錯誤してみる。
現状のDockerイメージ
Railsに関係する部分だけが削減対象かというとそうでもなく、
Dockerを導入し始めたばかりですのでイメージの最小化まで手がつけられていなかったのが現実で、基本的なところから色々と試して見ます。
今使っているDockerfileは恥ずかしながら、ansibleで実行しているコマンドをそのまま置き換えただけのものなので、インストーラなど実行ファイル以外のものがたくさん含まれています。
ubuntuのbase imageに
- apt-getで大量のパッケージを入れ
- rbenvをinstallし
- rubyの特定バージョンをinstallして
- bundle installする
という感じ。
一部省略していますが、Dockerfileは以下のようになっています。
/appというディレクトリの下に、sample(仮名)というソースを配置しています。
FROM ubuntu:trusty RUN apt-get update && \ apt-get -y install git curl make openssl zlib1g-dev libssl-dev libreadline-dev sysv-rc-conf build-essential mysql-client libmysqlclient-dev && \ mkdir -p /app && \ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv && \ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build && \ echo 'export PATH="~/.rbenv/bin:$PATH"' >> ~/.bashrc && \ echo 'eval "$(rbenv init -)"' >> ~/.bashrc && \ CONFIGURE_OPTS='--disable-install-rdoc' /root/.rbenv/bin/rbenv install 2.3.1 && \ /root/.rbenv/bin/rbenv global 2.3.1 ADD . /app/sample RUN /root/.rbenv/shims/gem install bundle && \ cd /app/sample && /root/.rbenv/shims/bundle install
なんとイメージサイズは892MBもありました。
rbenvのinstallとrubyのinstallが無駄ではないか?
そもそも真面目にDockerfile内でinstallしていることに疑問を持ち(インストール用に余計にパッケージが必要)、dockerhubからrubyのbaseimageを使ってみることにしました。
rubyが既に入っているので、それを入れるために必要だったのもを全て排除して見た結果のDockerfileがこちら。
FROM ruby:2.3.1 RUN mkdir -p /app ADD . /app/sample RUN cd /app/sample && /usr/local/bin/bundle install
なんとサイズが1.05GBGBに!なぜ増える!
ruby-alpineに変えてみる
よくよくみると、rubyのimage sizeが730MBもあることに気づきます。
結局rubyをinstallするために色々とパッケージを入れている模様。
ということで、127MBしかないruby-alpineを使ってみることに。
以下がDockerfile。alpineなので多少必要なパッケージを追加で入れています。
FROM ruby:2.3.1-alpine RUN apk add --update\ build-base \ git \ openssh \ mysql-dev \ linux-headers \ && \ mkdir -p /app RUN mkdir -p /app ADD . /app/sample RUN cd /app/sample && /usr/local/bin/bundle install
そしてイメージサイズは、857MB、、、あれ、、、、
結局何がサイズを大きくしてんの??
ruby:2.3.1-alpineのサイズ : 127MB
apk add した後 545MB
sample app ソースを追加した後 563MB
bundle install後 857MB
apk add と bundle installが大きい。まあ予想通り。
Multi-Stage Buildsを使おう
本題はここから。
Multi-Stage Buildsは、簡単にいうと、ビルド用のイメージを作った後に必要なファイルだけコピーして新しいデプロイ用などのイメージを作成できる機能です。
注) 諸々必要なものをinstallすると、ubuntuとalpineに大差がないと判明したので以後は何かと都合の良いubuntuで話を進めることに。
まず試したのは、今までのbuildイメージを先に作ってbundle installまでした後に、アプリのソース(/app以下)とruby(/root以下)のライブラリだけをコピーして来るパターン。
注2) Dockerfileを簡単にするために/rootにrbenv入れてますが本来は別ユーザー作ってそっちに入れた方がいいと思います。
FROM ubuntu:trusty as build-env RUN apt-get update && \ apt-get -y install git curl make openssl zlib1g-dev libssl-dev libreadline-dev sysv-rc-conf build-essential mysql-client libmysqlclient-dev && \ mkdir -p /app && \ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv && \ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build && \ echo 'export PATH="~/.rbenv/bin:$PATH"' >> ~/.bashrc && \ echo 'eval "$(rbenv init -)"' >> ~/.bashrc && \ CONFIGURE_OPTS='--disable-install-rdoc' /root/.rbenv/bin/rbenv install 2.3.1 && \ /root/.rbenv/bin/rbenv global 2.3.1 ADD . /app/sample RUN echo 'gem: --no-rdoc --no-ri' > /root/.gemrc && \ /root/.rbenv/shims/gem install bundle && \ cd /app/sample && /root/.rbenv/shims/bundle install FROM ubuntu:trusty RUN apt-get update && \ apt-get -y install mysql-client COPY --from=build-env /root /root COPY --from=build-env /app /app
mysql-clientだけ必要だったのでそれは追加。
これでイメージサイズが727MBに!
元々が892MBだったので165Mも削減!
こっそり.gemrc追加しましたが、これは1MBくらいしか削減効果ありませんでした。
OSを変える
Dockerfileを眺めていてふと気づく。
ubuntuが古い。
試しに、ubuntu:zestyをdocker pullしてみると、trustyよりもサイズが小さい!
昔誰かに、ubuntuを使っているならとりあえずdebianに変えて見たらdockerイメージが小さくなるよって言われたのを思い出しましたが、
debianのイメージサイズよりもubuntu:zestyの方が小さいようです。
ということで書き換え。
FROM ubuntu:zesty as build-env RUN apt-get update && \ apt-get -y install git curl make openssl zlib1g-dev libssl-dev libreadline-dev build-essential mysql-client libmysqlclient-dev && \ mkdir -p /app && \ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv && \ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build && \ echo 'export PATH="~/.rbenv/bin:$PATH"' >> ~/.bashrc && \ echo 'eval "$(rbenv init -)"' >> ~/.bashrc && \ CONFIGURE_OPTS='--disable-install-rdoc' /root/.rbenv/bin/rbenv install 2.3.1 && \ /root/.rbenv/bin/rbenv global 2.3.1 ADD . /app/sample RUN echo 'gem: --no-rdoc --no-ri' > /root/.gemrc && \ /root/.rbenv/shims/gem install bundle && \ cd /app/sample && /root/.rbenv/shims/bundle install FROM ubuntu:zesty RUN apt-get update && \ apt-get -y install tzdata openssl libmysqlclient-dev COPY --from=build-env /root /root COPY --from=build-env /app /app
多少必要なパッケージが変わったのでapt-getの部分を少し修正。
イメージサイズは614MBに!
OSを変えるだけでこんなに変わるとは、、、
いらなそうなファイルがないか探す
あんまりなさそうだったが、この辺は要らないので Dockerfileの最後にこれを追加。
bundle install後にできるgemsの中身はもっとせめて消せそうなものがありそうだけど、攻めすぎて壊しそうなので一旦断念。
c extensionを含むgemが割とサイズが大きそうで、実行ファイル以外消したらもうちょと小さくなりそうだけど、ROI低そうな感じがした。
RUN rm -fr /var/lib/apt/lists/*.lz4 && \ rm -fr ~/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/cache/*
これを消してもそこまで小さくならず、613MBに。
とても大事なことに最後に気づく、、、
配布用のイメージなので、bundle installは --without development test をつけてあげないとダメでした。。。
ということで最終的には、547MBになりました。
gemの削減が一番効果あるな、これは、、、
892MB => 547MB なので 345MBの削減になりました!
まとめ
- Multi-Stage Buildsは使った方がいい
- alpineとかじゃなくてもOS変えるだけでイメージ小さくなることがある
- 使わないgemとかバンバン消していきましょう!