💾

とっても軽いDistrolessコンテナで遊んでみた

2022/02/05に公開

Distroless

最近BusyBoxだけでどこまでプログラミングができるかを試して遊んでみたら思いの外遊べたので共有してみることにしました。友達に自慢しよう!

BusyBoxは組み込みLinuxにおけるスイス・アーミーナイフです。無料で使えるFLOSSなのでホームセンターに行けば買えるものではありません。

BusyBoxと標準CライブラリはAlpine Linuxで知りました。

https://git.busybox.net/busybox/

https://busybox.net/

さて、BusyBoxを使って気軽に遊びたいところですがお使いのパソコンがWindowsやMacだったりするとサポートされておらず遊べませんし、Linux上でビルドするのにも時間がかかり、組み込みLinuxではないDebianやUbuntu上で動かしても元となったコマンドがだいたい標準で入っているので面白みに欠けます。

では、Dockerfileを書いてBusyBoxだけ入ったコンテナイメージを作ればいいのでは……と思った矢先、どうやら世の中にはそんなコンテナイメージがいくつか存在しているようです。
Google Container Registryで公開されているDistrolessのdebug-nonrootがその一つとなります。

https://github.com/GoogleContainerTools/distroless

https://blog.inductor.me/entry/alpine-not-recommended

debugやdebug-nonrootはシェルがついてきてデバッグができるそうですが、別にBashをapt-getしているわけではありません。使えるシェルはBusyBoxのAshであり、すなわちBusyBoxを組み込んでいるのです。

ではでは、Distrolessコンテナで遊んでみましょう。

Static

Distrolessで最軽量なコンテナはStaticです。これはわずか3.42MBで、コンテナイメージを起動しても6.6MB程度です。Alpine Linuxより軽いのではないでしょうか。

$ docker run -it -p 8080:8080 gcr.io/distroless/static:debug-nonroot

今回はWebサーバーを立ち上げCGIを動かしてみるのでポートをあけて起動します。

~ $ mkdir -p www/cgi-bin
~ $ touch www/cgi-bin/index.cgi
~ $ chmod 755 www/cgi-bin/index.cgi
~ $ ls -R
.:
www

./www:
cgi-bin

./www/cgi-bin:
index.cgi

コンテナ内に入れたのでBusyBoxで使えるコマンドを使ってCGIファイルを作成します。

~ $ vi www/cgi-bin/index.cgi
~ $ cat www/cgi-bin/index.cgi
#!/busybox/sh
echo "Content-Type: text/html"
echo
echo "<h1>Hello, World!</h1>"

BusyBox viを使ってCGIを書きます。Perlなんてものは入っていないのでBusyBoxのAshで動かします。

~ $ httpd -f -p 8080 -h www

httpdというまるでApacheのようなhttpデーモンを動かします。なんとこのツールはcgi-binディレクトリに入っているCGIファイルを自動的にWebサーバーのルートディレクトリで呼び出してくれます。特に設定ファイルは必要ありません。

gitpod

できました。
すごいですね!

~ $ ls /usr/sbin
tzconfig
~ $ ls /usr/bin
~ $ cat /etc/os-release
PRETTY_NAME="Distroless"
NAME="Debian GNU/Linux"
ID="debian"
VERSION_ID="11"
VERSION="Debian GNU/Linux 11 (bullseye)"
HOME_URL="https://github.com/GoogleContainerTools/distroless"
SUPPORT_URL="https://github.com/GoogleContainerTools/distroless/blob/master/README.md"
BUG_REPORT_URL="https://github.com/GoogleContainerTools/distroless/issues/new"
~ $ export
export HOME='/home/nonroot'
export HOSTNAME='...'
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/busybox'
export PWD='/home/nonroot'
export SHLVL='1'
export SSL_CERT_FILE='/etc/ssl/certs/ca-certificates.crt'
export TERM='xterm'
~ $ top
...
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 nonroot  SN    1224  0.0   1  0.0 /busybox/sh
   25     1 nonroot  RN    1220  0.0   4  0.0 top

このように必要最低限のものしか入っていない、動いていないことがわかります。

~ $ wget https://www.debian.org/doc/manuals/debian-reference/debian-reference.en.txt
Connecting to www.debian.org (149.20.4.15:443)
wget: note: TLS certificate validation not implemented
saving to 'debian-reference.en.txt'
debian-reference.en. 100% |**********|  856k  0:00:00 ETA
'debian-reference.en.txt' saved

そういえばBusyBoxではWgetが使えます。なのでコンテナ内からインターネット上にあるファイルを簡単に取ってくることができます。
ただ今回動かしているStaticコンテナにはSSLに関するライブラリも存在しないのでhttps通信を行っても署名を検証していないが? みたいな表示が出てきますので気をつけましょう。

~ $ head -n10 debian-reference.en.txt 
Debian Reference

                             Osamu Aoki

Copyright © 2013-2021 Osamu Aoki

    This Debian Reference (version 2.89) (2021-11-28 07:52:48 UTC) is
    intended to provide a broad overview of the Debian system as a
    post-installation user's guide. It covers many aspects of system
    administration through shell-command examples for non-developers.

