macOS에서 nGrinder 설치 - 엔그라인더 환경 설정
📌 서론
우리가 진행하던 프로젝트의 베타 버전이 배포되었다. 이제 부하 테스트를 진행하기 위해 nGrinder를 사용해 보기로 했다. 실제로 부하 테스트를 진행하기 앞서 간단한 환경 설정을 하는 과정을 설명해 보겠다.
0. nGrinder란?
nGrinder는 오픈 소스 기반의 성능 부하 테스트 도구로, 네트워크 상에서 다수의 사용자 요청을 시뮬레이션하여 웹 애플리케이션의 성능을 테스트하고 분석할 수 있게 해 준다. Java 기반으로 개발되었으며, Naver에서 만들었다. 이 도구는 크게 두 가지 주요 컴포넌트로 구성되어 있다.
컨트롤러(Controller)
테스트의 생성, 관리, 모니터링, 그리고 결과 분석을 담당하는 중앙 관리 시스템이다. 사용자는 컨트롤러를 통해 테스트 스크립트를 작성하고, 부하 테스트를 실행할 에이전트를 관리한다. 또한, 테스트 실행 결과를 수집하고 분석하는 기능을 제공한다.
에이전트(Agent)
실제 부하(즉, HTTP 요청 등)를 생성하고 테스트 대상 시스템에 전송하는 역할을 한다. 여러 에이전트를 사용함으로써, 동시에 많은 수의 사용자 요청을 시뮬레이션하여 애플리케이션의 성능을 평가할 수 있다. 에이전트는 컨트롤러에 의해 조정되며, 테스트 실행에 필요한 스크립트와 명령을 컨트롤러로부터 받아 수행한다.
1. nGrinder 설치 및 실행
nGrinder 설치
최신 controller war 파일을 nGrinder 공식 GitHub 페이지에서 다운로드하자.
https://github.com/naver/ngrinder/releases
다운로드한 파일을 특정 경로에 저장하고, 해당 위치에 lib 폴더를 생성한다. 이 폴더는 임시 파일 저장소로 사용된다.
(나는 2024년 2월 기준으로 3.5.8 버전을 다운로드하였다.)
로컬 경로에 ngrinder 폴더를 만들어주고 그 경로로 war 파일을 이동시켜 줬다.
그리고 war 파일이 설치된 경로에 lib 폴더를 생성해 준다.
nGrinder 실행
war가 설치된 경로에서 Terminal을 열고, 다음 명령어를 입력하여 nGrinder를 실행한다.
java -Djava.io.tmpdir={방금 생성한 lib 폴더 경로} -jar ngrinder-controller-3.5.8.war --port=8300
!! 꼭 8300 포트가 아니어도 된다. 사용하지 않는 어느 포트든 괜찮다.
-Djava.io.tmpdir 옵션은 Java 임시 파일 저장소 경로를 설정하고, --port 옵션은 사용할 포트 번호를 지정한다. nGrinder 컨트롤러는 기본적으로 8080 포트를 사용하지만, 이 옵션을 통해 8300 포트로 변경하여 실행해 줬다.
실행이 성공하면, 지정된 포트로 웹 브라우저에서 nGrinder에 접속할 수 있다.
나 같은 경우에는 다음과 같은 명령어로 실행했다.
java -Djava.io.tmpdir=/Users/yijun/study/recipia/workspace/ngrinder/lib -jar ngrinder-controller-3.5.8.war --port=8300
🌟 중요: 만약에 위에서 lib 폴더를 생성 안 해주면 FileNotFoundException나 IOException이 발생한다. java 명령어가 자동으로 lib 폴더를 생성해주지 않으니 수동으로 꼭 생성해 주길 바란다.
정상 실행되면 다음과 같은 로그가 보인다.
이제 8300 포트로 접속할 수 있다.
2. nGrinder 접속
브라우저 url입력창에 위에서 설정한 port로 접속해 보자
localhost:{아까 실행한 포트 번호}
아래 화면이 뜨면 성공한 거다.
초기 로그인 정보는 모두 'admin'이다.
3. 에이전트 설치 및 실행
에이전트 설치
이제 에이전트를 설치해 보자.
로그인 성공 후 오른쪽 상단에 있는 계정을 클릭해 [에이전트 다운로드]를 눌러준다.
나는 에이전트도 위에서 생성한 ngrinder 폴더에 다운로드하였다.
해당 경로로 이동해서 다운로드한 에이전트를 압축 해제해 준다.
$ tar -xvf ngrinder-agent-{version}-localhost.tar
위 명령어를 실행하고 확인해 보면 ngrinder-agent 폴더가 생긴 걸 확인할 수 있다. (압축 해제 성공)
에이전트 실행
압축이 풀린 에이전트 폴더로 이동한다.
$ cd ngrinder-agent
해당 경로에서 에이전트를 실행해 준다.
./run_agent.sh
다음 로그가 보이면 실행에 성공한 모습이다.
실행이 완료되었으면 아까 접속했던 127.0.0.1:8300 접속 화면에서 에이전트 관리 탭으로 넘어가면 확인해 볼 수 있다.
에이전트 관리 화면은 다음과 같다.
4. 테스트 스크립트 작성
이제 테스트 스크립트를 작성해야 한다.
nGrinder 접속 화면에서 스크립트 메뉴를 클릭한다.
그리고 만들기 버튼을 클릭하고 [스크립트 만들기]를 클릭한다.
여기서 일단 스크립트 명과 테스트할 url을 입력해 준다.
테스트할 url에는 실제 테스트를 진행하고 싶은 API의 mapping 경로를 작성해 주면 된다.
그리고 [만들기] 버튼을 클릭한다.
그럼 어느 정도 채워진 스크립트를 제시해 준다. Groovy 언어로 작성된 이 스크립트는 테스트할 API에 대한 정보를 포함하고, 실제 부하 테스트를 어떻게 수행할지 정의한다.
나는 일단 임시로 전화번호 중복 테스트 코드를 작성해 봤다. 이 코드는 로그인이 필요하지 않은 케이스기 때문에 간단하게 작성했다.
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager
/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
public static Map<String, String> headers = [:]
public static Map<String, Object> params = [:]
public static List<Cookie> cookies = []
@BeforeProcess
public static void beforeProcess() {
HTTPRequestControl.setConnectionTimeout(300000)
test = new GTest(1, "127.0.0.1")
request = new HTTPRequest()
// Set header data
headers.put("Content-Type", "application/json")
// 전화번호 파라미터 추가
params.put("telNo", "01011111111")
grinder.logger.info("before process. 파라미터에 전화번호 추가")
}
@BeforeThread
public void beforeThread() {
test.record(this, "test")
grinder.statistics.delayReports = true
grinder.logger.info("before thread.")
}
@Before
public void before() {
request.setHeaders(headers)
CookieManager.addCookies(cookies)
grinder.logger.info("before. init headers and cookies")
}
@Test
public void test() {
// params에 이미 전화번호가 포함되어 있음
HTTPResponse response = request.POST("http://127.0.0.1:8081/member/management/checkDupTelNo", params)
if (response.statusCode == 301 || response.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
} else {
assertThat(response.statusCode, is(200))
}
}
}
테스트 스크립트 설명
클래스 선언 및 어노테이션
- @RunWith(GrinderRunner.class) 어노테이션은 JUnit 테스트 실행기를 nGrinder의 GrinderRunner로 설정한다. 이는 nGrinder의 실행 환경에 맞게 테스트를 구성하고 실행한다.
- TestRunner 클래스는 테스트 케이스의 컨테이너 역할을 한다. 여기서 실제 테스트를 정의하고 실행한다.
@BeforeProcess
- beforeProcess 메서드는 테스트 프로세스가 시작하기 전에 한 번 실행된다. 여기서 HTTP 요청의 타임아웃 설정, 테스트 객체 초기화, 요청 헤더와 파라미터 설정 등의 초기 설정 작업을 수행한다.
@BeforeThread
- beforeThread 메서드는 각 테스트 스레드가 시작하기 전에 실행된다. nGrinder에서는 각 스레드가 하나의 사용자를 시뮬레이션한다. 이 메서드에서는 주로 테스트 기록을 설정하고, 통계 보고의 지연 같은 스레드별 설정을 할 수 있다.
@Before
- before 메서드는 각 테스트 메서드가 실행되기 전에 호출된다. 이 메서드에서는 HTTP 요청에 헤더와 쿠키를 설정한다.
@Test
- test 메서드는 실제 테스트 케이스를 정의한다. 이 예제에서는 POST 요청을 사용하여 특정 URL(http://127.0.0.1:8081/member/management/checkDupTelNo)에 데이터를 전송하고, 응답 코드가 200인지 확인하여 API 응답을 검증한다. 응답 코드가 301 또는 302인 경우, 잘못된 리다이렉션을 경고한다.
작성이 완료되었으면 스크립트 위에 있는 [검증] 버튼을 클릭해 준다.
그럼 다음과 같은 에러가 찍힌다 ㅠㅠ
2024-02-13 14:08:53,544 INFO Setting of nGrinder local DNS successfully
2024-02-13 14:08:53,546 INFO The Grinder version 3.9.1
2024-02-13 14:08:53,547 INFO OpenJDK Runtime Environment 17.0.9+8-LTS: OpenJDK 64-Bit Server VM (17.0.9+8-LTS, mixed mode, sharing) on Mac OS X aarch64 14.1
2024-02-13 14:08:53,558 INFO time zone is KST (+0900)
2024-02-13 14:08:53,589 INFO worker process 0 of agent number 0
2024-02-13 14:08:53,598 INFO Instrumentation agents: byte code transforming instrumenter for Java
2024-02-13 14:08:54,023 ERROR Script error - Error while initialize test runner
net.grinder.engine.common.EngineException: Error while initialize test runner
at net.grinder.scriptengine.groovy.GroovyScriptEngine.<init>(GroovyScriptEngine.java:71)
at net.grinder.scriptengine.groovy.GroovyScriptEngineService.createScriptEngine(GroovyScriptEngineService.java:87)
at net.grinder.engine.process.ScriptEngineContainer.getScriptEngine(ScriptEngineContainer.java:105)
at net.grinder.engine.process.GrinderProcess.run(GrinderProcess.java:345)
at net.grinder.engine.process.WorkerProcessEntryPoint.run(WorkerProcessEntryPoint.java:87)
at net.grinder.engine.process.WorkerProcessEntryPoint.main(WorkerProcessEntryPoint.java:60)
Caused by: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: Unsupported class file major version 61
java.lang.IllegalArgumentException: Unsupported class file major version 61
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:196)
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:177)
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:163)
at groovyjarjarasm.asm.ClassReader.<init>(ClassReader.java:284)
at org.codehaus.groovy.ast.decompiled.AsmDecompiler.parseClass(AsmDecompiler.java:81)
at org.codehaus.groovy.control.ClassNodeResolver.findDecompiled(ClassNodeResolver.java:251)
at org.codehaus.groovy.control.ClassNodeResolver.tryAsLoaderClassOrScript(ClassNodeResolver.java:189)
at org.codehaus.groovy.control.ClassNodeResolver.findClassNode(ClassNodeResolver.java:169)
at org.codehaus.groovy.control.ClassNodeResolver.resolveName(ClassNodeResolver.java:125)
at
위 에러 로그는 Java 17을 사용하는 환경에서 nGrinder가 Groovy 스크립트를 컴파일할 때 "Unsupported class file major version 61" 오류가 발생했다는 내용이다.
이는 nGrinder (또는 Groovy 스크립트 엔진)가 Java 17로 컴파일된 클래스 파일의 버전을 지원하지 않음을 의미한다. nGrinder 버전 3.9.1이 Java 17과 완전히 호환되지 않을 가능성이 있다.
일단 이 nGrinder를 내 로컬에서 실행했는데 나는 자바 환경변수를 기본으로 17로 설정해 줬기 때문에 발생하는 오류였다.
그래서 일단 로컬 환경에서 jdk 버전을 11로 낮추고 다시 실행해보려고 한다. 이때 ngrinder를 종료하고 재실행해줘야 한다.
5. macOS에서 자바 환경변수 설정
설치된 jdk 목록 확인
일단 설치된 jdk 목록을 확인해 보자.
다음 명령어는 macOS에서 JDK가 설치된 목록을 조회하는 내용이다.
cd /Library/Java/JavaVirtualMachines
자바 환경변수로 설정할 jdk 경로 확인
그리고 자바 환경변수로 설정한 버전의 폴더 경로로 이동하자.
나는 jdk 11 버전으로 설정하기 위해서 11버전 폴더 경로로 이동해 줬다.
cd 설치된jdk폴더명/Contents/Home/
이동된 경로에서 pwd 명령어를 통해 현재 경로를 확인하고 복사해 준다.
자바 환경변수 설정하기
복사한 jdk 경로를 환경변수 JAVA_HOME으로 설정해 주자.
🔔 알림
macOS 최근 버전들에서 기본 쉘로 zsh(zshrc)를 사용한다. 이전에는 bash(bash_profile)가 기본 쉘이었으나, macOS Catalina(10.15)부터 zsh가 기본 쉘로 변경되었다. 따라서, bash_profile에 환경 변수를 설정하는 것보다 zshrc 파일에 설정하는 것이 적합하다.
자바 환경변수를 수정하기 전에 현재 자바 버전을 확인해 보자
java -version
💡 java -version과 java --version 명령어 차이
두 명령어는 기능적으로 동일한 목적을 가지고 있으며, 자바 버전 정보를 사용자에게 제공한다. 그러나 구문적인 차이로 인해, 특히 구 버전의 자바에서는 --version 옵션이 지원되지 않을 수 있으므로, 일반적으로 널리 호환되는 java -version 명령어 사용을 권장한다. Java 9 이상에서는 두 명령어 모두 사용할 수 있으며, 사용자의 선호나 스크립팅 요구 사항에 따라 선택할 수 있다.
바꾸기 전에는 jdk 17로 확인된다.
이제 zshrc 파일에 자바 환경변수를 추가해 주자.
다음 명령어로 쉘 파일을 열어준다.
vi ~/.zshrc
그다음에 해당 명령어를 추가해 준다.
export JAVA_HOME={아까 pwd로 가져온 jdk 전체 경로}
export PATH=$JAVA_HOME/bin:$PATH
이렇게 추가해 주고 :wq! 를 통해 나와준다.
변경사항을 적용하기 위해 쉘 설정을 다시 로드한다.
source ~/.zshrc
source 명령어는 지정된 파일의 내용을 읽어서, 현재 쉘 세션에서 실행한다. 즉, source ~/.zshrc는 ~/.zshrc 파일에 쓰인 모든 명령어와 설정을 현재 실행 중인 쉘에 적용한다. 파일을 수정한 후 터미널을 재시작하면 변경사항이 적용되지만, 이는 시간이 조금 더 걸릴 수 있다. source ~/.zshrc 명령어를 사용하면, 터미널을 재시작하지 않고도 바로 변경사항을 적용할 수 있다. 이는 개발 과정에서 시간을 절약해 주고 즉각적인 피드백을 가능하게 한다.
또한, source 명령어를 사용하면 설정 파일에 문제가 있을 경우 바로 에러 메시지를 볼 수 있다. 이를 통해 문제를 빠르게 파악하고 수정할 수 있다. 그리고 만약 여러 설정 파일이 있을 때, 특정 설정 파일만을 다시 로드하고 싶을 때 유용하다. 예를 들어, 복잡한 환경 설정을 가진 개발자는 특정 부분의 설정만을 바꾸고 즉시 적용할 수 있다.
~/.zshrc 파일은 단순히 터미널 환경 설정을 담고 있는 것이 아니라, 사용자의 터미널 사용 스타일을 맞춤화하고, 작업 효율을 높이는 중요한 역할을 한다. source ~/.zshrc 명령어는 zsh 설정을 효과적으로 관리하고, 변경사항을 빠르게 적용할 수 있는 중요한 도구다. 따라서 zsh 설정을 변경한 후에는 이 명령어를 사용하는 것이 좋다.
설정이 제대로 적용되었는지 확인하기 위해 Java 버전을 확인해 보자.
11 버전으로 잘 업데이트된 모습을 확인할 수 있다.
6. 테스트 스크립트 실행
이제 ngrinder와 에이전트를 실행시키고 검증을 다시 해보자.
다음과 같이 부하 테스트에 성공한 로그가 출력된다.
🔥 결론
nGrinder를 사용하여 부하 테스트를 준비하고 실행하는 방법을 자세히 알아봤다. 이 글에서 nGrinder 사용에 앞서 필요한 모든 환경 설정을 커버하고 있다. 이제 다음 장에서 실제 부하 테스트를 진행해 보자
같은 팀원인 "개발자의 서랍"님의 블로그도 방문해 주세요! 좋은 글이 많이 있습니다 :)
'Tools > nGrinder' 카테고리의 다른 글
nGrinder 스크립트 메뉴 접속 시 SqlJetException: CANTOPEN 에러 해결 (4) | 2024.02.15 |
---|---|
nGrinder를 사용하여 시스템 성능 한계와 최적 부하 지점 분석 (5) | 2024.02.14 |
nGrinder를 활용한 성능 테스트 - 기본에서 전략까지 (1) | 2024.02.14 |