From d05e3b9dd978265c150fc0a7b699b3123906152b Mon Sep 17 00:00:00 2001 From: dasomel Date: Wed, 20 May 2026 18:09:42 +0900 Subject: [PATCH 1/3] feat: add Dockerfile, docker-compose, and Kubernetes manifests The boot-sample-java-config repo has no container image definition or Kubernetes manifests, so the sample can only be run locally via the Maven Spring Boot plugin. Add a minimal but production-shaped set: - Dockerfile: multi-stage Maven 3.9 + Temurin 17 -> JRE Alpine, runs as a non-root user, exposes 8080, HEALTHCHECK against Actuator. - .dockerignore: keep build context small. - docker-compose.yml: single-service compose for quick demo runs, image tag parameterised via ${APP_VERSION:-5.0.0}. The sample uses HSQLDB in-memory so no DB sidecar is needed. - k8s/deployment.yaml: replica=1 RollingUpdate, runAsNonRoot, readOnlyRootFilesystem, drop ALL, resource requests/limits, separate liveness/readiness probes against Actuator. - k8s/service.yaml: ClusterIP exposing port 8080. Application code and properties are unchanged. --- .dockerignore | 13 +++++++++ Dockerfile | 35 +++++++++++++++++++++++ docker-compose.yml | 8 ++++++ k8s/deployment.yaml | 69 +++++++++++++++++++++++++++++++++++++++++++++ k8s/service.yaml | 15 ++++++++++ 5 files changed, 140 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 k8s/deployment.yaml create mode 100644 k8s/service.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b0a99af --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +target/ +*.log +.idea/ +.vscode/ +.git/ +.gitignore +.dockerignore +Dockerfile +docker-compose.yml +k8s/ +README*.md +*.iml +HELP.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8a0782b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# syntax=docker/dockerfile:1.7 +# Multi-stage build for egovframe-boot-sample-java-config. +# Stage 1 builds the executable Spring Boot jar with Maven. +# Stage 2 runs it on a minimal Temurin JRE 17 image as a non-root user. + +# ---------- Build ---------- +FROM maven:3.9-eclipse-temurin-17 AS build +WORKDIR /workspace + +COPY pom.xml ./ +RUN --mount=type=cache,target=/root/.m2 \ + mvn -B -ntp dependency:go-offline + +COPY src ./src +RUN --mount=type=cache,target=/root/.m2 \ + mvn -B -ntp -DskipTests package && \ + cp target/*.jar /workspace/app.jar + +# ---------- Runtime ---------- +FROM eclipse-temurin:17-jre-alpine AS runtime + +RUN addgroup -S app && adduser -S -G app app +USER app:app + +WORKDIR /app +COPY --from=build --chown=app:app /workspace/app.jar /app/app.jar + +ENV JAVA_OPTS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError" + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \ + CMD wget -qO- http://127.0.0.1:8080/actuator/health || exit 1 + +ENTRYPOINT ["sh","-c","exec java $JAVA_OPTS -jar /app/app.jar"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4b42467 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,8 @@ +services: + app: + build: . + image: egovframe-boot-sample-java-config:${APP_VERSION:-5.0.0} + container_name: egov-boot-sample + ports: + - "8080:8080" + restart: unless-stopped diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..72c7665 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: egovframe-boot-sample-java-config + labels: + app.kubernetes.io/name: egovframe-boot-sample-java-config + app.kubernetes.io/part-of: egovframe-sample +spec: + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + selector: + matchLabels: + app.kubernetes.io/name: egovframe-boot-sample-java-config + template: + metadata: + labels: + app.kubernetes.io/name: egovframe-boot-sample-java-config + app.kubernetes.io/part-of: egovframe-sample + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: app + # Replace with your registry/tag, e.g. ghcr.io//egovframe-boot-sample-java-config:5.0.0 + image: egovframe-boot-sample-java-config:5.0.0 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8080 + resources: + requests: + cpu: "200m" + memory: "384Mi" + limits: + cpu: "800m" + memory: "768Mi" + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: http + initialDelaySeconds: 20 + periodSeconds: 10 + failureThreshold: 6 + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: http + initialDelaySeconds: 60 + periodSeconds: 20 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000..68d8edc --- /dev/null +++ b/k8s/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: egovframe-boot-sample-java-config + labels: + app.kubernetes.io/name: egovframe-boot-sample-java-config + app.kubernetes.io/part-of: egovframe-sample +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: egovframe-boot-sample-java-config + ports: + - name: http + port: 8080 + targetPort: http From 34a8fad4d1db3920bd05f131c4afb07c0acf4f89 Mon Sep 17 00:00:00 2001 From: dasomel Date: Thu, 28 May 2026 23:13:10 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=ED=97=AC=EC=8A=A4=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EB=B8=8C=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EC=83=81=ED=99=94=20=EB=B0=8F=20k8s=20?= =?UTF-8?q?=EC=9A=B4=EC=98=81=20=EC=95=88=EB=82=B4=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pom.xml에 spring-boot-starter-actuator 추가 - application.properties에 actuator health 노출 및 readiness/liveness probe 설정 - Dockerfile HEALTHCHECK, k8s readiness/liveness 세 곳 모두 /actuator/health/* 경로 유효화하여 500 → 정상화, CrashLoop 방지 - k8s/README.md 신설: 빌드·배포·접속·헬스체크 확인 절차 안내 --- k8s/README.md | 145 ++++++++++++++++++++++ pom.xml | 4 + src/main/resources/application.properties | 5 + 3 files changed, 154 insertions(+) create mode 100644 k8s/README.md diff --git a/k8s/README.md b/k8s/README.md new file mode 100644 index 0000000..8c60f1c --- /dev/null +++ b/k8s/README.md @@ -0,0 +1,145 @@ +# egovframe-boot-sample-java-config — 컨테이너/쿠버네티스 운영 안내 + +## 사전 요구 사항 + +| 도구 | 최소 버전 | 용도 | +|------|-----------|------| +| Docker | 24 이상 | 이미지 빌드 및 Compose 실행 | +| kubectl | 1.27 이상 | 쿠버네티스 배포 | +| (선택) minikube / kind | — | 로컬 클러스터 | + +--- + +## 1. 이미지 빌드 + +```bash +# 프로젝트 루트에서 실행 +docker build -t egovframe-boot-sample-java-config:5.0.0 . +``` + +멀티 스테이지 빌드로 Maven 컴파일 → 실행 이미지(JRE 17 Alpine)를 순서대로 생성합니다. +빌드 캐시(`/root/.m2`)를 활용하려면 BuildKit을 활성화(`DOCKER_BUILDKIT=1`)하세요. + +--- + +## 2. Docker Compose로 로컬 실행 + +```bash +# 실행 +docker compose up -d + +# 로그 확인 +docker compose logs -f + +# 중지 및 볼륨 삭제 +docker compose down -v +``` + +접속: http://localhost:8080 + +헬스 확인: +```bash +curl -s http://localhost:8080/actuator/health +# {"status":"UP"} +``` + +--- + +## 3. 쿠버네티스 배포 + +### 3-1. 이미지 레지스트리 설정 + +`k8s/deployment.yaml`의 `image` 값을 실제 레지스트리 경로로 변경합니다. + +```yaml +# 예시 +image: ghcr.io//egovframe-boot-sample-java-config:5.0.0 +``` + +로컬 클러스터(minikube)를 사용하는 경우 이미지를 직접 로드합니다. + +```bash +minikube image load egovframe-boot-sample-java-config:5.0.0 +``` + +### 3-2. 매니페스트 적용 + +```bash +kubectl apply -f k8s/service.yaml +kubectl apply -f k8s/deployment.yaml +``` + +### 3-3. 배포 상태 확인 + +```bash +# Pod 상태 +kubectl get pods -l app.kubernetes.io/name=egovframe-boot-sample-java-config + +# Deployment 롤아웃 완료 대기 +kubectl rollout status deployment/egovframe-boot-sample-java-config +``` + +--- + +## 4. 접속 + +### NodePort (service.yaml 기준) + +```bash +# minikube 사용 시 +minikube service egovframe-boot-sample-java-config --url +``` + +일반 클러스터에서는 NodePort 값(기본 30080)으로 접속합니다. + +``` +http://<노드 IP>:30080 +``` + +### 포트 포워딩 (개발/테스트 용) + +```bash +kubectl port-forward service/egovframe-boot-sample-java-config 8080:8080 +``` + +접속: http://localhost:8080 + +--- + +## 5. 헬스체크 확인 + +애플리케이션이 `/actuator/health` 엔드포인트를 제공합니다. + +```bash +# 전체 상태 +curl -s http://localhost:8080/actuator/health + +# readiness +curl -s http://localhost:8080/actuator/health/readiness + +# liveness +curl -s http://localhost:8080/actuator/health/liveness +``` + +정상 응답 예시: +```json +{"status":"UP"} +``` + +k8s Probe 설정 요약: + +| Probe | 경로 | initialDelaySeconds | periodSeconds | +|-------|------|---------------------|---------------| +| readiness | /actuator/health/readiness | 20 | 10 | +| liveness | /actuator/health/liveness | 60 | 20 | + +Pod가 `Running` 상태이고 `READY` 열이 `1/1`이면 정상입니다. + +--- + +## 6. 삭제 + +```bash +kubectl delete -f k8s/deployment.yaml +kubectl delete -f k8s/service.yaml +``` diff --git a/pom.xml b/pom.xml index 217723c..633f362 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,10 @@ org.projectlombok lombok + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot spring-boot-starter-test diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3b59fc5..24e49c4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,10 @@ server.port=8080 +# Actuator 헬스 엔드포인트 설정 (Docker HEALTHCHECK / k8s probe 대응) +management.endpoints.web.exposure.include=health +management.endpoint.health.show-details=never +management.endpoint.health.probes.enabled=true + # \uc218\ub3d9 Bean\uc774 \uc790\ub3d9 Bean\uc744 \uc624\ubc84\ub77c\uc774\ub529\ud558\uac8c \uc124\uc815 spring.main.allow-bean-definition-overriding=true From ce81b0a9c289849b922d211a26c2df6540a6ad55 Mon Sep 17 00:00:00 2001 From: dasomel Date: Fri, 29 May 2026 22:10:18 +0900 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20k8s=20README=20=EC=A0=91=EC=86=8D?= =?UTF-8?q?=20=EC=95=88=EB=82=B4=EB=A5=BC=20service.yaml=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EA=B0=92=EA=B3=BC=20=EC=9D=BC=EC=B9=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- k8s/README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/k8s/README.md b/k8s/README.md index 8c60f1c..51e093c 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -83,18 +83,9 @@ kubectl rollout status deployment/egovframe-boot-sample-java-config ## 4. 접속 -### NodePort (service.yaml 기준) +### ClusterIP (service.yaml 기준) -```bash -# minikube 사용 시 -minikube service egovframe-boot-sample-java-config --url -``` - -일반 클러스터에서는 NodePort 값(기본 30080)으로 접속합니다. - -``` -http://<노드 IP>:30080 -``` +service.yaml은 `type: ClusterIP`로 설정되어 있으며, 클러스터 내부에서 `egovframe-boot-sample-java-config:8080`으로 접근할 수 있습니다. ### 포트 포워딩 (개발/테스트 용)