[Nginx] 02. 고성능 로드 밸런서

Date:     Updated:

카테고리:

태그:


00. 요약


알고리즘 설명
라운드 로빈 요청을 순차적으로 서버에 분배하는 간단한 방식
최소 연결 현재 연결이 가장 적은 서버로 요청을 보내는 방식
최소 시간 응답 시간이 가장 빠른 서버로 요청을 보내는 방식
일반 해시 응답 시간이 가장 빠른 서버로 요청을 보내는 방식
무작위 무작위로 서버를 선택하여 요청을 보내는 방식
IP 해시 클라이언트 IP 주소를 기반으로 서버를 선택하는 방식


기능 설명
스티키 쿠키(Sticky Cookie) 쿠키를 사용하여 클라이언트의 요청을 항상 동일한 서버로 보내는 기술
스티키 런(Sticky Learn) 요청과 응답을 검사하여 세션 식별자를 찾고, 이를 특정 서버와 연관시키는 방식
스티키 라우팅(Sticky Routing) 첫 요청에 대해 “라우트”를 할당하고, 이후 요청을 이 라우트 정보와 비교하여 동일한 서버로 보내는 방식
커넥션 드레이닝(Connection Draining) 서버를 유지보수 모드로 전환할 때 기존 연결을 유지하면서 새로운 연결은 받지 않는 기술
수동 헬스 체크 (Passive Health Checks) 실제 클라이언트 요청을 처리하는 동안 서버의 상태를 모니터링하는 방식, 오류 발생 시 해당 서버로의 트래픽을 중단
능동적인 헬스 체크 (Active Health Checks) 주기적으로 서버에 테스트 요청을 보내 상태를 확인하는 방식
슬로 스타트 (Slow Start) 새로 추가된 서버나 복구된 서버에 점진적으로 트래픽을 증가시키는 기능, 갑작스러운 부하를 방지함



01. HTTP, TCP, UDP 부하 분산


■ HTTP 부하분산

