とっても軽いDistrolessコンテナで遊んでみた
Distroless
最近BusyBoxだけでどこまでプログラミングができるかを試して遊んでみたら思いの外遊べたので共有してみることにしました。友達に自慢しよう!
BusyBoxは組み込みLinuxにおけるスイス・アーミーナイフです。無料で使えるFLOSSなのでホームセンターに行けば買えるものではありません。
BusyBoxと標準CライブラリはAlpine Linuxで知りました。
さて、BusyBoxを使って気軽に遊びたいところですがお使いのパソコンがWindowsやMacだったりするとサポートされておらず遊べませんし、Linux上でビルドするのにも時間がかかり、組み込みLinuxではないDebianやUbuntu上で動かしても元となったコマンドがだいたい標準で入っているので面白みに欠けます。
では、Dockerfileを書いてBusyBoxだけ入ったコンテナイメージを作ればいいのでは……と思った矢先、どうやら世の中にはそんなコンテナイメージがいくつか存在しているようです。
Google Container Registryで公開されているDistrolessのdebug-nonrootがその一つとなります。
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サーバーのルートディレクトリで呼び出してくれます。特に設定ファイルは必要ありません。
できました。
すごいですね!
~ $ 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というツールがあるのですが、これは以前紹介した記事を書いたのでそちらを読んでみてください。
~ $ exit
おわり。
組み込み用途で広く使われるプログラミング言語Luaを動かしてみようとしたところreadlineが見つからない的な表示が出てくるため使えませんでした。
ではreadlineを使わなくて良いLuaJITなら……と思いましたがglibcがないため使えません。
そこでBaseコンテナを使いましょう。
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というバイナリ配布版を使用しています。
これでシェル以外の方法で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
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