kubernetes에서 locust를 이용해 부하테스트해보기

kubernetes에서 locust를 이용해 부하테스트해보기

Locust를 이용한 부하테스트를 K8S를 사용해서 진행해보았습니다.

Locust

Locust는 파이썬으로 부하테스트를 할 수 있는 오픈소스입니다. 웹이나 앱에서 시스템이 처리할 수 있는 동시 사용자 수를 파악하기 위해서 사용하고, 서버가 감당할 수 있는 TPS/RPS(Transaction Per Second/Request Per Second)를 대략적으로 파악할 수 있습니다.

Locust 말고도 Jmeter나 nGrinder와 같은 부하테스트 도구가 있습니다만, 좀 더 쉽고 빠르게 테스트를 할 수 있고 python으로 간편하게 작업할 수 있다는 점에서 좋다고 생각되네요.

Locust란 말의 뜻은 썸네일에서 보이듯이 메뚜기떼를 말합니다!
그래서 Locust에서 부하를 주는 것을 swarming이라고 하는 것입니다.

locust를 보통 서버에 올려서 사용하지만 Kubernetes를 사용하는 환경이라면 이것을 k8s에 설치해서 부하테스트를 진행할 수 있습니다. 서버에 올려서 설치한다면 워커 수나 유저 수를 늘렸을 때 리소스의 한계가 있기 마련인데, k8s를 사용한다면 원하는 만큼 유저와 부하를 늘려서 테스트를 진행할 수 있습니다.(사실 서버에 올려져있는 다른 툴을 사용하다가 원하는 만큼 부하를 주지 못해서 k8s를 사용하게 되었답니다.)



Kubernetes in Locust

Locust를 쿠버네티스에 설치하기 위해서 Helm차트를 찾아보았습니다. 하지만 공식 차트는 따로 없는 것 같았습니다. 그래서 Delivery Hero 에서 제공하는 Helm 차트를 사용하기로 했습니다. 살펴보니 나름 사용할 만 했고 최근 커밋도 4개월 전에 있었습니다. 또 간단하게 스크립트를 적용해서 테스트를 할 수 있었기 때문에 이 차트로 설치해서 진행해보기로 했습니다.

Locust Helm Chart

Helm Chart는 여기에 제공되고 있습니다. 이 링크로 가면 어떻게 차트를 설치하고 사용하는지에 대해서 나와있습니다. 먼저 helm 차트를 등록하고 설치해주시면 됩니다.

helm repo add deliveryhero https://charts.deliveryhero.io/

helm install deliveryhero/locust

helm install my-release deliveryhero/locust

커스텀하게 values.yaml를 작성해서 설치하고 싶다면 다음 명령어를 사용합니다. 한 번 설치한 다음 커스텀할 값이 있다면 이렇게 설치하는 것도 좋아보이네요!

helm install my-release deliveryhero/locust -f values.yaml

Locust용 namespace를 만들어 놓고 설치하는 것을 추천드립니다. 해당 namespace에 리소스를 많이 줘야하는 상황이 있을 수 있기 때문에 k8s에 설치된 다른 앱들에 영향을 주지 않기위해서라도 namespace를 별도로 사용해주세요.



Chart 구성

이 차트에는 기본적으로 ConfigMap을 사용해서 부하테스트를 진행합니다. 샘플로 제공되어 있는 파일은 main.py와 lib에 있는 example_functions.py 입니다. 전자는 실제 실행하는 locust 스크립트이고, 다른 하나는 여기에 사용하는 라이브러리가 되겠습니다.

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-

from locust import HttpUser, task, between
from lib.example_functions import choose_random_page


default_headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'}


class WebsiteUser(HttpUser):
wait_time = between(1, 2)

@task(1)
def get_index(self):
self.client.get("/", headers=default_headers)

@task(3)
def get_random_page(self):
self.client.get(choose_random_page(), headers=default_headers)

main.py를 먼저 살펴보면 정말 간단하게 짜여있는 것을 볼 수 있습니다. 먼저 lib에서 example_functions를 갖고와서 미리 만들어둔 함수를 사용할 수 있다는 것이 보입니다. 그 이후에는 헤더를 지정하고, 그 밑 부분부터는 locust 스크립트 작성하는 것과 똑같습니다. locust의 스크립트 작성은 개념만 이해하고 있다면 전혀 어려운게 없어서 개념만 익히고 원하는 스크립트를 작성하시면 되겠습니다. 간단하게 아래에 작성했으니, 참고하시면 됩니다.



