NGINXで構築したWebサーバにロードバランサ機能を設定する。

# はじめに

本投稿はこちらの投稿の続編にあたります。
ソースや設定ファイルは差分の表記となりますので、ベースとなる成果物についての説明は本投稿においては割愛します。下記を参照願います。

https://qiita.com/shinya_sun_sun/items/d9f4b7eb4d29ee99b61d

# 目的

NGINXのロードバランサ機能の概要を理解、および、構築ができるようになる。

# 背景

参画したシステムではほぼ必ずと言っていいほどロードバランサの構成が取られていたが、インフラ側の構築経験がなかったため、自分で構築を体験してみたかった。NGINXを利用したWebサーバ構築を前回投稿していたため、そのソースコードを活用し知識を得ることができないかと思い、構築、投稿するに至った。

# ロードバランサとは

負荷分散をする機能のことを言います。
負荷分散をすることにより以下を得ることができます。
- サーバリソース使用率の最適化
- スループットの最大化
- レイテンシーの低減
- 耐障害性の向上

NGINXのロードバランサ機能には以下の分散方式が用意されています。

- round-robin
リクエストをノード1つ1つに対し順番振り分けます。
- least-connected
リクエストをアクティブ、かつ、接続数が最も少ないノードに振り分けます。
- ip-hash
クライアントのIPアドレスと接続先ノードを紐付け、クライアントのリクエストを常に同じノードへ振り分けます。

振り分けに関しては、細かな設定も用意されています。詳細は公式ドキュメントを参照願います。

https://nginx.org/en/docs/http/load_balancing.html

# 全体像

今回はこんな構成です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38724/dc82dbff-142b-3ccc-0cbf-3db8c8272a55.png)

# 環境

os: macOS Monterey 12.5
machine: MacBook Air(Retina, 13-inch, 2018)
cpu: 1.6GHz デュアルコアIntel Core i5

Dockerデスクトップをインストール済みです。

```zsh
% brew list --version | grep docker
docker 4.11.1,84025
```

# 今回のファイル

https://github.com/MamedenQ/docker_nginx_tomcat/tree/v2

# 内容

以降は、リポジトリのv1タグをベースに修正点の説明をしていきます。

https://github.com/MamedenQ/docker_nginx_tomcat/tree/v1

自身で動作させたい場合、リポジトリクローン後、本投稿の修正内容が反映されている`v2`タグをチェックアウトし、`NGINX設定を修正`セクションで説明している`nginx-server/nginx.conf`の振り分け先IPアドレスを環境に合わせるだけで後は手順通りに動かせます。

## Dockerfileの作成、修正

### NGINX
ポートの指定は`docker-compose.xml`に移行のため削除しました。
`docker-compose.xml`で設定ファイルが入っているディレクトリをマウントするようにしていましたが、これをファイルコピーに変更しました。（どちらでもよかったですが、なんとなくこっちにしました）。
設定ファイルの説明は後述します。

```diff_docker:nginx-server/Dockerfile
FROM nginx:latest

- EXPOSE 80
- 
- RUN rm -rf /etc/nginx/conf.d
+ RUN rm -f /etc/nginx/conf.d/*
+ COPY conf.d/nginx.conf /etc/nginx/conf.d
+ COPY nginx.conf /etc/nginx
```

### Tomcat-1
ポートの指定は`docker-compose.xml`に移行のため削除しました。
```diff_docker:tomcat-server1/Dockerfile
FROM tomcat:latest

- EXPOSE 8080
```

### Tomcat-2
Tomcatの設定で指定しているデフォルトのポート8080から8081に修正しました。
編集済みファイルをコピーする選択肢もありましたが、sedで済ませました。

```Dockerfile:tomcat-server2/Dockerfile(新規作成)
FROM tomcat:latest

RUN sed -i 's/Connector port="8080"/Connector port="8081"/' /usr/local/tomcat/conf/server.xml
```

### Tomcat-3
`Tomcat-2`と同様に8082ポートに修正しました。

```Dockerfile:tomcat-server3/Dockerfile(新規作成)
FROM tomcat:latest

RUN sed -i 's/Connector port="8080"/Connector port="8082"/' /usr/local/tomcat/conf/server.xml
```

## NGINX設定を修正

