본문으로 건너뛰기

DevOps Integration

1. 개요

ElastiCORE 프로젝트는 일반 Java 프로젝트와 달리 빌드 이전에 코드 생성 단계가 반드시 필요합니다. DSL 파일(.ecore)을 기반으로 Java 소스 코드를 생성하는 elcore Gradle 태스크가 컴파일보다 먼저 실행되어야 합니다.

표준 빌드 파이프라인은 다음 순서로 구성됩니다.

코드 체크아웃 → DSL 검증 → 코드 생성(elcore) → 컴파일 → 테스트 → 패키지 → 배포
정보

elcore 태스크는 Gradle의 compileJava 태스크에 자동으로 의존성이 연결되어 있지 않습니다. CI/CD 파이프라인이나 Dockerfile에서 명시적으로 elcore를 먼저 실행해야 합니다.


2. Gradle 빌드 명령어

로컬 개발 및 CI 환경에서 공통으로 사용하는 Gradle 명령어입니다.

# DSL에서 Java 코드만 생성
./gradlew elcore

# 코드 생성 후 전체 빌드
./gradlew elcore build

# 코드 생성, 빌드, 테스트 전체 실행
./gradlew elcore build test

# 프로덕션 배포용 실행 가능한 JAR 패키징
./gradlew elcore bootJar

# 클린 후 전체 재빌드 (캐시 문제 발생 시)
./gradlew clean elcore build
데몬 비활성화

CI 환경에서는 Gradle 데몬이 필요 없으므로 --no-daemon 플래그를 추가하면 메모리 사용량을 줄일 수 있습니다.

./gradlew --no-daemon elcore bootJar

3. GitHub Actions

.github/workflows/build.yml 파일을 프로젝트 루트에 생성하여 GitHub Actions 파이프라인을 구성합니다.

.github/workflows/build.yml
name: CI/CD Pipeline

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Generate code with ElastiCORE
run: ./gradlew --no-daemon elcore

- name: Build project
run: ./gradlew --no-daemon build -x test

- name: Run tests
run: ./gradlew --no-daemon test

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: build/reports/tests/

docker:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'

steps:
- name: Checkout source code
uses: actions/checkout@v4

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/my-app:latest
${{ secrets.DOCKER_USERNAME }}/my-app:${{ github.sha }}
시크릿 설정 필요

Docker Hub 연동을 위해 GitHub 저장소의 Settings → Secrets and variables → Actions에서 DOCKER_USERNAMEDOCKER_PASSWORD를 등록해야 합니다.


4. GitLab CI/CD

.gitlab-ci.yml 파일을 프로젝트 루트에 생성합니다. generate, build, test, deploy 4개의 스테이지로 구성합니다.

.gitlab-ci.yml
stages:
- generate
- build
- test
- deploy

variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"

cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/
- build/

# 스테이지 1: DSL → Java 코드 생성
generate:
stage: generate
image: gradle:8.5-jdk17
script:
- gradle elcore
artifacts:
paths:
- build/generated/
expire_in: 1 hour

# 스테이지 2: 컴파일 및 JAR 패키징
build:
stage: build
image: gradle:8.5-jdk17
script:
- gradle build -x test
artifacts:
paths:
- build/libs/*.jar
expire_in: 1 day

# 스테이지 3: 단위 테스트 및 통합 테스트
test:
stage: test
image: gradle:8.5-jdk17
script:
- gradle test
artifacts:
when: always
reports:
junit: build/test-results/test/**/TEST-*.xml
paths:
- build/reports/tests/
expire_in: 7 days

# 스테이지 4: 운영 환경 배포 (main 브랜치만)
deploy:
stage: deploy
image: docker:24
services:
- docker:24-dind
only:
- main
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest

5. Docker

Dockerfile (멀티 스테이지 빌드)

멀티 스테이지 빌드를 사용하여 최종 이미지 크기를 최소화합니다. 빌드 스테이지에서 elcore로 코드를 생성하고 JAR를 패키징한 뒤, 런타임 스테이지에서는 JRE만 포함된 가벼운 이미지를 사용합니다.

Dockerfile
# ─── Stage 1: Build ─────────────────────────────────────────────────────────
FROM gradle:8.5-jdk17 AS builder

WORKDIR /app

# 의존성 캐시 최적화: Gradle 래퍼와 빌드 파일을 먼저 복사
COPY gradlew gradlew
COPY gradle/ gradle/
COPY build.gradle settings.gradle ./

RUN chmod +x gradlew && ./gradlew dependencies --no-daemon || true

# 전체 소스 복사
COPY . .

# ElastiCORE 코드 생성 후 JAR 패키징
RUN ./gradlew --no-daemon elcore bootJar

# ─── Stage 2: Runtime ────────────────────────────────────────────────────────
FROM eclipse-temurin:17-jre-jammy

WORKDIR /app

# 보안을 위해 non-root 사용자 생성
RUN groupadd --system appgroup && useradd --system --gid appgroup appuser

