NGINX を使った TCP/UDP Proxy の構築

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

こんにちは。サイオステクノロジー 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 を構築してみます。

(ngx_stream_core_module)

(ngx_stream_upstream_module)

(ngx_stream_proxy_module)

また、HTTP Proxy の処理に関連するモジュールとして以下の様なものがあります。

(ngx_https_core_module)

(ngx_https_upstream_module)

(ngx_https_proxy_module)

■構成情報

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

======================================================================
                              |               +-----[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 それぞれの違い等が分かるような検証にも挑戦してみようと思います。

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

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

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


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



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

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる