Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
target/
*.log
.idea/
.vscode/
.git/
.gitignore
.dockerignore
Dockerfile
docker-compose.yml
k8s/
README*.md
*.iml
HELP.md
35 changes: 35 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
8 changes: 8 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
136 changes: 136 additions & 0 deletions k8s/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# 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/<org>/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. 접속

### ClusterIP (service.yaml 기준)

service.yaml은 `type: ClusterIP`로 설정되어 있으며, 클러스터 내부에서 `egovframe-boot-sample-java-config:8080`으로 접근할 수 있습니다.

### 포트 포워딩 (개발/테스트 용)

```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
```
69 changes: 69 additions & 0 deletions k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -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/<org>/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: {}
15 changes: 15 additions & 0 deletions k8s/service.yaml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Loading