# 빌드 스테이지에서 JAR만 복사
COPY --from=builder /app/build/libs/*.jar app.jar

# 파일 소유권 변경
RUN chown appuser:appgroup app.jar

USER appuser

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

이미지 빌드 및 실행

# 이미지 빌드
docker build -t my-elasticore-app:latest .

# 컨테이너 실행 (환경 변수로 설정 주입)
docker run -d \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_HOST=postgres \
-e DB_PASSWORD=secret \
--name my-app \
my-elasticore-app:latest

6. Docker Compose (개발 환경)

로컬 개발 환경에서 애플리케이션, PostgreSQL, Redis를 함께 실행하는 구성입니다.

docker-compose.yml
version: '3.8'

services:
app:
build: .
container_name: elasticore-app
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: dev
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: appdb
DB_USERNAME: appuser
DB_PASSWORD: apppassword
REDIS_HOST: redis
REDIS_PORT: 6379
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
restart: unless-stopped

postgres:
image: postgres:16-alpine
container_name: elasticore-postgres
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppassword
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network

redis:
image: redis:7-alpine
container_name: elasticore-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
networks:
- app-network

volumes:
postgres-data:
redis-data:

networks:
app-network:
driver: bridge
# 개발 환경 시작 (백그라운드)
docker compose up -d

# 로그 확인
docker compose logs -f app

# 환경 종료 및 볼륨 삭제
docker compose down -v
로컬 개발 팁

app 서비스를 제외하고 인프라만 실행한 뒤, IDE에서 직접 애플리케이션을 실행하면 코드 변경 사항을 즉시 반영할 수 있습니다.

# PostgreSQL과 Redis만 실행
docker compose up -d postgres redis

7. Kubernetes

Deployment

k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: elasticore-app
namespace: production
labels:
app: elasticore-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: elasticore-app
template:
metadata:
labels:
app: elasticore-app
spec:
containers:
- name: app
image: my-registry/elasticore-app:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: db.host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: elasticore-app-svc
namespace: production
spec:
selector:
app: elasticore-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP

ConfigMap (환경별 데이터소스 설정)

ElastiCORE의 env.yml에 정의된 datasource 설정을 환경별 ConfigMap으로 분리합니다.

k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
db.host: "postgres-service.production.svc.cluster.local"
db.port: "5432"
db.name: "appdb"
redis.host: "redis-service.production.svc.cluster.local"
redis.port: "6379"
spring.datasource.hikari.maximum-pool-size: "20"

Secret (민감 정보)

k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
stringData:
db-password: "your-secure-password"
db-username: "appuser"
시크릿 관리

Secret 리소스를 Git에 평문으로 커밋하지 마십시오. Sealed Secrets 또는 External Secrets Operator를 사용하여 안전하게 관리하는 것을 권장합니다.

env.yml 환경별 분리 전략

ElastiCORE env.yml의 설정값을 Kubernetes 환경에서는 ConfigMap과 Secret으로 주입하는 방식을 권장합니다.

src/main/resources/env.yml (예시)
datasource:
main:
driver: postgresql
host: ${DB_HOST}
port: ${DB_PORT:5432}
database: ${DB_NAME}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
pool:
maximumPoolSize: ${HIKARI_MAX_POOL_SIZE:10}

환경 변수는 Kubernetes Deployment의 env 또는 envFrom 필드를 통해 ConfigMap과 Secret에서 자동으로 주입됩니다.


8. 빌드 시 주의사항

elcore 태스크 실행 순서

핵심 주의사항

elcore 태스크는 반드시 compileJava 이전에 실행해야 합니다. CI/CD 파이프라인, Dockerfile, 스크립트 어디서든 ./gradlew elcore가 빌드보다 먼저 호출되어야 합니다.

# 올바른 순서
./gradlew elcore build

# 잘못된 순서 (생성된 코드 없이 컴파일 → 빌드 실패)
./gradlew build

DSL 변경 시 재생성 필요

.ecore DSL 파일을 수정한 경우 반드시 elcore 태스크를 다시 실행하여 Java 코드를 재생성해야 합니다. 생성된 코드가 최신 DSL 정의와 일치하지 않으면 런타임 오류가 발생할 수 있습니다.

# DSL 변경 후 클린 재생성
./gradlew clean elcore build

생성된 코드의 Git 제외 설정

생성된 Java 소스 코드는 DSL 파일로부터 언제든 재생성 가능하므로 Git 저장소에 포함하지 않는 것을 권장합니다.

.gitignore
# ElastiCORE 생성 코드
build/generated/
src/main/generated/

# Gradle 빌드 산출물
build/
.gradle/
생성 코드를 Git에 포함해야 하는 경우

일부 팀에서는 코드 리뷰나 감사 목적으로 생성된 코드를 Git에 포함하기도 합니다. 이 경우 .gitignore에서 해당 경로를 제외하고, PR에서 DSL 변경과 생성 코드 변경을 함께 리뷰하는 프로세스를 수립하십시오.