Kubernetes을 활용한 분산 부하 테스팅

  • Post author:
  • Post category:칼럼
  • Post comments:0 Comments
  • Post last modified:February 8, 2020

이미 공개한 글이지만 회사 블로그를 홍보할 겸 여기에 늦게나마 공개합니다.

동명의 글이 Google Cloud Platform에도 있으니 여기서는 여태까지 한 삽질과 교훈에 집중한다.

첫 시도 ngrinder

처음에는 ngrinder로 부하 테스트 환경을 구축하려 했다. 몇 달 전에 부하 테스트를 진행할 때 잠시 쓴 적이 있었기 때문에 굳이 다른 솔루션을 찾을 이유가 없었다. 하지만 결국 후회하고 다른 솔루션으로 넘어갔는데 그 이유를 중요한 순으로 꼽자면

  • 로컬 개발환경과 실제 환경이 차이가 많다. 로컬에서는 JUnit 기반으로 개발과 디버깅이 가능하다. 하지만 이렇게 작성한 코드를 ngrinder에 넣으려 하면 외부 라이브러리가 문제가 된다. .jar 등 패키지 파일을 업로드하는 방식이 아니라 Groovy 스크립트 따로 스크립트에서 사용하는 라이브러리 따로 업로드를 해야 하는데 상당히 번거롭다.
  • 웹 UI를 통해 설정한 내용이 내장 데이터베이스에 바이너리로 들어가기 때문에 ngrinder 데이터를 관리하기가 힘들다.
  • 개발이 활발하지 않다. 주력 개발자가 Naver를 떠났다는 이야기도 있긴 한데 아무튼 커밋 히스토리를 보면 개발이 정체되어 있는 건 분명하다.
  • 설계가 진보적이지 않다. 예를 들어 현재 쓰레드의 ID를 시스템이 직접 계산해서 주입하지 않고 개발자가 주어진 코드 스니펫을 Copy & Paste 해야 하는 이유를 모르겠다.

등이 있다. 이런 까닭에 좀더 간단한 솔루션을 찾아보았다.

대안

몇 가지 대안을 살펴보았는데

  • Artillery는 테스트 스크립트를 yaml로 기술하기 때문에 얼핏 쉬워보이지만 이런 식의 접근 방법은 매번 실망만 안겨주었다. 조금만 테스트 시나리오가 복잡해지면 일반적인 코딩보다 설정 파일이 훨씬 짜기가 어렵고 이해하기도 어렵다.
    config:
      target: 'http://my.app.dev'
      phases:
        - duration: 60
          arrivalRate: 20
      defaults:
        headers:
          x-my-service-auth: '987401838271002188298567'
    
    scenarios:
      - flow:
        - get:
            url: "/api/resource"
    
  • Gatling은 아직 분산 서비스를 지원하지 않아서 제외했다. 팀 내에 Scala 개발경험이 있는 사람이 극소수인 점도 문제였다.

Locust로 정착

이런 까닭에 Locust로 넘어왔다. 장점은

  • 파이썬 스크립트로 시나리오를 작성하니 내부에 개발인력이 충분하다.
  • 로컬환경과 실제 부하테스팅 환경이 동일하다. 즉, 디버깅하기 쉽다.
  • Locust 데이터를 Dockerize하기 쉽다.

한마디로 ngrinder에서 아쉬웠던 점이 모두 해결됐다. 반면 ngrinder에 비해 못한 면도 많긴 하다.

  • 통계가 세밀하지 않다.
  • 테스트 시나리오를 세밀하게 조정하기 힘들다.

현재로썬 그때그때 가볍게 시나리오를 작성해서 가볍게 돌려보는 게 중요하지 세밀함은 그리 중요하지 않아서 Locust가 더 나아 보인다. 시나리오는 몰라도 통계의 경우, DataDog 같은 모니터링 시스템에서 추가로 정보를 제공받기 때문에 큰 문제도 아니긴 하다.

결과물

Locust on Kubernetes