今回はheadで分かりやすく表示してみましたがlessなどのコマンドも使えるためインターネットから拾ってきたテキストファイルを読むことぐらいはできます。

ちなみにランタイムを含む外部ライブラリをすべて静的にコンパイルされたGoバイナリを動かすこともできます。
それを簡単に実現できるkoというツールがあるのですが、これは以前紹介した記事を書いたのでそちらを読んでみてください。

https://zenn.dev/tkithrta/articles/a4b9607cd58f12

~ $ exit

おわり。
組み込み用途で広く使われるプログラミング言語Luaを動かしてみようとしたところreadlineが見つからない的な表示が出てくるため使えませんでした。
ではreadlineを使わなくて良いLuaJITなら……と思いましたがglibcがないため使えません。
そこでBaseコンテナを使いましょう。

Base

https://github.com/GoogleContainerTools/distroless/blob/main/base

StaticとBaseの違いはglibc, libssl, opensslが入っているかどうかです。
ただこの3つを追加しただけでもかなりサイズが大きくなり、だいたい24MBぐらいになります。
まあDebian Slimより圧倒的に軽いので問題はなさそうです。
またSSLが追加されるので署名が検証されWgetを問題なく動かせるようになります。

$ docker run -it gcr.io/distroless/base:debug-nonroot
~ $ wget https://github.com/luapower/luajit/raw/master/bin/linux64/luajit
Connecting to github.com (192.30.255.112:443)
Connecting to raw.githubusercontent.com (185.199.111.133:443)
saving to 'luajit'
luajit               100% |**********|  570k  0:00:00 ETA
'luajit' saved
~ $ chmod 755 luajit
~ $ ./luajit
LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2021 Mike Pall. https://luajit.org/
JIT: ON SSE3 SSE4.1 BMI2 fold cse dce fwd dse narrow loop abc sink fuse
> print(math.pi)
3.1415926535898
> os.exit()
~ $ ls -lah
total 576K   
drwx------    1 nonroot  nonroot       40 Feb  4 00:00 .
drwxr-xr-x    1 nonroot  nonroot       21 Jan  1  1970 ..
-rw-------    1 nonroot  nonroot      104 Feb  4 00:00 .ash_history
-rwxr-xr-x    1 nonroot  nonroot   570.2K Feb  4 00:00 luajit
~ $ ls /usr/sbin
tzconfig
~ $ ls /usr/bin
c_rehash  openssl

できました。
LuaJITはLuapowerというバイナリ配布版を使用しています。

https://luapower.com/

これでシェル以外の方法でCGIを書けるようになりましたし、簡単なライブラリも使えるようになりました。
ただLuaは組み込み用途で広く使われているかもしれませんが世間的には広く使われていません。
そこでQuickJSを使いJavaScriptを動かしてみましょう。

$ docker run -it gcr.io/distroless/base:debug-nonroot
~ $ wget https://bellard.org/quickjs/binary_releases/quickjs-linux-x86_64-2021-03-27.zip
Connecting to bellard.org (51.15.168.198:443)
saving to 'quickjs-linux-x86_64-2021-03-27.zip'
quickjs-linux-x86_64 100% |**********|  803k  0:00:00 ETA
'quickjs-linux-x86_64-2021-03-27.zip' saved
~ $ unzip quickjs-linux-x86_64-2021-03-27.zip
Archive:  quickjs-linux-x86_64-2021-03-27.zip
  inflating: run-test262
  inflating: qjs
~ $ rm quickjs-linux-x86_64-2021-03-27.zip 
~ $ ./qjs
QuickJS - Type "\h" for help
qjs > console.log(Math.PI)
3.141592653589793
undefined
qjs > \q
~ $ ls -lah
total 2M     
drwx------    1 nonroot  nonroot       56 Feb  4 00:00 .
drwxr-xr-x    1 nonroot  nonroot       21 Jan  1  1970 ..
-rw-------    1 nonroot  nonroot      181 Feb  4 00:00 .ash_history
-rwxr-xr-x    1 nonroot  nonroot   915.5K Feb  4 00:00 qjs
-rwxr-xr-x    1 nonroot  nonroot   727.0K Feb  4 00:00 run-test262
~ $ ls /usr/sbin
tzconfig
~ $ ls /usr/bin
c_rehash  openssl

これでモダンなJavaScriptを動かせるようになりました。ES Modulesも使えます。
ただQuickJSで使えるAPIには限りあるため出来ることはとても少ないです。
どうしましょう。

そうだDenoを動かしてみましょう!
だめでした!
libgccが見つからないみたいな表示が出てきます。
それではCCコンテナを使うことにしましょう。

CC

https://github.com/GoogleContainerTools/distroless/blob/main/cc

glibcランタイムのlibgccを追加しており、RustやD言語のようなほとんど静的にコンパイルされたバイナリを動かすことができます。D言語ってなんだっけ……。
DenoはRustで書かれているので動かせそうですね。

