Svelte入門:SvelteKitでのノードサーバー -Vol.3-

スポンサーリンク
Svelte入門:SvelteKitでのノードサーバー -Vol.3- 用語解説
Svelte入門:SvelteKitでのノードサーバー -Vol.3-
この記事は約12分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

今日は、SvelteKitでのノードサーバーについて解説しています。

スポンサーリンク

背景

SvelteKitでのノードサーバーについて調査する機会がありましたので、その時の内容を備忘として記事に残しました。

ノードサーバー

スタンドアロン Node サーバーを生成するには、adapter-node を使用します。

使い方

npm i -D @sveltejs/adapter-node でインストールし、アダプターを svelte.config.js に追加します。

svelte.config.js
import adapter from '@sveltejs/adapter-node';

export default {
	kit: {
		adapter: adapter()
	}
};

Graceful shutdown

デフォルトでは、adapter-nodeSIGTERMまたはSIGINTシグナルを受信したとき、HTTPサーバーを適切にシャットダウンします。以下の手順で行われます:

  1. 新しいリクエストを拒否します(server.close
  2. すでに行われたが、まだレスポンスを受け取っていないリクエストが完了するのを待ち、接続がアイドル状態になったら閉じます(server.closeIdleConnections
  3. 最後に、SHUTDOWN_TIMEOUT秒後もまだアクティブな接続が残っている場合、それらを閉じます(server.closeAllConnections

この動作をカスタマイズしたい場合は、カスタムサーバーを使用できます。

HTTPサーバーがすべての接続を閉じた後に発生するsveltekit:shutdownイベントをリッスンできます。Nodeのexitイベントとは異なり、sveltekit:shutdownイベントは非同期操作をサポートし、サーバーにデータベース接続などの未完了の作業がある場合でも、すべての接続が閉じられると必ず発生します。

process.on('sveltekit:shutdown', async (reason) => {
    await jobs.stop();
    await db.close();
});

reasonパラメータは以下のいずれかの値を持ちます:

  • SIGINT – シャットダウンがSIGINTシグナルによってトリガーされた
  • SIGTERM – シャットダウンがSIGTERMシグナルによってトリガーされた
  • IDLE – シャットダウンがIDLE_TIMEOUTによってトリガーされた

この機能により、アプリケーションのシャットダウン処理をより細かく制御でき、リソースの適切なクリーンアップや進行中の操作の安全な終了を確実に行うことができます。

ソケットのアクティベーション

現在、ほとんどのLinuxオペレーティングシステムは、サーバーを起動し、サービスを実行・管理するために「systemd」と呼ばれる現代的なプロセスマネージャーを使用しています。サーバーを設定してソケットを割り当て、必要に応じてアプリを起動・スケーリングすることができます。これは「ソケットアクティベーション」と呼ばれます。この場合、OSはアプリに2つの環境変数 – LISTEN_PIDLISTEN_FDS – を渡します。その後、アダプターはファイルディスクリプタ3をリッスンします。これは作成する必要があるsystemdソケットユニットを参照します。

systemdソケットアクティベーションでもenvPrefixを使用できます。LISTEN_PIDLISTEN_FDSは常にプレフィックスなしで読み取られます。

ソケットアクティベーションを活用するには、以下の手順に従ってください。

この機能を使用することで、システムリソースをより効率的に利用し、必要に応じてアプリケーションを自動的に起動・停止することができます。これは特に、トラフィックが変動するアプリケーションや、リソースを最適化したい場合に有用です。ただし、設定には一定の技術的知識が必要で、systemdの理解が求められます。

step01

アプリをsystemdサービスとして実行します。これは、ホストシステム上で直接実行するか、コンテナ内(例えば、Dockerやsystemdポータブルサービスを使用)で実行することができます。さらに、アプリにIDLE_TIMEOUT環境変数を渡すと、IDLE_TIMEOUT秒間リクエストがない場合に適切にシャットダウンします。新しいリクエストが入ってくると、systemdが自動的にアプリを再起動します。

    /etc/systemd/system/myapp.service

    Copy[Service]
    Environment=NODE_ENV=production IDLE_TIMEOUT=60
    ExecStart=/usr/bin/node /usr/bin/myapp/build

    この設定により:

    1. アプリケーションは本番環境(NODE_ENV=production)で実行されます。
    2. 60秒間アイドル状態が続くと、アプリケーションは自動的にシャットダウンします(IDLE_TIMEOUT=60)。
    3. アプリケーションは指定されたパス(/usr/bin/myapp/build)で起動されます。

    この方法を使用することで、システムリソースを効率的に使用し、必要なときにのみアプリケーションを実行することができます。また、トラフィックの変動に応じて自動的にスケールアップ・ダウンすることも可能になります。

    step02

    対応するソケットユニットを作成します。アダプターは単一のソケットのみを受け付けます。

      /etc/systemd/system/myapp.socket

      [Socket]
      ListenStream=3000
      
      [Install]
      WantedBy=sockets.target

      この設定の説明:

      1. [Socket] セクション:
      • ListenStream=3000: このソケットは3000番ポートでTCP接続をリッスンします。
      1. [Install] セクション:
      • WantedBy=sockets.target: このソケットユニットは、システムのソケットターゲット(一般的なソケットアクティベーションの集合)の一部として有効化されます。

      このソケットユニットは、アプリケーションサービスと連携して動作します。systemdは、このソケットに接続があった際に自動的にアプリケーションを起動します。これにより、リソースを効率的に使用し、必要に応じてアプリケーションを起動することができます。

      注意点として、アダプターが単一のソケットのみを受け付けるという制限があります。複数のポートやソケットが必要な場合は、追加の設定や異なるアプローチが必要になる可能性があります。

      step03

      はい、その説明を日本語に翻訳いたします:

      sudo systemctl daemon-reloadを実行して、systemdが両方のユニットを認識していることを確認します。次に、sudo systemctl enable --now myapp.socketを使用して、ソケットをブート時に有効にし、即座に起動します。その後、localhost:3000に最初のリクエストが行われると、アプリが自動的に起動します。

        この手順の詳細:

        1. sudo systemctl daemon-reload
        • この命令は、systemdに設定ファイルの変更を認識させます。新しいユニットファイルを作成したり、既存のファイルを変更したりした後は、常にこのコマンドを実行する必要があります。
        1. sudo systemctl enable --now myapp.socket
        • enable: このソケットユニットをシステム起動時に自動的に開始するように設定します。
        • --now: ソケットユニットを即座に起動します。
        1. 自動起動の仕組み:
        • localhost:3000へのリクエストがあると、systemdはソケットを通じてそれを検知します。
        • systemdは関連付けられたサービス(この場合はmyapp.service)を自動的に起動します。
        • アプリケーションが起動し、リクエストを処理します。

        この設定により、アプリケーションはオンデマンドで起動し、リソースを効率的に使用することができます。アイドル状態が続くとアプリケーションは自動的にシャットダウンし、新しいリクエストがあると再び起動します。これは、トラフィックが変動するアプリケーションや、サーバーリソースを最適化したい場合に特に有用です。

        カスタムサーバ

        アダプターは、ビルドディレクトリに2つのファイル – index.jshandler.js – を作成します。index.jsを実行すること(例えば、デフォルトのビルドディレクトリを使用している場合はnode build)で、設定されたポートでサーバーが起動します。

        または、handler.jsファイルをインポートすることもできます。このファイルは、Express、Connect、Polka(あるいは組み込みのhttp.createServer)で使用するのに適したハンドラーをエクスポートしています。これを使って、独自のサーバーをセットアップすることができます:

        この方法は、より高度なカスタマイズや既存のサーバー設定との統合が必要な場合に特に有用です。例えば:

        1. 追加のミドルウェアを使用したい場合
        2. 複数のアプリケーションを1つのサーバーで提供したい場合
        3. 特定のルーティング要件がある場合
        4. カスタムエラーハンドリングやロギングを実装したい場合

        handler.jsを使用することで、SvelteKitアプリケーションを既存のNode.jsサーバー環境に柔軟に統合することができます。これにより、アプリケーションの構造をより細かく制御しつつ、SvelteKitの利点を活用することができます。

        my-server.js
        import { handler } from './build/handler.js';
        import express from 'express';
        
        const app = express();
        
        // add a route that lives separately from the SvelteKit app
        app.get('/healthcheck', (req, res) => {
        	res.end('ok');
        });
        
        // let SvelteKit handle everything else, including serving prerendered pages and static assets
        app.use(handler);
        
        app.listen(3000, () => {
        	console.log('listening on port 3000');
        });

        コード解説

        上記のコードは、Express.js を使用して SvelteKit アプリケーションをカスタムサーバーで実行するための設定を示しています。以下にこのコードの詳細な解説を提供します。

        1. インポート文:
        • import { handler } from './build/handler.js';
          • SvelteKit のビルド出力から handler をインポートしています。
          • この handler は SvelteKit アプリケーションのリクエストを処理します。
        • import express from 'express';
          • Express.js フレームワークをインポートしています。
        1. Express アプリケーションの初期化:
        • const app = express();
          • 新しい Express アプリケーションインスタンスを作成します。
        1. カスタムルートの追加:
        • app.get('/healthcheck', (req, res) => { res.end('ok'); });
          • /healthcheck エンドポイントを追加しています。
          • これは SvelteKit アプリケーションとは別に動作するルートです。
          • サーバーの健全性チェックに使用できます。
        1. SvelteKit ハンドラの統合:
        • app.use(handler);
          • SvelteKit の handler を Express ミドルウェアとして使用します。
          • これにより、SvelteKit アプリケーションが他のすべてのルートを処理します。
          • プリレンダリングされたページや静的アセットの提供も含まれます。
        1. サーバーの起動:
        • app.listen(3000, () => { console.log('listening on port 3000'); });
          • サーバーをポート 3000 で起動します。
          • 起動時にコンソールにメッセージを表示します。

        この設定の利点:

        1. カスタム機能の追加:
        • Express.js を使用することで、SvelteKit アプリケーションの外部に追加の機能やルートを実装できます。
        1. 柔軟性:
        • サーバーの動作をより細かく制御できます(例:ミドルウェアの追加、エラーハンドリングのカスタマイズ)。
        1. 既存のインフラストラクチャとの統合:
        • 既存の Express.js ベースのシステムに SvelteKit アプリケーションを統合しやすくなります。

        注意点と考慮事項:

        1. 環境変数:
        • ポート番号などの設定は、環境変数から読み込むようにすると柔軟性が増します。
        1. エラーハンドリング:
        • 本番環境では、適切なエラーハンドリングとロギングを追加することが重要です。
        1. セキュリティ:
        • Express.js アプリケーションのセキュリティベストプラクティス(HTTPS の使用、適切なヘッダーの設定など)を適用してください。
        1. スケーリング:
        • 高負荷環境では、ロードバランサーやリバースプロキシの使用を検討してください。
        1. 静的ファイルの扱い:
        • パフォーマンス向上のため、静的ファイルの提供は Nginx などの専用サーバーに任せることも検討できます。

        このアプローチを使用することで、SvelteKit アプリケーションを柔軟にカスタマイズし、既存のインフラストラクチャに統合することができます。ただし、追加の複雑さも生じるため、単純な SvelteKit アプリケーションの場合は、標準の node build コマンドでの起動で十分かもしれません。

        おわりに

        今日は、 SvelteKitでのノードサーバーについて解説しました。

        よっしー
        よっしー

        何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

        それでは、また明日お会いしましょう(^^)

        コメント

        タイトルとURLをコピーしました