NGINX 를 사용한 TCP/UDP Load Balancing 개요, 팁과 요령
해당 포스트는 NGINX의 Konstantin Pavlov가 nginx.conf 에서 발표한 TCP/UDP Load Balancing 에 대한 프레젠테이션을 수정한 것입니다. 전체 프레젠테이션 녹화본은 Youtube에서 볼 수 있습니다.

목차
1. TCP/UDP Load Balancing 소개
2. (1:00) TCP Load Balancing
3. (1:53) UDP Load Balancing
4. (3:31) TCP/UDP Load Balancer 튜닝
5. (6:18) TCP/UDP Active Health Check
6. (8:53) 액세스 제어 및 제어
7. (9:43) 클라이언트의 IP 주소를 백엔드에 전달
8. (11:46) TLS Termination
9. (12:32) TLS Re-Encryption
10. (13:05) TLS Wrapping
11. (14:40) Better Logging
12. (19:17) nginScript로 TCP/UDP Load Balancing 확장
13. (29:26) TCP/UDP nginxScript: 성능
1. TCP/UDP Load Balancing 소개
Konstantin Pavlov: 저는 NGINX의 시스템 엔지니어이고 전문 서비스 부서에서 일하고 있습니다. 이 세션에서는 NGINX에서 제공하는 TCP/UDP Load Balancer의 기능에 대해 자세히 알아보겠습니다.
Stream 모듈은 NGINX 1.9에 도입되었습니다. 그 이후로 상당히 성숙하고 잘 입증된 솔루션이 되었으며, NGINX의 HTTP Load Balancing 스택에 추가되었습니다.
지원되는 Load Balancing 방법, SSL/TLS 지원에 대한 개요를 제공하고 Active Health Check와 같은 NGINX Plus에서 제공하는 추가 기능에 대해 살펴 보겠습니다.
최소한의 구성과 최소한의 구성이 아닌 몇 가지 구성을 보여드리겠습니다. 또한 간단한 웹 애플리케이션 방화벽을 구축하는 방법과 같이 Stream 모듈과 nginxScript(현재 NGINX JavaScript 모듈)를 사용하기 위한 몇 가지 요령도 공유할 것입니다.
2. (1:00) TCP Load Balancing

TCP Load Balancing 의 경우 매우 간단합니다. 보시다시피 upstream 블록을 정의하고 있습니다. 먼저 NGINX의 기본 구성 파일에 stream 블록을 적의하고, 그 안에 내 도메인 이름 두 개의 MySQL 백엔드가 있는 upstream 블록을 정의하고 있습니다.
그런 다음 server 블록에서 listen 소켓을 정의하여 TCP 프로토콜을 수신하고 정의한 백엔드에 프록시합니다. 아주 쉽고 간단합니다. 보시다시피, NGINX에서 사용하는 HTTP 구성과 매우 유사합니다.
다음 슬라이드에서 좀 더 정교한 구성을 보여드리겠습니다.
3. (1:53) UDP Load Balancing

또한 NGINX에 UDP Load Balancing 입니다. 이는 고가용성과 UDP 서비스 확장이라는 두 가지 사용 사례에 주로 사용됩니다.
UDP 데이터그램이 NGINX로 들어오면 NGINX는 Passive Health Check를 사용하여 백엔드의 상태를 모니터링하거나, NGINX Plus의 경우 Active Health Check를 사용하여 모니터링합니다. 그리고 데이터그램에 대한 연결을 살아 있는 서버로 전달합니다.
이 구성에서는 DNS Load Balancing 을 수행하고 있습니다. 두 개의 백엔드로 구성된 upstream 블록을 정의했습니다. listen 지시문은 TCP 구성과 유사하지만 여기서는 udp 매개변수를 사용하여 NGINX가 이 포트에서 UDP 를 수신 대기하도록 지시합니다.
명심해야 할 것 중 하나는 NGINX UDP Load Balancing 은 백엔드에서 하나 이상의 응답을 기대하는 방식으로 구축된다는 것입니다. DNS의 경우 하나의 요청과 하나의 응답이 예상됩니다.
또한 오류 로그를 정의하여 UDP Load Balancing 의 로그를 살펴볼 수 있도록 했습니다.
4. (3:31) TCP/UDP Load Balancer 튜닝