GoogleCloudPlatform/distributed-load-testing-using-kubernetes에 있는 소소코드를 참고로 작업하면 된다. 단지 Dockerfile의 경우, 테스트 스크립트만 바뀌고 파이썬 패키지는 변경사항이 없는 경우에도 파이썬 스크립트 전체를 새로 빌드하는 문제가 있다.

# Add the external tasks directory into /tasks
ADD locust-tasks /locust-tasks

# Install the required dependencies via pip
RUN pip install -r /locust-tasks/requirements.txt

그러므로 이 부분을 살짝 고쳐주면 좋다.

ADD locust-tasks/requirements.txt /locust-tasks/requirements.txt

RUN pip install -r /locust-tasks/requirements.txt

ADD locust-tasks /locust-tasks

ngrinder on Kubernetes

ngrinder를 Kubernetes v1.4.0 위에서 돌리는데 사용한 설정은 다음과 같다. 참고로 dailyhotel/ngrinder-data는 ngrinder의 데이터만 따로 뽑아서 관리하는 도커 이미지이다.

Controller
apiVersion: v1
kind: Service
metadata:
  name: ngrinder
  labels:
    app: ngrinder
    tier: middle
    dns: route53
  annotations:
    domainName: "ngrinder.test.com"
spec:
  ports:
    # the port that this service should serve on
  - name: port80
    port: 80
    targetPort: 80
    protocol: TCP
  - name: port16001
    port: 16001
    targetPort: 16001
    protocol: TCP
  - name: port12000
    port: 12000
    targetPort: 12000
    protocol: TCP
  - name: port12001
    port: 12001
    targetPort: 12001
    protocol: TCP
  - name: port12002
    port: 12002
    targetPort: 12002
    protocol: TCP
  - name: port12003
    port: 12003
    targetPort: 12003
    protocol: TCP
  - name: port12004
    port: 12004
    targetPort: 12004
    protocol: TCP
  - name: port12005
    port: 12005
    targetPort: 12005
    protocol: TCP
  - name: port12006
    port: 12006
    targetPort: 12006
    protocol: TCP
  - name: port12007
    port: 12007
    targetPort: 12007
    protocol: TCP
  - name: port12008
    port: 12008
    targetPort: 12008
    protocol: TCP
  - name: port12009
    port: 12009
    targetPort: 12009
    protocol: TCP
  selector:
    app: ngrinder
    tier: middle
  type: LoadBalancer
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ngrinder
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: ngrinder
        tier: middle
    spec:
      containers:
      - name: ngrinder-data
        image: dailyhotel/ngrinder-data:latest
        imagePullPolicy: Always
        volumeMounts:
        - mountPath: /opt/ngrinder-controller
          name: ngrinder-data-volume
      - name: ngrinder
        image: ngrinder/controller:latest
        resources:
          requests:
            cpu: 800m
        ports:
        - containerPort: 80
        - containerPort: 16001
        - containerPort: 12000
        - containerPort: 12001
        - containerPort: 12002
        - containerPort: 12003
        - containerPort: 12004
        - containerPort: 12005
        - containerPort: 12006
        - containerPort: 12007
        - containerPort: 12008
        - containerPort: 12009
        volumeMounts:
        - mountPath: /opt/ngrinder-controller
          name: ngrinder-data-volume
      volumes:
      - name: ngrinder-data-volume
        emptyDir: {}
Agents
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: ngrinder-agent
spec:
  replicas: 5
  template:
    metadata:
      labels:
        app: ngrinder-agent
        tier: middle
    spec:
      containers:
      - name: ngrinder-agent
        image: ngrinder/agent:latest
        imagePullPolicy: Always
        resources:
          requests:
            cpu: 300m
        args: ["ngrinder.test.com:80"]
Author Details
Kubernetes, DevSecOps, AWS, 클라우드 보안, 클라우드 비용관리, SaaS 의 활용과 내재화 등 소프트웨어 개발 전반에 도움이 필요하다면 도움을 요청하세요. 지인이라면 가볍게 도와드리겠습니다. 전문적인 도움이 필요하다면 저의 현업에 방해가 되지 않는 선에서 협의가능합니다.
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments