こんにちは。サイオステクノロジー OSS サポート担当 Y です。
今回は NGINX で Reverse Proxy サーバ (TCP/UDP Proxy) を構築してみました。(※以下の内容は NGINX 1.13.10 にて検証しています。)
■はじめに
さて、今回は NGINX を使った Reverse Proxy サーバのお話です。NGINX では、HTTP Proxy (ngx_https_* モジュール) と TCP/UDP Proxy (ngx_stream_* モジュール) の Reverse Proxy を構築することが可能です。(TCP/UDP Proxy はバージョン “1.9.0” 以降)
今回の検証では以下のモジュールを利用して TCP/UDP の Reverse Proxy を構築してみます。
また、HTTP Proxy の処理に関連するモジュールとして以下の様なものがあります。
■構成情報
それでは環境を構築してみます。今回は以下の様な環境を構築してみました。
====================================================================== | +-----[web1 (NGINX)] [client]-------------------[proxy (NGINX)]----|-----[web2 (NGINX)] | +-----[web3 (NGINX)] subnet : 192.168.10.0/24 | subnet : 192.168.20.0/24 ======================================================================
上記の通り、2つのネットワーク (192.168.10.0/24, 192.168.20.0/24) を用意した上で、各ノードに以下の様な IP アドレスを設定し、client から直接 web サーバにはアクセスできないようにしています。(proxy サーバには 2つの NIC を用意し、両方のネットワークに接続できるようにしています。)
============================== client : 192.168.10.100/24 proxy : 192.168.10.2/24 192.168.20.2/24 web1 : 192.168.20.11/24 web2 : 192.168.20.12/24 web3 : 192.168.20.13/24 ==============================
上記構成では、client から proxy サーバにリクエストを送信すると、バックエンドの web1 ~ web3 にリクエストが振り分けられ、いずれかの web サーバからレスポンスが返ってくることを想定しています。
それでは、上記構成を実現するための各 proxy, web サーバの設定について見ていきます。(※各ディレクティブの詳細等は割愛していますので、前述したドキュメントも併せてご参照下さい。)
初めに各 proxy, web サーバ共通の設定です。各サーバには以下の様な共通の設定ファイル 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 では、エラーログ等の基本的な設定を行なっています。そして、https コンテキスト内の include ディレクティブにて /usr/local/nginx/conf.d/ 配下にある *.https.conf ファイルを、stream コンテキスト内の include ディレクティブにて /usr/local/nginx/conf.d/ 配下にある *.stream.conf ファイルを読み込むように設定しています。
次に、各 proxy, web サーバ毎に異なる設定を行います。このサーバ毎に異なる設定を行う設定ファイルは、前述した /usr/local/nginx/conf.d/ 配下の *.stream.conf, *.https.conf ファイルとして作成し、共通設定を行なっている nginx.conf から読み込まれるようにします。
まずは proxy サーバの設定です。proxy サーバには以下の様な設定ファイルを配置します。
[stream_proxy.stream.conf (proxy サーバの設定)] upstream backend { server web1.example.com:80; server web2.example.com:80; server web3.example.com:80; } server { listen 0.0.0.0:80; proxy_pass backend; }
はじめに、upstream ディレクティブにて任意の名前 (backend) を付けたグループを作成しています。グループ内に登録するサーバは upstream コンテキスト内の server ディレクティブで指定します。(※少しややこしいのですが、後述する server ディレクティブとは異なる server ディレクティブです。)
今回はバックエンドのサーバである web1 ~ web3 を upstream コンテキスト内の server ディレクティブに指定しています。(upstream コンテキスト内の server ディレクティブには、FQDN や IP アドレスを指定することが可能です。)
また、server ディレクティブによるバックエンドサーバの指定では、リクエストを転送する先 (web サーバ) の port 番号 “80” を明示的に指定しています。
https コンテキストを利用した HTTP Proxy の場合、port の指定を省略すると “80” 番 port が利用されますが、stream コンテキストを利用した TCP/UDP Proxy の場合、port 番号を明示的に指定することが必須のようです。port 番号が指定されていない場合、以下の様なエラーが出力されました。
[root@proxy /]# nginx -t -c /usr/local/nginx/conf/nginx.conf nginx: [emerg] no port in upstream "web1.example.com" in /usr/local/nginx/conf.d/stream_proxy.stream.conf:2 nginx: configuration file /usr/local/nginx/conf/nginx.conf test failed
次に、server ディレクティブ (upstream コンテキスト内の server ディレクティブとは異なる server ディレクティブ) にて proxy サーバがリクエストを受け取るための設定 (listen) 及びバックエンドサーバにリクエスト転送する設定 (proxy_pass) を行います。
listen ディレクティブでは proxy サーバがクライアントからリクエストを受け付ける IP アドレス及び port 番号 (ソケット) を指定します。
proxy_pass ディレクティブではリクエストを転送する設定を行いますが、今回は upstream ディレクティブにて設定したグループ “backend” を転送先に指定するため、引数に “backend” を指定しています。
次に、各 web サーバ側の設定です。各 web サーバには以下の様な設定ファイルをそれぞれ配置します。各 web サーバは “/” 宛のリクエストに対して “/usr/local/nginx/html” 配下の index1.html ~ index3.html を返すだけのシンプルな設定にしています。
[upstream1.https.conf (web1 サーバの設定)] server { listen 0.0.0.0:80; server_name localhost; location / { root /usr/local/nginx/html; index index1.html; } }
[upstream2.https.conf (web2 サーバの設定)] server { listen 0.0.0.0:80; server_name localhost; location / { root /usr/local/nginx/html; index index2.html; } }
[upstream3.https.conf (web3 サーバの設定)] server { listen 0.0.0.0:80; server_name localhost; location / { root /usr/local/nginx/html; index index3.html; } }
また、各 index*.html の中身は以下の様になっており、どの web サーバからのレスポンスであるかが分かるようにしてあります。
[index1.html (web1 サーバ用のコンテンツ)] index of web1
[index2.html (web2 サーバ用のコンテンツ)] index of web2
[index3.html (web3 サーバ用のコンテンツ)] index of web3
各サーバの設定については以上です。
■検証
では、前述した設定の環境で実際に動作を確認してみます。
初めに proxy サーバ上から web1 ~ web3 サーバにリクエストを送信してみます。
[root@proxy /]# curl https://web1.example.com/ index of web1 [root@proxy /]# [root@proxy /]# curl https://web2.example.com/ index of web2 [root@proxy /]# [root@proxy /]# curl https://web3.example.com/ index of web3 [root@proxy /]# [root@proxy /]# curl https://192.168.20.11/ index of web1 [root@proxy /]# [root@proxy /]# curl https://192.168.20.12/ index of web2 [root@proxy /]# [root@proxy /]# curl https://192.168.20.13/ index of web3 [root@proxy /]#
すると、上記の通り各 web サーバからレスポンスが返ってくることが確認できます。前述した通り、レスポンスの内容から、どの web サーバからのレスポンスであるかが確認できます。
次に、client から各 web サーバに対して直接リクエストを送信してみます。
[root@client /]# curl https://web1.example.com/ curl: (6) Could not resolve host: web1.example.com; Unknown error [root@client /]# [root@client /]# curl https://web2.example.com/ curl: (6) Could not resolve host: web2.example.com; Unknown error [root@client /]# [root@client /]# curl https://web3.example.com/ curl: (6) Could not resolve host: web3.example.com; Unknown error [root@client /]# [root@client /]# curl https://192.168.20.11/ curl: (7) Failed connect to 192.168.20.11:80; Connection timed out [root@client /]# [root@client /]# curl https://192.168.20.12/ curl: (7) Failed connect to 192.168.20.12:80; Connection timed out [root@client /]# [root@client /]# curl https://192.168.20.13/ curl: (7) Failed connect to 192.168.20.13:80; Connection timed out [root@client /]#
上記の通り、client と各 web サーバは別のネットワークセグメントに配置されているため、直接リクエストを送信できないことが確認できます。
最後に、client から proxy サーバに対してリクエストを送信してみます。
[root@client /]# curl https://proxy.example.com/ index of web1 [root@client /]# [root@client /]# curl https://proxy.example.com/ index of web2 [root@client /]# [root@client /]# curl https://proxy.example.com/ index of web3 [root@client /]#
すると、同一の URL リクエスト (Reverse Proxy として動作しているサーバ “proxy” 宛のリクエスト) に対して、web1, web2, web3 からのレスポンスが返ってきていることが確認できます。
さらに、20回ほど連続でリクエストを送信してみます。
[root@client /]# for i in `seq 1 20` ; do > curl https://proxy.example.com/ > done index of web1 index of web2 index of web3 index of web1 index of web2 index of web3 index of web1 index of web2 index of web3 index of web1 index of web2 index of web3 index of web1 index of web2 index of web3 index of web1 index of web2 index of web3 index of web1 index of web2 [root@client /]#
すると、上記の通り upstream ディレクティブで設定した 3台の web サーバから順にレスポンスが返ってきており、Reverse Proxy として動作している NGINX が 3台の web サーバにリクエストを振り分けていることが確認できました。
■最後に
今回はシンプルな構成で NGINX を使った TCP/UDP Proxy を構築してみましたが、正直なところクライアントから見た結果が以前実施した HTTP Proxy の検証と全く同じなので、あまり HTTP Proxy と TCP/UDP Proxy の違いを認識できる検証にはなりませんでした… (設定内容に若干違いがありましたが)
そのため、またの機会に HTTP Proxy と TCP/UDP Proxy それぞれの違い等が分かるような検証にも挑戦してみようと思います。