Dockerコンテナ用にnpm runの省メモリでエコな代替を書いた話
DockerでNodeJSのプログラムを動かしてるサーバーでhtopのプロセスツリーを眺めてるとこんな感じになってることが多々あります

npm runが67MiBとそれなりにメモリを使用している上にスレッドを量産しています。
メモリが潤沢な環境なら気にしなければいい話ではありますが、VPSなどのクラウド上で運用していてメモリに余裕がない場合や、RaspberryPiなどの性能が限られてるSBCで実行している場合にはそれなりに気になってきます。
このnpm runがやってることはpackage.jsonの指定されたscriptを実行してるだけです。これをやるためだけに67MiBもサーバーの貴重なメモリを持ってかれるのはエコではないということで、最近やたら流行っているRustで超簡易的な代替を書いてみたという訳です。
成果物
GitHubでnpmrunとして公開しています。
動作
やっていることはカレントディレクトリのpackage.jsonをパースしてコマンドライン引数で指定されたscriptを実行しているだけです。ただし以下の点が微妙に本来のnpm runと違います。
-
&&で複数のコマンドが指定されてる場合、同時に実行するのではなく内部的に分割して一つづつ実行します-
Command::newはコマンドを実行する関数なので&&みたいなシェル用の構文は使えないため - このままだとディレクトリの移動ができないのでコマンドが
cdだったらstd::env::set_current_dirでカレントディレクトリを移動するようにしてあります
-
使い方
Dockerfileに以下の内容を追記します。
まずはnpmrun-builderステージでnpmrunコマンド本体をビルドする部分を先頭の方に付け足します。
FROM rust:1-alpine as npmrun-builder
WORKDIR /src
RUN apk add --no-cache git alpine-sdk
RUN git clone https://github.com/nexryai/npmrun.git .
RUN cargo build --release
続いてnpmrun-builderでビルドした成果物をrunnerなど本体を実行するステージに引っ張ってきます。
Alpineをベースに使ってる場合なのでベースのディストリビューションによっては/usr/local/bin/の部分が変わってくるかもしれません。
COPY --from=npmrun-builder /src/target/release/npmrun /usr/local/bin/npmrun
CMDなり実行する部分のnpm runやそれに相当する部分をnpmrunに置き換えます。
ENV NODE_ENV=production
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["npmrun", "docker:start"]
Done
以下の画像のようにhtopのプロセスツリーを見た時npmrunで実行されてれば成功です。

まとめ
以上、私がVPS代金をケチるためにした努力の成果の紹介でした。お役に立てれば幸いです。
現段階ではまだ不完全な部分もそれなりに残ってるので、何か問題などあればGitHubからissueを生やしてくれると助かります。
Discussion