TCP 로드 밸런싱과 Galera Cluster를 활용한 MySQL 확장

NGINX | 2021년 7월 26일

[편집자 – 2016년에 처음 게시된 이 글은 이후 개정된 NGINX 기능을 사용하도록 업데이트되었습니다. 자세한 내용은 NGINX JavaScript 모듈을 활용한 고급 로깅 및 NGINX Plus 대시보드를 참조하세요.]

당사는 NGINX Plus R5에서 TCP 부하 분산을 도입한 이후, 후속 릴리스에서 지속적으로 기능을 추가하고 UDP 부하 분산도 지원해 왔습니다. 이 글에서는 TCP 부하 분산의 핵심 요구 사항과 NGINX Plus가 이를 충족하는 방법을 살펴봅니다.

NGINX Plus의 기능을 살펴보기 위해 확장된 데이터베이스 백엔드를 갖춘 애플리케이션의 주요 구성 요소를 나타내는 간단한 테스트 환경을 사용하겠습니다. 테스트 환경 구축에 대한 전체 지침은 부록을 참조하세요.

MySQL 서버의 부하 분산을 위한 테스트 환경에서는 MySQL 클라이언트와 Galera 클러스터 사이에 NGINX Plus를 배치합니다
MySQL 노드의 부하 분산을 위한 테스트 환경

이 환경에서 NGINX Plus는 데이터베이스 서버의 리버스 프록시 역할을 하며 기본 MySQL 포트인 3306에서 수신 대기합니다. 이를 통해 클라이언트에 간단한 인터페이스를 제공하는 동시에, 클라이언트에 아무런 영향을 주지 않고 백엔드 MySQL 노드를 확장하거나 오프라인으로 전환할 수도 있습니다. 테스트 환경에서는 프런트엔드 애플리케이션을 나타내는 클라이언트로 MySQL 명령줄 도구를 사용합니다.

이 문서에서 설명하는 많은 기능은 NGINX Open Source와 NGINX Plus 모두에 적용됩니다. 간결하게 설명하기 위해 이 문서에서는 NGINX Plus로 통칭하며, NGINX Open Source에서 사용할 수 없는 기능은 별도로 명시하겠습니다.

다음 사용 사례를 살펴보겠습니다:

TCP 부하 분산

애플리케이션에 부하 분산을 구성하기 전에 해당 애플리케이션이 데이터베이스에 연결되는 방식을 이해하는 것이 중요합니다. 대부분의 테스트에서는 MySQL 명령줄 도구인 mysql(1)을 사용하여 Galera 클러스터에 연결하고 쿼리를 실행한 다음 연결을 종료합니다. 그러나 많은 애플리케이션 프레임워크에서는 지연 시간을 최소화하고 데이터베이스 서버 리소스를 효율적으로 활용하기 위해 연결 풀을 사용합니다.

TCP 부하 분산은 stream 구성 컨텍스트에서 설정되므로, 기본 nginx.conf 파일에 stream 블록을 추가하여 기본 MySQL 부하 분산 구성을 만듭니다.

코드 예제를 로드하는 중…

이렇게 하면 TCP 부하 분산 구성이 기본 구성 파일과 분리됩니다. 그런 다음 stream.conf 파일을 nginx.conf와 동일한 디렉터리에 생성합니다. 기본적으로 conf.d 디렉터리는 http 구성 컨텍스트용으로 예약되어 있으므로 이 디렉터리에 stream 구성 파일을 추가해도 작동하지 않습니다.

코드 예제를 로드하는 중…

먼저 upstream 그룹을 정의하고 이름을 galera_cluster로 지정합니다. 이 그룹에는 Galera 클러스터의 MySQL 노드 3개가 포함됩니다. 테스트 환경에서는 각 노드에 localhost의 고유한 포트 번호를 통해 액세스할 수 있습니다. zone 지시문은 부하 분산 상태를 유지하기 위해 모든 NGINX Plus 워커 프로세스가 공유하는 메모리의 크기를 정의합니다. server{} 블록은 NGINX Plus가 클라이언트를 처리하는 방식을 구성합니다. NGINX Plus는 기본 MySQL 포트인 3306에서 수신 대기하고 모든 트래픽을 upstream 블록에 정의된 Galera 클러스터로 전달합니다.