$ docker run -it gcr.io/distroless/cc:debug-nonroot
~ $ wget https://github.com/denoland/deno/releases/download/v1.18.1/deno-x86_64-unknown-linux-gnu.zip
Connecting to github.com (192.30.255.112:443)
Connecting to objects.githubusercontent.com (185.199.108.133:443)
saving to 'deno-x86_64-unknown-linux-gnu.zip'
deno-x86_64-unknown- 100% |**********| 31.4M  0:00:00 ETA
'deno-x86_64-unknown-linux-gnu.zip' saved
~ $ unzip deno-x86_64-unknown-linux-gnu.zip
Archive:  deno-x86_64-unknown-linux-gnu.zip
  inflating: deno
~ $ rm deno-x86_64-unknown-linux-gnu.zip
~ $ ./deno
Deno 1.18.1
exit using ctrl+d or close()
> console.log(Math.PI)
3.141592653589793
undefined
> close()
~ $ ls -lah
total 85M    
drwx------    1 nonroot  nonroot       52 Feb  4 00:00 .
drwxr-xr-x    1 nonroot  nonroot       21 Jan  1  1970 ..
-rw-------    1 nonroot  nonroot      190 Feb  4 00:00 .ash_history
drwxr-xr-x    3 nonroot  nonroot       18 Feb  4 00:00 .cache
-rwxr-xr-x    1 nonroot  nonroot    84.6M Feb  4 00:00 deno
~ $ ls /usr/sbin
tzconfig
~ $ ls /usr/bin
c_rehash  openssl

やりました!
あとはBusyBox viを使ってとっても軽い開発環境を楽しみましょう!

Extra

$ docker images
REPOSITORY                 TAG             IMAGE ID       CREATED        SIZE
debian                     bullseye-slim   ...            10 days ago    80.4MB
debian                     bullseye        ...            10 days ago    124MB
gcr.io/distroless/static   debug-nonroot   ...            52 years ago   3.42MB
gcr.io/distroless/base     debug-nonroot   ...            52 years ago   21.3MB
gcr.io/distroless/cc       debug-nonroot   ...            52 years ago   23.7MB
$ docker run -it -u nobody busybox:latest
$ docker run -it -u nobody busybox:uclibc
$ docker run -it -u nobody busybox:glibc
$ docker run -it -u nobody busybox:musl
/ $ mkdir -p /home/nobody
/ $ export HOME=/home/nobody
/ $ cd ~
~/nobody $ 
$ docker run -it -u nobody alpine:latest
~ $ mkdir -p /tmp/nobody
~ $ export HOME=/tmp/nobody
~ $ cd ~
/tmp/nobody $ 
sh: ./luajit: not found
sh: ./deno: not found
./luajit: error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory
./deno: error while loading shared libraries: libdl.so.2: cannot open shared object file: No such file or directory
$ docker images
REPOSITORY                 TAG             IMAGE ID       CREATED        SIZE
busybox                    musl            ...            8 hours ago    1.43MB
busybox                    glibc           ...            8 hours ago    4.79MB
busybox                    latest          ...            8 hours ago    1.24MB
busybox                    uclibc          ...            8 hours ago    1.24MB
alpine                     latest          ...            2 months ago   5.58MB
やりたいですか?
$ docker run -it -p 8080:8080 gcr.io/distroless/cc:debug-nonroot
~ $ wget https://nodejs.org/dist/v16.13.2/node-v16.13.2-linux-x64.tar.xz
Connecting to nodejs.org (104.20.23.46:443)
saving to 'node-v16.13.2-linux-x64.tar.xz'
node-v16.13.2-linux- 100% |**********| 20.6M  0:00:00 ETA
'node-v16.13.2-linux-x64.tar.xz' saved
~ $ tar -xJf node-v16.13.2-linux-x64.tar.xz
~ $ cd node-v16.13.2-linux-x64
~/node-v16.13.2-linux-x64 $ bin/node bin/npm i -g hexo-cli
...
~/node-v16.13.2-linux-x64 $ bin/node bin/hexo init blog
INFO  Cloning hexo-starter https://github.com/hexojs/hexo-starter.git
WARN  git clone failed. Copying data instead
INFO  Install dependencies
WARN  Failed to install dependencies. Please run 'npm install' in "/home/nonroot/node-v16.13.2-linux-x64/blog" folder.
~/node-v16.13.2-linux-x64 $ cd blog
~/node-v16.13.2-linux-x64/blog $ ../bin/node ../bin/npm i --ignore-scripts
...
~/node-v16.13.2-linux-x64/blog $ ../bin/node ../bin/npm audit fix --force
...
~/node-v16.13.2-linux-x64/blog $ ../bin/node ../bin/hexo server -p 8080
INFO  Validating config
INFO  Start processing
INFO  Hexo is running at http://localhost:8080 . Press Ctrl+C to stop.
...
INFO  Have a nice day

遊んでくれてどうもありがとうございました。

Discussion