Locust file 작성하기

  • HttpUser

    • 부하를 가할 유저를 뜻한다.
    • 이 유저는 tasks 애트리뷰트에 선언된 작업 또는 @task 데코레이터가 붙여진 작업을 수행한다.
  • TaskSet

    • 유저가 수행할 작업들을 하나의 클래스로 만들어, tasks 애트리뷰트에 선언된 작업들 또는 @task 데코레이터가 붙여진 작업들 중 랜덤으로 수행한다.
  • task

    • HttpUser는 @task 데코레이터가 붙은 메서드를 찾아 수행하게 된다.

    • weight 파라미터를 넣어주면 각 테스크마다 가중치를 부여할 수 있다.

      1
      2
      3
      4
      5
      6
      7
      @task(weight=1)
      def first_task(self):
      pass

      @task(weight=2)
      def second_task(self):
      pass
      • second_task가 실행될 확률은 first_task의 두 배 이다.
  • between

    • HttpUser나 TaskSet의 wait_time 애트리뷰트를 사용할 때 해당 함수를 사용할 수 있다.
    • wait_time = between(1, 4) 선언해주면 1초 ~ 4초 사이 간격으로 랜덤하게 작업이 수행된다는 뜻이다.
    • 당연하게 constant라는 이름의 함수도 제공해서 일정한 간격으로 작업을 수행할 수 있도록 해준다.
    • wait_time = constant(5) 는 5초마다 작업을 수행한다.



lib/example_functions.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 # -*- coding: utf-8 -*-

import random


def choose_random_page():
pages = [
'/policies/privacy/',
'/contact/',
'/about/',
'/search/howsearchworks/crawling-indexing/',
'/search/howsearchworks/algorithms/'
]

return random.choice(pages)

example_functions.py에는 원하는 함수를 작성해놓으면 됩니다. 여기서는 테스트할 페이지들을 나열해놓았네요.



이렇게 main.py와 example_functions.py를 작성했다면 이것을 해당 namespace에 configmap으로 등록해줍니다. kubectl명령어를 사용합니다. configmap과 locust를 함께 설치하고 싶다면 다음 명령어를 사용합니다.

helm install locust -n {namespace} deliveryhero/locust \ --set loadtest.name=loadtest \ --set loadtest.locust_locustfile_configmap=my-loadtest-locustfile \ --set loadtest.locust_lib_configmap=my-loadtest-lib

이렇게 설치되면 locust의 master와 worker가 올라오게 되는데, 정상적으로 설치되었다면 바로 UI에 접속해서 부하테스트를 진행할 수 있습니다.

그런데 궁금한 점은 이게 어떻게 pod로 전달되어서 부하테스트가 진행되는지입니다. configmap으로 등록되었는데 이게 어떻게 Pod에 들어갈 수 있게 되는 걸까요? 바로 configmap을 volume mount시키기 때문입니다.

pod의 yaml를 확인하면 어떻게 이 코드들이 실행되는지 볼 수 있습니다. 아래는 제가 생성한 locust worker 파드의 yaml 일부 입니다.

1
2
3
4
5
volumeMounts:
- mountPath: /mnt/locust
name: locustfile
- mountPath: /mnt/locust/lib
name: lib

보시면 volumeMounts가 되어있는 것이 나와있고 파드의 /mnt/locust에 configmap으로 잡은 locustfile과 lib가 있는 것을 볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
volumes:
- configMap:
defaultMode: 420
name: my-loadtest-lib
name: lib
- configMap:
defaultMode: 420
name: my-loadtest-locustfile
name: locustfile
- configMap:
defaultMode: 420
name: locust-config

volume은 configMap으로 아까 전에 지정한 my-loadtest-lib과 my-loadtest-locustfile 입니다. 이렇게 되면 방금 configMap으로 지정한 코드들이 volume으로 생성되고 파드가 만들어지면서 /mnt/locust 경로에 마운트 됩니다. 이렇게 마운트 되었다면, 해당 경로에서 코드들이 실행되는 것입니다.



UI 접속방법

띄워진 Pod에 접근하는 여러 방법이 있겠지만, port forwarding하는 것을 추천합니다. load balancer를 붙여서 띄우는 건 그닥 추천하지 않는 방법인데 그 이유는 로드 테스트 용 툴인데 아무에게나 접속되는 점이 좋아보이지 않기 때문입니다. 부하테스트 주소를 누군가 알아낸다면 원하지 않는 곳으로 Ddos 공격을 할 수 있는 환경이 조성되어 버립니다. 그래서! port forward를 사용해서 접속해보겠습니다.