물론 TCP 및 UDP Load Balancer를 미세 조정할 수 있습니다.
이전 슬라이드에서는 가중 Round Robin Load Balancing 알고리즘을 사용하는 기본 [upstream] 구성만 보여드렸습니다. 하지만 다른 선택 사항도 있습니다. [예를 들어 원격 주소의 해시를 기반으로 Load Balancing 을 사용하면 IP 주소를 기반으로 세션 선호도를 활성화할 수 있습니다. 또는 최소 연결 수 [최소 연결 알고리즘]를 사용할 수도 있습니다. 이 경우 NGINX는 활성 연결 수가 가장 적은 서버로 UDP 데이터그램 또는 TCP 연결을 전달합니다.
NGINX Plus에서는 Least Time Load Balancing(최소 시간 Load Balancing) 방법도 사용할 수 있습니다. 가장 빠른 연결 시간, 백엔드에서 첫 번째 바이트 수신 또는 마지막 바이트 수신(전체 응답을 의미)을 기준으로 [서버를] 선택할 수 있습니다. 슬라이드의 오른쪽 구성에서 해당 방법을 구현하는 방법을 확인할 수 있습니다.

HTTP Load Balancer와 마찬가지로 가중치, 서버가 다운된 것으로 간주되기 전 최대 연결 실패 횟수 또는 서버가 다운된 것으로 간주되기 위해 이러한 연결 실패가 발생해야 하는 시간과 같은 서버별 매개변수를 정의할 수 있습니다. 서버를 다운 또는 백업 서버로 명시적으로 표시할 수도 있습니다.
NGINX Plus에서는 백엔드에 대한 최대 연결 수를 설정할 수도 있습니다. 이 예제에서는 이미 20개를 초과하는 연결이 있는 경우 NGINX Plus에서 새 연결을 생성하지 않습니다. slow_start 매개변수는 서버의 가중치를 0에서 공칭 값으로 점진적으로 이동하도록 NGINX에 지시합니다. 예를 들어 백엔드에 일종의 워밍업이 필요한 경우, 시작하자마자 많은 수의 새 요청으로 넘쳐나지 않도록 하는 데 유용할 수 있습니다.
service 파라미터를 사용하여 DNS SRV 레코드를 쿼리하여 upstream 그룹을 채울 수도 있습니다. 이 경우 resolve 매개변수도 포함해야 합니다. 이 구성을 사용하면 [백엔드 서버의] IP 주소가 변경되었거나 서비스에 대한 DNS에 새 항목이 있을 때 NGINX를 다시 시작할 필요가 없습니다.
5. (6:18) TCP/UDP Active Health Check

이전 슬라이드에서 언급했듯이 max_fails 매개변수를 사용하여 Passive Health Check를 사용하도록 설정했지만, NGINX Plus에서는 Active 비동기 Health Check도 사용할 수 있습니다.
여러 IMAP 서버 앞에 Load Balancer 가 있다고 가정해 보겠습니다. (슬라이드에는 하나만 표시되어 있지만, 그 이상은 맞지 않기 때문입니다.) IMAP 서버가 있지만 IMAP 서버의 상태는 실제로 기본 제공 HTTP 서버에 게시된다고 가정해 보겠습니다.
health_check 지시문에 port 매개변수를 사용하면 NGINX가 [Health Check를 보낼 때] 일반 IMAP 포트에 연결하지 않고 다른 포트[여기서는 8080]에 연결하도록 지시합니다. match 블록에서 NGINX가 보내는 요청과 예상되는 특정 응답을 정의하고 있습니다. 여기서는 이 호스트의 상태 코드를 요청하고 있으며, Health Check를 통과하려면 200 OK가 되어야 합니다.
또한 서버를 다운으로 표시하기 전에 Health Check가 시간 초과될 때까지 기다리는 데 많은 시간을 소비하고 싶지 않기 때문에 health_check_timeout을 낮은 값으로 설정하고 있습니다.

물론 TCP/UDP 세계에서는 일반적으로 clear-text protocol을 사용하지 않습니다. 예를 들어 DNS에 대한 Health Check를 구현하는 경우 hex_encoded 데이터를 전송해야 합니다.
이 특정 구성에서는 nginx.org에 대한 DNS A 리소스 레코드를 요청하는 페이로드를 서버에 전송합니다. Health Check를 통과하려면 서버가 expect 지시문에 지정된 16진수로 인코딩된 IP 주소로 응답해야 합니다.
6. (8:53) 액세스 제어 및 제어

stream 모듈은 몇 가지 면에서 HTTP 모듈과 매우 유사합니다. 이 모듈을 사용하면 가상 서버에 액세스하는 사용자를 제어하고 리소스 사용을 제한할 수 있습니다.
구성은 HTTP server 블록과 거의 동일합니다. deny 및 allow 지시문을 사용하여 특정 IP 주소를 가진 클라이언트 또는 특정 네트워크에서 클라이언트의 서비스 액세스를 허용할 수 있습니다. limit_conn 및 limit_conn_zone을 사용하여 서버에 대한 동시 연결 수를 제한할 수 있습니다. 또한 원하는 경우 백엔드 서버와의 다운로드 및 업로드 속도를 제한할 수 있습니다.
7. (9:43) 클라이언트의 IP 주소를 백엔드에 전달

TCP/UDP Load Balancer를 사용할 때 가장 큰 문제 중 하나는 클라이언트의 IP 주소를 전달하는 것입니다. 비즈니스 요구 사항에 따라 이 작업이 필요할 수도 있지만 프록시에 해당 정보가 없을 수도 있습니다. 물론 HTTP에는 이를 아주 쉽게 수행할 수 있는 방법이 있습니다. 기본적으로 X-Forwarded-For header 등을 삽입하기만 하면 됩니다. 그렇다면 TCP Load Balancer 에서는 무엇을 할 수 있을까요?
가능한 해결책 중 하나는 HTTP 기반 PROXY 프로토콜을 사용하는 것입니다. 백엔드 측에서 proxy_protocol 지시문을 사용하여 활성화할 수 있습니다. NGINX는 기본적으로 들어오는 연결을 PROXY 프로토콜로 래핑하고 클라이언트의 IP 주소와 메시지를 수신할 프로토콜을 포함하여 백엔드에 전달합니다.
물론 이는 프록시가 전달되는 백엔드도 PROXY 프로토콜을 사용해야 한다는 의미이기도 합니다. 백엔드가 PROXY 프로토콜을 사용하는지 확인해야 한다는 점이 가장 큰 단점입니다.

클라이언트 IP 주소를 전달하는 또 다른 방법은 proxy_bind 지시문과 함께 transparent 매개변수를 사용하는 것입니다. 이는 클라이언트의 IP 주소를 사용하여 백엔드의 소켓에 바인딩하도록 NGINX에 지시합니다.
안타깝게도 이를 위해서는 NGINX측에서 구성해야 할 뿐만 아니라 Linux에서 라우팅 테이블을 구성하고 IP 테이블을 조작해야 합니다. 하지만 가장 나쁜 점은 worker processor가 superuser 또는 root ID를 사용하고 있는지 확인해야 한다는 것입니다. 보안 관점에서 볼 때 이는 가장 피하고 싶은 것입니다.
8. (11:46) TLS Termination

보안에 대해 말하자면, NGINX가 Stream 모듈로 TLS 암호화를 처리하는 방법에는 여러 가지가 있습니다.
첫 번째 작동 모드 중 하나는 TLS Termination입니다. listen 지시문에 ssl 매개변수를 포함시켜 구성하고 HTTP Load Balancer와 마찬가지로 SSL 인증서와 키를 제공하면 됩니다.
proxy_ssl 지시문을 사용하면 NGINX가 TLS를 제거[암호 해독]하고 암호화되지 않은 연결을 백엔드로 전달하도록 지시합니다. 예를 들어 이 지시문은 TLS를 지원하지 않은 애플리케이션에 TLS 지원을 추가하는 데 사용할 수 있습니다.
9. (12:32) TLS Re-Encryption

또 다른 모드는 연결을 다시 암호화하는 것입니다.
기본적으로 NGINX는 지정된 소켓에서 수신 요청을 대기하고 암호를 해독한 다음 백엔드로 보내기 전에 다시 암호화합니다.
사용 방법은 다음과 같습니다. proxy_ssl on 지시문을 사용하여 백엔드에 TLS 암호화를 사용할 수 있도록 설정한 다음, proxy_ssl_verify on으로 백엔드를 확인하도록 지정하고, proxy_ssl_trusted_certificate로 인증서 위치를 제공해야 합니다.
10. (13:05) TLS Wrapping

물론 NGINX에서 TLS를 사용하는 또 다른 방법은 일반 텍스트 요청에 대해 non-TLS 포트에서 수신하고 백엔드에 대한 연결을 암호화하는 것입니다.
11. (14:40) Better Logging

Load Balancer에 어떤 일이 일어나고 있는지 모니터링하고 분석해야 한다는 것은 누구나 알고 있습니다.
다행히도 stream 모듈에 대한 액세스 로그(Access Log)를 활성화하는 기능이 있습니다. 이제 원하는 방식으로 로그를 재구성할 수 있습니다. 이렇게 하면 모니터링 또는 로깅 소프트웨어와 최적으로 작동하도록 구성할 수 있습니다. 이 기능은 기본적으로 켜져 있지 않지만, NGINX stream 블록에서 access_log 지시문을 지정하기만 하면 됩니다.
기본적으로 로그 항목은 슬라이드에 마지막 줄처럼 보입니다. 이는 HTTP 로깅과 매우 유사합니다. HTTP 로그 파서 중 하나가 이를 파싱할 수도 있습니다. 클라이언트 IP 주소, 현지 시간, 프로토콜(TCP/UDP)이 있습니다. 그리고 연결 상태도 확인할 수 있습니다. HTTP에서 NGINX로 작업하는 데 익숙한 사람이라면 누구나 익숙할 것이므로 HTTP의 상태 코드를 재사용하기로 결정했습니다. [여기서 200은 성공적인 TCP 응답을 나타냅니다.]
그런 다음 클라이언트에 보낸 바이트 수[158]와 클라이언트에서 받은 바이트 수[90]를 기록합니다. 마지막으로 세션에 걸린 전체 시간과 연결을 제공한 백엔드의 IP 주소 및 포트인 업스트림 주소가 있습니다.
물론 원하는 로그 형식을 정의할 수 있으며, NGINX에서 사용할 수 있는 모든 변수를 재사용할 수 있습니다.’
12. (19:17) nginxScript로 TCP/UDP Load Balancing 확장

[Editor – 다음 사용 사례는 원래 nginxScript라고 불렸던 NGINX JavaScript 모듈의 많은 사용 사례 중 하나에 불과합니다. 블로그의 나머지 부분에서는 기록과 일치하도록 원래 이름이 유지됩니다. 사용 사례의 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하세요.
NGINX Plus R23 이상에서는 js_import 지시문이 이 섹션에서 설명한 js_include 지시문을 대체합니다. 자세한 내용은 NGINX JavaScript 모둘에 대한 참조 문서 – 예제 구성 섹션에 NGINX 구성 및 JavaScript 파일에 대한 올바른 구문이 나와 있습니다.]
이 구성 스니펫은 nginxScript를 사용하여 다른 슬라이드와 거의 동일한 작업을 수행합니다. 응답에 클라이언트의 원격 주소를 표시하고 있습니다.
이 nginx.conf에서는 동적 스트림 nginxScript 모듈을 로드합니다. stream 블록에서는 특별한 js_include 지시문을 사용합니다. 이 지시문은 우리가 사용할 모든 JavaScript 코드가 포함된 stream.js에 액세스하도록 NGINX에 지시합니다.
server 블록에서는 js_set 지시문을 사용하여 JavaScript 함수의 반환 값인 $foo 변수의 값을 설정합니다.
마지막으로 클라이언트에 대한 TCP 연결에서 해당 값을 반환합니다.
stream.js에서 foo()라는 함수를 정의합니다. 여기서 s는 기본적으로 해당 함수를 통해 전달되는 세션 객체입니다. 무슨 일이 일어나고 있는지 확인하기 위해 몇 가지 로깅을 수행하고 있으며, stream 객체 내에서 내장 변수[s.remoteAddress]로 사용할 수 있는 원격 주소를 반환하고 있습니다.
13. (29:26) TCP/UDP nginScript: 성능

JavaScript로 일부 처리를 수행하는 경우 성능에 약간의 타격이 있을 수 있습니다.
여기서는 NGINX에서 하나의 worker를 사용했으며, HTTP 백엔드가 Load Balancer에 의해 처리되는 일반적인 시나리오에서 초당 요청에 대한 [성능 타격]을 측정했습니다. 처음 두 줄이 기준 시나리오 입니다.
보시가시피 JavaScript를 활성화하는 것만으로도 약 10%의 성능 저하가 발생했습니다. noop은 함수 핸들러를 JavaScript 머신에 전달하고 있지만, 실제로는 아무 작업도 하지 않는다는 의미입니다. 그냥 함수에서 반환할 뿐입니다. JavaScript 코드를 호출하고 있지만 아무 작업도 수행하지 않습니다. 이미 약 10%의 성능 저하가 발생합니다.
정규 표현식을 사용하면 상황이 더 나빠집니다. 30%의 성능 저하가 발생합니다. 웹 애플리케이션 방화벽이 in-place 필터링을 수행하지만, 속도가 느리기 때문에 어느 정도 예상된 결과라고 생각합니다. 정말 느립니다.
이 수치는 2010년형 Xeon 서버에서 나온 것이므로 사용자에 따라 상당히 다를 수 있지만 전체 비율은 비슷할 것입니다.
NGINX Plus를 직접 사용해 보시려면 30일 무료 평가판을 신청하거나 NGINX STORE에 연락하여 문의하십시오.
NGINX에 대한 최신 정보들을 빠르게 전달받고 싶으시다면, 아래의 뉴스레터를 구독하세요.
댓글을 달려면 로그인해야 합니다.