diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9589c8c0..ae1712d6 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -72,7 +72,8 @@ jobs: envs: >- DB_PASSWORD,GCS_BUCKET,GCS_BACKUP_BUCKET, KAKAO_CLIENT_ID,KAKAO_CLIENT_SECRET,KAKAO_REDIRECT_URI_PROD,KAKAO_REDIRECT_URI_STAGING,KAKAO_ADMIN_KEY, - JWT_SECRET_KEY,FIREBASE_SERVICE_ACCOUNT_KEY,FCM_FAKE_LATENCY_MS + JWT_SECRET_KEY,FIREBASE_SERVICE_ACCOUNT_KEY,FCM_FAKE_LATENCY_MS, + OTLP_METRICS_ENABLED,OTEL_URL,OTEL_AUTH,OTLP_STEP script: | chmod +x /home/ubuntu/cockple/scripts/deploy.sh bash /home/ubuntu/cockple/scripts/deploy.sh \ @@ -89,5 +90,9 @@ jobs: KAKAO_ADMIN_KEY: ${{ secrets.KAKAO_ADMIN_KEY }} JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} FIREBASE_SERVICE_ACCOUNT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} - # [부하테스트용] FCM 지연(ms) 토글. GitHub Variable로 관리(미설정 시 빈값→deploy.sh에서 0). PR 없이 값 변경+deploy 재실행으로 on/off. FCM_FAKE_LATENCY_MS: ${{ vars.FCM_FAKE_LATENCY_MS }} + # 관측성(OTLP) — URL/토글/step은 Variable, 인증 토큰은 Secret + OTLP_METRICS_ENABLED: ${{ vars.OTLP_METRICS_ENABLED }} + OTEL_URL: ${{ vars.OTEL_URL }} + OTEL_AUTH: ${{ secrets.OTEL_AUTH }} + OTLP_STEP: ${{ vars.OTLP_STEP }} diff --git a/.gitignore b/.gitignore index 5add0cde..b87032d3 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ k6 ### logs ### logs/ +/result-bulkhead-*.json diff --git a/build.gradle b/build.gradle index 97dfd314..3d82ba61 100644 --- a/build.gradle +++ b/build.gradle @@ -102,6 +102,10 @@ dependencies { // flyway implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' + + // observability (Actuator + OTLP 메트릭 export) + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'io.micrometer:micrometer-registry-otlp' } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml index bdbf2b50..637838ed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,6 +84,11 @@ services: SPRING_PROFILES_ACTIVE: staging LOG_PATH: /logs FCM_FAKE_LATENCY_MS: ${FCM_FAKE_LATENCY_MS:-0} + # 관측성(Observability) — OTLP 메트릭 push (staging 전용). 미설정 시 off. + OTLP_METRICS_ENABLED: ${OTLP_METRICS_ENABLED:-false} + OTEL_URL: ${OTEL_URL:-} + OTEL_AUTH: ${OTEL_AUTH:-} + OTLP_STEP: ${OTLP_STEP:-1m} DB_PASSWORD: ${DB_PASSWORD} GCS_BUCKET: ${GCS_BUCKET} KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID} diff --git a/result-bulkhead-before.json b/result-bulkhead-before.json deleted file mode 100644 index 7aeddc6b..00000000 --- a/result-bulkhead-before.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "root_group": { - "name": "", - "path": "", - "id": "d41d8cd98f00b204e9800998ecf8427e", - "groups": {}, - "checks": { - "[notif] status 200": { - "passes": 1567, - "fails": 0, - "name": "[notif] status 200", - "path": "::[notif] status 200", - "id": "9fc740afe9cf21a0327678441a348978" - }, - "[ws] status 101": { - "name": "[ws] status 101", - "path": "::[ws] status 101", - "id": "3cc6802624f263d3928ed6de98f3c352", - "passes": 2, - "fails": 0 - } - } - }, - "metrics": { - "ws_connecting": { - "max": 1155.4575, - "p(90)": 1155.34769, - "p(95)": 1155.402595, - "avg": 1154.90845, - "min": 1154.3594, - "med": 1154.90845 - }, - "ws_msgs_sent": { - "count": 1651, - "rate": 4.985553421339417 - }, - "ws_msgs_received": { - "count": 3304, - "rate": 9.977146277471492 - }, - "http_reqs": { - "count": 1567, - "rate": 4.731897160047769 - }, - "http_req_receiving": { - "p(90)": 0.7683200000000002, - "p(95)": 0.9485099999999989, - "avg": 0.21188544990427544, - "min": 0, - "med": 0, - "max": 6.9107 - }, - "checks": { - "passes": 1569, - "fails": 0, - "value": 1 - }, - "vus_max": { - "value": 12, - "min": 12, - "max": 12 - }, - "chat_messages_received": { - "count": 1648, - "rate": 4.97649426915043 - }, - "iterations": { - "count": 1569, - "rate": 4.737936594840427 - }, - "ws_sessions": { - "count": 2, - "rate": 0.006039434792658289 - }, - "http_req_duration{expected_response:true}": { - "p(90)": 2076.8168400000004, - "p(95)": 2721.69289, - "avg": 1151.817254243779, - "min": 431.0899, - "med": 977.7864, - "max": 4974.7035 - }, - "http_req_blocked": { - "p(90)": 0, - "p(95)": 0, - "avg": 1.902208615188258, - "min": 0, - "med": 0, - "max": 315.6305 - }, - "http_req_connecting": { - "max": 148.6131, - "p(90)": 0, - "p(95)": 0, - "avg": 0.895541671984684, - "min": 0, - "med": 0 - }, - "chat_messages_sent": { - "count": 1649, - "rate": 4.979513986546759 - }, - "http_req_sending": { - "med": 0, - "max": 3.377, - "p(90)": 0.6262200000000001, - "p(95)": 0.7184799999999996, - "avg": 0.17865022335673258, - "min": 0 - }, - "vus": { - "value": 2, - "min": 2, - "max": 12 - }, - "data_sent": { - "count": 251316, - "rate": 758.9032971758552 - }, - "http_req_waiting": { - "p(95)": 2721.5110299999997, - "avg": 1151.4267185705173, - "min": 430.2286, - "med": 977.2512, - "max": 4974.1908, - "p(90)": 2076.08822 - }, - "chat_e2e_latency": { - "min": 350, - "med": 404, - "max": 5422, - "p(90)": 3371.499999999998, - "p(95)": 4390.65, - "avg": 937.6231796116505, - "thresholds": { - "p(95)<3000": true - } - }, - "notif_req_duration": { - "med": 977.7864, - "max": 4974.7035, - "p(90)": 2076.8168400000004, - "p(95)": 2721.69289, - "avg": 1151.817254243779, - "min": 431.0899 - }, - "ws_session_duration": { - "min": 331155.5802, - "med": 331155.89955, - "max": 331156.2189, - "p(90)": 331156.15502999997, - "p(95)": 331156.186965, - "avg": 331155.89955 - }, - "notif_errors": { - "passes": 0, - "fails": 1567, - "thresholds": { - "rate<0.10": false - }, - "value": 0 - }, - "http_req_failed": { - "fails": 1567, - "passes": 0, - "value": 0 - }, - "iteration_duration": { - "avg": 1574.7665509878918, - "min": 431.6325, - "med": 982.5982, - "max": 331156.2189, - "p(90)": 2088.631680000001, - "p(95)": 2724.6312 - }, - "http_req_duration": { - "p(90)": 2076.8168400000004, - "p(95)": 2721.69289, - "avg": 1151.817254243779, - "min": 431.0899, - "med": 977.7864, - "max": 4974.7035 - }, - "data_received": { - "count": 1517803, - "rate": 4583.336123300564 - }, - "http_req_tls_handshaking": { - "max": 181.0315, - "p(90)": 0, - "p(95)": 0, - "avg": 1.003557562220804, - "min": 0, - "med": 0 - } - } -} \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 963d1537..eeb16e2b 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -27,6 +27,10 @@ KAKAO_ADMIN_KEY=${KAKAO_ADMIN_KEY} JWT_SECRET_KEY=${JWT_SECRET_KEY} FIREBASE_SERVICE_ACCOUNT_KEY=${FIREBASE_SERVICE_ACCOUNT_KEY} FCM_FAKE_LATENCY_MS=${FCM_FAKE_LATENCY_MS:-0} +OTLP_METRICS_ENABLED=${OTLP_METRICS_ENABLED:-false} +OTEL_URL=${OTEL_URL:-} +OTEL_AUTH=${OTEL_AUTH:-} +OTLP_STEP=${OTLP_STEP:-1m} EOF echo "${FIREBASE_SERVICE_ACCOUNT_KEY}" > /home/ubuntu/cockple/firebase-service-account.json diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ef55b172..6120c937 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -103,6 +103,30 @@ async: fcm: fake-latency-ms: 0 +# 관측성(Observability) — OTLP로 메트릭 push +# 기본 off, staging(및 추후 prod)에서 env로만 켠다. +management: + otlp: + metrics: + export: + enabled: ${OTLP_METRICS_ENABLED:false} + url: ${OTEL_URL:http://localhost:4318}/v1/metrics + step: ${OTLP_STEP:1m} + headers: + Authorization: ${OTEL_AUTH:} + opentelemetry: + resource-attributes: + service.name: ${spring.application.name} + deployment.environment: ${SPRING_PROFILES_ACTIVE:local} + metrics: + distribution: + percentiles-histogram: + http.server.requests: true + endpoints: + web: + exposure: + include: health # 웹 노출 최소화 (메트릭은 OTLP push라 endpoint 불필요) + logging: level: org.hibernate.SQL: WARN