前回Nginx Plusをインストールしましたので、次は共有メモリゾーンについての記事となります。
今回はクライアントからNginx Plus向けHTTP通信が発生した場合でもセッションが維持され通信できることを確認するところまでやってみたいと思います。
最終的な構成はHTTPS通信でかつ2台のNginx plusでVIPアドレスを持てるところまでやりたいと思いますが、Keepalivedによる冗長化はほかの記事でも紹介されているケースが多いためまずはNginx Plusならではのセッション維持 "sticky learn"および共有メモリゾーンについて試していきます。
1. 構成 / 前提
※仮想環境(ESXi)を利用した前提となっています。もし物理環境で実施する場合は読み替えて頂ければと思います。
ESXi上に3つの仮想スイッチ(#0以外はアップリンクとなる物理アダプタは不要)を作成してください。仮想マシンとそれぞれのOSに割り当てたアドレスは下記の通りとなります。

Nginx Plusがインストールされており、Tomcatについてもパッケージですでにインストール済み、Nginx/TomcatともにFireWalld/Selinuxについても無効化済みであることを前提として進めます。
また、ホスト名は下記の通り設定しています。hostnamectlコマンドで適宜変更してください。
- Nginx#1: nginx-plus-01
- Nginx#2: nginx-plus-02
- tomcat#1: tomcat-01
- tomcat#2: tomcat-02
# hostnamectl コマンド
hostnamectl set-hostname nginx-plus-01
前提の最後となりますが、ipv6も無効化しています。方法はこちらの記事のipv6無効化をご確認ください。
2. Tomcat 設定変更
tomcat#1/#2ともに動作検証用にパッケージを入れておきます。この時だけ、一時的にインターネットに疎通できるよう仮想スイッチを割り当ててください。
yum install -y tcpdump wget vim
次にCookieを払い出すための簡易的なアプリケーションサーバを作りたいと思います。Source作成前に以下のコマンドを実行します
yum install java-1.8.0-openjdk-devel.x86_64 java-1.8.0-openjdk-headless.x86_64 java-1.8.0-openjdk.x86_64
wget https://dlcdn.apache.org//commons/lang/binaries/commons-lang3-3.12.0-bin.tar.gz
tar zxvf commons-lang3-3.12.0-bin.tar.gz
mv *.jar /usr/share/tomcat/lib/
mkdir -p webapps/example/WEB-INF/classes/servlet
cd webapps
chown tomcat:tomcat -R example
cd ..
Sourceファイルを作成します。
vim example/WEB-INF/classes/servlet/SampleServlet.java
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.commons.lang3.RandomStringUtils;
import java.net.InetAddress;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;
@WebServlet("/SampleServlet")
public class SampleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String CookieValue = RandomStringUtils.randomAlphabetic(20);
Cookie cookie = new Cookie("TomcatCookie", CookieValue);
cookie.setMaxAge(60*60*24);
response.addCookie(cookie);
InetAddress ia = InetAddress.getLocalHost();
String hostname = ia.getHostName();
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Hello world</title>");
out.println("</head>");
out.println("<body>");
out.println("<p>Hello world</p>");
out.println("<p>" + hostname + "</p>");
out.println("</body>");
out.println("</html>");
}
}
サイトにアクセスした際にCookie(名前はTomcatCookie)がセットされるように構成しました。またアクセスしたサイトのセッション維持確認のため、ホスト名を表示させるようにしてみました。※正直、そんなにJavaに明るくないのでこの辺りは突っ込みどころ満載かもしれませんが、とりあえず動けばよいレベルということでご容赦ください。
では、下記コマンドでコンパイルします。
javac -classpath /usr/share/tomcat/lib/tomcat-servlet-3.0-api.jar:/usr/share/tomcat/lib/commons-lang3-3.12.0.jar:./webapps/example/WEB-INF/classes webapps/example/WEB-INF/classes/servlet/SampleServlet.java
systemctl restar tomcat
※もしこの時点でアクセス確認をしてみたい方はアップリンクにつながるような状態を作っていただき、下記URLからアクセスしてください。
下記のような画面が出ていてCookieも払い出されていればOkです。