locust의 포트는 기본적으로 8089를 사용합니다. 원하지 않는다면 values.yaml를 수정해서 재배포하시면 됩니다.

kubectl --namespace {namespace} port-forward service/locust 8089:8089

Locust UI

Start swarming을 하면 메뚜기 떼들이 몰려들기 시작합니다! 🦗🦗🦗🦗

아래는 실험 결과에 대한 그래프들입니다.

Locust 실험 결과 - TPS

Locust 실험 결과 - Response Time

Locust 실험 결과 - Users



삽질하면서 얻어낸 꿀 팁

라이브러리 설치하기

바로 사용하면 좋겠지만, locust 스크립트에 다른 라이브러리를 사용하게 된다면 어떨까요? 아마 locust 파드들이 제대로 올라오지 못하게 될 것입니다. crashloopbackoff가 계속 보이게 되겠습니다… 만약 설치가 필요한 라이브러리가 있다면 설치된 namespace의 configmap을 잘 살펴보시길 바랍니다.

namespace의 configmap에는 locust-config의 이름으로 등록된 것이 있는데 여기에는 docker-entrypoint.sh이라는 쉘 파일이 있습니다. 이 파일은 다음과 같이 짜여져 있는데요,

1
2
3
4
5
6
#!/bin/sh

set -eu


exec /opt/venv/bin/locust $@

엄청나게 간단하게 짜여져 있는 코드입니다. 단순히 locust를 실행하는 쉘 파일인 것입니다. 만약 pandas라는 라이브러리를 설치하고 싶다면 다음과 같이 넣어주면 됩니다.

1
2
3
4
5
6
7
#!/bin/sh

set -eu

pip3 install pandas

exec /opt/venv/bin/locust $@

locust가 python으로 동작하므로 별도로 pip를 설치해 줄 필요가 없어서 이렇게만 넣어주면, locust가 올라올때 라이브러리가 설치된 채로 실행됩니다.



Locust 조정

Locust로 엄청 많은 부하를 주고 싶은 마음은 굴뚝같습니다만 실제로 유저를 1000, 2000씩 주게되면 워커들이 죽기 시작합니다. 이로 인해 워커가 다시 올라올때까지 Request Time이 증가하게 되어 실험 결과를 오염시켜버리게 되는데요, 이를 미리 막기 위해 워커들을 늘려서 지정한 유저를 나눠서 실행하도록 해야합니다. 워커들은 deployment의 replicas를 찾아서 원하는 만큼 늘려주면 됩니다. master 또한 수 많은 워커들의 결과를 취합하다보면 파드가 터져버려서 UI가 동작하지 않기도 하고 결과 값 자체를 잃어버리기도 합니다. master도 숫자를 늘려서 해결하면 되지 않을까 싶지만, master는 하나로 운영하는게 좋습니다. 하나로 안전하게 운영하기 위해서는 리소스를 충분히 많이 늘려주면 됩니다. 역시 master의 deployment를 찾아서 resource를 원하는 만큼 조정하시면 되겠습니다.

1
2
3
4
resources:
requests:
cpu: 500m
memory: 1000Mi

아마 resource가 yaml에 작성되어 있지 않을텐데, 걱정하지 마시고 resource 부분을 넣어서 실행해주세요. 워커도 또한 할당된 유저를 제대로 소화하지 못한다면 위와 같이 리소스를 늘려주면 해결됩니다.



K8S를 사용할 때 주의할 점

위에서 설정한대로 잘 되면 좋겠지만 마스터와 워커에 리소스를 늘려서 사용하다보면, k8s에 있는 노드의 리소스를 다 써버리게 됩니다. 이로 인해 노드에 node pressure가 일어나면서 다른 앱들에 장애가 발생할 수 있는 요소가 생겨버리게 되는 것입니다. 이를 막기 위해 노드를 충분히 늘려주고 테스트를 진행해야 합니다. 충분한 노드가 없다면 리소스를 Locust 워커들이 쭉쭉 소비하게 되면서 노드에 장애가 발생하고, 다른 앱들에 영향이 가게 됩니다. 큰 노드를 하나 만들어 놓고 태그를 붙인다음, 해당 태그가 있는 곳에만 locust 파드들이 뜨도록 affinity 또는 node selector를 설정하는 것도 방법이 되겠습니다.

그리고 실험이 종료되면 꼭 노드들을 정리해주세요!




Reference

kubernetes에서 locust를 이용해 부하테스트해보기

http://tkdguq05.github.io/2023/02/25/locust-on-kubernetes/

Author

SangHyub Lee, Jose

Posted on

2023-02-25

Updated on

2023-12-08

Licensed under

Comments