Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Node.jsのDockerイメージのマルチコア対応をがんばってみた

$
0
0

いろいろ頑張ってみたものの、Docker本家とかその他の情報によると、Dockerfile内部でマルチコア対応は頑張らない方が良いらしい。せっかくがんばったので供養のためにまとめておきます。

マルチコア対応したい!

Node.jsはシングルコアで動くのでマルチコア対応したいですよね?コア数が生かせないのはかっこ悪いですよね?サーバーにそのままデプロイする場合はpm2とかのプロセスマネージャを使います。pm2のウェブサイトをみると、Docker用のpm2があるじゃないですね。

というわけでそれを使うようにしてみました。

  • package.jsonのdependenciesにはpm2のみ
  • package.jsonのその他の依存はdevDependenciesに
  • npm run buildでサーバーコードはdist/index.jsというシングルjsファイルになる(@zeit/ncc利用)

package.jsonは以下の通り(ESLintとかビルドに不要なものは省いた)。

package.json
{"name":"webserver","version":"1.0.0","scripts":{"build":"ncc build src/main.ts"},"dependencies":{"pm2":"^4.4.0"},"devDependencies":{"@types/body-parser":"^1.19.0","@types/compression":"^1.7.0","@types/express":"^4.17.7","@zeit/ncc":"^0.22.3","body-parser":"^1.19.0","compression":"^1.7.4","express":"^4.17.1","http-graceful-shutdown":"^2.3.2","typescript":"^3.9.7"}}

サンプルのウェブアプリはこんな感じで作ってみました。大事なのはシグナルを受け取って終了するということです。

src/main.ts
importexpress,{Request,Response}from"express";importcompressionfrom"compression";importbodyParserfrom"body-parser";importgracefulShutdownfrom"http-graceful-shutdown";constapp=express();app.use(compression());app.use(bodyParser.json());app.use(bodyParser.urlencoded({extended:true}));app.get("/",(req:Request,res:Response)=>{res.json({message:`hello ${req.headers["user-agent"]}`,});});consthost=process.env.HOST||"0.0.0.0";constport=process.env.PORT||3000;constserver=app.listen(port,()=>{console.log("Server is running at http://%s:%d",host,port);console.log("  Press CTRL-C to stop\n");});gracefulShutdown(server,{signals:"SIGINT SIGTERM",timeout:30000,development:false,onShutdown:async(signal:string)=>{console.log("... called signal: "+signal);console.log("... in cleanup");// shutdown DB or something},finally:()=>{console.log("Server gracefulls shutted down.....");},});

こちらがDockerfileです。pm2とpm2-runtimeはだけはパスを通すようにしています。

Dockerfile
# ここから下がビルド用イメージFROM node:12-buster AS builderWORKDIR appCOPY package.json package-lock.json ./RUN npm ci
COPY tsconfig.json ./COPY src ./srcRUN npm run build

# ここから下が実行用イメージFROM node:12-buster-slim AS runnerWORKDIR /opt/appCOPY package.json package-lock.json ./RUN npm ci --prodRUN ln-s /opt/app/node_modules/.bin/pm2 /usr/local/bin/pm2
RUN ln-s /opt/app/node_modules/.bin/pm2-runtime /usr/local/bin/pm2-runtime
COPY ecosystem.config.js ecosystem.config.jsCOPY --from=builder /app/dist ./USER nodeEXPOSE 3000CMD ["pm2-runtime", "start", "ecosystem.config.js"]

PM2の設定ファイルは次の通り。instances: "max"が勇者の証。

ecosystem.config.js
module.exports={apps:[{name:"greeting-server",script:"/opt/app/index.js",env:{NODE_ENV:"development",},env_production:{NODE_ENV:"production",},instances:"max",exec_mode:"cluster",},],};

これでビルドして実行すると、コア数分プロセスが立ち上がっていることがわかります(Dockerは4コア使うように設定してあります)。

$ docker build -t webserver .$ docker --name webserver --rm-it webserver
$ docker exec webserver pm2 list
┌─────┬────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id│ name               │ namespace   │ version │ mode    │ pid      │ uptime│ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 18       │ 55s    │ 0    │ online    │ 0%       │ 40.8mb   │ node     │ disabled │
│ 1   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 25       │ 55s    │ 0    │ online    │ 0%       │ 41.3mb   │ node     │ disabled │
│ 2   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 32       │ 55s    │ 0    │ online    │ 0%       │ 41.2mb   │ node     │ disabled │
│ 3   │ greeting-server    │ default     │ 1.0.0   │ cluster │ 39       │ 55s    │ 0    │ online    │ 0%       │ 41.2mb   │ node     │ disabled │
└─────┴────────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

pm2-runtimeはpm2 --no-daemon相当のツールで、フォアグラウンドで動作し、シグナルなどはDockerの作法に従ってきちんと動作するように作られています。

めでたしめでたし・・・ではなかった

Dockerのベストプラクティスを諸々調べると、どれもnode スクリプトで起動せよ、プロセスマネージャとかランチャーは挟むな、と書かれています。ランチャー(npm run)はシグナルを適切に伝達しないということで、pm2-runtimeはそこはきちんとしているので、停止できないとかクリティカル無問題はないです。とはいえ、オーケストレーションツール側でオートスケール、みたいな話とちょっと喧嘩する可能性があるので、そこはもっと深く検証が必要なのかもしれません。

便利なところといえば、ロードバランサー的なものをおかなくても手っ取り早くパフォーマンスはあげられる・・・ぐらいですかね。

pm2はSaaSでObservabilityなサービスをしているようですね。pm2-runtimeはそこにつなげるためのエージェントという色合いが強いのかも・・・という気もしました。npm install pm2すると30MBぐらいどかっとイメージが大きくなってしまうのもいまいちだし、とりあえずこの努力はこのエントリーで供養します。


Viewing all articles
Browse latest Browse all 8691

Trending Articles