3. Nginx設定変更
DNS環境を準備していないので、hostsの定義を追記します。
vim /etc/hosts
10.20.0.1 tomcat01.local
10.20.0.2 tomcat02.local
何となくフォワードの設定は不要かもと思いましたがはまるのも嫌だったので、この時は入れておりました。
vim /etc/sysctl.conf
net.ipv4.ip_forward=1
:wq
sysctl -p
では、nginx.confの設定を編集します。
vim /etc/nginx/nginx.conf
31 #include /etc/nginx/conf.d/*.conf;
32 upstream backend {
33 zone backend 64k;
34 server tomcat01.local:8080;
35 server tomcat02.local:8080;
36 sticky learn
37 create=$upstream_cookie_TomcatCookie
38 lookup=$cookie_TomcatCookie
39 zone=client_sessions:1m
40 timeout=1h
41 sync;
42 }
43 server {
44 listen 80;
45 location / {
46 proxy_pass http://backend/example/;
47 proxy_http_version 1.1;
48 proxy_set_header Upgrade $http_upgrade;
49 proxy_set_header Connection "upgrade";
50 proxy_set_header Cookie $http_cookie;
51 proxy_cookie_flags one httponly;
52 proxy_cookie_path / "/; HTTPOnly; Secure";
53 }
54 }
55 }
<-- 省略 -->
75 stream {
76 server {
77 listen 10.30.0.1:9000;
78 zone_sync;
79 zone_sync_server 10.30.0.1:9000;
80 zone_sync_server 10.30.0.2:9000;
81 }
82 }
31行目はnginx.confのみで記述したかったので、コメントアウトしました。
upstream backendは振り分け先のノード定義(tomcat#1/#2)になります。sticky learn はNginx#1/#2間の共有メモリゾーンを利用することを宣言しています。また、アップストリームサーバ(tomcat#1/#2)で発行されたCookieをクライアントに渡せるようにcreateおよびlookupを実行しています。
75行目から82行目はセッション共有メモリゾーンをやり取りする情報を記載しています。2号機は下記の通り設定しました。
75 stream {
76 server {
77 listen 10.30.0.2:9000;
78 zone_sync;
79 zone_sync_server 10.30.0.1:9000;
80 zone_sync_server 10.30.0.2:9000;
81 }
82 }
Nginxをリスタートさせます。
nginx -t
systemctl restart nginx\
4. アクセス確認
端末のhostsに以下の定義を追加してからブラウザでアクセスします。
C:\Windows\System32\drivers\etc\hosts
# 127.0.0.1 localhost
# ::1 localhost
10.2.0.112 www.nginx.local
10.2.0.113 www.nginx.local
[http://www.nginx.local/SampleServlet]

同一ブラウザから複数回アクセスしても同じtomcat-01が表示されてセッション維持が成功していることがわかります。
シークレットウィンドウで同じアドレスにアクセスするとtomcat-02が表示されました。
5 障害テスト
Nginx#1/#2間で共有メモリゾーンを利用したセッションミラーが成功しているか確認します。
取り急ぎ、どちらのNginxあてに通信しているか確認するため下記コマンドを実行します。※tcpdumpが入ってないければyum install tcpdumpでインストールしてから実施してください。
tcpdump -i ens224 port 8080 -nnn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens224, link-type EN10MB (Ethernet), capture size 262144 bytes
01:41:27.494155 IP 10.20.0.100.47176 > 10.20.0.1.8080: Flags [S], seq 1281942087, win 29200, options [mss 1460,sackOK,TS val 35009342 ecr 0,nop,wscale 7], length 0
01:41:27.494645 IP 10.20.0.1.8080 > 10.20.0.100.47176: Flags [S.], seq 4154850868, ack 1281942088, win 28960, options [mss 1460,sackOK,TS val 30313331 ecr 35009342,nop,wscale 7], length 0
01:41:27.494674 IP 10.20.0.100.47176 > 10.20.0.1.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 35009343 ecr 30313331], length 0
01:41:27.494719 IP 10.20.0.100.47176 > 10.20.0.1.8080: Flags [P.], seq 1:523, ack 1, win 229, options [nop,nop,TS val 35009343 ecr 30313331], length 522: HTTP: GET /example/SampleServlet HTTP/1.1
01:41:27.494859 IP 10.20.0.1.8080 > 10.20.0.100.47176: Flags [.], ack 523, win 235, options [nop,nop,TS val 30313332 ecr 35009343], length 0
01:41:27.503289 IP 10.20.0.1.8080 > 10.20.0.100.47176: Flags [P.], seq 1:338, ack 523, win 235, options [nop,nop,TS val 30313340 ecr 35009343], length 337: HTTP: HTTP/1.1 200 OK
01:41:27.503312 IP 10.20.0.100.47176 > 10.20.0.1.8080: Flags [.], ack 338, win 237, options [nop,nop,TS val 35009351 ecr 30313340], length 0
01:41:27.503437 IP 10.20.0.100.47176 > 10.20.0.1.8080: Flags [F.], seq 523, ack 338, win 237, options [nop,nop,TS val 35009351 ecr 30313340], length 0
01:41:27.503615 IP 10.20.0.1.8080 > 10.20.0.100.47176: Flags [F.], seq 338, ack 524, win 235, options [nop,nop,TS val 30313340 ecr 35009351], length 0
01:41:27.503628 IP 10.20.0.100.47176 > 10.20.0.1.8080: Flags [.], ack 339, win 237, options [nop,nop,TS val 35009352 ecr 30313340], length 0
1号機経由で通信しているようなので、Nginx#1のnginxを停止します。
systemctl stop nginx
では、先ほどアクセスしたページを確認してみます。
それぞれ同じページにアクセスできていることが確認できました!
ちなみにtcpdumpの結果は下記の通りでした。(tomcat-01向け)
01:45:11.911492 IP 10.20.0.101.50186 > 10.20.0.1.8080: Flags [S], seq 130561515, win 29200, options [mss 1460,sackOK,TS val 35193758 ecr 0,nop,wscale 7], length 0
01:45:11.911575 IP 10.20.0.1.8080 > 10.20.0.101.50186: Flags [S.], seq 2236893508, ack 130561516, win 28960, options [mss 1460,sackOK,TS val 30537748 ecr 35193758,nop,wscale 7], length 0
01:45:11.911774 IP 10.20.0.101.50186 > 10.20.0.1.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 35193758 ecr 30537748], length 0
01:45:11.911778 IP 10.20.0.101.50186 > 10.20.0.1.8080: Flags [P.], seq 1:523, ack 1, win 229, options [nop,nop,TS val 35193759 ecr 30537748], length 522: HTTP: GET /example/SampleServlet HTTP/1.1
01:45:11.911821 IP 10.20.0.1.8080 > 10.20.0.101.50186: Flags [.], ack 523, win 235, options [nop,nop,TS val 30537749 ecr 35193759], length 0
01:45:11.920842 IP 10.20.0.101.50186 > 10.20.0.1.8080: Flags [.], ack 338, win 237, options [nop,nop,TS val 35193768 ecr 30537758], length 0
01:45:11.920851 IP 10.20.0.1.8080 > 10.20.0.101.50186: Flags [P.], seq 1:338, ack 523, win 235, options [nop,nop,TS val 30537758 ecr 35193759], length 337: HTTP: HTTP/1.1 200 OK
01:45:11.921032 IP 10.20.0.101.50186 > 10.20.0.1.8080: Flags [F.], seq 523, ack 338, win 237, options [nop,nop,TS val 35193768 ecr 30537758], length 0
01:45:11.921468 IP 10.20.0.1.8080 > 10.20.0.101.50186: Flags [F.], seq 338, ack 524, win 235, options [nop,nop,TS val 30537758 ecr 35193768], length 0
01:45:11.921531 IP 10.20.0.101.50186 > 10.20.0.1.8080: Flags [.], ack 339, win 237, options [nop,nop,TS val 35193768 ecr 30537758], length 0
01:45:15.455954 IP 10.20.0.101.50194 > 10.20.0.1.8080: Flags [S], seq 2382834799, win 29200, options [mss 1460,sackOK,TS val 35197303 ecr 0,nop,wscale 7], length 0
01:45:15.456050 IP 10.20.0.1.8080 > 10.20.0.101.50194: Flags [S.], seq 3361694803, ack 2382834800, win 28960, options [mss 1460,sackOK,TS val 30541293 ecr 35197303,nop,wscale 7], length 0
01:45:15.456172 IP 10.20.0.101.50194 > 10.20.0.1.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 35197303 ecr 30541293], length 0
01:45:15.456203 IP 10.20.0.101.50194 > 10.20.0.1.8080: Flags [P.], seq 1:523, ack 1, win 229, options [nop,nop,TS val 35197303 ecr 30541293], length 522: HTTP: GET /example/SampleServlet HTTP/1.1
01:45:15.456269 IP 10.20.0.1.8080 > 10.20.0.101.50194: Flags [.], ack 523, win 235, options [nop,nop,TS val 30541293 ecr 35197303], length 0
01:45:15.458011 IP 10.20.0.1.8080 > 10.20.0.101.50194: Flags [P.], seq 1:338, ack 523, win 235, options [nop,nop,TS val 30541295 ecr 35197303], length 337: HTTP: HTTP/1.1 200 OK
01:45:15.458289 IP 10.20.0.101.50194 > 10.20.0.1.8080: Flags [.], ack 338, win 237, options [nop,nop,TS val 35197305 ecr 30541295], length 0
01:45:15.458313 IP 10.20.0.101.50194 > 10.20.0.1.8080: Flags [F.], seq 523, ack 338, win 237, options [nop,nop,TS val 35197305 ecr 30541295], length 0
01:45:15.458461 IP 10.20.0.1.8080 > 10.20.0.101.50194: Flags [F.], seq 338, ack 524, win 235, options [nop,nop,TS val 30541295 ecr 35197305], length 0
01:45:15.458517 IP 10.20.0.101.50194 > 10.20.0.1.8080: Flags [.], ack 339, win 237, options [nop,nop,TS val 35197305 ecr 30541295], length 0
01:45:17.320574 IP 10.20.0.101.50200 > 10.20.0.1.8080: Flags [S], seq 4286977024, win 29200, options [mss 1460,sackOK,TS val 35199167 ecr 0,nop,wscale 7], length 0
01:45:17.320622 IP 10.20.0.1.8080 > 10.20.0.101.50200: Flags [S.], seq 2592438487, ack 4286977025, win 28960, options [mss 1460,sackOK,TS val 30543157 ecr 35199167,nop,wscale 7], length 0
01:45:17.320690 IP 10.20.0.101.50200 > 10.20.0.1.8080: Flags [.], ack 1, win 229, options [nop,nop,TS val 35199168 ecr 30543157], length 0
01:45:17.320880 IP 10.20.0.101.50200 > 10.20.0.1.8080: Flags [P.], seq 1:523, ack 1, win 229, options [nop,nop,TS val 35199168 ecr 30543157], length 522: HTTP: GET /example/SampleServlet HTTP/1.1
01:45:17.320885 IP 10.20.0.1.8080 > 10.20.0.101.50200: Flags [.], ack 523, win 235, options [nop,nop,TS val 30543158 ecr 35199168], length 0
01:45:17.329274 IP 10.20.0.1.8080 > 10.20.0.101.50200: Flags [P.], seq 1:338, ack 523, win 235, options [nop,nop,TS val 30543166 ecr 35199168], length 337: HTTP: HTTP/1.1 200 OK
01:45:17.329361 IP 10.20.0.101.50200 > 10.20.0.1.8080: Flags [.], ack 338, win 237, options [nop,nop,TS val 35199176 ecr 30543166], length 0
01:45:17.329437 IP 10.20.0.101.50200 > 10.20.0.1.8080: Flags [F.], seq 523, ack 338, win 237, options [nop,nop,TS val 35199176 ecr 30543166], length 0
01:45:17.329638 IP 10.20.0.1.8080 > 10.20.0.101.50200: Flags [F.], seq 338, ack 524, win 235, options [nop,nop,TS val 30543166 ecr 35199176], length 0
01:45:17.329708 IP 10.20.0.101.50200 > 10.20.0.1.8080: Flags [.], ack 339, win 237, options [nop,nop,TS val 35199177 ecr 30543166], length 0
送信元のNginxは#2になっていますが、降り先はtomcat#1になっていることがわかります。
その他
今回はHTTPのセッション維持についてみていきました。次回はこちらをHTTPS化させて上でKeepalivedによる冗長化を実現したいと思います。