이 기본 구성이 제대로 작동하는지 테스트하려면 MySQL 클라이언트를 사용하여 연결된 Galera 클러스터 노드의 호스트 이름을 확인할 수 있습니다.

$ echo "SHOW VARIABLES WHERE Variable_name = 'hostname'" | mysql --protocol=tcp --user=nginx --password=plus -N 2> /dev/nullhostname	node1

로드 밸런싱이 제대로 작동하는지 확인하려면 동일한 명령을 다시 실행하면 됩니다.

$ !!;!!;!!hostname	node2
hostname	node3
hostname	node1

이는 기본 라운드 로빈 부하 분산 알고리즘이 올바르게 작동하고 있음을 보여 줍니다. 그러나 애플리케이션이 위에서 제안한 대로 연결 풀을 사용하여 데이터베이스에 액세스하는 경우, 라운드 로빈 방식으로 클러스터에 대한 연결을 열면 각 노드의 연결 수가 불균형해질 가능성이 큽니다. 또한 연결은 유휴 상태(애플리케이션의 쿼리를 기다리는 상태)일 수도 있고 쿼리를 처리하느라 사용 중일 수도 있으므로, 연결 하나를 특정 워크로드와 동일시할 수 없습니다. 수명이 긴 TCP 연결에는 더 적합한 부하 분산 알고리즘인 최소 연결을 사용할 수 있으며, 이는 least_conn 지시어로 구성합니다:

코드 예제를 로드하는 중…

이제 클라이언트가 데이터베이스에 대한 새 연결을 열면 NGINX Plus는 현재 연결 수가 가장 적은 클러스터 노드를 선택합니다.

고가용성 및 상태 확인

데이터베이스 워크로드를 클러스터 전체에 분산하면 얻을 수 있는 큰 이점은 고가용성도 제공된다는 것입니다. 위에서 설명한 구성에서는 새로운 TCP 연결을 설정할 수 없는 경우 NGINX Plus가 해당 서버를 “다운” 상태로 표시하고 TCP 패킷 전송을 중지합니다.

이러한 방식으로 다운된 서버를 처리하는 것 외에도, NGINX Plus는 자동 사전 예방 상태 점검을 수행하도록 구성하여 사용할 수 없는 서버를 클라이언트 요청이 전송되기 전에 감지할 수 있습니다(이 기능은 NGINX Plus에서만 제공됩니다). 또한 애플리케이션 수준의 상태 점검을 통해 서버의 가용성을 테스트할 수 있습니다. 즉, 각 서버에 요청을 보내 정상 상태임을 나타내는 응답이 수신되는지 확인할 수 있습니다. 이를 반영하여 구성을 다음과 같이 확장합니다.

코드 예제를 로드하는 중…

이 예에서 match 블록은 MySQL 프로토콜 버전 10 핸드셰이크를 시작하는 데 필요한 요청 및 응답 데이터를 정의합니다. health_check 지시문은 server{} 블록에서 이 패턴을 적용하여, NGINX Plus가 실제로 새 연결을 수락할 수 있는 서버에만 MySQL 연결을 전달하도록 합니다. 이 경우 20초마다 상태 검사를 수행하고, 한 번이라도 실패하면 해당 서버를 TCP 부하 분산 풀에서 제외하며, 상태 검사가 2회 연속 성공하면 해당 서버에 대한 부하 분산을 재개합니다.

로깅 및 진단

NGINX Plus는 유연한 로깅 기능을 제공하므로 모든 TCP/UDP 처리 내용을 디버깅이나 오프라인 분석을 위해 기록할 수 있습니다. MySQL과 같은 TCP 프로토콜의 경우 NGINX Plus는 연결이 종료될 때 로그 항목을 기록합니다. log_format 지시문은 로그에 표시할 값을 정의합니다. Stream 모듈에서 사용할 수 있는 변수 중 원하는 것을 선택할 수 있습니다. 로그 형식은 stream 컨텍스트에서 stream.conf 파일의 맨 위에 정의합니다.

코드 예제를 로드하는 중…

로깅을 활성화하려면 access_log 지시어를 server{} 블록에 추가하고, 로그 파일의 경로와 이전 코드 조각에서 정의한 로그 형식의 이름을 지정합니다.

코드 예제를 로드하는 중…

그러면 아래 예시와 같은 로그 항목이 생성됩니다.

$ tail -3 /var/log/nginx/galera_access.log192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 1611 127.0.0.1:33063 0.000 0.003 12.614 12.614
192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 8337 127.0.0.1:33061 0.001 0.001 11.181 11.181
192.168.91.1 [23/Jul/2021:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460

NGINX JavaScript 모듈을 활용한 고급 로깅

NGINX JavaScript는 ‘NGINX 네이티브’ 프로그래밍 방식의 구성 언어입니다. NGINX 및 NGINX Plus를 위한 고유한 JavaScript 구현체로, 서버 측 사용 사례와 요청별 처리를 위해 특별히 설계되었습니다.

[편집자 – 다음 사용 사례는 NGINX JavaScript 모듈의 여러 사용 사례 중 하나일 뿐입니다. 전체 사용 사례 목록은 NGINX JavaScript 모듈 사용 사례를 참조하세요.

이 게시물은 s) 객체를 사용하도록 업데이트되었습니다. 이 객체는 NGINX JavaScript 0.2.4에서 Stream 모듈용으로 리팩터링되었으며, js_import 지시문은 NGINX JavaScript 0.4.0에서 도입되었습니다.]

TCP/UDP 부하 분산을 위한 Stream 모듈에서 NGINX JavaScript는 요청 및 응답 패킷의 내용에 액세스할 수 있도록 합니다. 즉, SQL 쿼리에 해당하는 클라이언트 요청을 검사하고 SQL 메서드와 같은 유용한 요소(예: SELECT 또는 UPDATE)를 추출할 수 있습니다. 그런 다음 NGINX JavaScript는 이러한 값을 일반 NGINX 변수로 사용할 수 있게 합니다. 이 예제에서는 JavaScript 코드를 /etc/nginx/sql_method.js에 저장합니다.

코드 예제를 로드하는 중…

The getSqlMethod() 함수에는 현재 패킷을 나타내는 JavaScript 객체(s)가 전달됩니다. 이 객체의 fromUpstream 및 buffer 같은 속성을 통해 패킷과 그 컨텍스트에 필요한 정보를 얻을 수 있습니다.

먼저 업스트림 MySQL 서버에서 오는 패킷은 검사할 필요가 없으므로 TCP 패킷이 클라이언트에서 오는지 확인합니다. 여기서는 처음 두 패킷에 핸드셰이크 및 인증 정보가 포함되어 있으므로 세 번째 클라이언트 패킷에 주목합니다. 세 번째 클라이언트 패킷에는 SQL 쿼리가 포함되어 있습니다. 그런 다음 이 문자열의 시작 부분을 methods 배열에 정의된 SQL 메서드 중 하나와 비교합니다. 일치하는 항목을 찾으면 결과를 전역 변수 $method에 저장하고 오류 로그에 항목을 기록합니다. NGINX JavaScript 로그는 심각도 error log에 info 수준으로 기록되므로 기본적으로 표시되지 않습니다.

The setSqlMethod() 함수는 같은 이름의 NGINX 변수가 평가될 때 호출됩니다. 이때 해당 변수에는 $method 값이 할당되며, 이 값은 getSqlMethod() 함수 호출을 통해 얻은 NGINX JavaScript 전역 변수입니다.

이 NGINX JavaScript 코드는 단일 쿼리가 실행되는 MySQL 명령줄 클라이언트용으로 설계되었습니다. 복잡한 쿼리나 장시간 유지되는 연결을 통한 여러 쿼리를 정확하게 캡처하지는 못하지만, 이러한 사용 사례에 맞게 코드를 조정할 수 있습니다. NGINX JavaScript 모듈을 설치하고 활성화하는 방법은 NGINX Plus 관리자 가이드를 참조하세요.

로그에 SQL 메서드를 포함하려면 $sql_method 변수를 log_format 지시문에 추가합니다.

코드 예제를 로드하는 중…

또한 NGINX Plus가 NGINX JavaScript 코드를 실행할 시점과 방법을 지정하도록 구성을 확장해야 합니다.

코드 예제를 로드하는 중…

