NGINX を使った TCP/UDP Proxy の構築 (その 2)

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【4/18開催】VSCode Dev Containersで楽々開発環境構築祭り〜Python/Reactなどなど〜
Visual Studio Codeの拡張機能であるDev Containersを使ってReactとかPythonとかSpring Bootとかの開発環境をラクチンで構築する方法を紹介するイベントです。
https://tech-lab.connpass.com/event/311864/

こんにちは。サイオステクノロジー OSS サポート担当 Y です。

今回は NGINX で PostgreSQL へのアクセス (Slave サーバに対する参照アクセスのみ) を負荷分散する Reverse Proxy サーバ (TCP/UDP Proxy) を構築してみました。(※以下の内容は NGINX 1.13.12/PostgreSQL 10.4 にて検証しています。)

■はじめに

前回 NGINX を使って TCP/UDP Proxy を構築してみましたが、バックエンドが Web サーバであり、前々回 の HTTP Proxy との違いが分かり辛い内容になってしまっていました。

そこで、今回は Web サーバではなく、DB サーバ (PostgreSQL) をバックエンドにして TCP/UDP Proxy を構築してみました。

今回の検証では以下のモジュールを利用して TCP/UDP の Reverse Proxy を構築してみます。

ngx_stream_core_module

ngx_stream_upstream_module

ngx_stream_proxy_module

■構成情報

今回は以下の様な環境を構築してみました。

======================================================================
                    |              [DB0 (PostgreSQL, Master)]--->+
                    |                                            |
                    |              (↓↓レプリケーション↓↓)         |
                    |                                            |
                    |          +---[DB1 (PostgreSQL, Slave1)]<---+
[client]-----[proxy (NGINX)]---|---[DB2 (PostgreSQL, Slave2)]<---+
                    |          +---[DB3 (PostgreSQL, Slave3)]<---+
 subnet             | subnet
 192.168.10.0/24    | 192.168.20.0/24
======================================================================

上記の通り、2つのネットワーク (192.168.10.0/24, 192.168.20.0/24) を用意した上で、各ノードに以下の様な IP アドレスを設定し、client から直接 DB サーバにはアクセスできないようにしています。(proxy サーバには 2つの NIC を用意し、両方のネットワークに接続できるようにしています。)

============================================================
client       : 192.168.10.100/24 (client.example.com)
proxy        : 192.168.10.40/24 (proxy.example.com) 
               192.168.20.50/24
DB0 (Master) : 192.168.20.30/24 (pg_master.example.com)
DB1 (Slave1) : 192.168.20.31/24 (pg_slave1.example.com)
DB2 (Slave2) : 192.168.20.32/24 (pg_slave2.example.com)
DB2 (Slave3) : 192.168.20.33/24 (pg_slave3.example.com)
============================================================

また、今回は PostgreSQL のストリーミングレプリケーション環境を構築し、各 Slave サーバを NGINX による Reverse Proxy のバックエンドとして設定します。NGINX を使って “クライアントから PostgreSQL の Slave サーバに対する接続” を振り分けるイメージ (いわゆる DB の参照負荷分散) です。

上記構成では、client から proxy サーバにリクエストを送信すると、バックエンドの DB1 ~ DB3 にリクエストが振り分けられ、いずれかの DB (Slave) サーバからレスポンスが返ってくることを想定しています。

それでは、上記構成を実現するための各 proxy, DB サーバの設定について見ていきます。(※各ディレクティブの詳細等は割愛していますので、前述したドキュメントも併せてご参照下さい。また、今回は NGINX に主眼を置いた内容なので、PostgreSQL のストリーミングレプリケーション環境の構築方法等については割愛します。)

まず初めに、proxy サーバの設定です。proxy サーバには以下の様な設定ファイル nginx.conf (/usr/local/nginx/conf/nginx.conf) を配置します。

[nginx.conf]
user  nginx;
worker_processes  1;

error_log  logs/error.log  notice;
pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

https {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$https_referer" '
                      '"$https_user_agent" "$https_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /usr/local/nginx/conf.d/*.https.conf;
}

