이 포스팅은 제가 작성한 UPbrella 프로젝트의 기술 블로그에 작성한 nGrinder 자동화 포스팅을 옮겨온 것입니다.
1. 문제 정의
nGrinder를 사용해서 부하테스트를 하는 이유는 지난 게시글를 통해 알아보았습니다.
하지만 매번 배포를 할때마다 개발자가 nGrinder 서버를 띄워서 테스트 하는건 자원의 낭비라고 생각해서 자동화하기로 결정하였습니다.
2. nGrinder 자동화 도입
2-1 설정하기
java 11 과 docker가 설치되었다고 가정하고 진행하겠습니다.
도커로 ngrinder를 pull 하고 실행해줍니다.
sudo docker pull ngrinder/controller:3.5.5-p1
latest 버전이 아닌 3.5.5-p1을 명시해준 이유는 3.5.6 이상부터는 Script Error가 발생하고,
3.5.4 이하 버전은 agent Error가 빈번히 발생하여서 선택하였습니다.
https://github.com/naver/ngrinder/issues/940
sudo docker run -d -p 80:80 ngrinder/controller:3.5.5-p1
ec2의 public ip로 접속해보면 ngrinder가 실행중인 것을 볼 수 있습니다.
이제 controller의 쉡 스크립트를 작성해보겠습니다.
#!/bin/bash
export EC2_SERVER_IP=52.79.180.25
source ~/.bashrc
sudo systemctl restart docker
sudo docker rm $(sudo docker ps -a -q)
sudo docker run -d -e EC2_SERVER_IP=$EC2_SERVER_IP -v ~/ngrinder-controller:/opt/ngrinder-controller -p 80:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller:3.5.5-p1
- 쉘 스크립트 설명
#!/bin/bash
- 이 줄은 스크립트가 Bash 쉘을 사용하여 실행되어야 함을 지정합니다.
source ~/.bashrc
- 사용자의
.bashrc
파일을 소스화하여, 해당 파일에 정의된 환경 변수와 함수를 현재 쉘 세션에 로드합니다.
- 사용자의
sudo systemctl restart docker
sudo
권한을 사용하여 Docker 시스템 서비스를 재시작합니다.
sudo docker rm $(sudo docker ps -a -q)
- 현재 모든 Docker 컨테이너의 ID를 가져와(
sudo docker ps -a -q
) 이를 사용하여 모든 Docker 컨테이너를 삭제합니다(sudo docker rm
).
- 현재 모든 Docker 컨테이너의 ID를 가져와(
sudo docker run -d -e EC2_SERVER_IP=$EC2_SERVER_IP -v ~/ngrinder-controller:/opt/ngrinder-controller -p 80:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller
sudo
권한으로 새 Docker 컨테이너를 실행합니다. 여기에서 사용하는 옵션은 다음과 같습니다:d
: Docker 컨테이너를 백그라운드에서 실행합니다 (데몬 모드).e EC2_SERVER_IP=$EC2_SERVER_IP
:EC2_SERVER_IP
환경 변수를 컨테이너 내부의 환경 변수로 설정합니다.EC2_SERVER_IP
는 스크립트 실행 전에 쉘 환경에 설정되어 있어야 합니다.v ~/ngrinder-controller:/opt/ngrinder-controller
: 호스트 시스템의~/ngrinder-controller
디렉터리를 컨테이너의/opt/ngrinder-controller
에 마운트합니다.p 80:80
: 호스트 시스템의 80번 포트를 컨테이너의 80번 포트에 매핑합니다.p 16001:16001
: 호스트 시스템의 16001번 포트를 컨테이너의 16001번 포트에 매핑합니다.p 12000-12009:12000-12009
: 호스트 시스템의 12000번에서 12009번 포트를 컨테이너의 동일한 포트 범위에 매핑합니다.ngrinder/controller
: 사용할 Docker 이미지를 지정합니다.
agent 설치
sudo docker pull ngrinder/agent:3.5.5-p1
agent도 controller와 동일한 이유로 3.5.5-p1을 선택하였습니다.
#!/bin/bash
source ~/.bashrc
export CONTROLLER_IP=52.79.180.25
sudo systemctl restart docker
sudo docker rm $(sudo docker ps -a -q)
sudo docker run -v ~/ngrinder-agent:/opt/ngrinder-agent -d ngrinder/agent:3.5.5-p1 $CONTROLLER_IP:80
agent를 실행한 후 agent 관리로 들어가면 agent가 등록된 것을 확인할 수 있습니다.
- nGrinder controller 와 agent 분리 이유
- 리소스 경쟁:
controller
와agent
가 같은 시스템 리소스(예: CPU, 메모리)를 사용하므로, 특히 테스트 중에 성능 저하가 발생할 수 있습니다. - 고장 격리:
controller
또는agent
중 하나에 문제가 발생하면, 다른 컴포넌트도 영향을 받을 수 있습니다. 두 컴포넌트를 분리하여 실행하면, 하나의 컴포넌트에 문제가 발생해도 다른 컴포넌트에는 영향을 미치지 않습니다.
- 리소스 경쟁:
2 - 2 GitHub Action
upbrella 개발팀은 dev 서버는 code deploy를 활용해서 배포하고 있습니다.
nGrinder에 대한 설명은 CodeDeploy 이후 부분부터 입니다.
name: Upbrella DEV CI
on:
push:
branches: [ "release-dev" ]
env:
WORKING_DIRECTORY: ./
CODE_DEPLOY_APPLICATION_NAME: upbrella-dev-deploy
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: UpbrellaServerDev
S3_BUCKET_NAME: upbrella-storage
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'adopt'
- name: Set application properties
run: |
touch src/main/resources/application.properties
echo "${{ secrets.APPLICATION_PROPERTIES_DEV }}" > src/main/resources/application.properties
echo "${{ secrets.APPLICATION_PROPERTIES_TEST }}" > src/test/resources/application.properties
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew clean build
env:
WORKING_DIRECTORY: ${{ env.WORKING_DIRECTORY }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
shell: bash
- name: Upload to S3
run:
aws s3 cp $GITHUB_SHA.zip s3://${{ env.S3_BUCKET_NAME }}/server-dev-deploy/$GITHUB_SHA.zip --region ${{ secrets.AWS_REGION }}
- name: Code Deploy
run: |
aws deploy create-deployment \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=${{ env.S3_BUCKET_NAME }},bundleType=zip,key=server-dev-deploy/$GITHUB_SHA.zip
# 여기서부터 nGrinder 설정입니다.
# test에 필요한 ec2 인스턴스를 aws cli를 통해 실행
- name: Start nGrinder EC2 - (Controller, Agent)
run: aws ec2 start-instances --instance-ids ${{ secrets.AWS_EC2_NGRINDER_CONTROLLER }} ${{ secrets.AWS_EC2_NGRINDER_AGENT }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }}
# Dev server 완전히 실행될 때까지 기다림
- name: Sleep for 30 seconds - waiting controller,agent run
run: sleep 30s
shell: bash
# EC2- Ngrinder Controller를 실행
- name: nGrinder Controller Start
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.AWS_EC2_CONTROLLER_IP }}
username: ubuntu
key: ${{ secrets.AWS_SECRET_PEM }}
script: |
bash ./controller.sh
# nGrinder Agent 실행
- name: nGrinder Agent Start
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.AWS_EC2_AGENT_IP }}
username: ubuntu
key: ${{ secrets.AWS_SECRET_PEM }}
script: |
bash ./agent.sh
- name: waiting controller,agent run
run: sleep 30s
shell: bash
# 업브렐라 서비스의 방문자 통계를 따라 최대 30명의 사용자가 동시접속한다고 가정하였습니다.
- name: nGrinder Test
uses: fjogeleit/http-request-action@v1
with:
url: 'http://${{ secrets.AWS_EC2_CONTROLLER_IP }}/perftest/api'
method: 'POST'
username: ${{ secrets.NGRINDER_ADMIN_USERNAME }}
password: ${{ secrets.NGRINDER_ADMIN_PASSWORD }}
customHeaders: '{"Content-Type": "application/json"}'
data: '{"param" : "${{ secrets.AWS_EC2_TEST_SERVER_IP }}", "testName" : "upbrella-test", "tagString" : "upbrella-test", "description" : "upbrella-test", "scheduledTime" : "", "useRampUp": false, "rampUpType" : "PROCESS", "threshold" : "D", "scriptName" : "upbrella-test.groovy", "duration" : 240000, "runCount" : 1, "agentCount" : 1, "vuserPerAgent" : 30, "processes" : 2, "rampUpInitCount" : 0, "rampUpInitSleepTime" : 0, "rampUpStep" : 1, "rampUpIncrementInterval" : 1000, "threads": 15, "testComment" : "upbrella-test", "samplingInterval" : 2, "ignoreSampleCount" : 0, "status" : "READY"}'
timeout: '60000'
- name: waiting test for 300 seconds
run: sleep 300s
shell: bash
# Ngrinder Rest Api 를 통해 테스트 결과 조회
- name: Get nGrinder test result ...
uses: satak/webrequest-action@master
id: NgrinderTestResult
with:
url: 'http://${{ secrets.AWS_EC2_CONTROLLER_IP }}/perftest/api?page=0'
method: GET
username: ${{ secrets.NGRINDER_ADMIN_USERNAME }}
password: ${{ secrets.NGRINDER_ADMIN_PASSWORD }}
- name: send test result to slack
uses: 8398a7/action-slack@v3
with:
text: '${{ steps.NgrinderTestResult.outputs.output }}'
status: ${{ job.status }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always() # Pick up events even if the job fails or is canceled.
- name: Stop Ngrinder EC2 - (Controller, Agent, Test Server)
run: aws ec2 stop-instances --instance-ids ${{ secrets.AWS_EC2_NGRINDER_CONTROLLER }} ${{ secrets.AWS_EC2_NGRINDER_AGENT }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
이제 배포가 완료되면 nGrinder를 위한 EC2 2대를 실행하고 부하테스트를 실행하게 되었습니다.
테스트의 결과는 Slack 알림을 통해서 받아볼 수 있도록 설정하였습니다.
3. 마무리
nGrinder 자동화를 하면서 많은 에러를 맞이하였습니다.
version 문제로 인한 에러를 찾기 위해 nGrinder 공식 문서와 커뮤니티를 몇 시간이나 돌아다니면서 저와 같은 에러를 찾을 수 있었고,
no TPS Error에 대한 에러는 구글링해도 나오지 않아 몇시간의 삽질을 통해 해결할 수 있었습니다. (url를 ip가 아닌 dns로 지정해주니 해결되었습니다.)
이번 게시글을 통해 nGrinder 자동화하는 방법에 대해서 알아보았습니다. 현재 플로우는 업브렐라 api중 복잡한 쿼리만을 테스트하고 있습니다.다음 게시글을 통해 실제 유저 플로우대로 진행하는 방법에 대해 알아보도록 하겠습니다.
'업브렐라' 카테고리의 다른 글
캐시를 통한 성능 최적화 (0) | 2023.09.25 |
---|---|
Loki를 통한 로그 모니터링 (0) | 2023.09.24 |
JPA Repository는 Entity만을 조회해야 할까? (0) | 2023.09.24 |
nGrinder 적용기 (0) | 2023.09.24 |
WireMock 적용기 (0) | 2023.09.24 |