목적
EKS 환경에서 Java Spring APP의 Metric을 Prometheus로 Scrap하고 k6를 통해서 부하테스트 진행
최종적으로 Pod, Node, DB 사이징&튜닝 및 finops 구현을 위한 테스트 목적
Test Application
Java Spring에 TimeLeaf FormData로 구성되어있고 DB 연결이 가능한 간단한 Demo 어플리케이션
어플리케이션 구성 : 회원 등록/조회, 상품 등록/조회, 상품 주문/조회/취소 기능 Spring Boot APP
DB 커넥션 등 yaml 파일 수정하여 재사용하여 ECR에 이미지 로드
https://github.com/sgwon96/devopsTest
기존 h2로 어플리케이션 내의 Memory DB 사용하는것을 AWS RDS로 교체
1차 application.yml
spring:
datasource:
url: jdbc:mysql://${DB_NAME}-ap-northeast-2.rds.amazonaws.com:3306/
username: ${USER}
password: ${PWD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
위를 추가하여 DB Connection 진행
ECR 빌드
ECR은 public 레지스트리로 진행했고 아래는 Mac 기준 빌드 Docker Build 방법
* Mac에서는 Default 값으로 빌드 진행시 arm으로 빌드됨
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/
docker build -t finops-test . --platform linux/amd64
docker tag ${ECR_Resistry}:latest public.ecr.aws/${ECR_Resistry}:plain
docker push public.ecr.aws/${ECR_Resistry}:plain
Deploy Kubernetes
간단하게 Service와 Deploy만 배포하는 구조
서비스는 NodePort로 선정해 미리 생성해둔 ALB에 TargetGroupBinding 리소스를 사용해 연결
관련 링크 https://weq155.tistory.com/22
LoadTest 진행을 위해 Limit 설정은 제외
Deployment.yaml
## Deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: devops-spring-deployment
spec:
selector:
matchLabels:
app: devops-spring-app
replicas: 1
template:
metadata:
labels:
app: devops-spring-app
spec:
containers:
- name: core
image: public.ecr.aws/${ECR_Resistry}:plain
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
resources:
requests:
cpu: 1000m
memory: 3Gi
Service.yaml
추후 Prometheus ServiceMonitor 리소스 사용을 위해 ports에 Name을 기입
apiVersion: v1
kind: Service
metadata:
name: devops-spring-service
labels:
app: spring
spec:
type: NodePort
ports:
- port: 8080
name: web
targetPort: 8080
selector:
app: devops-spring-app
이후 ALB와 TGB 연결을 진행하고 리스너 설정이 완료되면 ALB 주소로 접속해서 접속 가능하다
Prometheus Service Monitor 설정
Prometheus Service Monitor는 Prometheus에서 만든 CRD이다.
기존 새로운 Scrap Config를 yaml 파일을 수정해서 재배포해야하는 불편함을 해소하기위해
Prometheus Operator를 개발하였고 Custom Resource를 배포하여 Prometheus의 config를 수정한다.
그래서 Prometheus Operator가 포함된 kube-prometheus-stack Helm Chart를 수정하여 배포
Grafana는 해당 클러스터에 사전에 구성되어있었기 때문에
아래의 pod만 배포
Spring APP Metric 노출 구성
Java Spring에서 Prometheus Metirc을 노출하기위해 몇가지 설정이 필요
아래와 같이 actuator와 promethus dependency를 추가해서 Metric 노출을 설정
application.yml에서 endpoint를 노출하고 prometheus의 접근을 허용
## build.gradle
// 프로메테우스
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
## application.yml
management:
endpoints:
web:
exposure:
include: "health,info,prometheus"
ServiceMonitor 구성
ServiceMonitor를 구성할때에 주의할 점은 Label 설정과 endpoint 설정이다.
이전에 endpoint 설정을 port Name만 설정했을때에는 Default 설정으로 endpoint가 아래와 같이 구성되어 Spring에서 노출한 주소와 달라서 Scrap을 실패했다.
http://172.3.1.171:8080/metric |
만약 ServiceMonitor를 배포했는데도 Metric이 Scarp 되지 않는다면
아래 명령어를 사용하여 Prometheus UI에 접속해서 Target을 확인해보자
kubectl port-forward svc/prometheus-kube-prometheus-prometheus 9090 -n prometheus
아래와 같이 State UP이 되어야 정상적으로 스크랩
ServiceMonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: spring-app-monitor
labels:
release: prometheus
spec:
selector:
matchLabels:
app: spring # 서비스 레이블과 일치해야 합니다.
endpoints:
- port: web # 서비스 포트 이름
path: /actuator/prometheus # Spring Boot 메트릭 노출 경로
참고 : Prometheus Service Monitor는 configure 적용이 안됨 리소스를 삭제하고 재배포 해야 적용
Spring Metric 항목
Spring Boot 애플리케이션에서 Prometheus를 통해 메트릭을 스크레이핑할 때 수집되는 데이터 항목은 다양하고 상세.
이는 Spring Boot의 Actuator 및 Micrometer 라이브러리를 통해 자동으로 많은 종류의 메트릭을 제공
여기에는 시스템, JVM, 로깅, HTTP 요청, 데이터베이스 연결 등 다양한 영역의 메트릭이 포함
주요 Metric 데이터 항목
측정 항목은 크게 아래와 같고 항목별로 세부사항들이 Metric으로 노출됨
1. JVM 메트릭:
2. HTTP 요청 메트릭:
3. Tomcat 메트릭 (웹 애플리케이션 서버 사용 시)
4. 시스템 메트릭
5. 프로세스 메트릭
6. Garbage Collection 메트릭
7. 데이터베이스 연결 메트릭 (데이터 소스 사용 시)
Grafana DashBorad 구성
내가 원하는 측정항목은 디스크 CPU, MEM, RPS 전체 + 개별 URI 였고, 해당 대시보드를 구성
하지만 LoadTest를 정확하게 진단하기 위해서는 부족한 자료가 많았고
추가적으로 K6와 InfluxDB 자료 사용
K6와 InfluxDB
K6는 local에서 설치해서 사용했고 InfluxDB는 Grafana가 구성된 EKS Cluster에 설치
InfluxDB 설치 코드
helm repo add influxdata https://helm.influxdata.com/
helm repo update
helm upgrade -i influxdb influxdata/influxdb -n influxdb --create-namespace -f influxdb/values.yaml
K6 Test
InfluxDB가 정상적으로 설치가 되면 port-forward를 통해 output을 전달 가능하다 (LB 사용도 가능)
테스트를 시작하기전에 Grafana DashBorad를 구성
Grafana 등록
Influxdb의 svc 정보를 등록해 Datasource 연결
helm 설치시 위와 같이 설치했다면
URL = http://influxdb.influxdb.svc.cluster.local:8086/
이후 스크롤을 내려 아래의 Database 명을 입력해줘야 연결 가능
대시보드 구성
Grafana Labs에 InfluxDB Datasource로 구성한 최근 대시보드를 사용
https://grafana.com/grafana/dashboards/13719-k6-load-testing-results-by-groups/
그리고 앞에 DataSource로 설정한 DB Name 대로 대시보드를 수정
아래와 같이 구성 된걸 볼 수 있다.
K6 코드 구성
로컬환경에서 테스트가 아니기 때문에 k6 port-forward 후 output을 전송
사용한 k6 코드는 앞의 Application에 User를 Post하는 코드로
__VU와 __ITER 값으로 중복이 되지않는 유저를 생성하고
해당 어플리케이션은 단순 JSON으로 post하면 오류가 발생해서
문제를 해결하다보니Spring 타임리프 폼데이터 구조였기 때문에 아래 로직도 폼데이터에 맞게 구성
이후 계속 유저 100에서 Down
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '5m', target: 200 },
{ duration: '5m', target: 200 },
{ duration: '5m', target: 0 },
],
// vus: 10, // 최대 가상 사용자 수
};
export default function () {
const url = 'http://ALB/members/new'; // 요청을 보낼 URL
const vu = __VU; // 현재 가상 사용자 번호
const iter = __ITER; // 현재 반복 번호
const payload = {
name: `User-${vu}-${iter}`, // 각 요청마다 고유한 사용자 이름
city: `City-${vu}-${iter}`, // 각 요청마다 고유한 도시 이름
street: '123 Broadway',
zipcode: '10007'
};
const params = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
// 객체를 URL 인코딩된 문자열로 변환
const formData = Object.keys(payload).map(key =>
`${encodeURIComponent(key)}=${encodeURIComponent(payload[key])}`
).join('&');
const response = http.post(url, formData, params);
check(response, {
'is status 200 or 302': (r) => r.status === 200 || r.status === 302, // 성공적인 응답 확인
});
sleep(1); // 1초 대기
}
Test 실행
k6 run k6/ProductPost2.js -o influxdb=http://127.0.0.1:8086/myk6db
Test 결과
max user에서 처리량이 낮아지고 duration이 점점 증가하면서 속도 저하 발생이지만 결과값은 올바르게 출력
Trouble Shooting
1. DB MaxConnection 문제
처음 테스트를 실행할때에 DB 사이즈를 아무리 올려도 유저가 100명을 넘지 못하고 Timeout 발생
이유는 Hikari에서 Max Connection pool을 10으로 고정해놓았기 때문이었다.
2. DB Connection 끊기지 않는 문제?
1번 문제에서 어플리케이션 Datasource를 수정해서 maximum-pool-size: 2000으로 진행
테스트가 종료된 이후에도 RDS에 Connection 2000이 유지되는 문제
Hikari는 Default 설정으로 maximum-pool-size가 minimum-idle과 동일하게 설정 - 커넥션 생성 삭제에 리소스를 쓰지않기 위해
그래서 운영 어플리케이션이면 문제가 없겠지만 단기적인 테스트 어플리케이션이기 때문에 수정하고 로그도 추가
최종 Test yml
# application.yml
spring:
datasource:
url: jdbc:mysql://RDS.rds.amazonaws.com:3306/
username: testuser
password: awsbackup133
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 3000
maximum-pool-size: 2000
max-lifetime: 30000
minimum-idle: 10
idle-timeout: 10000
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
logging.level:
org.hibernate.SQL: debug
com.zaxxer.hikari: debug
management:
endpoints:
web:
exposure:
include: "health,info,prometheus"
이렇게 간단한 테스트를 마무리하고 추후에 튜닝 테스트 진행은 추후 업로드 하도록 하겠습니다
'Engineering' 카테고리의 다른 글
Hashicorp Nomad란? 기능 설명과 설치 가이드 (1) | 2024.06.18 |
---|---|
Cast.ai란 component 및 작동원리 (0) | 2024.05.21 |
AWS CloudTrail & CloudWatch & Lambda를 활용한 리소스 생성, 삭제 추적 (0) | 2024.04.25 |
K6 load testing (0) | 2024.04.05 |
EKS Prometheus CPU & Memory DashBoard 구성 (0) | 2024.04.03 |