できるかなと思ってdistrolessでRubyをやってみたら、できた。とりあえず ruby -v はできます。ruby:2.5.1-slimが178MBのところを53MBにまでできたのでサイズが半分以上減ってる。https://t.co/Me72qjgyg3#container_build
— うなすけ (@yu_suke1994) 2018年10月9日
distroless とは
Googleが公開している、ランタイムとして使用するためのDockerイメージです。
GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.
しかし公式で提供されている言語の中にはRubyが含まれていません。
ならやってみましょう。
distrolessについての参考URL
目標
-
$ ruby -v
ができる - sinatra でコンテナからレスポンスを返すことができる
これを目指してがんばっていきます。
$ ruby -v
ができるまで
突然ですが、 ruby:2.5.3-stretch
というオフィシャルイメージにおける /usr/local/bin/
以下を見てみましょう。
$ docker run --rm -it ruby:2.5.3-stretch bash
root@c494cf51c146:/# ls -al /usr/local/bin/
total 188
drwxrwsr-x 1 root staff 102 Oct 18 23:33 .
drwxrwsr-x 1 root staff 84 Oct 18 23:33 ..
-rwxr-xr-x 1 root staff 605 Oct 18 23:33 bundle
-rwxr-xr-x 1 root staff 607 Oct 18 23:33 bundler
-rwxr-xr-x 1 root staff 4839 Oct 18 23:33 erb
-rwxr-xr-x 1 root staff 548 Oct 18 23:33 gem
-rwxr-xr-x 1 root staff 192 Oct 18 23:33 irb
-rwxr-xr-x 1 root staff 589 Oct 18 23:33 rake
-rwxr-xr-x 1 root staff 940 Oct 18 23:33 rdoc
-rwxr-xr-x 1 root staff 190 Oct 18 23:33 ri
-rwxr-xr-x 1 root staff 148928 Oct 18 23:33 ruby
-rwxr-xr-x 1 root staff 655 Oct 18 23:33 update_rubygems
root@c494cf51c146:/#
なるほど、これを雑に全部持ってくれば Ruby の動作において過不足なさそうですし、 $ ruby -v
も動きそうです。
FROM ruby:2.5.3-stretch as ruby
FROM gcr.io/distroless/base
COPY --from=ruby /usr/local/bin/ /usr/local/bin
CMD ["/usr/local/bin/ruby", "-v"]
$ docker build --no-cache -t distroless-ruby .
...snip...
$ docker run --rm distroless-ruby
/usr/local/bin/ruby: error while loading shared libraries: libruby.so.2.5: cannot open shared object file: No such file or directory
だめでした。共有ライブラリが見付からないので ruby が実行できません。
ので、共有ライブラリも持ってきます。
FROM ruby:2.5.3-stretch as ruby
FROM gcr.io/distroless/base
COPY --from=ruby /usr/local/lib/ /usr/local/lib
COPY --from=ruby /usr/local/bin/ /usr/local/bin
CMD ["/usr/local/bin/ruby", "-v"]
$ docker build --no-cache -t distroless-ruby .
...snip...
$ docker run --rm distroless-ruby
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
よさそうですね。
sinatra でコンテナからレスポンスを返すことができるまで
公式サイトの例にある、この単純なsinatra appを動かすことを目標とします。
require 'sinatra'
configure do
set :bind, '0.0.0.0'
end
get '/' do
'Hello world!'
end
# frozen_string_literal: true
source "https://rubygems.org"
gem "sinatra"
そして、このようなDockerfileを作成し、buildしてみます。
FROM distroless-ruby
EXPOSE 4567
WORKDIR /app
COPY . .
RUN ["/usr/local/bin/bundle", "install"]
CMD ["/usr/local/bin/bundle", "exec", "ruby", "server.rb"]
RUN
と CMD
が exec形式1なのは、distrolessには sh
が含まれていないために、単に bundle install
と書くとshellが無くてエラーになるためです。
$ docker build --no-cache -t distroless-ruby-sinatra .
Sending build context to Docker daemon 2.074MB
Step 1/6 : FROM distroless-ruby
---> c07af2ab24f5
Step 2/6 : EXPOSE 4567
---> Running in 7aa0572c9251
Removing intermediate container 7aa0572c9251
---> 2d33d9d70ece
Step 3/6 : WORKDIR /app
---> Running in 0189cc6b0a87
Removing intermediate container 0189cc6b0a87
---> 7d922895beac
Step 4/6 : COPY . .
---> aa73bbf36f8e
Step 5/6 : RUN ["/usr/local/bin/bundle", "install"]
---> Running in acd4f58ea502
Don't run Bundler as root. Bundler can ask for sudo if it is needed, and
installing your bundle as root will break this application for all non-root
users on this machine.
/usr/local/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require': libz.so.1: cannot open shared object file: No such file or directory - /usr/local/lib/ruby/2.5.0/x86_64-linux/zlib.so (LoadError)
...snip...
ダメでしたね。zlib
が無いので、どこかから持ってくるしかないです。(distrolessにはパッケージマネージャーがない)
依存するライブラリを持ってくる
zlib
はどこにあるのでしょうか。
ruby
のバイナリを持ってきている元、 ruby:2.5.3-stretch
、つまりDebian stretch で zlib
をインストールする際には、 apt install zlib1g
というコマンドを実行します。
Debian -- stretch の zlib1g パッケージに関する詳細
ならば、このときインストールされるファイルを持ってくればいいことになります。
それは dpkg --listfiles
で取得できるので、見てみましょう。
$ docker run --rm -it ruby:2.5.3-stretch bash
root@1ed6fdb8acb4:/# dpkg --listfiles zlib1g
/.
/lib
/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu/libz.so.1.2.8
/usr
/usr/share
/usr/share/doc
/usr/share/doc/zlib1g
/usr/share/doc/zlib1g/changelog.Debian.gz
/usr/share/doc/zlib1g/changelog.gz
/usr/share/doc/zlib1g/copyright
/lib/x86_64-linux-gnu/libz.so.1
実体は /lib/x86_64-linux-gnu/libz.so.1.2.8
のようですね。これらをコピーしてきましょう。
FROM ruby:2.5.3-stretch as ruby
FROM gcr.io/distroless/base
COPY --from=ruby /lib/x86_64-linux-gnu/libz.so.* /lib/x86_64-linux-gnu/
COPY --from=ruby /usr/local/lib/ /usr/local/lib
COPY --from=ruby /usr/local/bin/ /usr/local/bin
CMD ["/usr/local/bin/ruby", "-v"]
同様に、libyaml
も無くエラーになってしまうので、最終的に sinatraのbundle install
が成功する distroless-ruby のDockerfileはこうなります。
FROM ruby:2.5.3-stretch as ruby
FROM gcr.io/distroless/base
COPY --from=ruby /lib/x86_64-linux-gnu/libz.so.* /lib/x86_64-linux-gnu/
COPY --from=ruby /usr/lib/x86_64-linux-gnu/libyaml* /usr/lib/x86_64-linux-gnu/
COPY --from=ruby /usr/local/lib/ /usr/local/lib
COPY --from=ruby /usr/local/bin/ /usr/local/bin
CMD ["/usr/local/bin/ruby", "-v"]
docker run sinatra
それでは、 sinatraを動かしてみます。
$ curl -v http://localhost:4567
* Rebuilt URL to: http://localhost:4567/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 4567 (#0)
> GET / HTTP/1.1
> Host: localhost:4567
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 12
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Server: WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18)
< Date: Thu, 25 Oct 2018 15:42:46 GMT
< Connection: Keep-Alive
<
* Connection #0 to host localhost left intact
Hello world!
$ docker run --rm -it -p 4567:4567 distroless-ruby-sinatra
[2018-10-25 14:47:17] INFO WEBrick 1.4.2
[2018-10-25 14:47:17] INFO ruby 2.5.3 (2018-10-18) [x86_64-linux]
== Sinatra (v2.0.4) has taken the stage on 4567 for development with backup from WEBrick
[2018-10-25 14:47:17] INFO WEBrick::HTTPServer#start: pid=1 port=4567
172.17.0.1 - - [25/Oct/2018:14:47:21 +0000] "GET / HTTP/1.1" 200 12 0.0158
172.17.0.1 - - [25/Oct/2018:14:47:21 UTC] "GET / HTTP/1.1" 200 12
- -> /
動きましたね。
サイズ
$ docker image ls | grep ruby
distroless-ruby latest 53.3MB
ruby 2.5.3-alpine3.8 45.1MB
ruby 2.5.3-slim-stretch 178MB
ruby 2.5.3-stretch 869MB
なるほど、alpineよりは大きいですが、slimの半分くらいにはなっていますね。
今後の課題
sinatraで数行程度という、軽く依存もほとんどないRuby scriptが動くようにはなりました。しかしデータベースに接続する、Railsのような大きなアプリケーションは依存関係を解決するのが難しそうです。
また、 Ruby は バッククオートでシェルコマンドを実行できる言語機能がありますが、これも動きません。busyboxなどを入れる必要がありそうです。
`cat foo.txt` # /bin/cat が無いので動作しない