인공 부하를 가함으로써 서버의 성능을 계측할 수 있는 테스트 도구로 JMeter로 나름 마음을 굳혔다. nGrinder는 자바 17과 호환이 되지 않는다는 내용을 많이 봐서 어쩔 수 없는 거겠지만... 다른 테스트 툴을 확인해보는 것보다는 우선 하나라도 먼저 익숙해지는 게 좋을 것 같아서 마음을 굳혔다.
완벽하진 않지만(사실 절반도 안 완벽하지만) 간단하게나마 JMeter를 다뤄는 보았고, 대충의 결과가 어떻게 나오는 지는 확인했으나 그걸 어떻게 유의미하게 활용하는 건지는 이제 본격적인 개발 진입에서 부딪히며 익히겠지. 그것도 블로깅으로 남겨야겠다.
다음으로 보는 내용은 사실 모니터링 툴(프로메테우스, 그라파나)을 확인하려고 했지만 공부할 때에도 많이 접했던 도커(Docker)를 먼저 보게 되었고, 얘는 아직도 이해를 못 한 상태(...)다. 그래서 나름대로 블로깅으로 남기면서 복습을 해보면 뭔가 생각이 떠오를 것 같아서 적는 기록.
근데 이 단계부터 난관이었음(...)
예전에 프론트엔드를 공부할 때 설치한 homebrew
를 통해 바로 제이미터를 설치할 수 있어서 brew install jmeter
로 설치한 후, 터미널에서 jmeter
명령어로 실행시키더니
/opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin/jmeter: line 199: 7014 Trace/BPT trap: 5 "$JAVA_HOME/bin/java" $ARGS $JVM_ARGS $JMETER_OPTS -jar "$PRGDIR/ApacheJMeter.jar" "$@"
하.... 뭔 문제일까 싶어 진짜 어제 이 잡듯이 뒤졌는데, 오히려 jmeter
명령어로 실행이 잘 되고 GUI로 직접 접속하려니까 안 된다는 분이 계시질 않나... 직접 아파치 깃헙 레포 이슈를 뒤져가면서 찾아봤는데 나랑 일치하는 케이스가 하나 있었다.
https://github.com/apache/jmeter/issues/6163
근데 윗분도 그닥 해결책은 찾은 건 아닌듯
그래서 직접 설치파일을 하나하나 뒤져보면서 실행시킬 방법을 찾다가 현재로써는 이게 최선인 듯하다.
open /opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin
터미널을 통해 직접 bin 폴더를 열어주고, ApacheJMeter.jar
파일을 실행시키면, JMeter GUI가 뜨면서 실행된다. 이 방법이 정석적인 해결책인지는 모르겠지만, 우선은 정상적으로 플러그인 매니저도 설치되고 테스트도 수행된다. 근원적인 해결책을 찾아 정상 실행시키고 싶은데... 일단은 이거로 해보고 추후에 문제점이 발생하면 그때 가서 여러 방책을 강구해야겠다.
테스트 목적을 위해 Spring JPA 기반으로 mySql 기반 RD를 연동시킨 서버 코드를 짰다.
// controller
@RestController
@AllArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/api/{userId}")
public String getMapping(@PathVariable Long userId) {
return "Welcome! " + userService.getUsername(userId);
}
// @GetMapping("/api")
// public String getMapping(@RequestParam Long userId) {
// return "Welcome! " + userService.getUsername(userId);
// }
}
테스트 케이스는 DB에 저장된 10개의 데이터 중에서 특정 데이터를 검색하는 경우를 상정(경로 변수, 쿼리 매개값)해서 진행했다. 조금 더 깊이 파고들면 테스트를 돌리기 위한 JMeter 서버의 구동 환경과 프로젝트 서버의 구동 환경을 다르게 해야 정확한 계측값이 나온다고 하는데, 우선은 로컬에서 프로젝트와 JMeter를 같이 구동시켰다. 어차피 목적은 JMeter를 간략하게나마 다루는 방법에 익숙해지는 거라서.
유닛 테스트에서 사용자를 가정해주기 위한 스레드 생성을 해준다.(한글 번역 개구리네) 자바 공부를 했을 때, 스레드라는 것은 어떤 프로그램이 동작을 수행함에 있어 단위 작업이라고 이해했었다. 말인 즉슨, 스레드 하나가 곧 사용자가 프로그램을 통해 수행하려고 하는 작업 하나로 대치할 수 있다...(맞나?) 그래서 스레드를 통해 가상 사용자를 생성하는듯?
생성된 스레드 그룹에서 스레드의 수, 램프업 타입(단위 시간당 스레드 생성 요청, 여기서는 1초에 10000번의 스레드 생성을 요구하고 있다.), 루프 카운트(반복 횟수, 단위 스레드당 2번의 반복을 요구하므로 총 20000번의 테스트가 이뤄진다) 요소를 지정해준다.
가상 사용자를 생성해줬으므로, 해당 사용자들이 어떤 요청을 보낼 것인지에 대한 설정이 필요하다. 컨트롤러를 웹 상에서의 HTTP 메소드 호출을 위해 작성했으므로, HTTP 요청을 설정한다.
HTTP 요청 설정에서, 아래의 추가 버튼을 통해 쿼리 매개값의 케이스를 추가할 수 있다. 내가 지금 테스트하는 것은 경로 변수에 대한 테스트이므로, 직접 명시해주는 것으로 경로를 지정한다. 아마 변수를 다양하게 둬서 랜덤하게 요청을 보내는 것도 가능할 것 같은데... 아직 그건 찾지 못했다 ㅎ;
테스트 결과를 출력하기 위해서 Assertions과 리스너를 세팅한다. 리스너에 있는 jp@gc
는 플러그인 매니저 기반으로 설치한 추가 테스트 툴이다. 기본적인 JMeter 그래프는 너무 그래픽이 꾸져서(...) 사실 저 결과들을 어떻게 활용해야 할 지를 파악하고 또 어떤 결과들이 있는지 확인하는 것이 중요한데, 이 부분은 우선 차후로 미룰 예정.
테스트를 돌리면 아래와 같은 결과들을 확인할 수 있다. 캡쳐본은 TPS에 대한 결과 확인... 인데 TPS가 1000단위라서 놀랐지만, 아마 내 예상은 프로젝트가 간단하니까 스레드 생성이 용이했던 거겠지..ㅎ 하며 생각 중이다.
간단하게나마 JMeter를 다뤄보고, 다음 공부 주제는 모니터링 툴을 할까 싶다가 우선은 왠지 프로젝트 진행에서 자주 접하고 활용하게 될 도커를 공부한 다음, 모니터링 툴을 살펴보고 내일 성능개선 방법들에 대해 공부할 예정이다.
도커는... 진짜 많이 듣기는 했는데, 얘가 왜 필요한지는 여전히 이해가 안 된 상태라서 우선 도커의 등장 배경부터 찬찬히 보면서 공부를 하고, 직접 실습을 간략하게 해볼 생각. 그래서 정말 두서없이 글 작성이 이뤄질 것 같다.
애플리케이션이 있다. 그리고 운영체제(윈도우, 맥...)가 있다.
어떤 애플리케이션은 특정 운영체제에서만 돌아간다.
혹은 어떤 애플리케이션을 작동시키리면 특정 애플리케이션이 있어야만 작동할 수 있다.
이 모든 것들이 애플리케이션의 실행 환경을 아우르는 케이스다. 근데 하나의 운영체제 내에서 모든 실행 환경을 커버하는 것이 불가능할 수 있다. 예를 들면 A
프로그램과 B
프로그램이 실행되는 데에 있어 C
프로그램이 필요하다고 생각한다. 근데, A
가 요구하는 것은 C
의 0.5버전이고, B
가 요구하는 것은 C
의 1.5버전이라면...?
일반적으로 한 컴퓨터 내에 여러 버전의 애플리케이션이 설치되지 않으므로 위의 상황은 충분히 일어날 수 있으며 결국 A
와 B
의 동시 실행은 불가능하게 된다. 보통 이런 상황을 의존성 충돌이라고 한다.
위의 케이스처럼 두 개의 프로그램이 요구하는 동일한 한 프로그램의 버전이 상이할 때, 의존성이 충돌할 수 있는데 도커는 이 점을 해결하는 역할을 맡는다. 그리고 그것에 있어 핵심 키워드가 컨테이너다. 단순히 버전 차이 외에도 다양한 환경 상이점이 존재할 것으로 생각되지만 우선은 이해하기 쉬운 버전 차이로 이해하기.
컨테이너 기술은 컨테이너 단위마다 애플리케이션을 철저히 격리해서 고유한 의존성을 갖추게 하여 구성한다. 한 컴퓨터에는 여러 개의 컨테이너가 존재하며 각각의 컨테이너 안에 애플리케이션을 배치하여 각자의 고유 의존성을 보존시킴으로써 애플리케이션 실행 환경을 격리하게 된다. 격리 대상은 프로세스, 네트워크, 파일 시스템이 된다. 여기서 주요하게 봐야 할 격리 대상은 네트워크다.
뭔 말이냐믄... A
컨테이너에 192.168.1.10
, B
컨테이너에 192.168.1.10
처럼 서로 다른 IP를 할당할 수 있게 된다. 근데, 외부와 통신하기 위해서는 컨테이너 각자가 고유한 IP를 갖고 있기 때문에 컨테이너 포트포워딩을 사용해야 외부와 통신할 수 있다.
공부하다가 그럼 도커와 가상머신이 같은 건가... 했는데, 일단은 도커 같은 리눅스 기반 컨테이너 기술의 접근 방식과 가상머신의 접근 방식은 다르다는 정도로만 이해하고 있기.
협업이 이뤄지는 개발의 경우에, 각자의 컴퓨터에 존재하는 프로그래밍 언어 및 기타 툴들의 버전이 같다는 보장이 없다. 심지어 운영체제도 다르기 때문에 시스템 환경 변수 설정에도 시간이 많이 소모될 수 있다.
도커는 이런 문제를 해결할 수 있는데, 도커가 실행 중이라면, 어떠한 운영체제든 상관없이 같은 명령어로 즉시 환경을 구축하고 실행할 수 있게 된다. 정확히는 Docker Compose를 활용하면 여러 개의 도커 컨테이너를 동시에 실행할 수 있다. 보편적으로 프론트엔드 개발자의 개발 환경에 백엔드 코드가 갖춰져 있을 리는 없을 텐데, 그것을 명령어 한 줄로 곧바로 실행 환경을 갖출 수 있게 한다.
즉, 개발 환경에서의 도커의 이점은 운영 체제에 상관없이 즉시 애플리케이션 실행 환경을 만들 수 있고, 개발을 컨테이너 위에서 진행할 경우 모든 개발팀이 동일한 환경 하에 개발을 진행할 수 있게 된다.
배포 환경 역시 마찬가지이다. 배포라는 것은 애플리케이션이 특정 런타임 환경 위에서 실행되고, 사용자에게 이를 제공한다"는 것인데, 이는 앞서 말한 실행 환경 구성과 본질적으로 다를 것이 없다. 그저 서비스를 인터넷상에 공개적으로 노출하느냐, 내 컴퓨터 상에서 로컬에서 혼자 작동하느냐의 차이일 뿐.(오... 이해하기 쉬운 표현... 오오...)
그것과 관련해서는 EC2 상에 도커를 설치하거나, 혹은 도커 컨테이너를 EC2에서 실행시킬 수 있는 ECS를 활용하는 배포 방법이 있는데, 얘네는 나중에 보기.
팀원 중 한 명이 nginx를 활용하고 싶다는 얘기를 했던 게 기억이 났다. 아마 nginx(물론 얘도 공부해야겠지만...)가 프록시 서버를 기반으로 로드 밸런싱을 통해 분산 처리를 수행할 수 있는 웹 서버로 기억하고 있다. 도커는 로드 밸런싱을 통해 증설되는 서버의 실행 환경을 일치시킬 수 있다는 장점을 가진다. 동일한 애플리케이션 구성(이미지)을 바탕으로 새로운 서버에 해당 애플리케이션을 컨테이너로 실행하고, 로드 밸런서에 이 서버를 추가하기만 하면 된다.
이 부분은 로드 밸런싱에 대해 공부하고 다시 파보기.
컨테이너
도커 기술을 기반으로 의존성, 네트워크, 파일 시스템 등의 환경에 구애받지 않고 애플리케이션을 구동시킬 수 있는 일종의 상자
이미지
실행되는 모든 컨테이너는 이미지라는 애플리케이션 및 애플리케이션 구성을 함께 담아놓은 템플릿으로 실행되는데, 이를 이용해 즉시 컨테이너를 만들 수 있다.
또한, 이미지를 이용해 여러 개의 컨테이너를 생성할 수 있어서 이를 기반으로 애플리케이션의 수평 확장이 가능하다(이 역시 로드 밸런싱 공부하고 다시 보기). 이미지는 기본 이미지(base image)로부터 (마치 git을 사용하는 것처럼) 변경 사항을 추가 / 커밋해서 또 다른 이미지를 만들 수도 있다.
특정 애플리케이션을 도커 컨테이너에서 돌릴 수 있도록 이미지화하는 것을 도커라이징 혹은 컨테이너화라고 한다.
레지스트리
이미지는 레지스트리에 저장되는데, 대표적인 이미지 레지스트리로는 Docker Hub, Amazon ECR이 있다. 도커 CLI에서 이미지를 이용해 컨테이너를 생성할 때, 호스트 컴퓨터에 이미지가 존재하지 않는다면 기본 레지스트리로부터 다운로드 받게 된다.
많은 개념 정리 및 참조가 된 블로그
https://velog.io/@thyoondev/%EC%99%9C-%EB%8F%84%EC%BB%A4Docker%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80
사실 다음 공부는 모니터링 툴(프로메테우스, 그라파나)이 될 예정이었지만 우선은 CI / CD 구축이 먼저일 것 같아서 도커를 기반으로 CI / CD를 구축하는 것이 중요할 듯 싶다. 그래서 머리박치기로 우선 진행해볼 예정.