본문 바로가기
Engineering

EKS에서 Spring APP Metric 추적 및 K6 LoadTest

by weq155 2024. 5. 14.
반응형
반응형

목적

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

 

GitHub - sgwon96/devopsTest: devopst Test

devopst Test. Contribute to sgwon96/devopsTest development by creating an account on GitHub.

github.com

기존 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

 

AWS EKS ALB 하나로 여러 Ingress 구성 ( Ingress Group, TargetGroupBinding)

Ingress Grouping을 사용하여 비용을 절감하고 대상 그룹 바인딩을 사용하여 기존 로드 밸런서를 통합하는 방법 쿠버네티스의 클러스터 외부의 클라이언트에 로드밸런서 또는 Ingress 리소스 유형의

weq155.tistory.com

 

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/

 

k6 Load Testing Results By Groups | Grafana Labs

Thank you! Your message has been received!

grafana.com

그리고 앞에 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"

 

 

이렇게 간단한 테스트를 마무리하고 추후에 튜닝 테스트 진행은 추후 업로드 하도록 하겠습니다

반응형