diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..20a088516 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +target +.mvn +.git +.github +.idea +*.iml +Dockerfile* +node_modules +build +out diff --git a/.github/workflows/ci-petclinic-eks.yaml b/.github/workflows/ci-petclinic-eks.yaml index 0ad081fac..1bce6e212 100644 --- a/.github/workflows/ci-petclinic-eks.yaml +++ b/.github/workflows/ci-petclinic-eks.yaml @@ -29,7 +29,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: "25" + java-version: "21" cache: maven - name: Maven build & test @@ -61,7 +61,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: "25" + java-version: "21" cache: maven - name: Configure AWS credentials (OIDC) diff --git a/.github/workflows/ci-petclinic-gke.yaml b/.github/workflows/ci-petclinic-gke.yaml index ce778a069..7fbf9c770 100644 --- a/.github/workflows/ci-petclinic-gke.yaml +++ b/.github/workflows/ci-petclinic-gke.yaml @@ -31,7 +31,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: "25" + java-version: "21" cache: maven - name: Maven build & test @@ -63,7 +63,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: temurin - java-version: "25" + java-version: "21" cache: maven - name: Authenticate to Google Cloud (WIF) diff --git a/Dockerfile b/Dockerfile index 20954e271..3018290e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ # syntax=docker/dockerfile:1.6 ARG JAVA_VERSION=21 -ARG MAVEN_IMAGE=maven:3.9-eclipse-temurin-21 -ARG JDK_IMAGE=eclipse-temurin:21-jdk -ARG DISTROLESS_IMAGE=gcr.io/distroless/base-debian12:nonroot +ARG MAVEN_IMAGE=maven:3.9-eclipse-temurin-${JAVA_VERSION} +ARG JDK_IMAGE=eclipse-temurin:${JAVA_VERSION}-jdk +ARG RUNTIME_IMAGE=eclipse-temurin:${JAVA_VERSION}-jre # ========================= # 1) Build @@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/root/.m2 \ COPY . . RUN --mount=type=cache,target=/root/.m2 \ - mvn -q -DskipTests package + mvn -q -DskipTests clean package RUN set -eux; \ JAR="$(ls -1 target/*.jar | head -n 1)"; \ @@ -37,7 +37,6 @@ RUN set -eux; \ mkdir -p /whatap; \ tar -xzf /tmp/whatap.agent.java.tar.gz -C /whatap; \ rm -f /tmp/whatap.agent.java.tar.gz; \ - # agent jar 찾기 AGENT_JAR="$(find /whatap -maxdepth 6 -type f \ \( -name 'whatap.agent*.jar' -o -name '*whatap*agent*.jar' \) | head -n 1)"; \ test -n "$AGENT_JAR"; \ @@ -46,48 +45,44 @@ RUN set -eux; \ chmod -R a=rX /whatap # ========================= -# 3) jlink Slim JRE +# 3) Runtime (fallback-first: temurin JRE) # ========================= -FROM ${JDK_IMAGE} AS jre -WORKDIR /jrebuild - -COPY --from=builder /app/app.jar ./app.jar - -RUN set -eux; \ - DEPS="$(jdeps --ignore-missing-deps --multi-release=21 --print-module-deps app.jar)"; \ - jlink --strip-debug --no-man-pages --no-header-files --compress=2 \ - --add-modules "$DEPS,\ -java.desktop,java.management,jdk.management,java.sql,java.naming,\ -java.logging,java.security.jgss,jdk.security.auth,java.security.sasl,java.instrument,\ -jdk.crypto.ec,jdk.unsupported,java.xml" \ - --output /opt/jre - -# ========================= -# 4) Runtime (Distroless) -# ========================= -FROM ${DISTROLESS_IMAGE} +# 기본 목표는 “정상 기동”이므로 디버깅이 용이한 eclipse-temurin JRE를 사용한다. +# distroless/jlink 최적화는 별도 커밋에서 재적용한다. +FROM ${RUNTIME_IMAGE} WORKDIR /app -COPY --from=jre /opt/jre /opt/jre -COPY --from=builder /app/app.jar ./app.jar +COPY --from=builder /app/app.jar /app/app.jar +COPY --from=whatap_agent /whatap /whatap -# ✅ 핵심: /whatap 을 nonroot(65532)가 소유하도록 복사 -COPY --from=whatap_agent --chown=65532:65532 /whatap /whatap +ENV JAVA_TOOL_OPTIONS="" \ + JAVA_OPTS="" \ +ENABLE_WHATAP=false \ +WHATAP_HOME=/whatap -ENV JAVA_HOME=/opt/jre -ENV PATH="/opt/jre/bin:${PATH}" +# ENTRYPOINT wrapper: ENABLE_WHATAP=true 일 때만 agent 옵션을 추가한다. +COPY <<'EOF' /entrypoint.sh +#!/bin/sh +set -eu -# ✅ whatap home 명시(에이전트가 내부 파일 쓸 때 경로 확정) -ENV WHATAP_HOME=/whatap +JAVA_ARGS=${JAVA_OPTS:-} +case "${ENABLE_WHATAP:-false}" in + true|TRUE|True) + JAVA_ARGS="$JAVA_ARGS -javaagent:/whatap/whatap.agent.jar" + JAVA_ARGS="$JAVA_ARGS -Dwhatap.paramkey=/whatap/paramkey.txt" + JAVA_ARGS="$JAVA_ARGS -Dwhatap.server.host=${WHATAP_SERVER_HOST:-}" + JAVA_ARGS="$JAVA_ARGS -Dlicense=${WHATAP_LICENSE:-}" + JAVA_ARGS="$JAVA_ARGS -Dwhatap.micro.enabled=${WHATAP_MICRO_ENABLED:-}" + JAVA_ARGS="$JAVA_ARGS --add-opens=java.base/java.lang=ALL-UNNAMED" + ;; +esac -ENV JAVA_TOOL_OPTIONS="\ --javaagent:/whatap/whatap.agent.jar \ --Dwhatap.paramkey=/whatap/paramkey.txt \ ---add-opens=java.base/java.lang=ALL-UNNAMED \ --XX:+UseContainerSupport \ --XX:MaxRAMPercentage=75 \ --XX:+ExitOnOutOfMemoryError \ --XX:+AlwaysActAsServerClassMachine" +exec java $JAVA_ARGS -XX:+UseContainerSupport -XX:MaxRAMPercentage=75 \ + -XX:+ExitOnOutOfMemoryError -XX:+AlwaysActAsServerClassMachine \ + -jar /app/app.jar +EOF + +RUN chmod +x /entrypoint.sh EXPOSE 8080 -ENTRYPOINT ["/opt/jre/bin/java", "-jar", "/app/app.jar"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index c2eec708c..603ab2261 100644 --- a/README.md +++ b/README.md @@ -110,3 +110,28 @@ CSP별 Ingress, Registry, Secret 차이로 매니페스트 분리 본 프로젝트는 멀티클라우드 환경에서 GitOps 기반 CI/CD와 운영 분리를 어떻게 설계할 수 있는지를 보여주는 예제입니다. + +--- + +## 9. Spring Boot 3.x 다운그레이드 메모 (Boot 4.0.0-M3 → Boot 3.2.5) + +- 참고 문서: *Spring Boot 3 Migration Guide*, *Spring Boot 3.2 Release Notes*, *Spring Framework 6.x*. +- Jakarta 네임스페이스 전환 이후 API 의존성은 Boot 3.x 기본 BOM을 따라가며, Boot 4 / Spring 7 전용 의존성은 제거/하향 조정했다. +- Java 21 런타임을 유지하며, 모듈 경계 문제를 피하기 위해 `--add-opens=java.base/java.lang=ALL-UNNAMED` 옵션을 Whatap 활성화 시에만 주입한다. +- distroless/jlink 최적화는 별도 커밋으로 재적용 예정이며, 현재는 디버깅 및 호환성을 우선한 `eclipse-temurin:21-jre` 런타임을 사용한다. + +### 잠재 호환 이슈 및 대응 +- 서드파티 라이브러리의 Spring 7 전용 API 사용 → Boot 3.x BOM 기준으로 의존성 하향. 필요 시 BOM에 맞는 버전으로 재빌드. +- 에이전트/모듈 경계(`--add-opens`) 누락 시 JVM 시작 실패 가능 → Whatap 활성화 경로에서만 옵션을 주입하고, 기본 OFF 모드로 기동 보장. +- 컨테이너 디버깅(쉘 유무) → distroless 대신 temurin JRE를 기본값으로 사용, 추후 최적화는 별도 PR로 진행. + +--- + +## 10. WhaTap APM 토글 사용법 + +- 기본값: OFF (`ENABLE_WHATAP=false`). +- 활성화: 컨테이너/Deployment 환경 변수로 아래를 설정한다. + - `ENABLE_WHATAP=true` + - `WHATAP_LICENSE`, `WHATAP_SERVER_HOST`, `WHATAP_MICRO_ENABLED` +- 이미지에 `/whatap/whatap.agent.jar`, `/whatap/paramkey.txt`가 포함되어 있으며, 활성화 시 엔트리포인트 래퍼가 자동으로 JVM 옵션을 주입한다. +- 외부 Secret(ExternalSecret: `whatap-apm-secret`)은 라이선스/서버/마이크로 트레이싱 키만 제공하며, `paramkey.txt`는 이미지에 포함된 파일을 사용한다. diff --git a/build.gradle b/build.gradle index c0b4c0a6f..b3f4f3c3f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'checkstyle' - id 'org.springframework.boot' version '4.0.0-M3' + id 'org.springframework.boot' version '3.2.5' id 'io.spring.dependency-management' version '1.1.7' id 'org.graalvm.buildtools.native' version '0.11.1' id 'org.cyclonedx.bom' version '3.0.0' @@ -13,11 +13,11 @@ plugins { gradle.startParameter.excludedTaskNames += [ "checkFormatAot", "checkFormatAotTest" ] group = 'org.springframework.samples' -version = '4.0.0-SNAPSHOT' +version = '3.2.5-SNAPSHOT' java { toolchain { - languageVersion = JavaLanguageVersion.of(25) + languageVersion = JavaLanguageVersion.of(21) } } @@ -77,7 +77,7 @@ checkstyleNohttp { } tasks.withType(JavaCompile).configureEach { - options.release = 17 + options.release = 21 options.errorprone { disableAllChecks = true } diff --git a/k8s/aws/20-petclinic-Deployments-postgre.yaml b/k8s/aws/20-petclinic-Deployments-postgre.yaml index 65931160e..2de3111ce 100644 --- a/k8s/aws/20-petclinic-Deployments-postgre.yaml +++ b/k8s/aws/20-petclinic-Deployments-postgre.yaml @@ -37,17 +37,15 @@ spec: - name: SPRING_DATASOURCE_DRIVER_CLASS_NAME value: "org.postgresql.Driver" - name: APP_VERSION - value: "A-4.0.0-49c6082" + value: "A-3.2.x" - name: NODE_IP valueFrom: {fieldRef: {fieldPath: status.hostIP}} - name: NODE_NAME valueFrom: {fieldRef: {fieldPath: spec.nodeName}} - name: POD_NAME valueFrom: {fieldRef: {fieldPath: metadata.name}} - # ✅ Whatap + 기존 JVM 옵션 (덮어쓰기 주의 OK) - - name: JAVA_TOOL_OPTIONS - value: >- - -javaagent:/whatap/whatap.agent.jar -Dwhatap.server.host=$(WHATAP_SERVER_HOST) -Dlicense=$(WHATAP_LICENSE) -Dwhatap.micro.enabled=$(WHATAP_MICRO_ENABLED) -Dwhatap.paramkey=/whatap/paramkey.txt -XX:+UseContainerSupport -XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError -XX:+AlwaysActAsServerClassMachine + - name: ENABLE_WHATAP + value: "false" ports: - name: http containerPort: 8080 diff --git a/k8s/gcp/10-petclinic-secret-whatap.yaml b/k8s/gcp/10-petclinic-secret-whatap.yaml new file mode 100644 index 000000000..ebf7a3d5f --- /dev/null +++ b/k8s/gcp/10-petclinic-secret-whatap.yaml @@ -0,0 +1,26 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: whatap-apm-secret + namespace: petclinic-ns +spec: + refreshInterval: 1h + secretStoreRef: + name: gcp-secretmanager + kind: ClusterSecretStore + target: + name: whatap-apm-secret + creationPolicy: Owner + data: + - secretKey: WHATAP_LICENSE + remoteRef: + key: finalproj-dev-whatap-apm + property: license + - secretKey: WHATAP_SERVER_HOST + remoteRef: + key: finalproj-dev-whatap-apm + property: server_host + - secretKey: WHATAP_MICRO_ENABLED + remoteRef: + key: finalproj-dev-whatap-apm + property: micro_enabled diff --git a/k8s/gcp/20-petclinic-Deployments-postgre.yaml b/k8s/gcp/20-petclinic-Deployments-postgre.yaml index 25219c1c0..a345fee6a 100644 --- a/k8s/gcp/20-petclinic-Deployments-postgre.yaml +++ b/k8s/gcp/20-petclinic-Deployments-postgre.yaml @@ -35,6 +35,8 @@ spec: envFrom: - secretRef: name: petclinic-db-secret + - secretRef: + name: whatap-apm-secret # 🔹 여기: Spring profile을 postgres용으로 env: @@ -48,7 +50,9 @@ spec: - name: SPRING_JPA_HIBERNATE_DDL_AUTO value: "none" - name: APP_VERSION - value: "local" + value: "A-3.2.x" + - name: ENABLE_WHATAP + value: "false" ports: - name: http diff --git a/pom.xml b/pom.xml index e3a2f692b..49c352197 100644 --- a/pom.xml +++ b/pom.xml @@ -5,13 +5,13 @@ org.springframework.boot spring-boot-starter-parent - 4.0.0-M3 + 3.2.5 org.springframework.samples spring-petclinic - 4.0.0-SNAPSHOT + 3.2.5-SNAPSHOT petclinic @@ -72,11 +72,6 @@ spring-boot-starter-test test - - org.springframework.boot - spring-boot-starter-restclient - test - @@ -316,6 +311,7 @@ org.cyclonedx cyclonedx-maven-plugin + 2.9.1