upstream backend {
    server 10.10.12.45:80 weight=1;
    server app.example.com:80 weight=2;
    server spare.example.com:80 backup;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

예시 구성에서는 포트 80에서 두 개의 서버에 부하를 분산하고, 하나는 백업 서버로 설정된다.

weight 매개변수는 서버에 전달되는 요청의 비율을 조정한다. (예를 들어, 한 서버의 가중치가 5이고 다른 서버의 가중치가 1이라면, 첫 번째 서버는 두 번째 서버보다 5배 더 많은 트래픽을 처리하게 된다. 기본값은 1이다.)

HTTP 업스트림 모듈은 HTTP 요청의 부하 분산을 제어하며, 유닉스 소켓, IP 주소, DNS 레코드의 조합으로 구성된 목적지 풀을 정의있다. 각 서버는 server 지시문에 의해 정의되며, 선택적 매개변수를 통해 요청 라우팅을 더욱 세밀하게 조정할 수 있다.

NGINX Plus를 사용하면 추가적인 매개변수를 제공하여 연결 제한, DNS 해상도 제어 및 서버 연결 증가를 관리할 수 있다.


■ TCP 부하분산

# /etc/nginx/nginx.conf 파일의 예:

user nginx;
worker_processes auto;
pid /run/nginx.pid;

stream {
    include /etc/nginx/stream.conf.d/*.conf;
}
# /etc/nginx/stream.conf.d/mysql_reads.conf 파일의 예:

upstream mysql_read {
    server read1.example.com:3306 weight=5;
    server read2.example.com:3306;
    server 10.10.12.34:3306 backup;
}

server {
    listen 3306;
    proxy_pass mysql_read;
}

예시 구성에서는 포트 3306에서 MySQL 데이터베이스 읽기 복제본 간에 부하를 분산하고, 백업 서버를 설정한다. (스트림 구성은 conf.d 폴더가 아닌 stream.conf.d라는 별도의 폴더에 추가해야 하며, nginx.conf 파일에서 스트림 블록을 열고 새로운 폴더를 포함해야 합니다.)

HTTP 컨텍스트(애플리케이션 계층)와 스트림 컨텍스트(전송 계층)는 OSI 모델의 서로 다른 계층에서 작동한다.

TCP 연결의 리버스 프록시 속성을 변경하는 여러 옵션이 있으며, SSL/TLS 검증, 타임아웃 등을 설정할 수 있다.

NGINX Plus를 사용하면 TCP 부하 분산을 위한 추가 기능을 제공하며, 상태 점검에 대한 내용은 후속에서 다루어진다.


■ UDP 부하분산

stream {
    upstream ntp {
        server ntp1.example.com:123 weight=2;
        server ntp2.example.com:123;
    }
    server {
        listen 123 udp;
        proxy_pass ntp;
    }
}

예시 구성에서는 UDP 프로토콜을 사용하여 두 개의 NTP 서버 간에 부하를 분산하며, listen 지시문에서 udp 매개변수를 사용한다.

stream {
    server {
        listen 1195 udp reuseport;
        proxy_pass 127.0.0.1:1194;
    }
}

여러 패킷을 주고받아야 하는 서비스의 경우 reuseport 매개변수를 지정할 수 있으며, 이러한 서비스의 예로는 OpenVPN, VoIP(음성 인터넷 프로토콜), 가상 데스크톱 솔루션 및 DTLS(데이터그램 전송 계층 보안)가 있다.

부하 분산기가 필요한 이유는 DNS A 레코드나 SRV 레코드에서 여러 호스트를 관리할 수 있지만, 다양한 부하 분산 알고리즘과 DNS 서버에 대한 부하 분산이 가능하기 때문이다.

TCP와 마찬가지로 UDP 부하 분산은 스트림 모듈에서 사용할 수 있으며, 대부분 동일한 방식으로 구성된다. 주요 차이점은 listen 지시어가 데이터그램과 작업하기 위한 열린 소켓을 지정한다는 것이다.

proxy_response 지시어는 NGINX가 업스트림 서버에서 보낼 수 있는 예상 응답 수를 지정한다.

proxy_timeout 지시어는 클라이언트 또는 프록시 서버 연결에서 두 개의 연속적인 읽기 또는 쓰기 작업 사이의 시간을 설정하며, 이 시간이 지나면 연결이 종료된다.

reuseport 매개변수는 NGINX에 각 워커 프로세스에 대해 개별 수신 소켓을 생성하도록 지시한다. 이를 통해 커널은 클라이언트와 서버 간에 여러 패킷을 처리하기 위해 워커 프로세스 간에 수신 연결을 분산할 수 있다. (reuseport 기능은 Linux 커널 3.9 이상, DragonFly BSD 및 FreeBSD 12 이상에서만 작동된다.)



02. 부하 분산 알고리즘


알고리즘 설명
라운드 로빈 기본 부하 분산 방식으로, 서버 목록에 따라 요청을 분배하며, 가중치를 고려한 가중 라운드 로빈도 사용할 수 있다.
최소 연결 열린 연결 수가 가장 적은 서버로 요청을 프록시한다. 이 방법도 가중치를 고려하며, 지시어는 ‘least_conn’ 이다.
최소 시간 열린 연결 수가 가장 적은 서버로 요청을 프록시한다. 이 방법도 가중치를 고려하며, 지시어는 ‘least_conn’ 이다.
일반 해시 요청에 대한 해시를 생성하여 부하를 분산한다. 지시어는 ‘hash’
무작위 서버를 무작위로 선택하여 부하를 분산한다. 지시어는 ‘random’
IP 해시 클라이언트 IP 주소를 해시로 사용하여 요청을 동일한 서버로 프록시한다. 세션 상태를 유지하는 데 유용하다. 지시어는 ‘ip_hash’



03. 스티키 쿠키(Sticky Cookie) - Nginx Plus


upstream backend {
    server backend1.example.com;
    server backend2.example.com;
    sticky cookie 
        affinity 
        expires=1h 
        domain=.example.com 
        httponly 
        secure 
        path=/;
}

스티키 쿠키(Sticky Cookie)는 로드 밸런싱 환경에서 사용되는 세션 관리 기술로, 사용자가 웹사이트를 떠났다가 다시 돌아왔을 때도 동일한 세션을 유지 (=동일한 백엔드 서버로 라우팅) 할 수 있도록 도와준다.

예시 구성에서는 affinity라는 이름의 쿠키를 생성하며, 1시간 후 만료되고, 정되고 1시간 후 만료된다. 클라이언트 측에서 사용할 수 없고, HTTPS를 통해서만 전송되며, 모든 경로에 대해 유효하다.

NGINX Plus는 이 쿠키를 추적하여 이후의 요청을 동일한 서버로 전달할 수 있다. 쿠키 매개변수의 첫 번째 값은 쿠키 이름이며, 추가 매개변수는 만료 시간, 도메인, 경로, 클라이언트 측 소비 가능 여부 등을 설정할 수 있다.



04. 스티키 런(Sticky Learn) - Nginx Plus


upstream backend {
    server backend1.example.com:8080;
    server backend2.example.com:8081;
    sticky learn
        create=$upstream_cookie_cookiename
        lookup=$cookie_cookiename
        zone=client_sessions:2m;
}

스티키 런(Sticky Learn)은 로드 밸런싱 환경에서 클라이언트의 요청을 항상 같은 서버로 보내도록 설정하는 기법으로, 다른 스티키 세션 방식들보다 더 정교하고 유연한 세션 관리를 가능하게 한다. 클라이언트 측에 쿠키를 저장하지 않고, 모든 세션 정보를 서버 측의 공유 메모리 영역에 보관한다.

예시 구성에서는 COOKIENAME이라는 쿠키를 응답 헤더에서 찾고, 요청 헤더에서 동일한 쿠키로 기존 세션을 조회한다.

쿠키의 이름은 애플리케이션에 따라 다르며, 자주 사용되는 쿠키 이름은 jsessionid나 phpsessionid와 같은 기본값이 설정되어 있는 경우가 많다.



05. 스티키 라우팅(Sticky Routing) - Nginx Plus


map $cookie_jsessionid $route_cookie {
    ~.+\.(?P<route>\w+)$ $route;
}

map $request_uri $route_uri {
    ~jsessionid=.+\.(?P<route>\w+)$ $route;
}

upstream backend {
    server backend1.example.com route=a;
    server backend2.example.com route=b;
    sticky route $route_cookie $route_uri;
}

로드 밸런싱 환경에서 클라이언트의 요청을 항상 동일한 서버로 라우팅하는 방식으로, Sticky Session이나 Session Persistence라고도 불린다.

예시에서는 Java 세션 ID를 쿠키에서 추출하고, 요청 URI에서 jsessionid 매개변수를 찾아서 변수에 매핑한다.

sticky route 지시문은 여러 변수를 사용하여 첫 번째 비어 있지 않은 값을 라우트로 사용한다. jsessionid 쿠키가 사용되면 backend1으로, URI 매개변수가 사용되면 backend2로 라우팅된다.

스티키 라우트 지시어는 NGINX Plus의 공유 메모리 영역 내에서 지정한 클라이언트 세션 식별자를 추적하는 세션을 생성하여, 이 세션 식별자로 원래 요청과 동일한 업스트림 서버에 요청을 일관되게 전달합니다.



06. 커넥션 드레이닝(Connection Draining) - Nginx Plus


# NGINX Plus API를 통해 drain 매개변수를 사용하여 NGINX에게 이미 추적되고 있지 않은 새로운 연결을 중지하도록 지시
$ curl -X POST -d '{"drain":true}' \
 'http://nginx.local/api/3/http/upstreams/backend/servers/0'
{
 "id":0,
 "server":"172.17.0.3:80",
 "weight":1,
 "max_conns":0,
 "max_fails":1,
 "fail_timeout": "10s",
 "slow_start": "0s",
 "route": "",
 "backup": false,
 "down": false,
 "drain": true
}

서버를 유지보수하거나 종료할 때, 해당 서버로 향하는 새로운 연결을 차단하면서도 기존에 이미 처리 중인 연결은 안전하게 완료될 수 있도록 하는 기법으로, 이 기능은 서버가 정상적인 상태에서 종료되거나 제거될 때, 클라이언트 요청을 중단하지 않고 처리할 수 있게 해준다.

예시에서는 curl 명령어를 사용하여 drain 매개변수를 설정하는 방법을 보여준다.

특정 서버에 대해 드레인을 구성하려면 server 지시어에 drain 매개변수를 추가해야 한다. drain 매개변수가 설정되면 NGINX Plus는 새로운 세션을 이 서버로 전송하는 것을 중지하지만, 현재 세션은 계속 제공된다.



07. 수동 헬스 체크 (Passive Health Checks)


upstream backend {
    server backend1.example.com:1234 max_fails=3 fail_timeout=3s;
    server backend2.example.com:1234 max_fails=3 fail_timeout=3s;
}

NGINX 헬스 체크를 사용하여 부하 분산 시 건강한 업스트림 서버만 사용하도록 설정한다. 이 설정은 클라이언트 요청에 대한 응답을 모니터링하여 업스트림 서버의 건강을 수동으로 체크한다.

수동 모니터링은 클라이언트 요청에 의해 NGINX를 거치는 동안 실패하거나 타임아웃된 연결을 감지한다.

수동 헬스 체크는 기본적으로 활성화되어 있으며, 여기 언급된 매개변수를 통해 동작 방식을 조정할 수 있다. 기본 max_fails 값은 1이며, 기본 fail_timeout 값은 10초이다.



08. 능동적인 헬스 체크 (Active Health Checks) - NGINX Plus


# HTTP의 경우, health_check 지시어를 사용하여 로케이션 블록 내에서 헬스 체크를 설정한다. 
http {
    server {
        # ...
        location / {
            proxy_pass http://backend;
            health_check interval=2s 
                         fails=2 
                         passes=5 
                         uri=/ 
                         match=welcome;
        }
    }
    
    # 헬스 체크 조건
    match welcome {
        status 200;
        header Content-Type = text/html;
        body ~ "Welcome to nginx!";
    }
}
# TCP/UDP 서비스의 스트림 헬스 체크도 비슷하게 설정된다.
stream {
    # ...
    server {
        listen 1234;
        proxy_pass stream_backend;
        health_check interval=10s 
                     passes=2 
                     fails=3;
        health_check_timeout 5s;
    }
    # ...
}

예시에서는 매 2초마다 URI “/”에 GET 요청을 보내고, 업스트림 서버의 응답이 특정 조건을 만족해야 한다.

1) 헬스 체크는 GET 요청만 수행하며, 서버는 5번 연속으로 헬스 체크를 통과해야 건강한 것으로 간주된다.

2) 2번 연속 실패할 경우 불건강으로 간주된다.

3) 응답은 정의된 매치 블록과 일치해야 하며, 여기서 상태 코드는 200, Content-Type은 ‘text/html’, 응답 본문에는 “Welcome to nginx!”가 포함되어야 한다.

NGINX Plus에서는 수동 또는 능동 건강 점검을 통해 서버를 모니터링할 수 있으며, 응답 코드 이상의 다양한 기준을 측정할 수 있다. 건강 점검의 빈도, 통과 횟수, 실패 횟수 및 기대되는 결과를 설정할 수 있으며, 복잡한 논리를 위해 match 블록에서 변수를 사용할 수 있다.



08. 슬로 스타트 (Slow Start) - NGINX Plus


upstream {
    zone backend 64k;
    server server1.example.com slow_start=20s;
    server server2.example.com slow_start=15s;
}

Slow Start는 TCP 혼잡 제어 메커니즘 중 하나로, 네트워크의 혼잡을 방지하면서 효율적으로 데이터 전송 속도를 높이는 방법이다.

1) 연결이 시작될 때 혼잡 윈도우 크기(cwnd)를 1 MSS(최대 세그먼트 크기)로 설정한다.

2) 각 ACK(확인 응답)를 받을 때마다 cwnd를 지수적으로 증가시킨다.

3) 윈도우 크기가 임계값(ssthresh)에 도달하면, 혼잡 회피(Congestion Avoidance) 단계로 전환된다.

이 설정은 서버가 풀에 다시 추가된 후에 점진적으로 트래픽을 증가시킨다. server1은 20초에 걸쳐 연결 수를 점진적으로 증가시키고, server2는 15초에 걸쳐 증가시킨다.

이 기능은 서버가 캐시를 채우거나 데이터베이스 연결을 설정하는 동안 트래픽으로 인해 과부하되지 않도록 도와준다. 이 기능은 헬스 체크에서 실패했던 서버가 다시 정상 상태로 돌아와 로드 밸런싱 풀에 재진입할 때 적용되며, NGINX Plus에서만 사용할 수 있다. Slow Start는 hash, ip_hash, 또는 random 로드 밸런싱 방법과는 함께 사용할 수 없다.

NGINX 카테고리 내 다른 글 보러가기

댓글 남기기