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

こんにちは。サイオステクノロジー 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;
}

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

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_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/*.http.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 が活躍する場が増えていくのではないかと思います。

ご覧いただきありがとうございます! この投稿は役に立ちましたか? 役に立った 役に立たなかった

Be the first to comment

コメント投稿

Your email address will not be published.


*