diff --git a/build.gradle b/build.gradle index e0a42d5..3d36f46 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { // Monitoring implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'io.micrometer:micrometer-registry-prometheus' + implementation 'io.micrometer:micrometer-registry-statsd' // DB schema manager implementation 'org.flywaydb:flyway-mysql' @@ -81,6 +81,10 @@ bootJar { dependsOn("openapi3") } +jar { + enabled = false +} + jacoco { toolVersion = '0.8.9' } diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..17299ce --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,17 @@ +FROM eclipse-temurin:17-jdk-alpine AS builder +WORKDIR /workspace +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +RUN java -Djarmode=layertools -jar app.jar extract + +FROM eclipse-temurin:17-jre-alpine +WORKDIR /workspace + +ADD https://dtdg.co/latest-java-tracer ./dd-java-agent.jar + +COPY --from=builder /workspace/dependencies/ ./ +COPY --from=builder /workspace/spring-boot-loader/ ./ +COPY --from=builder /workspace/snapshot-dependencies/ ./ +COPY --from=builder /workspace/application/ ./ + +ENTRYPOINT ["java", "-javaagent:./dd-java-agent.jar", "org.springframework.boot.loader.launch.JarLauncher"] diff --git a/docker/docker-compose.datadog.yml b/docker/docker-compose.datadog.yml new file mode 100644 index 0000000..cfa62de --- /dev/null +++ b/docker/docker-compose.datadog.yml @@ -0,0 +1,30 @@ +services: + datadog-agent: + image: gcr.io/datadoghq/agent:7 + container_name: datadog-agent + environment: + - DD_API_KEY=${DD_API_KEY} + - DD_SITE=us5.datadoghq.com + - DD_APM_ENABLED=true + - DD_APM_NON_LOCAL_TRAFFIC=true + - DD_APM_IGNORE_RESOURCES=/monitoring/health + - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true + - DD_LOGS_ENABLED=true + - DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true + - DD_PROCESS_AGENT_ENABLED=true + - DD_TAGS=env:${ENV:-dev},project:debate-timer + ports: + - "8125:8125/udp" # Metrics (StatsD) + - "8126:8126/tcp" # APM (Trace) + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro # 로그 수집 필수 + - /proc/:/host/proc/:ro + - /sys/fs/cgroup/:/host/sys/fs/cgroup/:ro + networks: + - debate-timer-net + healthcheck: + test: ["CMD", "agent", "health"] + interval: 60s + timeout: 10s + retries: 5 + start_period: 120s diff --git a/docker/docker-compose.spring.yml b/docker/docker-compose.spring.yml new file mode 100644 index 0000000..a678bbe --- /dev/null +++ b/docker/docker-compose.spring.yml @@ -0,0 +1,75 @@ +x-app-template: &app-template + image: debatetimer/debate_timer:${ENV:-dev} + environment: + - SERVER_FORWARD_HEADERS_STRATEGY=framework + - SPRING_PROFILES_ACTIVE=${PROFILE:-dev,monitor} + - TZ=Asia/Seoul + + - DD_AGENT_HOST=datadog-agent + - DD_SERVICE=debate-timer + - DD_ENV=${ENV:-dev} + - DD_VERSION=1.0.0 + - DD_LOGS_INJECTION=true + - DD_PROFILING_ENABLED=true + - DD_PROFILING_ALLOCATION_ENABLED=true + - DD_PROFILING_HEAP_ENABLED=true + + - MANAGEMENT_STATSD_METRICS_EXPORT_ENABLED=true + - MANAGEMENT_STATSD_METRICS_EXPORT_FLAVOR=datadog + - MANAGEMENT_STATSD_METRICS_EXPORT_HOST=datadog-agent + - MANAGEMENT_STATSD_METRICS_EXPORT_PORT=8125 + - MANAGEMENT_STATSD_METRICS_EXPORT_PROTOCOL=UDP + + networks: + - debate-timer-net + + depends_on: + traefik: + condition: service_healthy + datadog-agent: + condition: service_healthy + + healthcheck: + test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8083/monitoring/health" ] + interval: 60s + retries: 5 + start_period: 300s + +services: + app-blue: + <<: *app-template + container_name: app-blue + labels: + - "traefik.enable=true" + - "traefik.http.routers.app-blue.rule=Host(`api.${ENV:-dev}.debate-timer.com`)" + - "traefik.http.routers.app-blue.entrypoints=websecure" + - "traefik.http.routers.app-blue.tls=true" + - "traefik.http.routers.app-blue.tls.certresolver=myresolver" + - "traefik.http.services.app-blue.loadbalancer.server.port=8080" + - "traefik.http.routers.app-blue.service=app-blue" + + - "traefik.http.routers.app-blue-monitor.rule=PathPrefix(`/`)" + - "traefik.http.routers.app-blue-monitor.entrypoints=monitoring" + - "traefik.http.routers.app-blue-monitor.service=app-blue-monitor-svc" + - "traefik.http.services.app-blue-monitor-svc.loadbalancer.server.port=8083" + + - "com.datadoghq.ad.logs=[{\"source\": \"java\", \"service\": \"debate-timer\"}]" + + app-green: + <<: *app-template + container_name: app-green + labels: + - "traefik.enable=true" + - "traefik.http.routers.app-green.rule=Host(`api.${ENV:-dev}.debate-timer.com`)" + - "traefik.http.routers.app-green.entrypoints=websecure" + - "traefik.http.routers.app-green.tls=true" + - "traefik.http.routers.app-green.tls.certresolver=myresolver" + - "traefik.http.services.app-green.loadbalancer.server.port=8080" + - "traefik.http.routers.app-green.service=app-green" + + - "traefik.http.routers.app-green-monitor.rule=PathPrefix(`/`)" + - "traefik.http.routers.app-green-monitor.entrypoints=monitoring" + - "traefik.http.routers.app-green-monitor.service=app-green-monitor-svc" + - "traefik.http.services.app-green-monitor-svc.loadbalancer.server.port=8083" + + - "com.datadoghq.ad.logs=[{\"source\": \"java\", \"service\": \"debate-timer\"}]" diff --git a/docker/docker-compose.traefik.yml b/docker/docker-compose.traefik.yml new file mode 100644 index 0000000..54e2a2c --- /dev/null +++ b/docker/docker-compose.traefik.yml @@ -0,0 +1,39 @@ +services: + traefik: + image: traefik:v2.11 + container_name: traefik + ports: + - "80:80" + - "443:443" + - "8080:8080" + - "8083:8083" + environment: + - DOCKER_API_VERSION=1.44 + command: + - "--api.insecure=true" + - "--ping=true" + - "--log.level=INFO" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + + - "--entrypoints.web.address=:80" + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + - "--entrypoints.websecure.address=:443" + + - "--certificatesresolvers.myresolver.acme.tlschallenge=true" + - "--certificatesresolvers.myresolver.acme.email=debatetimekeeping@gmail.com" + - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" + + - "--entrypoints.monitoring.address=:8083" + healthcheck: + test: ["CMD", "wget", "--spider", "--quiet", "http://localhost:8080/ping"] + interval: 60s + timeout: 10s + retries: 5 + start_period: 120s + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ../letsencrypt:/letsencrypt + networks: + - debate-timer-net diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..9bb209d --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,11 @@ +name: debate-timer-server + +include: + - docker-compose.traefik.yml + - docker-compose.spring.yml + - docker-compose.datadog.yml + +networks: + debate-timer-net: + name: debate-timer-net + driver: bridge diff --git a/scripts/init/auto-swap.sh b/scripts/init/auto-swap.sh new file mode 100644 index 0000000..56e97a9 --- /dev/null +++ b/scripts/init/auto-swap.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -e # 스크립트 실행 중 에러 발생 시 즉시 중단 (안전 장치) + +# --- 설정 변수 --- +SWAP_FILE="/swapfile" +SWAP_SIZE=${1:-"2G"} +FSTAB_FILE="/etc/fstab" + +# 1. 루트 권한 확인 +if [ "$EUID" -ne 0 ]; then + echo "오류: 이 스크립트는 root 권한(sudo)으로 실행해야 합니다." + exit 1 +fi + +# 2. 기존 스왑 파일 존재 여부 확인 +if [ -f "$SWAP_FILE" ]; then + echo "알림: $SWAP_FILE 이 이미 존재합니다." + echo "작업을 중단합니다." + exit 0 +fi + +echo "=== Swap 메모리 생성 시작 ($SWAP_SIZE) ===" + +# 3. Swap 파일 생성 +fallocate -l $SWAP_SIZE $SWAP_FILE +echo " -> 파일 생성 완료" + +# 4. 권한 설정 (600) +chmod 600 $SWAP_FILE +echo " -> 권한 설정 완료" + +# 5. Swap 활성화 +mkswap $SWAP_FILE > /dev/null +swapon $SWAP_FILE +echo " -> Swap 활성화 완료" + +# 6. /etc/fstab 등록 (재부팅 후에도 유지) +if ! grep -q "$SWAP_FILE" "$FSTAB_FILE"; then + echo "$SWAP_FILE swap swap defaults 0 0" >> "$FSTAB_FILE" + echo " -> fstab 등록 완료 (자동 실행 설정)" +fi + +echo "=== 모든 작업이 완료되었습니다 ===" +echo "[현재 메모리 상태]" +free -h diff --git a/scripts/init/init-docker.sh b/scripts/init/init-docker.sh new file mode 100644 index 0000000..c515ffa --- /dev/null +++ b/scripts/init/init-docker.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e # 스크립트 실행 중 에러 발생 시 즉시 중단 (안전 장치) + +DOCKER_USER=$1 +DOCKER_TOKEN=$2 + +echo "=== Docker 설치를 시작합니다 ===" + +echo "1. 필수 패키지 설치 및 GPG 키 설정 진행" +sudo apt-get update +sudo apt-get install -y ca-certificates curl gnupg +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes +sudo chmod a+r /etc/apt/keyrings/docker.gpg + +echo "2. 리포지토리 설정 진행" +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +echo "3. Docker Engine 설치 진행" +sudo apt-get update +sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +echo "4. 사용자 권한 설정 진행" +sudo usermod -aG docker $USER + +echo "5. 로그인 진행 (변수 입력했을 경우에만)" +if [ -n "$DOCKER_USER" ] && [ -n "$DOCKER_TOKEN" ]; then + echo "입력된 정보로 Docker Hub 로그인을 시도합니다..." + + if echo "$DOCKER_TOKEN" | sg docker -c "docker login -u \"$DOCKER_USER\" --password-stdin"; then + echo "✅ 로그인 성공! (config.json이 생성되었습니다)" + else + echo "❌ 로그인 실패. 아이디/토큰을 확인하거나 'newgrp docker' 후 다시 시도하세요." + fi +else + echo "로그인 정보가 입력되지 않아 로그인을 건너뜁니다." + echo "추후 'docker login' 명령어로 로그인하세요." +fi + +echo "6. Docker 권한이 적용된 새로운 쉘로 전환" +exec newgrp docker diff --git a/scripts/init/init-letsencrypt.sh b/scripts/init/init-letsencrypt.sh new file mode 100644 index 0000000..5223ca6 --- /dev/null +++ b/scripts/init/init-letsencrypt.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd "$(dirname "$0")" || exit 1 + +mkdir -p ../../letsencrypt +touch ../../letsencrypt/acme.json +chmod 600 ../../letsencrypt/acme.json + +echo "✅ 스크립트 위치($(pwd))에 letsencrypt 폴더를 생성했습니다." diff --git a/src/main/resources/application-monitor.yml b/src/main/resources/application-monitor.yml index d9809cb..682582b 100644 --- a/src/main/resources/application-monitor.yml +++ b/src/main/resources/application-monitor.yml @@ -1,4 +1,13 @@ management: + statsd: + metrics: + export: + enabled: true + flavor: datadog + host: ${MANAGEMENT_STATSD_METRICS_EXPORT_HOST:localhost} + port: 8125 + protocol: UDP + server: port: 8083