`upstream apps`がロードバランサで振り分けられる先の指定となります。
IPアドレスは環境に合わせ修正してください。
振り分け先以外、特に指定がなければラウンドロビンが指定されます。今回はラウンドロビン設定で進めます。

```nginx:nginx-server/nginx.conf(新規作成)
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/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  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    upstream apps {
        server 192.168.x.x:8080;
        server 192.168.x.x:8081;
        server 192.168.x.x:8082;
    }

    include /etc/nginx/conf.d/*.conf;
}
```

リクエスト分散するため、URLの直接指定から`nginx-server/nginx.conf`で定義した分散先となるよう修正します。

```diff_nginx:nginx-server/conf.d/nginx.conf
server {
    listen 80;
    server_name localhost;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
-       proxy_pass http://192.168.x.x:8080;
+       proxy_pass http://apps;
        # proxy_pass http://host.docker.internal:8080;
    }
}
```

## コンテンツをアプリごとに用意

tomcat-server-1,2,3それぞれにコンテンツを用意します。
リクエストした際にどのノードにアクセスしたかわかるよう、番号を変えるようにします。
以下はtomcat-server-1アプリのコンテンツの例です。()の中の番号がそれぞれ変わります。
```html:tomcat-server1/webapps/sample/sample.html
<h1>hello sample.html (1)</h1>
```

## docker-compose.ymlの修正
サービスごとに説明します。
- nginx-server
NGINXのDockerfileにて設定ファイルをコピーするように修正したため、ボリュームに指定していた設定ファイルが配置されているディレクトリ`conf.d`を外してます。
ポートの指定は本ファイルの定義に集約するようにしたため80ポートの指定を追加しています。
- tomcat-server-1,2,3
基本的には同じ構成となります。特に説明を入れるところはないので、ソース差分のみ掲載とします。

```diff_xml
version: "3"
services:
    nginx-server:
        build:
            context: ./nginx-server
        volumes:
-           - ./nginx-server/conf.d:/etc/nginx/conf.d
            - ./nginx-server/log:/var/log/nginx
+       expose:
+           - 80
        ports:
            - 80:80

-   tomcat-server:
+   tomcat-server-1:
        build:
-           context: ./tomcat-server
+           context: ./tomcat-server1
        volumes:
-           - ./tomcat-server/webapps:/usr/local/tomcat/webapps
-           - ./tomcat-server/log:/usr/local/tomcat/logs
+           - ./tomcat-server1/webapps:/usr/local/tomcat/webapps
+           - ./tomcat-server1/log:/usr/local/tomcat/logs
+       expose:
+           - 8080
        ports:
            - 8080:8080
+   tomcat-server-2:
+       build:
+           context: ./tomcat-server2
+       volumes:
+           - ./tomcat-server2/webapps:/usr/local/tomcat/webapps
+           - ./tomcat-server2/log:/usr/local/tomcat/logs
+       expose:
+           - 8081
+       ports:
+           - 8081:8081
+
+   tomcat-server-3:
+       build:
+           context: ./tomcat-server3
+       volumes:
+           - ./tomcat-server3/webapps:/usr/local/tomcat/webapps
+           - ./tomcat-server3/log:/usr/local/tomcat/logs
+       expose:
+           - 8082
+       ports:
+           - 8082:8082
```

## コンテナをビルド

```
% docker-compose build
```

## コンテナを起動

```zsh
% docker-compose up -d
```

## 実行結果

`http://localhost/sample/sample.html`へアクセスを行い、3つのノードが順番にアクセスされるかを検証してみます。
リロードをするたびにリクエスト先のHTMLが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38724/35334150-f42f-e02d-2923-644cf8592809.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38724/75e2975c-acbe-f5cd-c060-ac72bafc51d7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38724/c2662249-888e-2310-436c-16dd78c4e905.png)
ちゃんと分散されてますね。

# コンテナの起動を終了

```
% docker-compose down
```

# 最後に

業務で構築していたものはもっと高度なものだったかもしれないが、ロードバランサを構築するという目的は達成できた。

疑問に思ったのは、今回は振り分け先が3ノードだったため設定ファイルを書き換えることで対応したが、大量アクセスを想定し大量のノードを構築する場合、ポート等、ノードごとで固有の設定はどうやって効率よく設定ができるのだろうと思った。何かループ処理や変数などのロジックを組み込めるのか、最適なやり方があれば今後の課題としてクリアにしておきたい。