ecsimsw

Nginx 요청 호출 수 제한과 접근 가능 IP 제한 본문

Nginx 요청 호출 수 제한과 접근 가능 IP 제한

JinHwan Kim 2023. 12. 17. 08:15

이전 글 - 리버스 프록시 개념과 Nginx, ELB 로 로드밸런싱 실습

리버스 프록시 개념이 처음이거나 Nginx config 를 처음 다뤄본다면 이전 글을 먼저 보고 오는 것을 추천한다.

해당 글에선 리버스 프록시와 로드 밸런싱 개념, 다중 WAS에서 Sesstion 관리, Nginx 와 AWS ALB로 로드밸런싱 실습을 다뤘다.

Rate limit, Ip white list

프로젝트를 배포할 때마다 무서운 것들이 있다. 악의적인 과도한 요청, 접속해선 안될 곳에 접근이 가능, 암호화되지 않은 데이터... 특히 가장 먼저 서버를 보여주는 주변 사람들이 웹 개발자가 많아서 더 무섭다. 이 사람들은 일반 사용자랑 달리 아주 자연스럽게 서버를 테스트할 테니 말이다. 그리고 실제로 꼼꼼하게 방어하지 않았다가 악의적인 과도한 요청으로 서버가 다운되어 큰 문제가 생겼던 경험도 있었다. 

 

이번에는 Nginx 를 전면에 둬 서버 요청 속도 제한, 사용자 ip 필터링을 설정하는 방법을 정리하려 한다. 요청 속도 제한으로 DDoS와 같은 악의적인 과요청을 막아 WAS를 보호하고 사용자 Ip 필터링으로 API 서버와 관리자 서버에 접근 가능한 IP를 전면에서 필터링해보자.

 

 

 

요청 속도 제한 

1. 제한 타입 

 

요청의 제한 기준을 정의한다.

 

limit_req_zone : 요청 제한
limit_conn_zone : 커넥션 제한

 

2. 제한 기준

 

제한의 기준이 될 메트릭을 설정한다. 예를 들어 binary_remote_addr 라고 한다면 클라이언트 IP 를 기준으로 요청 / 커넥션 양을 제한하고, server_name으로 한다면 서버를 기준으로 제한한다. 

 

binary_remote_addr : 클라이언트의 IP를 기준
server_name : 서버를 기준
binary_remote_addr:$uri : 요청 path를 기준

 

3. zone

 

Nginx 은 Leaky bucket 알고리즘을 사용한다. 버킷에 요청을 담았다가 처리하고 오버플로우 되는 요청들은 버려진다. zone 은 이 버킷을 말한다. 버킷의 사이즈와 이름을 정할 수 있고, 그 이름으로 제한마다 서로 다른 버킷을 사용할 수 있다.

 

zone name : zone name
share memory assign : bucket size

 

4. burst


최대 제한 요청 수를 넘어선 요청, 커넥션을 burst 값만큼 큐잉한다. 그리고 이를 처리가 가능한 시점에서 처리한다.

 

예를 들어 요청을 10r/s 로 0.1초에 하나의 요청으로 설정한 상황에서 11개의 요청이 한 번에 들어오면 burst가 없는 상황에선 1개의 요청이 버려지지만 burst가 1 이상이면 오버된 1개를 보관했다가 다음 1초 후에 처리하게 된다. (0.1초마다 1개씩, 1초 후 queue poll)


5. nodelay

 

버려지는 요청을 막고자 burst 를 사용했지만 요청 처리가 너무 늦는다. 그렇다고 burst 만큼 rate 를 올리면 원하는 속도 제한이 불가능하다. 이때 Nodelay 옵션을 사용할 수 있다. 

 

burst 로 오버된 요청을 큐잉하면서도 오버된 요청을 서버에 바로 보내 처리한다. 대신 queue 에선 원래 처리 가능해야 했던 시점에서 poll이 일어난다. 위 예시라면 11번째의 요청도 앞선 10개의 요청과 마찬가지로 바로 처리되고 queue offer 된다. 그리고 1초 후 queue에서 poll 하는 것으로 queue 에서 요청이 빠지는 속도 제한은 그대로 유지할 수 있다.

 

6. limit_req_status

 

제한이 발생했을 때 응답할 상태 코드를 말한다. 기본 값은 503 서버 에러이고, 4XX 에러에선 429 (Too many request)을 사용하는 것도 좋다.

 

7. 사용 예시 1 / 제한 정의

 

아래는 picup 의 요청 제한을 설정한 nginx.config 예시이다.

 

http {
    limit_req_zone $binary_remote_addr; zone=default_rate_limit:10m rate=5r/s;
    
    upstream k8s-ingress {
        server ${SERVER_IP:PORT};
    }

    server {
        listen       80;
        server_name  localhost;

        location /api/ {
           proxy_pass http://k8s-ingress;
           proxy_set_header Host            $host;        
           proxy_set_header X-Forwarded-For $remote_addr; 

           limit_req zone=default_rate_limit burst=5 nodelay;
           limit_req_status 429;
        }
    }
}

 

default_rate_limit 이라는 이름으로 사용자 ip 당 요청 수를 기준으로 초당 5개 요청 이상 제한을 규칙으로, 10mb 의 버킷 사이즈를 갖는 제한을 정의한다.

 

server에서 이 default_rate_limit 제한을 사용하되 burst는 5, nodelay로 queueing 되는 요청을 바로 처리하도록 한다. 제한이 넘어가면 429, Too many request 를 응답한다. 

 

8. 사용 예시 2 / 제한 제외 정의

 

사용자 ip 마다 제한을 풀어주고 싶은 경우 아래처럼 제한의 기준 메트릭을 ip 범위마다 달리하는 것으로 제한을 무시할 수 있다. 

 

아래는 picup에서 사용한 방식이다. default로 사용자 ip 당 요청을 제한하되, localhost, 배포 서버 내부, 개발 pc 들에선 이 제한을 풀어 자유롭게 테스트하도록 하였다. (xxx 는 이 글에서 실제 ip를 숨기기 위해 사용했다. 실제 설정 파일에선 구체적인 ip 값이 포함되어 있다.) 

 

http {
    geo $apply_limit {
        default           $binary_remote_addr;
        127.0.0.1/24      '';                    # localhost
        183.XXX.XXX.XXX/24  '';                  # prod server
        192.168.0.0/16    '';                    # dev env subnet
    }
    limit_req_zone $apply_limit zone=default_rate_limit:10m rate=5r/s;
}

 

Ip white list

Nginx 에서 라우팅 할 엔드포인트 별로 접속할 수 있는 ip 를 달리 하고 싶었다. 이는 allowdeny를 사용하여 간단하게 설정할 수 있다. 

 

아래는 picup의 모니터링 시스템 nginx 설정 파일의 일부이다. localhost, 배포 서버, 개발 pc subnet 만 허용하고 나머지는 deny 하여 외부에서 모니터링 시스템으로의 접근을 제한한다.

 

server {
    allow 127.0.0.1/24;                    # localhost
    allow 183.xxx.xxx.xxx/24;              # prod - external ip,
    allow 192.168.0.0/16;                  # prod - internal subnet
    deny all;
}

 

Comments