コンテナ時代におけるデバッグ手段としてのminimized perlの検討
このスクラップはブログを書くための調査のためのメモ
ブログで主張したいこと
課題
- Webサービスでリクエストを受けるためのコンテナイメージの中身はできるだけダイエットしたい
- デプロイやスケールアウト時の起動時間の短縮のため
- 例としてGoの場合は、実行バイナリのみ
- 以上の状況で問題が起こった時にデバッグする手段が乏しい
- コンテナ内からネットワークが外部へ通るかどうか
- サービスディスカバリー等での名前解決ができるかどうか
- 想定外のプロセスがポートを握っていないかどうか
- デバッグのためにプログラムを送り込む手段が乏しいケースがある
- ECS Execではs3などを経由する以外ではバイナリを送り込むのは難しい
解決案
- miniperlをコンテナイメージに仕込んでおく
- デバッグ用のプログラムをPerlで書く
- Pure Perl用のCPANモジュールやプラグマはFatpackで1枚にする
- コンソール上でコピペしてプログラムを送り込む
- miniperlで実行する
miniperlのバイナリサイズは他の言語ランタイムに比べて少ないか?
以下のDockerfileで実験。とりあえずお手軽なAlipine Linux。環境はaarch64
FROM alpine:3.19.0
ARG RUNTIME
RUN apk add --no-cache $RUNTIME
ENTRYPOINT ["/bin/ash"]
そして、ビルドと計測のためのMakefileを用意する。
build/%:
docker build --build-arg RUNTIME=$* -t alpine-$* .
docker inspect alpine-$* | jq -r '(.[].Size / 1024 / 1024 * 100 | round) / 100'
| ランタイム | Size | ランタイムバージョン |
|---|---|---|
| (ランタイムなし) | 7.37MB | - |
| miniperl | 10.84MB | 5.38.2 |
| perl | 44.68MB | 5.38.2 |
| ruby | 26.66MB | 3.2.2 |
| mruby | 8.41MB | 3.2.0 |
| python3 | 51.48MB | 3.11.6 |
| nodejs | 59.18MB | 20.10.0 |
| deno | 86.15MB | 1.38.1 |
miniperlはmrubyの次に軽いことがわかる。
miniperlはどこまで実用的か?
use strictなどのプラグマは使えない
/ # miniperl -e 'use strict'
Can't locate strict.pm in @INC (you may need to install the strict module) (@INC entries checked: .) at -e line 1.
BEGIN failed--compilation aborted at -e line 1.
/ # miniperl -e 'use warnings'
Can't locate warnings.pm in @INC (you may need to install the warnings module) (@INC entries checked: .) at -e line 1.
BEGIN failed--compilation aborted at -e line 1.
Fatpackすればどうだろうか?
App::FatpackerというCPANモジュールがある。useしているCPANモジュールをまとめて一枚岩のスクリプトにするモジュールである。
ここに標準出力からJSONを読んでdumpするスクリプトがある。
#!perl
use strict;
use warnings;
use utf8;
use JSON::PP qw/decode_json/;
use Data::Dumper;
my $json = do { local $/; <STDIN> };
my $data = decode_json($json);
warn Dumper $data;
$ $ echo '{"hello": "world"}' | perl json-parse.pl
$VAR1 = {
'hello' => 'world'
};
これをFatpackする。
$ fatpack pack json-parse.pl
が、コアモジュールは含まれていない。
App::FatPacker::Simpleなどを試してみたが、うまくいかない。
staticperlが使えるかどうか
staticperlというモジュールがある。これで最低限のコアモジュールをバンドルした上で、実行できないか。
darwinだと使えなかったので、perl:5.38-slim上で実行する。
$ docker run --rm -it perl:5.38-slim /bin/bash
$ apt update && apt install build-essential
$ cpanm --notest App::Staticperl
$ staticperl install
が、gccでビルドエラーが出る。perlも5.22だし、ちょっと一旦保留
relocatable perlにPerl::Stripを適用する
relocatable-perlというポータブルなperlを提供するdistributionがある
ダウンロードページを見ると10MB〜20MBぐらいで圧縮されている。
Perl::Stripというstaticperlで使われているminifyするモジュールがある。relocatable perlに添付されているcore moduleをPerl::Stripで圧縮すれば、機能は落とさずに容量だけ小さくというのはできないか。たとえばコアモジュールはpodが多く書かれているが、Perl::Stripeはそれを落とすのでだいぶ圧縮されるのではないか。
出来れば単一バイナリに仕込めたらなおよしだが...
実際に軽くしていく。
展開直後の容量。アーキテクチャはdarwin arm64
$ du -h -d 1 perl-darwin-arm64
4.5M perl-darwin-arm64/bin
62M perl-darwin-arm64/lib
67M perl-darwin-arm64
バイナリ部分は4.5MBしかない。
.pmと.plにPerl::Stripを適用していく。
$ cpanm --notest Perl::Strip
$ find . -name '*.pm' | xargs perlstrip
$ find . -name '*.pl' | xargs perlstrip
さらにpodも消す
$ find . -name '*.pod' -delete
$ du -h -d 1 perl-darwin-arm64
4.5M perl-darwin-arm64/bin
45M perl-darwin-arm64/lib
49M perl-darwin-arm64
67MBから49MBまで減らした。ここから圧縮する。
$ tar cf perl-darwin-arm64.tar perl-darwin-arm64
$ gzip -9 --stdout perl-darwin-arm64.tar > perl-darwin-arm64.tar.gz
$ du -h perl-darwin-arm64.tar.gz
13M perl-darwin-arm64.tar.gz
13MBまで圧縮された。が、元々が16.9MBなので3.9MBほどしか削減できなかった。
あまりやりたくはないが、不必要なコアモジュールを消していく。
- Test系
- `cd lib/5.38.2; rm -R Test Test2 Test.pm Test2.pm
- ヘッダファイル
rm -Rf lib/5.38.2/darwin-2level/CORE- CPANモジュールをインストールする時以外は不必要
これで7.8MBまで圧縮された。
Perl::Stripかけたら、no warnings 'experimental::builtin';が効かなくなった。
Perl::Stripをかけなくても、Test系とCOREのヘッダファイルを消すだけで9.4MB。xzだと6MB。展開すると、37MB。
- lib/5.38.2/darwin-2level/CORE はヘッダファイル以外も含まれているため、これを消すのはまずそう
PAR::Packerが意外と実用的かもしれない
以下のスクリプトをPAR::Packerでビルドしてみた。
#!perl
use v5.38;
use strict;
use warnings;
use utf8;
use JSON::PP;
use Data::Dumper;
use Path::Tiny;
my $stdin = do { local $/; <STDIN> };
eval $stdin;
if ($@) {
print "Error: $@\n";
}
$ pp eval.pl
6.1MBのバイナリができた。(darwin aarch64)
適当に実行してみる。
$ echo 'use Path::Tiny; say path("./")->absolute->stringify;' | ./evalperl
実行できる。コンテナ環境でもできるかどうかは要検討