stream {
    include /usr/local/nginx/conf.d/*.stream.conf;
}

前回前々回と同じように、この nginx.conf では、エラーログ等の基本的な設定を行なっています。そして、stream コンテキスト内の include ディレクティブにて /usr/local/nginx/conf.d/ 配下にある *.stream.conf ファイルを読み込むように設定しています。

次に、クライアントからのアクセスを各 DB に振り分けるための設定を行います。この設定を行う設定ファイルは、前述した /usr/local/nginx/conf.d/ 配下の *.stream.conf ファイルとして作成し、エラーログ等の基本的な設定を行なっている nginx.conf から読み込まれるようにします。

[db_proxy.stream.conf]
upstream backend {
    server pg_slave1.example.com:5432;
    server pg_slave2.example.com:5432;
    server pg_slave3.example.com:5432;
}

server {
    listen       0.0.0.0:5555;
    proxy_pass   backend;
}

はじめに、upstream ディレクティブにて任意の名前 (backend) を付けたグループを作成しています。グループ内に登録するサーバは upstream コンテキスト内の server ディレクティブで指定します。(※少しややこしいのですが、後述する server ディレクティブとは異なる server ディレクティブです。)

今回はバックエンドのサーバである DB1 ~ DB3 を upstream コンテキスト内の server ディレクティブに指定しています。(upstream コンテキスト内の server ディレクティブには、FQDN や IP アドレスを指定することが可能です。)

また、upstream コンテキスト内の server ディレクティブによるバックエンドサーバの指定では、リクエストを転送する先の port 番号 “5432” (DB サーバ上 PostgreSQL が listen している port) を指定しています。

次に、server ディレクティブ (upstream コンテキスト内の server ディレクティブとは異なる server ディレクティブ) にて proxy サーバがクライアントからのリクエストを受け取るための設定 (listen) 及びバックエンドサーバにリクエスト転送する設定 (proxy_pass) を行います。

listen ディレクティブでは proxy サーバがクライアントからリクエストを受け付ける IP アドレス及び port 番号 (ソケット) を指定します。

proxy_pass ディレクティブではリクエストを転送する設定を行いますが、今回は upstream ディレクティブにて設定したグループ “backend” を転送先に指定するため、引数に “backend” を指定しています。

Reverse Proxy (TCP/UDP Proxy) として動作させる NGINX の設定は上記で終了です。

前述した通り、今回 PostgreSQL 側の設定やレプリケーション環境の構築方法については割愛するのですが、後述する動作検証のために各 DB (PostgreSQL) の Slave サーバで以下の様な設定を行なっておきます。

[DB1 (Slave1) サーバの postgresql.conf 抜粋]
# Add settings for extensions here
user_conf.slave_name='slave1'
[DB2 (Slave2) サーバの postgresql.conf 抜粋]
# Add settings for extensions here
user_conf.slave_name='slave2'
[DB3 (Slave3) サーバの postgresql.conf 抜粋]
# Add settings for extensions here
user_conf.slave_name='slave3'

PostgreSQL のストリーミングレプリケーションは、バイナリレベル (物理的なファイルレベル) でデータをレプリケーションするので、基本的に各 Slave の内容は完全に一致します。

しかし、今回の検証ではクライアントから Reverse Proxy (TCP/UDP Proxy) 経由で PostgreSQL に接続した際に、どの Slave サーバに接続が振り分けられているかを確認する必要があるため、上記の通り各 Slave サーバの設定ファイルにユーザ定義の設定値 (user_conf.slave_name) 及び値 (slave1 ~ slave3) を設定しておきます。

各サーバの設定については以上です。

■検証

では、前述した設定の環境で実際に動作を確認してみます。

まず、Master の PostgreSQL にてテスト用のテーブル “hoge” を作成し、適当な値 (a = 0) のレコードを一行格納します。

postgres=# CREATE TABLE hoge (a int);
CREATE TABLE
postgres=# 
postgres=# INSERT INTO hoge VALUES (0);
INSERT 0 1
postgres=# 
postgres=# SELECT * FROM hoge;
 a 
---
 0
(1 row)

次に、各 Slave にデータがレプリケーションされているかを確認します。

[postgres@pg_slave1 ~]$ hostname
pg_slave1.example.com
[postgres@pg_slave1 ~]$ 
[postgres@pg_slave1 ~]$ psql -h localhost -p 5432 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave1
(1 row)

[postgres@pg_slave1 ~]$ 
[postgres@pg_slave1 ~]$ psql -h localhost -p 5432 -c "SELECT * FROM hoge"
 a 
---
 0
(1 row)
[postgres@pg_slave2 ~]$ hostname
pg_slave2.example.com
[postgres@pg_slave2 ~]$ 
[postgres@pg_slave2 ~]$ psql -h localhost -p 5432 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave2
(1 row)

[postgres@pg_slave2 ~]$ 
[postgres@pg_slave2 ~]$ psql -h localhost -p 5432 -c "SELECT * FROM hoge"
 a 
---
 0
(1 row)
[postgres@pg_slave3 ~]$ hostname
pg_slave3.example.com
[postgres@pg_slave3 ~]$ 
[postgres@pg_slave3 ~]$ psql -h localhost -p 5432 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave3
(1 row)

[postgres@pg_slave3 ~]$ 
[postgres@pg_slave3 ~]$ psql -h localhost -p 5432 -c "SELECT * FROM hoge"
 a 
---
 0
(1 row)

上記の通り、各 Slave にて Master で作成したテーブル “hoge” 及び INSERT したデータ (a = 0 のレコード) がレプリケーションされていることが確認できます。

それでは、NGINX にて構築した Reverse Proxy (TCP/UDP Proxy) 経由で各 Slave に接続し、DB 内のデータを参照できるか確認してみます。

[postgres@client ~]$ hostname
client.example.com
[postgres@client ~]$ 
[postgres@client ~]$ psql -h proxy.example.com -p 5555 -c "SELECT * FROM hoge"
 a 
---
 0
(1 row)

上記の通り、client から “proxy サーバ上の NGINX が listen している 5555 port” に対して psql で接続すると、バックエンドにある PostgreSQL からの応答を得る (“hoge” テーブルの内容を参照する) ことができました。

今度は、NGINX にて各 PostgreSQL の Slave に接続が振り分けられているかを確認してみます。

[postgres@client ~]$ hostname
client.example.com
[postgres@client ~]$ 
[postgres@client ~]$ psql -h proxy.example.com -p 5555 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave2
(1 row)

[postgres@client ~]$ 
[postgres@client ~]$ psql -h proxy.example.com -p 5555 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave3
(1 row)

[postgres@client ~]$ 
[postgres@client ~]$ psql -h proxy.example.com -p 5555 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave1
(1 row)

[postgres@client ~]$ 
[postgres@client ~]$ psql -h proxy.example.com -p 5555 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave2
(1 row)

[postgres@client ~]$ 
[postgres@client ~]$ psql -h proxy.example.com -p 5555 -c "show user_conf.slave_name"
 user_conf.slave_name 
----------------------
 slave3
(1 row)

接続先の Slave を確認するために設定しておいた “user_conf.slave_name” の値を取得すると、上記の様に各 Slave に対して順番に接続している (NGINX が順番にリクエストを振り分けている) ことが確認できます。

■最後に

今回は PostgreSQL をバックエンドにして NGINX の TCP/UDP Proxy 環境を構築してみました。stream ディレクティブを利用した TCP/UDP Proxy では、パケットをそのままバックエンドに中継 (分散) してくれるので、今回検証した PostgreSQL のように、HTTP 以外の接続も負荷分散することができます。

PostgreSQL の場合は TCP を利用した接続ですが、DNS 等の UDP を利用した接続を負荷分散することも可能であり、様々なシステムでクライアント数が増加している昨今において、今後 NGINX が活躍する場が増えていくのではないかと思います。

アバター画像
About サイオステクノロジーの中の人 41 Articles
サイオステクノロジーで働く中の人です。
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる