【nginx】nginxのチューニングとローカルプロキシの話

# はじめに
速いhttpサーバが欲しかったので昔々10年くらい前にやったnginxのチューニングを再現してみようと思った。
あと、実際その時フロントエンドサーバのパフォーマンス改善用として作ったローカルプロキシも紹介する。
なお、チューニングに正解はない。これはたまたま当たったときの話。

# nginxが速いっていう話
### 今回の構成
シンプルな感じに同一ネットワークでt3.microを使いapache1とnginx1とclient1を作った。
名前のとおりapache1はapache、nginx1はnginxを動かしているサーバ。
client1は文字通りclientとして各EC2ヘhttpリクエストを出すサーバ。
httpリクエスト(ベンチマーク)は手軽にabコマンド使う。
abコマンドはデフォルトだとKeepAlive使わないので-kオプションを使う。
ちなみに登場人物全員selinuxはdisabledにしている。
![Untitled Diagram (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092362/680ad034-6e2b-4832-d29a-407bb24816bc.jpeg)

### デフォルト設定(http)
まずはそれぞれデフォルトの状態で比較
デフォルトでもうnginxが速い。昔はデフォルトだとここまで差はついてなかった気がする。

```ruby:apacheデフォルト
# ab -c 10 -n 10000 -k http://apache1/index.html |grep "Requests per second"
Requests per second:    15801.16 [#/sec] (mean)
```

```ruby:nginxデフォルト
# ab -c 10 -n 10000 -k http://nginx1/index.html |grep "Requests per second"
Requests per second:    39050.75 [#/sec] (mean)
```

### デフォルト設定(https)
httpsはこんな感じ。2倍くらい違う。

```ruby:apacheデフォルト(https)
# ab -c 10 -n 10000 -k https://apache1/index.html |grep "Requests per second"
Requests per second:    11478.42 [#/sec] (mean)
```

```ruby:nginxデフォルト(https)
# ab -c 10 -n 10000 -k https://nginx1/index.html |grep "Requests per second"
Requests per second:    21561.81 [#/sec] (mean)
```

### nginxチューニング
nginx1のnginx.confを↓に差し替える。

```ruby:nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log crit ;
pid /run/nginx.pid;
worker_rlimit_nofile 100000;

events {
    worker_connections 32000;
    multi_accept        on;
    use                 epoll;
}

http {
    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  off;

    sendfile           on;
    keepalive_timeout  30;
    access_log        off;
    tcp_nopush         on;
    tcp_nodelay        on;
    reset_timedout_connection on;
    client_body_timeout 10;
    send_timeout 2;
    keepalive_requests 100000;
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    types_hash_max_size 2048;
    gzip on;
    gzip_disable msie6;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 32 16k;
    gzip_min_length 250;
    gzip_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  _;
        root         /usr/share/nginx/html;
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

# Settings for a TLS enabled server.

    server {
        listen       443 ssl http2 default_server;
        listen       [::]:443 ssl http2 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        ssl_certificate "/etc/pki/server.crt";
        ssl_certificate_key "/etc/pki/server.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers PROFILE=SYSTEM;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

}
```
serverディレクティブはデフォルトから変更していないので任意で大丈夫。
worker_processesが1なのは意図しているもの。チューニングだけが目的であればautoでも構わない。
worker_connectionはもっと少なくてもいい実際そんなに使わない(使えない？)ので。

あと忘れずに以下の設定も入れてnginxをrestartする。

```ruby:kernel関連の設定
ulimit -n 100000
sysctl -w net.ipv4.ip_local_port_range='1024 65535'
echo "100000" > /proc/sys/fs/file-max
```

### チューニング後
改めて比較するためにデフォルト時のものも記載する。
一番下が今回のチューニングした後のもの。
nginxのデフォルトから50％前後伸びている。apacheのデフォルトからは4倍。

```ruby:apacheデフォルト
# ab -c 10 -n 10000 -k http://apache1/index.html |grep "Requests per second"
Requests per second:    15801.16 [#/sec] (mean)
```

```ruby:nginxデフォルト
# ab -c 10 -n 10000 -k http://nginx1/index.html |grep "Requests per second"
Requests per second:    39050.75 [#/sec] (mean)
```

```ruby:nginxチューニング後
# ab -c 10 -n 10000 -k http://nginx1/index.html |grep "Requests per second"
Requests per second:    60312.90 [#/sec] (mean)
```


### チューニング後(https)
httpsも比較してみる。
nginxのデフォルトから2倍以上伸びてしまっているがこれは出来すぎ。
ブレ幅もあるので少なくともhttpと同様にnginxのデフォルトから50%↑くらい伸びているくらいの認識。

```ruby:apacheデフォルト(https)
# ab -c 10 -n 10000 -k https://apache1/index.html |grep "Requests per second"
Requests per second:    11478.42 [#/sec] (mean)
```

```ruby:nginxデフォルト(https)
# ab -c 10 -n 10000 -k https://nginx1/index.html |grep "Requests per second"
Requests per second:    21561.81 [#/sec] (mean)
```

```ruby:nginxチューニング後(https)
# ab -c 10 -n 10000 -k https://nginx1/index.html |grep "Requests per second"
Requests per second:    50646.50 [#/sec] (mean)
```

### 大きいファイル
比較はここまでの予定だったが気になったのでindex.htmlを40kbくらいのファイルにしてみた。
こちらもhttps。ブレ幅はやはりあるがファイルのサイズによって大きく倍率が変わることはなかった。
すくなくともファイルサイズで著しく性能が変わるチューニングではなさそうだ。

```ruby:apacheデフォルト(40k)
# ab -c 10 -n 10000 -k https://apache1/index.html |grep "Requests per second"
Requests per second:    6686.85 [#/sec] (mean)
```

```ruby:nginxデフォルト(40k)
# ab -c 10 -n 10000 -k https://nginx1/index.html |grep "Requests per second"
Requests per second:    14857.72 [#/sec] (mean)
```

```ruby:nginxチューニング後(40k)
# ab -c 10 -n 10000 -k https://nginx1/index.html |grep "Requests per second"
Requests per second:    18544.55 [#/sec] (mean)
```

チューニングの話はここまで。

# ローカルプロキシ
ここからは実際にフロントエンドサーバに適用した時の話。
先ほど変更したnginx.confを以下のようにしてローカルプロキシ化する。

```ruby:nginxチューニング後(40k)
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log crit ;
pid /run/nginx.pid;
worker_rlimit_nofile 100000;

events {
    worker_connections 32000;
    multi_accept        on;
    use                 epoll;
}

http {
    log_format  main  ' -  [] "" '
                      '  "" '
                      '"" ""';
    access_log  off;

    sendfile           on;
    keepalive_timeout  30;
    access_log        off;
    tcp_nopush         on;
    tcp_nodelay        on;
    reset_timedout_connection on;
    client_body_timeout 10;
    send_timeout 2;
    keepalive_requests 100000;
    open_file_cache max=200000 inactive=20s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
    types_hash_max_size 2048;
    gzip on;
    gzip_disable msie6;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 32 16k;
    gzip_min_length 250;
    gzip_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    server {
        listen  8080 default_server;
        server_name  _;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
     }

    server {
        listen  8443 ssl http2 default_server;
        ssl on;
        ssl_certificate /etc/pki/server.crt;
        ssl_certificate_key /etc/pki/server.key;
        server_name  _;
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers PROFILE=SYSTEM;
        ssl_prefer_server_ciphers on;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
     }

     upstream backend {
          server 127.0.0.1:80;
          keepalive 10240;
     }
}

```



カーネル周りの分もふくめてちゃんと[job](https://github.com/vildi-5567/rundeck-scm/blob/master/nginx%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7-7f9f2e50-52ca-4b39-bffc-ba542c1b5766.xml)にしている↓。




<details><summary>nginxローカルプロキシ</summary><div>



```test
- defaultTab: nodes
  description: チューニングしたnginxのconfigを使ってローカルプロキシする。
  executionEnabled: true
  id: 7f9f2e50-52ca-4b39-bffc-ba542c1b5766
  loglevel: INFO
  name: nginxローカルプロキシ
  nodeFilterEditable: false
  nodefilters:
    dispatch:
      excludePrecedence: true
      keepgoing: false
      rankOrder: ascending
      successOnEmptyNodeFilter: false
      threadcount: '1'
    filter: 'name: apache1'
  nodesSelectedByDefault: true
  plugins:
    ExecutionLifecycle: null
  scheduleEnabled: true
  sequence:
    commands:
    - description: ulimit変更
      exec: sudo ulimit -n 100000
    - description: net.ipv4.ip_local_port_range変更
      exec: sudo sysctl -w net.ipv4.ip_local_port_range='1024 65535'
    - description: ' /proc/sys/fs/file-max変更'
      exec: sudo sysctl fs.file-max=100000
    - description: yum clean packages(なぜかyum通らないので)
      exec: sudo yum clean packages
    - description: nginxインストール
      exec: sudo yum install -y nginx
    - description: nginx.conf変更
      script: |-
        sudo tee /etc/nginx/nginx.conf <<EOF >/dev/null
        user nginx;
        worker_processes 1;
        error_log /var/log/nginx/error.log crit ;
        pid /run/nginx.pid;
        worker_rlimit_nofile 100000;

        events {
            worker_connections 32000;
            multi_accept        on;
            use                 epoll;
        }

        http {
            log_format  main  ' -  [] "" '
                              '  "" '
                              '"" ""';
            access_log  off;

            sendfile           on;
            keepalive_timeout  30;
            access_log        off;
            tcp_nopush         on;
            tcp_nodelay        on;
            reset_timedout_connection on;
            client_body_timeout 10;
            send_timeout 2;
            keepalive_requests 100000;
            open_file_cache max=200000 inactive=20s;
            open_file_cache_valid 30s;
            open_file_cache_min_uses 2;
            open_file_cache_errors on;
            types_hash_max_size 2048;
            gzip on;
            gzip_disable msie6;
            gzip_vary on;
            gzip_proxied any;
            gzip_comp_level 6;
            gzip_buffers 32 16k;
            gzip_min_length 250;
            gzip_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon;

            include             /etc/nginx/mime.types;
            default_type        application/octet-stream;

            server {
                listen  8080 default_server;
                server_name  _;

                location / {
                    proxy_pass http://backend;
                    proxy_http_version 1.1;
                    proxy_set_header Connection "";
                }
             }

            server {
                listen  8443 ssl http2 default_server;
                ssl on;
                ssl_certificate /etc/pki/server.crt;
                ssl_certificate_key /etc/pki/server.key;
                server_name  _;
                ssl_session_cache shared:SSL:1m;
                ssl_session_timeout  10m;
                ssl_ciphers PROFILE=SYSTEM;
                ssl_prefer_server_ciphers on;

                location / {
                    proxy_pass http://backend;
                    proxy_http_version 1.1;
                    proxy_set_header Connection "";
                }
             }

             upstream backend {
                  server 127.0.0.1:80;
                  keepalive 10240;
             }
        }

        EOF
    - description: nginx起動
      exec: sudo service nginx start
    keepgoing: false
    strategy: node-first
  uuid: 7f9f2e50-52ca-4b39-bffc-ba542c1b5766
```
</div></details>




で、これを当時フロントエンドサーバに適用したら3割速くなった(負荷が下がった)という話。
ただ、今回の環境ではapache1のEC2へ実際に適用しても速くはならなかった。
業界的にapache側にもそれ相応のチューニングが入っていたはずなのでプロキシにも相性があるということなのだろうか。
結構頑張って試行錯誤したのだが何しろ当時のことをあまり覚えていなかった。私はまた挫折した。
(Centos8のapacheはeventMPMらしいしそのあたりも影響しているんだろうか)

# あとがき
今回ローカルプロキシの再現チャレンジとしては惨敗の結果に終わってしまったが目的のものは手に入ったのでよかった。
Webサーバをいくつか作りたいのであとはこれをなんとなく使いやすい仕組みに組み込んでラクに生活したいと思っている。
ただ、多分なんにもラクではない「ansible with rundeck」を試してみるいい機会なのではないかと今なにかに囁かれている。私はラクがしたい。
あと最近アウトプットに重点を置きすぎてユーモアが欠乏している感を感じている。早急な対策が必要。