Nginx Plus 共有メモリゾーンを試してみました。（１回目：HTTP セッション維持の確認）

前回Nginx Plusをインストールしましたので、次は共有メモリゾーンについての記事となります。

今回はクライアントからNginx Plus向けHTTP通信が発生した場合でもセッションが維持され通信できることを確認するところまでやってみたいと思います。

最終的な構成はHTTPS通信でかつ２台のNginx plusでVIPアドレスを持てるところまでやりたいと思いますが、Keepalivedによる冗長化はほかの記事でも紹介されているケースが多いためまずはNginx Plusならではのセッション維持 "sticky learn"および共有メモリゾーンについて試していきます。　

## 1. 構成 / 前提

※仮想環境（ESXi）を利用した前提となっています。もし物理環境で実施する場合は読み替えて頂ければと思います。

ESXi上に３つの仮想スイッチ(#0以外はアップリンクとなる物理アダプタは不要）を作成してください。仮想マシンとそれぞれのOSに割り当てたアドレスは下記の通りとなります。

<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/e5389b0a-78b5-ee52-fbbd-b5398431f1b7.png">

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

```bash
# hostnamectl コマンド　
hostnamectl set-hostname nginx-plus-01 
```

前提の最後となりますが、ipv6も無効化しています。方法はこちらの記事のipv6無効化をご確認ください。

https://qiita.com/nw-engineer/items/ce9ad6514c02c828682a#2-%E4%BA%8B%E5%89%8D%E8%A8%AD%E5%AE%9A


## 2. Tomcat 設定変更

tomcat#1/#2ともに動作検証用にパッケージを入れておきます。この時だけ、一時的にインターネットに疎通できるよう仮想スイッチを割り当ててください。

```bash
yum install -y tcpdump wget vim
```

次にCookieを払い出すための簡易的なアプリケーションサーバを作りたいと思います。Source作成前に以下のコマンドを実行します

```bash
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ファイルを作成します。

```java
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に明るくないのでこの辺りは突っ込みどころ満載かもしれませんが、とりあえず動けばよいレベルということでご容赦ください。

では、下記コマンドでコンパイルします。

```bash
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からアクセスしてください。

- http://10.2.0.xx:8080/example/SampleServlet

下記のような画面が出ていてCookieも払い出されていればOkです。
<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/379e553e-5323-9212-696f-7813d2b60c1a.png">

<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/2a08143f-075e-13aa-6dcb-868042261da0.png">


## 3. Nginx設定変更

DNS環境を準備していないので、hostsの定義を追記します。

```bash
vim /etc/hosts
10.20.0.1   tomcat01.local
10.20.0.2   tomcat02.local
```

何となくフォワードの設定は不要かもと思いましたがはまるのも嫌だったので、この時は入れておりました。

```bash
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号機は下記の通り設定しました。

```bash
 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をリスタートさせます。

```bash
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]

<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/db0efec0-95a4-474d-d14a-aee95d3019b5.png">

同一ブラウザから複数回アクセスしても同じtomcat-01が表示されてセッション維持が成功していることがわかります。
シークレットウィンドウで同じアドレスにアクセスするとtomcat-02が表示されました。

<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/a87ba161-f427-e5b3-a03b-9aecbfcecfaf.png">　

## 5 障害テスト

Nginx#1/#2間で共有メモリゾーンを利用したセッションミラーが成功しているか確認します。

取り急ぎ、どちらのNginxあてに通信しているか確認するため下記コマンドを実行します。※tcpdumpが入ってないければyum install tcpdumpでインストールしてから実施してください。　

```bash
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を停止します。

```bash
systemctl stop nginx
```

では、先ほどアクセスしたページを確認してみます。

<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/4c810642-22f9-519e-f328-6a1fbd596573.png">　

<img width="800" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2335933/85d61c49-e10f-2398-b272-ef36fa3432df.png">　

それぞれ同じページにアクセスできていることが確認できました！
ちなみにtcpdumpの結果は下記の通りでした。(tomcat-01向け)

```bash
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による冗長化を実現したいと思います。