Nginx Stream Moduleマスター:Database、Redis、MQTTのTCP/UDPロードバランシング

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Webだけではないロードバランシングの活用

以前、50人規模の管理システムが突然ボトルネックに陥り、冷や汗をかいたことがあります。当時、私はNginxを単なるPHPやNode.jsを動かすためのWebサーバーとしてしか見ていませんでした。

データベースやRedisのロードバランシング(Load Balancing – LB)といえば、すぐにHAProxyをインストールしていました。しかし、運用するツールが増えすぎると、トラブル発生時のデバッグが非常に困難になります。そんな時、トランスポート層(Layer 4)で接続を処理できる強力な機能、Nginx Stream Moduleに出会いました。

MySQLクラスターの過負荷や、何千ものIoTデバイスがMQTTブローカーに接続するための単一のアクセスポイント(Single Entry Point)が必要な場合、Nginx Streamは最もコスト効率が高く効果的なソリューションです。新しいソフトウェアを追加する代わりに、既存のNginxインスタンスを活用して、データベースやRedisのトラフィックを振り分けることができます。このアプローチにより、インフラの複雑さを大幅に軽減できます。

Nginx Stream Moduleの確認と有効化

設定をコピーしてすぐに実行するのは避けましょう。すべてのNginxビルドにStream Moduleが組み込まれているわけではありません。Ubuntuのデフォルトビルドでは、コアを軽量に保つためにこのモジュールが分離されていることがよくあります。システムが準備できているか確認するには、次のコマンドを入力します:

nginx -V 2>&1 | grep --color -o with-stream

画面に何も表示されない場合は、--with-streamモジュールが不足しています。Ubuntu/Debian環境では、追加は非常に簡単です:

sudo apt update && sudo apt install libnginx-mod-stream -y

重要な注意点:/etc/nginx/nginx.confファイルを開き、ファイルの冒頭にinclude /etc/nginx/modules-enabled/*.conf;という行があるか確認してください。この行がないと、インストールしたモジュールが読み込まれません。

標準設定:HTTPとの混同に注意

これはエンジニアがよくやってしまう「古典的な」ミスで、修正に一晩中かかることもあります。通常のWeb設定はhttp { ... }ブロック内に記述しますが、TCP/UDPの設定は独立している必要があり、httpブロックと同階層に配置します。標準的な構造は以下の通りです:

user www-data;
worker_processes auto;

events {
    worker_connections 1024;
}

# TCP/UDP専用セクション
stream {
    include /etc/nginx/conf.d/stream/*.conf;
}

http {
    # Webサイトの設定はここに記述
    ...
}

プロフェッショナルな管理のために、私は常に/etc/nginx/conf.d/stream/という専用ディレクトリを作成します。MySQLやRedisなどの各サービスは、その中に個別の設定ファイルとして保存します。

応用事例:MySQL、Redis、MQTT

1. MySQLクラスターのロードバランシング

システムに2つのMySQL Read-Replicaノードがあると仮定します。目標は、アプリケーションがポート3306でNginxの単一IPを参照するようにすることです。NginxはLeast Connectionsアルゴリズムを使用して、最も空いているノードにトラフィックを転送します。

# ファイル: /etc/nginx/conf.d/stream/mysql.conf
upstream mysql_servers {
    least_conn;
    server 10.0.0.10:3306 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:3306 max_fails=3 fail_timeout=30s;
}

server {
    listen 3306;
    proxy_pass mysql_servers;
    proxy_connect_timeout 5s;
    proxy_timeout 60s; # 重いクエリには非常に重要
}

実体験からのアドバイス: proxy_timeoutを低く設定しすぎないでください。40〜50秒かかるレポート用クエリがある場合、タイムアウトを30秒に設定すると、途中で接続が切断され、アプリケーションがエラーを出し続けます。

2. Redisのフェイルオーバー(予備構成)

キャッシュとして使用するRedisの場合、速度が最優先事項です。Sentinelを使用せずにバックアッププランを用意したい場合は、backupキーワードを使用します。予備ノードは、メインノードがダウンしたときにのみ動作します。

upstream redis_backend {
    server 10.0.0.20:6379;
    server 10.0.0.21:6379 backup;
}

server {
    listen 6379;
    proxy_pass redis_backend;
}

3. IoT向け10,000台のMQTTデバイスの制御

MQTTはTCPベースのプロトコルです。センサーデバイスの数が急増すると、単一のブローカーではCPUの負荷が高すぎることがよくあります。私はhashメカニズムを使用して、特定のデバイスが常に同じブローカーに安定して接続されるようにしています。

upstream mqtt_cluster {
    hash $remote_addr consistent;
    server 10.0.0.30:1883;
    server 10.0.0.31:1883;
}

server {
    listen 1883;
    proxy_pass mqtt_cluster;
}

セキュリティ:ブルートフォース対策とIP漏洩防止

Nginxをプロキシとして使用すると、背後のサーバーにはNginxのIPしか表示されません。監査目的などでアプリケーションがクライアントの実際のIPを必要とする場合は、Proxy Protocolについて調べてみてください。ただし、使用しているMySQLやRedisのバージョンがこのプロトコルをサポートしているか確認を忘れないでください。

データベースポートへのパスワード総当たり(ブルートフォース)攻撃を防ぐため、私は常に同時接続数を制限しています。例えば、各IPからDBへの同時接続を最大5つまでに制限します:

stream {
    limit_conn_zone $binary_remote_addr zone=db_limit:10m;

    server {
        listen 3306;
        limit_conn db_limit 5;
        proxy_pass mysql_servers;
    }
}

確認とモニタリング

修正が完了したら、nginx -tを実行して構文を確認します。問題がなければ、systemctl reload nginxでサービスをリロードします。Nginxが意図したポートでリッスンしているか確認するには、ssを使用します:

ss -tlnp | grep -E '3306|6379|1883'

デフォルトでは、Nginx Streamのログはかなり簡素です。送受信バイト数やセッションの生存時間を簡単に追跡できるように、独自のログフォーマットを定義することをお勧めします:

log_format tcp_stats '$remote_addr [$time_local] ' 
                     '$protocol $status $bytes_sent $bytes_received ' 
                     '$session_time';
access_log /var/log/nginx/stream_access.log tcp_stats;

Nginx Streamをマスターすることで、ミドルウェアに追加のリソースを割くことなく、複雑なインフラ課題をスマートに解決できました。この共有が、皆さんのシステムをよりスムーズに最適化する助けになれば幸いです。

Share: