A/B 테스트 배포, NGINX Plus API Gateway로 시작하기
이번 포스트는 MSA 배포를 위한 NGINX의 Split Clients 모듈이 A/B 테스트 를 수행하는 데 사용되는 방식을 동적으로 변경하는 하나의 사용 사례에 대해 설명합니다.
Key-Value Store 기능은 HTTP 트래픽을 위해 NGINX Plus R13에서 도입되었으며 NGINX Plus R14에서는 TCP/UDP(Stream) 트래픽으로 확장되었습니다. 이 기능은 NGINX 구성을 reload할 필요 없이 NGINX Plus 구성의 일부로 사용할 수 있는 값을 동적으로 유지하기 위한 API를 제공합니다. 이 기능에 대한 여러 가지 사용 사례가 있으며, 고객이 이를 활용할 수 있는 다양한 방법을 찾을 것이라고 확신합니다.
목차
1. A/B 테스트란?
2. Key-Value Store
3. A/B 테스트를 위한 분할 클라이언트
4. 분할 클라이언트에서 Key-Value Store 사용
5. A/B 테스트 시작하기
1. A/B 테스트란?
MSA(Microservice Architecture)는 서비스를 작은 단위로 분할하여 개발 및 배포하는 아키텍처 패턴입니다. 이러한 MSA 환경에서 서비스의 개선과 변화를 수용하기 위해서는 안정적인 배포 과정과 테스트가 필요합니다.
이러한 배포와 테스트 중 하나인 ‘ A/B 테스트 ‘ 는 새로운 버전의 서비스를 일부 사용자에게 노출하고 그 결과를 측정하여 새로운 기능이나 변경 사항이 전체 사용자에게 배포될 수 있는지를 결정하는 방법입니다. A/B 테스트를 통해 서비스의 성능, 사용자 경험, 매출 등의 지표를 개선하고, 사용자의 반응을 빠르게 확인하여 조정할 수 있습니다.
MSA 환경에서 A/B 테스트를 수행하기 위해서는 여러 가지 방법이 있지만, 이 포스트에서는 NGINX Plus API Gateway를 활용한 무중단 배포와 A/B 테스트 방법에 대해 다룰 것입니다.
2. Key-Value Store
NGINX Plus API는 NGINX Plus가 런타임에 액세스할 수 있는 Key-Value Pairs 세트를 유지하는 데 사용할 수 있습니다. 예를 들어 사이트(또는 특정 URL)에 액세스할 수 없는 클라이언트 IP 주소의 거부(deny) 목록을 유지하려는 사용 사례를 살펴보겠습니다.
Key는 $remote_addr 변수에 캡처된 클라이언트 IP 주소입니다. 값은 $denylist_status라는 변수로, 클라이언트 IP 주소가 거부 목록에 있음을 나타내기 위해 1로 설정되고 거부 목록에 포함되지 않음을 0으로 설정합니다.
이를 구성하려면 다음 단계를 수행합니다.
- Key-Value Pairs을 저장할 공유 메모리 zone 설정합니다.(keyval_zone 지시문)
- zone 이름을 지정하십시오.
- 할당할 최대 메모리 양을 지정합니다.
- 옵션으로, 저장할 상태 파일을 지정하여 NGINX Plus를 다시 시작해도 항목이 유지되도록 합니다.
상태 파일의 경우 이전에 /etc/nginx/state_files 디렉토리를 만들고 NGINX Worker Processes를 실행하는 권한 없는 사용자가 쓰기 가능하도록 했습니다(구성의 다른 곳에서 user 지시문에 정의됨). 여기서는 Key-Value Pairs을 저장하기 위한 denylist.json 파일을 생성하기 위해 keyval_zone 지시문에 state 매개 변수를 포함합니다.
keyval_zone zone=denylist:64k
state=/etc/nginx/state_files/denylist.json;
NGINX Plus R16 이상에서는 두 가지 추가 Key-Value 기능을 활용할 수 있습니다.
- keyval_zone 지시문에 timeout 매개변수를 추가하여 Key-Value Store의 항목에 대한 만료 시간을 설정합니다. 예를 들어 2시간 동안 주소를 거부 목록에 추가하려면 timeout=2h를 추가합니다.
- keyval_zone 지시문에 sync 매개변수를 추가하여 NGINX Plus 인스턴스 클러스터 전체에서 Key-Value Store를 동기화합니다. 이 경우에는 timeout 매개 변수도 포함해야 합니다.
따라서 2시간 동안 거부(deny) 목록에 있는 주소의 동기화된 Key-Value Store를 사용하도록 예제를 확장하려면 다음과 같은 지시문을 사용합니다.
keyval_zone zone=denylist:64k timeout=2h sync
state=/etc/nginx/state_files/denylist.json;
다음으로 key-value Pair을 정의하는 keyval 지시문을 추가합니다. Key가 클라이언트 IP 주소($remote_addr)이고 값이 $denylist_status 변수에 할당되도록 지정합니다.
keyval $remote_addr $denylist_status zone=denylist;
Key-Value Store에서 Pair을 만들려면 HTTP POST 요청을 사용합니다. 예를 들면
# curl -iX POST -d '{"10.11.12.13":1}' http://localhost/api/3/http/keyvals/denylist
기존 Key-Value Pair의 값을 수정하려면 HTTP PATCH 요청을 사용하십시오. 예를 들면
# curl -iX PATCH -d '{"10.11.12.13":0}' http://localhost/api/3/http/keyvals/denylist
Key-Value Pair을 제거하려면 HTTP PATCH 요청을 사용하여 값을 null로 설정합니다. 예를 들면
# curl -iX PATCH -d '{"10.11.12.13":null}' http://localhost/api/3/http/keyvals/denylist
3. A/B 테스트 를 위한 분할 클라이언트
클라이언트 분할 모듈(split_client)을 사용하면 선택한 요청 특성에 따라 Upstream 그룹 간에 수신 트래픽을 분할할 수 있습니다. 다른 Upstream 그룹으로 전달할 수신 트래픽의 백분율로 분할을 정의합니다. 일반적인 사용 사례는 소량의 트래픽을 새 버전에 전송하고 나머지는 현재 버전으로 전송하여 애플리케이션의 새 버전을 테스트하는 것입니다. 이 예에서는 트래픽의 5%를 새 버전인 appversion2의 Upstream 그룹으로 보내고 나머지(95%)를 현재 버전인 appversion1로 보냅니다.
요청의 클라이언트 IP 주소를 기반으로 트래픽을 분할하므로 split_clients 지시문의 첫 번째 매개변수를 NGINX 변수 $remote_addr로 설정합니다. 두 번째 매개변수를 사용하여 변수 $upstream을 Upstream 그룹의 이름으로 설정합니다.
기본 구성은 다음과 같습니다.
split_clients $remote_addr $upstream {
5% appversion2;
* appversion1;
}
upstream appversion1 {
# ...
}
upstream appversion2 {
# ...
}
server {
listen 80;
location / {
proxy_pass http://$upstream;
}
}
4. 분할 클라이언트에서 Key-Value Store 사용
NGINX Plus R13 이전에는 분할 비율을 변경하려면 구성 파일을 편집하고 구성을 reload해야 했습니다. Key-Value Store를 사용하면 reload할 필요 없이 Key-Value Pair에 저장된 백분율 값을 NGINX Plus에서 제공하는 RESTFul API로 동적 변경하기만 하면 reload할 필요 없이 그에 따라 분할이 변경됩니다.
이전 섹션의 사용 사례를 바탕으로 NGINX Plus가 appversion2로 전송되는 트래픽 양에 대해 다음 옵션을 지원하기로 결정했다고 가정해 보겠습니다. 0%, 5%, 10%, 25%, 50% 및 100%. 또한 호스트 헤더(NGINX 변수 $host에서 캡처됨)를 기반으로 분할을 수행하려고 합니다. 다음 NGINX Plus 구성은 이 기능을 구현합니다.
먼저 Key-Value Store를 설정합니다.
keyval_zone zone=split:64k state=/etc/nginx/state_files/split.json;
keyval $host $split_level zone=split;
초기 사용 사례에서 언급한 것처럼 실제 배포에서는 클라이언트 IP 주소 $remote_addr과 같은 요청 특성을 기반으로 분할하는 것이 좋습니다. 그러나 curl과 같은 도구를 사용하는 간단한 테스트에서는 모든 요청이 단일 IP 주소에서 오므로 관찰할 분할이 없습니다.
테스트를 위해 우리는 대신 더 임의적인 값인 $request_id를 기준으로 분할합니다. 구성을 테스트에서 Production로 쉽게 전환할 수 있도록 server 블록 $client_ip에 새 변수를 만들어 테스트용으로 $request_id로 설정하고 Production 용으로 $remote_addr로 설정합니다. 그런 다음 split_clients 구성을 설정합니다.
각 분할 백분율(0%의 경우 split0, 5%의 경우 split5 등)에 대한 변수는 별도의 split_clients 지시문에서 설정됩니다.
split_clients $client_ip $split0 {
* appversion1;
}
split_clients $client_ip $split5 {
5% appversion2;
* appversion1;
}
split_clients $client_ip $split10 {
10% appversion2;
* appversion1;
}
split_clients $client_ip $split25 {
25% appversion2;
* appversion1;
}
split_clients $client_ip $split50 {
50% appversion2;
* appversion1;
}
split_clients $client_ip $split100 {
* appversion2;
}
이제 Key-Value Stroe와 split_clients가 구성되었으므로 $upstream 변수를 적절한 분할 변수에 지정된 Upstream 그룹으로 설정하도록 map을 설정할 수 있습니다.
map $split_level $upstream {
0 $split0;
5 $split5;
10 $split10;
25 $split25;
50 $split50;
100 $split100;
default $split0;
}
마지막으로, Upstream 그룹과 가상 서버에 대한 나머지 구성이 있습니다. Key-Value Store 및 실시간 활동 모니터링 대시보드에 사용되는 NGINX Plus API도 구성했습니다. 다음은 NGINX Plus의 상태 대시보드입니다.
upstream appversion1 {
zone appversion1 64k;
server 192.168.50.100;
server 192.168.50.101;
}
upstream appversion2 {
zone appversion2 64k;
server 192.168.50.102;
server 192.168.50.103;
}
server {
listen 80;
status_zone test;
#set $client_ip $remote_addr; # Production
set $client_ip $request_id; # For testing only
location / {
proxy_pass http://$upstream;
}
location /api {
api write=on;
# in production, directives restricting access
}
location = /dashboard.html {
root /usr/share/nginx/html;
}
}
이제 이 구성을 사용하여 NGINX Plus에 API 요청을 보내고 호스트명에 대해 $split_level 값을 설정하여 appversion1 및 appversion2 Upstream 그룹 간에 트래픽을 분할하는 방법을 제어할 수 있습니다. 예를 들어 http://www.example.com에 대한 트래픽의 5%가 appversion2 Upstream 그룹으로 전송되고 www2.example.com에 대한 트래픽의 25%가 appversion2로 Upstream 그룹으로 전송되도록 다음 두 가지 요청을 NGINX Plus로 전송할 수 있습니다.
# curl -iX POST -d '{"www.example.com":5}' http://localhost/api/3/http/keyvals/split
# curl -iX POST -d '{"www2.example.com":25}' http://localhost/api/3/http/keyvals/split
http://www.example.com의 값을 10으로 변경하려면.
# curl -iX PATCH -d '{"www.example.com":10}' http://localhost/api/3/http/keyvals/split
값을 지우려면.
# curl -iX PATCH -d '{"www.example.com":null}' http://localhost/api/3/http/keyvals/split
이러한 각 요청 후에 NGINX Plus는 즉시 새로운 분할 값을 사용하기 시작합니다.
전체 구성 파일은 다음과 같습니다.
# Set up a key‑value store to specify the percentage to send to each
# upstream group based on the 'Host' header.
keyval_zone zone=split:64k state=/etc/nginx/state_files/split.json;
keyval $host $split_level zone=split;
split_clients $client_ip $split0 {
* appversion1;
}
split_clients $client_ip $split5 {
5% appversion2;
* appversion1;
}
split_clients $client_ip $split10 {
10% appversion2;
* appversion1;
}
split_clients $client_ip $split25 {
25% appversion2;
* appversion1;
}
split_clients $client_ip $split50 {
50% appversion2;
* appversion1;
}
split_clients $client_ip $split100 {
* appversion2;
}
map $split_level $upstream {
0 $split0;
5 $split5;
10 $split10;
25 $split25;
50 $split50;
100 $split100;
default $split0;
}
upstream appversion1 {
zone appversion1 64k;
server 192.168.50.100;
server 192.168.50.101;
}
upstream appversion2 {
zone appversion2 64k;
server 192.168.50.102;
server 192.168.50.103;
}
server {
listen 80;
status_zone test;
# In each 'split_clients' block above, '$client_ip' controls which
# application receives each request. For a production application, we set it
# to '$remote_addr' (the client IP address). But when testing from just one
# client, '$remote_addr' is always the same; to get some randomness, we set
# it to '$request_id' instead.
#set $client_ip $remote_addr; # Production
set $client_ip $request_id; # Testing only
location / {
proxy_pass http://$upstream;
}
# Configure the NGINX Plus API and dashboard. For production, add directives
# to restrict access to the API, for example 'allow' and 'deny'.
location /api {
api write=on;
# in production, directives restricting access
}
location = /dashboard.html {
root /usr/share/nginx/html;
}
}
5. A/B 테스트 시작하기
이것은 Key-Value Store로 A/B 테스트 를 수행할 수 있는 작업의 한 가지 예에 불과합니다. 요청 속도 제한(Rate Limit), 대역폭 제한 또는 연결 제한에도 유사한 방법을 사용할 수 있습니다.
NGINX Plus API Gateway를 직접 사용해 보거나 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.