💭

Nuxt.jsを「正しく」終了する

2021/12/12に公開

はじめに

この記事はNuxt.js Advent Calendar2021の12日目の記事です。
11日目は@Skmt3PさんのNuxtのコンポーネントをWeb Componentとして利用するでした。(web component触ってきてないからへぇって気持ちで読まさせていただきました)

概要

hooks自体を調べていたときにcloseという項目がありました。そして、説明には

Nuxt インスタンスが正しく終了したとき

というのがありました。

「正しく」とは一体…となって原文を見てみると

Nuxt instance is gracefully closing.

ということで正しく停止=gracefully closingという形であることが判明し、自分が今まで正しく停止していなさそうだと言うことを認識しました。(自虐

今回はNuxt.jsを「正しく」停止するためには、というかこのcloseはどうやってやれば呼ばれるのか、何をすればいいのかを調べてみました。

今回、Nuxt.jsのアプリケーションはTypeScriptで実装しています。
そのため、npm run startに関してはnuxt startが動くはずです。

そもそもアプリケーションを停止するとは

こちらの記事がとてもわかり易かったです。
https://qiita.com/megmogmog1965/items/86da1dcb42cb5c6a4d14
要するにSIGTERMの命令で終了するように実装をしましょうということのようです。(dockerや、k8s環境で実行しているだろうという強い圧力)

そもそも正しくないのか?

とりあえず以下のように実装してみます(nuxtをanyにしているのはめんどいからですすいません

nuxt.config.ts
import { close } from './hooks'

export default {
  hooks: {
    close
  }
}
hooks.ts
export const close = (nuxt: any) => {
  console.log('close')
  return new Promise((resolve) => {
      // 動作確認のために3秒待つ
      setTimeout(() => resolve(1), 3000)
  })
}

上記の実装をした状態で、npm run startしてみます。
そして以下のコマンドのようにSIGTERMを送ってみます。
pgrep -f npm | xargs kill
そうすると

[1]    xxxxxxx terminated  npm run start

といって停止していることがわかります。SIGTERMをうけとれず、どうやら「正しく」停止していないようです。

「正しく」停止させてみる

SIGTERMのイベントを監視するには、hooksのready eventに仕込む のが楽そうです(引数としてnuxtのインスタンスが取れるので)

これを実装するには、例えば以下のようになります。

nuxt.config.ts
- import { close } from './hooks'
+ import { ready, close } from './hooks'

export default {
  hooks: {
+    ready,
    close
  }
}
hooks.ts
+ export const ready = (nuxt: any) => {
+  console.log('ready')
+  process.on("SIGTERM", () => {
+    console.info("Shutdown Server for sigterm...");
+    nuxt.close();
+  })
+ }

export const close = (nuxt: any) => {
    console.log('close')
    return new Promise((resolve) => {
        // 動作確認のために3秒待つ
        setTimeout(() => resolve(1), 3000)
    })
}

上記の実装をした状態で、npm run startしてみます。
その後先程の通りSIGTERMを送ると、期待通り停止される(stopといってから、3秒待って停止される)ことがわかるかと思います。
正しく停止されたっぽいです。

Docker上で「正しく」停止させる

ではdocker上で問題なく行けるか、かんたんに試してみます。以下のようなDockerfileを用意し、コンテナを動かし停止させます。(docker stopは内部のプロセスに対してSIGTERMを送ります)

FROM node:14.18.2

WORKDIR /app
COPY . .
RUN npm i && npm run build

CMD ["npm", "run", "start"]
docker build -t john:test .
docker run -it --name john-test john:test

// 別ターミナルで
docker stop john-test

すると、stopがでないし、3秒待つこともなく終了しました。

いろいろ調べたところdockerのstopではpid 1に対して送っているということらしいです
コンテナの中を確認したところ、以下のようになっていたので、npmのプロセスがstopされたということになりそうですね。

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.6  1.9 682384 38820 pts/0    Ssl+ 13:39   0:00 npm
root          19  0.0  0.0   1868   416 pts/0    S+   13:39   0:00 sh -c nuxt start
root          20  1.0  3.0 707152 61552 pts/0    Sl+  13:39   0:00 node /app/node_modules/.bin/nuxt
root          31  0.0  0.1   3744  2900 pts/1    Ss   13:39   0:00 /bin/bash
root          39  0.0  0.1   5860  2384 pts/1    R+   13:39   0:00 ps aux

ローカルの時はnpm run startのプロセスに対して送られていたようなので停止できたっぽいです。

ではどうするかですが、いろいろ試したところ以下のようにするのが一番良さそうでした。

FROM node:14.18.2

WORKDIR /app
COPY . .
RUN npm i && npm run build

- CMD ["npm", "run", "start"]
+ CMD ["npx", "nuxt", "start"]

Docker内部のプロセスは以下のようになっており、docker stopを実施したところ、期待通りに正しく終了させることができました。

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  2.8  2.9 706864 60868 pts/0    Ssl+ 15:21   0:00 node /usr/local/bin/npx nuxt start
root          19  0.0  0.1   3744  2860 pts/1    Ss   15:21   0:00 /bin/bash
root          28  0.0  0.1   5860  2380 pts/1    R+   15:21   0:00 ps aux

まとめ

Nuxt.jsを使っていながら、勝手に終了させていたので、今考えてみればGracefullではなかったかと思います(そもそもそんなに重たい処理をするものを作っていない+ユーザがあまりいなかった)
ただ、Nuxt.js上でBFFなどを実装していくに当たり、このあたり、気をつけて実装をしていく必要があるかと感じています。

と書きながら、現状のNuxt.jsが本当にGracefully shutdownしていないのかは別途確かめたほうがいいと思います。今回のはあくまでcloseが呼ばれるためにはという感じだったので。

Discussion