먼저 js_import 지시문으로 NGINX JavaScript 코드의 위치를 지정하고, js_set 지시문을 사용하여 NGINX Plus가 $sql_method 변수를 평가해야 할 때 setSqlMethod() 함수를 호출하도록 합니다. 그런 다음 server{} 블록 내에서 js_filter 지시문을 사용하여 패킷이 처리될 때마다 호출할 함수를 지정합니다. 선택적으로 error_log 지시문에 info 옵션을 추가하여 NGINX JavaScript 로깅을 활성화할 수 있습니다.

이 추가 구성을 적용하면 액세스 로그는 이제 다음과 같이 표시됩니다.

$ tail -3 /var/log/nginx/galera_access.log192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 1611 127.0.0.1:33063 0.000 0.003 12.614 12.614 UPDATE
192.168.91.1 [23/Jul/2021:17:42:18 +0100] TCP 200 369 8337 127.0.0.1:33061 0.001 0.001 11.181 11.181 SELECT
192.168.91.1 [23/Jul/2021:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460 UPDATE

NGINX Plus 대시보드

[편집자 – 이 섹션은 여기에서 원래 설명했던 별도의 확장 상태 모듈을 대체하고 더 이상 사용되지 않도록 하는 NGINX Plus API를 참조하도록 업데이트되었습니다.]

MySQL 활동을 상세히 로깅하는 것뿐만 아니라, NGINX Plus 실시간 활동 모니터링 대시보드에서 실시간 메트릭과 업스트림 MySQL 서버의 상태를 확인할 수 있습니다(NGINX Open Source는 더 적은 메트릭을 제공하며, Stub Status API를 통해서만 확인할 수 있습니다).

NGINX Plus 대시보드는 NGINX Plus R7에서 도입되었으며 NGINX Plus API를 위한 웹 인터페이스를 제공합니다. 별도의 server{} 블록을 http 컨텍스트의 /etc/nginx/conf.d/dashboard.conf 파일에 새로 추가하여 이 기능을 활성화합니다:

코드 예제를 로드하는 중…

또한 MySQL 서비스의 모니터링 데이터를 수집할 수 있도록 server{} 블록을 stream.conf에서 status_zone 지시어로 업데이트해야 합니다.

코드 예제를 로드하는 중…

이 구성을 적용하면 포트 8080에서 NGINX Plus 대시보드를 사용할 수 있습니다. 다음 스크린샷에는 3개의 MySQL 서버가 표시되며, 각 서버에서 현재 진행 중인 여러 연결의 세부 정보와 현재 상태를 확인할 수 있습니다. 포트 33062에서 수신 대기 중인 노드에는 이전에 18.97초 동안 한 차례 짧은 중단이 발생했음을 확인할 수 있습니다(DT 열에 표시됨).

NGINX Plus 실시간 활동 모니터링 대시보드를 사용하면 MySQL 노드의 부하를 분산할 때 서버 상태를 추적할 수 있습니다.
NGINX Plus 실시간 활동 모니터링 대시보드를 사용하면 MySQL 서버의 상태를 추적할 수 있습니다

동시 쓰기 시 고려 사항

Galera Cluster는 각 MySQL 서버 노드를 읽기와 쓰기를 모두 수행하는 마스터 데이터베이스로 제공합니다. 많은 애플리케이션에서는 읽기 작업이 쓰기 작업보다 훨씬 많기 때문에, 멀티마스터 데이터베이스 클러스터가 제공하는 유연성을 고려하면 여러 클라이언트가 동일한 테이블 행을 동시에 업데이트할 위험은 충분히 감수할 만합니다. 동시 쓰기가 발생할 위험이 더 높은 경우에는 두 가지 옵션이 있습니다.

  1. 읽기용과 쓰기용으로 각각 별도의 업스트림 그룹을 만들고, 서로 다른 포트에서 수신하도록 합니다. 클러스터에서 하나 이상의 노드를 쓰기 전용으로 지정하고, 모든 노드를 읽기 그룹에 포함합니다. 읽기 및 쓰기 작업에 적합한 포트를 선택하도록 클라이언트 코드를 업데이트해야 합니다. 이 접근 방식은 블로그의 NGINX Plus를 활용한 고급 MySQL 부하 분산에서 설명하며, MySQL 서버 노드가 많은 대규모 확장 환경에 적합합니다.
  2. 단일 업스트림 그룹을 유지하고 쓰기 오류를 감지하도록 클라이언트 코드를 수정합니다. 쓰기 오류가 감지되면 동시 작업이 끝난 후 다시 시도하기 전에 코드가 지수 백오프를 수행합니다. 이 접근 방식은 블로그의 NGINX Plus 및 Galera Cluster를 활용한 MySQL 고가용성에서 설명하며, 클러스터 노드를 쓰기 전용으로 지정할 경우 고가용성이 저하될 수 있는 소규모 클러스터에 적합합니다.

요약

이 글에서는 MySQL과 같은 TCP(또는 UDP) 애플리케이션의 부하 분산에 필요한 몇 가지 핵심 요소를 살펴보았습니다. NGINX Plus는 모든 기능을 갖춘 TCP/UDP 로드 밸런서를 제공하여 트래픽 유형에 관계없이 뛰어난 성능, 안정성, 보안 및 확장성을 갖춘 애플리케이션을 제공할 수 있도록 지원합니다.

NGINX Plus를 사용해 보려면 지금 30일 무료 체험을 시작하거나 사용 사례에 대해 논의하려면 문의해 주세요.

부록: 테스트 환경 만들기

테스트 환경은 격리된 상태에서 동일하게 재현할 수 있도록 가상 머신에 설치됩니다. 하지만 물리적 '베어 메탈' 서버에 설치하지 못할 이유는 없습니다.

NGINX Plus 설치

See the NGINX Plus 관리자 가이드를 참조하세요.

MySQL용 Galera Cluster 설치

이 예제에서는 각 노드에 Docker 컨테이너를 사용하여 단일 호스트에 Galera Cluster를 설치합니다. 다음 지침은 Docker로 Galera 시작하기를 바탕으로 작성되었으며, Docker Engine과 MySQL 명령줄 도구가 모두 이미 설치되어 있다고 가정합니다.

  1. Docker 이미지가 각 Galera 컨테이너에 복사할 기본 MySQL 구성 파일(my.cnf)을 만듭니다.

    코드 예제를 로드하는 중…

  2. Galera 기본 Docker 이미지를 가져옵니다.
$ sudo docker pull erkules/galera:basic
  1. 첫 번째 Galera 노드(node1)를 생성하고 기본 MySQL 포트를 33061로 노출합니다.
$ sudo docker run -p 33061:3306 --detach=true --name node1 -h node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://
  1. 두 번째 Galera 노드(node2)를 생성합니다. MySQL 포트는 33062로 노출되며 클러스터 간 통신을 위해 node1에 연결됩니다.
$ sudo docker run -p 33062:3306 --detach=true --name node2 -h node2 --link node1:node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://node1
  1. 세 번째이자 마지막 Galera 노드(node3)를 node2와 동일한 방식으로 생성합니다. MySQL 포트는 33063으로 노출됩니다.
$ sudo docker run -p 33063:3306 --detach=true --name node3 -h node3 --link node1:node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://node1
  1. 호스트에서 클러스터에 원격으로 액세스하는 데 사용할 수 있는 nginx라는 사용자 계정을 생성합니다. 이 작업은 Docker 컨테이너 내부에서 mysql(1) 명령을 실행하여 수행합니다.
$ sudo docker exec -ti node1 mysql -e"GRANT ALL PRIVILEGES ON *.* TO 'nginx'@'172.17.0.1' IDENTIFIED BY 'plus'"
  1. 호스트에서 TCP 프로토콜을 사용하여 Galera 클러스터에 연결할 수 있는지 확인합니다.
$ mysql --protocol=tcp -P 33061 --user=nginx --password=plus -e "SHOW DATABASES"mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
  1. 마지막으로, 다른 클러스터 노드 중 하나에서 동일한 명령을 실행하여 nginx 사용자 계정이 복제되었고 클러스터가 올바르게 작동하는지 확인합니다.
$ mysql --protocol=tcp -P 33062 --user=nginx --password=plus -e "SHOW DATABASES"mysql: [Warning] Using a password on the command line interface can be insecure.
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+


공유
태그: F5 NGINX, 2021, Tech

작성자 소개

관련 블로그 게시물

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
NGINX | 2023. 11. 30.

시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric

이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
NGINX | 2023. 03. 23.

F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법

앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
NGINX | 2021. 09. 27.

Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션

개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX | 2021. 07. 06.

진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법

NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX | 2021. 06. 30.

NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리

NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
NGINX | 2015. 03. 25.

Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유

Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.