Nginx 接続数制限 (limit_conn) を設定してab検証する

2月 23, 2023AWS,EC2,Middleware,nginx

概要

  • Nginx では標準で提供されている limit_conn モジュールを使用して、接続数制限を提供します。
  • 接続数制限によって、同一の接続元からの接続が制限でき、DoS攻撃から防御できます。あるいは、TCPの接続数を制限することによって、高負荷時にもWebサービスの処理を継続させることが目的です。
  • 今回は、Nginx 接続数制限 (limit_conn) の設定方法、ab (apache bench) による接続数制限の検証方法をまとめます。

 

事前準備

  • EC2 の Amazon Linux 2 インスタンスを起動します。
  • インスタンスのUser data 設定に、下記を記載します。Amazon Linux 2 のnginx は、amazon-linux-extrasを使用します。amazon-linux-extras の詳細はこちらを参照。
#!/bin/bash
sudo yum update -y
sudo amazon-linux-extras install nginx1 -y 
sudo systemctl enable nginx
sudo systemctl start nginx

 

 

  • EC2 にログイン後、nginx サービスの状態を確認します。
[ec2-user@ip-xx-xx-xx-xx ~]$ systemctl status nginx

[ec2-user@ip-xx-xx-xx-xx ~]$ nginx -v
nginx version: nginx/1.22.1

 

  • テストページも表示されました。

 

Nginx に接続数制限 (limit_conn) を設定

  • 接続数制限 (limit_conn)は、2種類の設定があります。1つは同じIPアドレスからの接続数を制限する設定(例:1個のクライアントから10個の接続を許可)、もう1つはserver nameに対する接続数を制限する設定(例:server nameに対する接続をトータル10個まで許可)になります。

同じIPアドレスからの接続数を制限する設定

  • limit_conn のzone を定義します。キーに $binary_remote_addr変数を指定します。sizeは $binary_remote_addr変数を保持する領域です。IPv4 の場合1つのアドレスに4byte が必要です。次のサンプルでは、10MB を指定しています。size の領域が消費されると、nginx はそれ以降のすべての要求に対してエラーを返します 。

limit_conn_zone $binary_remote_addr zone=name:size;

limit_conn zone number;

 

  • 以下は、同じIPアドレスからの接続数が 1を超えるとリクエストにエラーを返します。IP アドレスごとに 1つの同時接続のみを許可しています。
