🎃

負荷分散

に公開

Docker + Nginx によるシンプルなロードバランシング構成とその拡張

本記事では、Docker上でNginxをリバースプロキシ兼ロードバランサーとして構成し、Node.jsベースのアプリケーションコンテナ複数台に対してリクエストを分散する方法を解説します。

1. 構成概要

以下の構成で作成していきます:

  • ホスト:Docker Desktop 上で構築
  • Nginxコンテナ:リバースプロキシおよびロードバランサー
  • アプリケーションコンテナ:Node.jsを利用しレスポンスを返すHTTPサーバー

2. ファイル構成

以下は本構成におけるディレクトリおよびファイルの全体像です:

project-root/
├── docker-compose.yml        
├── nginx.conf                 
├── app1/
│   ├── Dockerfile            
│   ├── index.js               
│   └── package.json           
├── app2/
│   ├── Dockerfile            
│   ├── index.js
│   └── package.json
├── app3/
│   ├── Dockerfile            
│   ├── index.js
│   └── package.json

3. アプリケーションコンテナ

index.js

各Node.jsアプリは、異なるポートで起動し、識別可能なレスポンスを返します。

  • 例:app1/index.js
const http = require('http');
const PORT = 3001; # ポートをそれぞれ別のものにする

http.createServer((req, res) => {
  res.end('Hello from APP1'); # 識別できるようにコメントを変える
}).listen(PORT, () => {
  console.log(`APP1 running at http://localhost:${PORT}`);
});

他のアプリ(app2, app3)は、ポート番号とメッセージをそれぞれ変更する。

package.json

Node.jsアプリケーションでは、package.json はプロジェクトの設定や依存パッケージ、実行スクリプトなどを管理するファイルです。

  • 例:app1/package.json
{
  "name": "app1", # アプリごとに名前を変更する
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  }
}

他のアプリ(app2, app3)は、nameをそれぞれ変更する。

4. Nginx構成例

nginx.confを編集しこの設定により、Nginxは順番に各アプリケーションへHTTPリクエストを振り分ける(今回はラウンドロビン)。

http {
  upstream backend {
    server host.docker.internal:3001;
    server host.docker.internal:3002;
    server host.docker.internal:3003;
  }

  server {
    listen 80;

    location / {
      proxy_pass http://backend;
    }
  }
}

5. ロードバランシングアルゴリズムの変更

Nginxは以下のようなロードバランシング方式をサポートしている。設定はupstreamディレクティブで指定する。

5.1 ラウンドロビン(デフォルト)

upstream backend {
  server host.docker.internal:3001;
  server host.docker.internal:3002;
  server host.docker.internal:3003;
}

5.2 重み付きラウンドロビン

upstream backend {
  server host.docker.internal:3001 weight=3;
  server host.docker.internal:3002 weight=1;
  server host.docker.internal:3003 weight=1;
}

5.3 least_conn(最小接続数優先)

upstream backend {
  least_conn;
  server host.docker.internal:3001;
  server host.docker.internal:3002;
  server host.docker.internal:3003;
}

5.4 ip_hash(クライアントIPベース)

upstream backend {
  ip_hash;
  server host.docker.internal:3001;
  server host.docker.internal:3002;
  server host.docker.internal:3003;
}

6. アルゴリズム選定指針

アルゴリズム 特徴 適用例
ラウンドロビン 最も基本的な方式で、リクエストを均等に分配する 簡単な負荷分散に適する
weight サーバーごとの処理能力に応じて重み付けで分配 異なる性能のサーバー群に対応可能
least_conn 現在の接続数が最も少ないサーバーに振り分ける 長時間接続が発生するアプリに適している
ip_hash クライアントIPごとに同一サーバーに振り分ける セッション維持が必要なサービスに適する

7. Node.js アプリ用 Dockerfile

各アプリケーションディレクトリ(例: app1, app2, app3)に以下の Dockerfile を配置します。3つのアプリはポート番号とレスポンスだけが違うだけなので、同じDockerfileを使えます。

FROM node:18-alpine
# 作業ディレクトリ作成
WORKDIR /app
# アプリケーションファイルのコピー
COPY . .
# 依存関係のインストール
RUN npm install
# アプリケーションの起動コマンド
CMD ["node", "index.js"]

8. docker-compose

docker-compose.yml は、複数のDockerコンテナをまとめて管理・起動するための構成ファイルです。
今回のように Nginx ロードバランサーと複数のアプリケーションコンテナを同時に構築・起動する際に非常に便利です。

version: "3"

services:
  nginx:
    image: nginx
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app1
      - app2
      - app3

  app1:
    build: ./app1
    ports:
      - "3001:3001"

  app2:
    build: ./app2
    ports:
      - "3002:3002"

  app3:
    build: ./app3
    ports:
      - "3003:3003"

9. おわりに

NginxはDocker環境でも手軽にリバースプロキシおよびロードバランサーとして機能させることができ、構成もシンプルで扱いやすいです。
アプリケーションの特性に応じて、最適なロードバランシング方式を選択することで、可用性とスケーラビリティの向上が見込めます。

Discussion