Open10

コンテナ時代におけるデバッグ手段としてのminimized perlの検討

macopymacopy

このスクラップはブログを書くための調査のためのメモ

ブログで主張したいこと

課題

  • Webサービスでリクエストを受けるためのコンテナイメージの中身はできるだけダイエットしたい
    • デプロイやスケールアウト時の起動時間の短縮のため
    • 例としてGoの場合は、実行バイナリのみ
  • 以上の状況で問題が起こった時にデバッグする手段が乏しい
    • コンテナ内からネットワークが外部へ通るかどうか
    • サービスディスカバリー等での名前解決ができるかどうか
    • 想定外のプロセスがポートを握っていないかどうか
  • デバッグのためにプログラムを送り込む手段が乏しいケースがある
    • ECS Execではs3などを経由する以外ではバイナリを送り込むのは難しい

解決案

  • miniperlをコンテナイメージに仕込んでおく
  • デバッグ用のプログラムをPerlで書く
  • Pure Perl用のCPANモジュールやプラグマはFatpackで1枚にする
  • コンソール上でコピペしてプログラムを送り込む
  • miniperlで実行する
macopymacopy

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の次に軽いことがわかる。

macopymacopy

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などを試してみたが、うまくいかない。

macopymacopy

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だし、ちょっと一旦保留

macopymacopy

relocatable perlにPerl::Stripを適用する

relocatable-perlというポータブルなperlを提供するdistributionがある

ダウンロードページを見ると10MB〜20MBぐらいで圧縮されている。

Perl::Stripというstaticperlで使われているminifyするモジュールがある。relocatable perlに添付されているcore moduleをPerl::Stripで圧縮すれば、機能は落とさずに容量だけ小さくというのはできないか。たとえばコアモジュールはpodが多く書かれているが、Perl::Stripeはそれを落とすのでだいぶ圧縮されるのではないか。

出来れば単一バイナリに仕込めたらなおよしだが...

macopymacopy

実際に軽くしていく。

展開直後の容量。アーキテクチャは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ほどしか削減できなかった。

macopymacopy

あまりやりたくはないが、不必要なコアモジュールを消していく。

  • 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まで圧縮された。

macopymacopy

Perl::Stripかけたら、no warnings 'experimental::builtin';が効かなくなった。

Perl::Stripをかけなくても、Test系とCOREのヘッダファイルを消すだけで9.4MB。xzだと6MB。展開すると、37MB。

macopymacopy
  • lib/5.38.2/darwin-2level/CORE はヘッダファイル以外も含まれているため、これを消すのはまずそう
macopymacopy

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

実行できる。コンテナ環境でもできるかどうかは要検討