http {
    limit_conn_zone $binary_remote_addr zone=perip:10m;

    ...

    server {

        ...

        location / {
            limit_conn perip 1;
            limit_conn_log_level error;
            limit_conn_status 599;
        }

 

 

server nameに対する接続数を制限する設定

  • こちらは、接続元 (IPアドレス)を問わず、server nameに対する全体の同時接続数を制限する設定になります。
  • limit_conn のzone を定義します。キーに $server_name変数を指定します。sizeは $server_name変数を保持する領域です。size の領域が消費されると、nginx はそれ以降のすべての要求に対してエラーを返します 。

limit_conn_zone $server_name zone=name:size;

limit_conn zone number;

 

  • 以下は、server nameに対する接続数が 1を超えるとリクエストにエラーを返します。 1台の同時接続のみを許可しています。
http {
    limit_conn_zone $server_name zone=persever:10m;

    ...

    server {

        ...

        location / {
            limit_conn persever 1;
            limit_conn_log_level error;
            limit_conn_status 599;
        }

 

limit_conn_status 補足

  • 拒否されたリクエストに返すステータス コードが設定可能です。ステータス コードは任意です。今回のサンプルでは、一般的なエラーと接続数制限を区別するため、ステータス コードに 599 を設定しています。

limit_conn_status code;

 

 

ab (apache bench) による接続数制限の検証

ab (apache bench) の概要

  • 接続数制限の検証に、ab を使用します。ab は、Apache HTTP server benchmarking tool であり、通称 apache bench と呼ばれます。1回のコマンド実行によって、1つのURLに対するリクエストを指定した数だけ発行します。
  • ab を使用するには、httpd (Apache) をインストールする必要があります。 (例: yum install httpd)
  • 以下にオプションを使用します。
    • nオプション: 発行するリクエスト数
    • cオプション: 同時実行する数

 

同じIPアドレスからの接続数制限を検証

  • 3台の接続元 (IPアドレス)から同時に接続を行いました。
  • 2台はLinux のabコマンド、1台はPC のWebブラウザです。(サーバー側の処理にsleep を入れ、3台の同時接続を実現しています)
  • 実行したコマンド
    • ab -n 10 -c 10 http://11.22.33.44/

 

  • 検証結果は同一の接続元で見ると初回のみ成功、以降はエラー応答になりました。なお、ab はPreflight request を自動的に発行するようで、1回目のリクエストは結果として無視することにします。
  • 1台目Linux のabコマンドの結果: 初回は成功(200) / 以降のリクエストはエラー(599)
Concurrency Level:      10
Time taken for tests:   6.308 seconds
Complete requests:      10
Failed requests:        8
   (Connect: 0, Receive: 0, Length: 8, Exceptions: 0)
Non-2xx responses:      8
  • 2台目Linux のabコマンドの結果: 初回は成功(200) / 以降のリクエストはエラー(599)
Concurrency Level:      10
Time taken for tests:   6.285 seconds
Complete requests:      10
Failed requests:        8
   (Connect: 0, Receive: 0, Length: 8, Exceptions: 0)
Non-2xx responses:      8

 

  • 3台目PC のWebブラウザの結果: 成功(200) (単発)

 

server nameに対する接続数制限を検証

  • 3台の接続元 (IPアドレス)から同時に接続を行いました。
  • 2台はLinux のabコマンド、1台はPC のWebブラウザです。(サーバー側の処理にsleep を入れ、3台の同時接続を実現しています)
  • 実行したコマンド
    • ab -n 10 -c 10 http://11.22.33.44/

 

  • 検証結果は1台の接続元のみ成功、2台目以降の接続元はエラー応答になりました。 ※こちらは、接続元 (IPアドレス)を問わず、server nameに対する全体の接続数を制限する設定になります。
  • 1台目Linux のabコマンドの結果: 初回は成功(200) / 以降のリクエストはエラー(599) (ab はPreflight request を自動的に発行するようで、1-2回目のリクエストは無視しますね)
Concurrency Level:      10
Time taken for tests:   6.066 seconds
Complete requests:      10
Failed requests:        8
   (Connect: 0, Receive: 0, Length: 8, Exceptions: 0)
Non-2xx responses:      8
  • 2台目Linux のabコマンドの結果: すべてエラー(599)
Concurrency Level:      10
Time taken for tests:   0.002 seconds
Complete requests:      10
Failed requests:        0
Non-2xx responses:      10

 

  • 3台目PC のWebブラウザの結果: エラー(599)

 

 

nginxのlimit_connディレクティブが機能しない?

  • 最初、nginx のテストページを使用した際に、limit_connディレクティブが機能しませんでした。調べたところ、nginx のModule ngx_http_limit_conn_module マニュアルに以下の記述がありました。そのため、サーバー側の処理が軽量の場合、同時の接続としてカウントされない様です。
    • Not all connections are counted. A connection is counted only if it has a request being processed by the server and the whole request header has already been read.
    • (すべての接続がカウントされるわけではない。サーバーによって処理されているリクエストがありリクエストヘッダの全体がすでに読み込まれている場合のみカウントされる。)
  • 次に、nginx のテストページではなく、proxy_pass ディレクティブを使用しました。プロキシ先には、AWS の API Gateway – Lambda を配置、Lambda には、デフォルトのサンプルに sleep (3秒)の処理のみを追加しています。sleep以外に、サイズの大きいファイルをダウンロードする方法でもテスト出来ました。

 

 

参考

nginx マニュアル

 

nginxログのサンプル

access.log

1.100.100.100 - - [19/Mar/2023:14:05:12 +0000] "GET / HTTP/1.0" 599 0 "-" "ApacheBench/2.3" "-"
1.100.100.102 - - [19/Mar/2023:14:05:15 +0000] "GET / HTTP/1.0" 200 60696048 "-" "ApacheBench/2.3" "-"

error.log

2023/03/19 14:05:12 [error] 3909#3909: *9 limiting connections by zone "perserver", client: 1.100.100.100, server: _, request: "GET / HTTP/1.0", host: "11.22.33.44"

 

検証に使用した nginx.confサンプル

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

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

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=persever:10m;

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

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

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

        location / {
            proxy_pass https://xxxyyyzzz.execute-api.ap-northeast-1.amazonaws.com/dev;
            # Allow 1 connections for each IP address.
            limit_conn perip 1;
            # Allow connections from 1 clients.
            limit_conn persever 1;
            limit_conn_log_level error;
            limit_conn_status 599;
        }

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

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

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/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;
#
#        error_page 404 /404.html;
#            location = /40x.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#            location = /50x.html {
#        }
#    }

}

 

検証に使用した Lambda pythonサンプル

import json
from time import sleep

def lambda_handler(event, context):
    # TODO implement
    sleep(3)
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

 

AWS,EC2,Middleware,nginx

Posted by takaaki