안정적인 서비스를 오픈하기 위한 조건은 무엇일까요? 좋은 아키텍처도 물론 중요하지만, 트래픽을 견딜 수 있는 부하 테스트가 중요합니다. 이번 블로그 글에서는 부하 테스트 관련 글을 작성하고자 합니다.
이벤트 대응 프로세스
부하 테스트를 알아보기 위해 이벤트 대응 프로세스를 알아보겠습니다. (좋은 영상을 공유해주신 장준엽님 감사합니다! 영상 링크)
이벤트 랜딩 페이지 점검 : 부하가 제일 몰리는 구간으로 중요시 봐야하는 구간입니다. 페이지 랜딩 용량으로 7MB 정도(문제가 없는 수준)를 추천합니다.
ELB Pre-Warmining 신청 : AWS ELB도 내부적 하드웨어 장비로 미리 늘려 놔야 합니다. 보통 ELB에 IP 할당 2개로 초당 200~300 트래픽이 처리가 가능하며 그 이상의 이벤트 발생시 사전 신청이 필요합니다.
부하 테스트 / 성능지연 원인 분석 : 부하 테스트가 진행되는 단계입니다. 보통 오픈소스 도구를 통해 진행합니다. (아래 내용 계속)
이벤트 모니터링 : 공유해주신 CloudWatch 주요 모니터링 지표입니다. 생소한 지표로 Surge 큐 Length는 ELB 뒷단의 웹서버가 처리하는 못하는 것을 확인하기 위한 지표라 합니다.
이벤트 대응 프로세스 중 부하 테스트 단계를 진행하겠습니다. 과부하 테스트 도구로는 대표적으로 Jmeter, K6, Locust 등 존재하나 이번 글에서는 Loucst를 다룰 예정입니다.
Locust
Locust는 오픈소스 부하 테스트 도구입니다. Locust는 파이썬 코드 기반으로 테스트 케이스가 작성되어 쉬운 스크립팅을 통해 복잡한 테스트 시나리오를 구성할 수 있습니다. (Jmeter 는 Java, k6는 JavaScript) 그리고 Loucst는 마스터-슬레이브 구조로 여러 기기에서 분산 테스트를 진행할 수 있어 대량의 트래픽을 시뮬레이션 할 수 있는 이점이 있으며 웹 UI를 제공하여 테스트 결과에 대해 빠르게 파악할 수 있습니다. 아래 화면은 예시 화면입니다.
참고로 분산 테스트는 다른 도구들도 다 지원됩니다. 특별히 Locust를 선택한 이유는 필자가 파이썬에 친화적이고 , git repo Star가 가장 많아 자료를 쉽게 찾을 수 있는 점, 웹 UI와 동시에 CLI 가 제공되기 때문이였습니다.
한가지 염두했던 점은 Locust가 파이썬 기반이라 분산 테스트에 효과적으로 테스트할 수 있을까였습니다. 원리를 찾아보니 세부 동작은 gevent라 하여 코루틴 라이브러리로 동작한다고 하네요. 코루틴은 프로그램이 여러 진입점을 가지고 비동기적으로 실행되도록 지원하는 프로그램 구성 요소로 비동기 태스크를 쉽게 구성 및 관리할 수 있고 효과적인 동시성 처리를 관리할 수 있게 만듭니다. 또한, Locust 내부적으로 특정 클라이언트(Locust Class) 를 지원하여 여러 타입의 클라이언트를 동시에 만들어서 실행할 수 있다고 합니다.
특별히 확인할 점은 두가지입니다. Redis 기반으로 잡 스케쥴링을 관리하며, 실제 잡은 워커 노드에 할당하여 진행한다는 점입니다. 워커 노드안에 loucst.execute(), loucst.vaildate() 는 워커 노드 실행시 잡이 수행되는 함수입니다. 각 함수가 순차적으로 진행되는데요. 테스트 시나리오 구성시 워크 노드나 전체 노드에 설치 프로그램이 필요할 수 있는데 setup & teardown(실행시 전체 한번), on_start & on_stop (클라이언트 실행마다 한번) 함수를 통해 시나리오를 구성할 수 있습니다.
배포
EKS에 Locust를 배포하여 테스트해보겠습니다. 테스트 예제 애플리케이션은 지난 아키텍처 글에서 활용한 투표 애플리케이션을 활용하겠습니다.
투표 애플리케이션 배포 참고
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# git clone git clone https://github.com/HanHoRang31/blog-share.git
cd blog-chare/k8s-app/vote-app
tree
# 네임스페이스 생성 후 변경kubectl create ns vote
# 서비스 배포 kubectl apply -f .
# ExternaDNS 추가 (없으면 생략) ## 각자 자신의 도메인 정보 입력MyDOMAIN1=<각자 자신의 nginx 도메인 지정>
MyDOMAIN1=vote.hanhorang.link
MyDOMAIN2=result.hanhorang.link
kubectl annotate service vote "external-dns.alpha.kubernetes.io/hostname=$MyDOMAIN1."kubectl annotate service result "external-dns.alpha.kubernetes.io/hostname=$MyDOMAIN2."
# repo 설치helm repo add deliveryhero https://charts.deliveryhero.io/
helm repo update
helm repo list
---
NAME URL
deliveryhero https://charts.deliveryhero.io/
# 매개 변수 확인 helm show values deliveryhero/locust > values.yaml
---
loadtest:
# loadtest.name -- 이 부하 테스트에 사용될 리소스 및 설정의 이름 name: example
# loadtest.locust_locustfile -- locustfile의 이름 locust_locustfile: main.py
# loadtest.locust_locustfile_path -- locustfile의 경로 (끝에 슬래시 제외) locust_locustfile_path: "/mnt/locust"# loadtest.locust_locustfile_configmap -- locustfile을 포함하는 configmap의 이름 (기본값은 예제 locustfile 사용) locust_locustfile_configmap: "example-locustfile"# loadtest.locust_lib_configmap -- 라이브러리를 포함하는 configmap의 이름 (기본값은 예제 라이브러리 사용) locust_lib_configmap: "example-lib"# loadtest.locust_host -- 부하 테스트할 대상 호스트 locust_host: https://www.google.com
# loadtest.pip_packages -- 설치할 추가 파이썬 pip 패키지의 목록 pip_packages: []# loadtest.environment -- 마스터와 워커 모두에게 적용될 환경 변수 environment: {}# VAR: VALUE.
.
worker:
# worker.image -- 사용자 정의 도커 이미지를 포함한 태그 image: ""# worker.logLevel -- 로그 레벨. INFO 또는 DEBUG 가능 logLevel: INFO
replicas: 1# worker.pdb.enabled -- worker 파드에 대한 PodDisruptionBudget를 생성할지 여부 pdb:
enabled: false hpa:
enabled: false minReplicas: 1 maxReplicas: 100 targetCPUUtilizationPercentage: 40
차트 매개 변수는 깃허브 링크에서 확인이 가능합니다. 차트 변수로 특별하게 볼점은 다음과 같습니다.
테스트 스크립트는 loadtest 변수 (locust_locustfile_configmap) 설정이나 경로를 지정한다음 볼륨을 통해 직접 삽입합니다.(loadtest.locust_locustfile_path) 를 통해 가져옵니다.
워커노드에 대한 과부하 설정은 worker에서 진행합니다.
과부하 테스트
해당 블로그 글에서는 투표 애플리케이션에 대해 과부하 테스트 스크립트를 configmap으로 작성하여 업데이트하여 진행하겠습니다. 과부하 테스트 스크립트는 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Execute in root of repository, and maybe this file has existed.cat <<EOF > locustfile.py && cat locustfile.py
from locust import HttpUser, task, between
import random
class UserBehavior(HttpUser):
wait_time = between(1, 2.5)
@task(1)
def get_home_page(self):
self.client.get("http://result.hanhorang.link/")
@task(2)
def post_vote(self):
vote = ['a', 'b'] # a represents 'Cats', b represents 'Dogs'
self.client.post("http://vote.hanhorang.link/",
data={'vote': random.choice(vote)})
EOF
코드는 간단하다. wait_time 변수 설정으로 1초 ~2.5초 사이의 무작위 대기 시작을 가진 후 @task(1) 과 @task(2) 작업을 1:2 비율로 작업을 수행합니다. @task(1)은 투표 결과화면(밑 화면 오른쪽) 을 조회하고 @task(2)는 dog, cat 투표를 무작위 진행(밑 화면 왼쪽)합니다.
배포를 완료하면 ingress ADDRESS 를 입력하여 locust 웹 UI로 접속하여 테스트를 진행하겠습니다.
왼쪽 처음 화면에서 매개 변수을 입력(밑 사진)하여 테스트를 진행하자. 매개변수는 다음과 같습니다.
Number of Users : 동시 사용자 수
Spawn Rate: 빠르게 추가되는 수치, 1로 설정했으므로 초당 1명씩 사용자를 추가한다.
Host: 이는 부하 테스트의 대상이 되는 웹 사이트나 애플리케이션의 URL
테스트를 진행하면 config에서 설정한 스크립트 구성에서의 task 비율만큼 post와 get 함수가 1:2 비율로 호출됨을 알 수 있습니다.
과부하 테스트를 진행하겠습니다, 밑의 결과 화면을 보면 다음과 같이 사용자가 1000 명에서 응답 시간이 증가함을 알 수 있습니다. 많게는 50000ms로 50초에 해당되어 실제 운영시 추가 작업이 필요함을 알 수 있는데요, Task 함수의 응답시간을 보면 Post 함수에서 지연시간이 발생하는 것을 알 수 있습니다.
가장 먼저 처리한 조치로 POST 응답시간을 최소화시키기 위해 해당 기능 파드인 vote, worker, redis 파드를 증가시켜 봤지만 파드 사용량이 분산되었을 뿐 처리 결과는 똑같았습니다. 다음 단계로는 파드 내 로그를 직접 확인해서 조치해야겠네요.
과부하 테스트(2)
실제 과부하 테스트를 한다면 처음 페이지에서 이벤트 페이지까지의 트래픽이 구성됩니다. 예를 들면, 할인행사시, e-마켓 메인페이지에서 할인행사 이벤트 페이지까지로 생각하시면 이해하기 편하실 것 같습니다. 과부하 테스트에서도 해당 단계의 테스트가 필요합니다.
앞 서 투표 예플리케이션에서는 랜덤으로 투표 1,2를 찍는 것을 테스트했는데요. 랜덤이라 분산되는 시나리오였습니다. 여기서는 투표 1,2를 랜덤으로 찍고 결고화면까지 가는 것을 코드로 구성하겠습니다.
cat<<EOF>locustfile.py&&catlocustfile.pyfromlocustimportHttpUser,task,between,SequentialTaskSetimportrandomclassUserBehavior(SequentialTaskSet):@taskdefpost_vote(self):vote=['a','b']# a represents 'Cats', b represents 'Dogs'self.client.post("http://vote.hanhorang.link/",data={'vote':random.choice(vote)})@taskdefget_home_page(self):self.client.get("http://result.hanhorang.link/")classMyUser(HttpUser):tasks=[UserBehavior]wait_time=between(1,2.5)EOF
주요 변경사항은 코드 내 클래스 구성입니다. Myuser 클래스 구성으로 순차적으로 작업 코드를 구성하였습니다. 이렇게 된다면 Locust Client 는 먼저 랜덤으로 A,B를 투표하고 결과 페이지를 조회하는 시나리오로 진행하게 됩니다.
부하 테스트 팁
오늘은 오픈소스 Locust를 통해 부하테스트를 다뤘습니다. 부하테스트를 진행하니 성능지연 원인 분석도 확인이 필요한 단계로 생각되네요. AWS 공식 문서를 찾아보면 EKS에서 부하 테스트 팁을 확인할 수 있습니다. 거의 파드 라이프사이클, HPA, CA 구성 관련 팁이 있네요. 향후 구성시 고려해봐야겠습니다. 다음 글은 부하테스트에 이어서 성능지연에 관한 원인 분석 글을 다뤄볼 예정입니다.