애플리케이션이 로컬에서는 잘 돌아가는데 운영 환경에 올리면 설정값 하나 때문에 터진 경험, 다들 있지 않을까? 설정을 코드에서 분리하는 건 당연한데, "어떻게" 분리하느냐가 의외로 까다롭습니다.

Quarkus 설정의 기본 구조

Quarkus의 설정 시스템은 MicroProfile Config 표준을 구현한 SmallRye Config 위에 구축되어 있습니다.

한 줄 정의: Quarkus 설정은 SmallRye Config 기반으로, 다양한 Config Source에서 값을 읽어 우선순위에 따라 병합하는 시스템입니다.

기본 설정 파일은 src/main/resources/application.properties이며, YAML 형식도 지원합니다.

PROPERTIES
# application.properties
quarkus.http.port=8080
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydb
quarkus.datasource.username=dev_user
quarkus.datasource.password=dev_pass
YAML
# application.yaml (quarkus-config-yaml 익스텐션 필요)
quarkus:
  http:
    port: 8080
  datasource:
    db-kind: postgresql
    jdbc:
      url: jdbc:postgresql://localhost:5432/mydb
    username: dev_user
    password: dev_pass

YAML을 사용하려면 quarkus-config-yaml 익스텐션을 추가해야 합니다. properties와 YAML이 동시에 존재하면 ** 둘 다 읽히지만 **, 같은 키가 있을 경우 properties가 우선합니다.


@ConfigProperty — 개별 값 주입

가장 기본적인 설정값 주입 방식입니다. Spring의 @Value와 유사합니다.

JAVA
@ApplicationScoped
public class GreetingService {

    @ConfigProperty(name = "greeting.message")
    String message;

    @ConfigProperty(name = "greeting.suffix", defaultValue = "!")
    String suffix;

    // Optional로 감싸면 값이 없어도 에러가 나지 않음
    @ConfigProperty(name = "greeting.prefix")
    Optional<String> prefix;

    public String greet(String name) {
        return prefix.orElse("") + message + " " + name + suffix;
    }
}

@ConfigProperty의 특징을 정리하면 이렇습니다.

  • name: properties 파일에서 읽어올 키 이름
  • defaultValue: 값이 없을 때 사용할 기본값 (문자열로 지정)
  • Optional<T>로 감싸면 값이 없어도 시작 시 에러 없음
  • 값이 없고 defaultValue도 없고 Optional도 아니면 ** 빌드 시 에러** 발생

공부하다 보니 Spring의 @Value와 거의 동일한데, 한 가지 차이가 있었습니다. Quarkus는 필수 설정값이 빠져 있으면 ** 빌드 타임에** 잡아줍니다. Spring은 런타임 시작 시에야 IllegalArgumentException이 터지죠.


@ConfigMapping — 타입 안전한 설정 바인딩

설정값이 여러 개이고 구조가 있다면 @ConfigMapping이 훨씬 깔끔합니다. Spring의 @ConfigurationProperties에 대응하는 기능입니다.

JAVA
@ConfigMapping(prefix = "app.database")
public interface DatabaseConfig {

    String host();

    int port();

    @WithDefault("mydb")
    String name();

    // 중첩 설정도 인터페이스로 표현
    Pool pool();

    interface Pool {
        @WithDefault("5")
        int minSize();

        @WithDefault("20")
        int maxSize();

        @WithDefault("PT30S")
        Duration idleTimeout();
    }
}
PROPERTIES
# application.properties
app.database.host=localhost
app.database.port=5432
app.database.name=mydb
app.database.pool.min-size=10
app.database.pool.max-size=50
app.database.pool.idle-timeout=PT1M

사용할 때는 CDI 주입으로 바로 가져옵니다.

JAVA
@ApplicationScoped
public class DatabaseService {

    @Inject
    DatabaseConfig config;

    public String getJdbcUrl() {
        return String.format("jdbc:postgresql://%s:%d/%s",
            config.host(), config.port(), config.name());
    }

    public void logPoolConfig() {
        Log.infof("풀 크기: %d ~ %d, 유휴 타임아웃: %s",
            config.pool().minSize(),
            config.pool().maxSize(),
            config.pool().idleTimeout());
    }
}

@ConfigProperty vs @ConfigMapping 선택 기준

구분@ConfigProperty@ConfigMapping
형태필드 주입인터페이스
적합한 경우설정값 1~2개구조화된 설정 그룹
타입 변환문자열 기반메서드 반환 타입으로 자동 변환
중첩 구조불편함중첩 인터페이스로 깔끔하게 표현
검증제한적Bean Validation 적용 가능
Spring 대응@Value@ConfigurationProperties

설정값이 3개 이상이고 논리적으로 한 그룹이라면 @ConfigMapping을 쓰는 게 좋습니다. 인터페이스라서 구현 없이 선언만 하면 되고, Quarkus가 빌드 타임에 구현체를 생성해줍니다.


프로파일별 설정 분리

Quarkus는 %{profile} 접두사로 프로파일별 설정을 분리합니다.

PROPERTIES
# 공통 설정
quarkus.http.port=8080
app.greeting=Hello

# 개발 환경 전용
%dev.quarkus.http.port=8080
%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/devdb
%dev.quarkus.log.level=DEBUG

# 테스트 환경 전용
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb
%test.quarkus.log.level=INFO

# 운영 환경 전용
%prod.quarkus.http.port=80
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://prod-db:5432/proddb
%prod.quarkus.log.level=WARN

Quarkus에는 세 가지 기본 프로파일이 있습니다.

  • dev: quarkus dev로 실행할 때 자동 활성화
  • test: 테스트 실행 시 자동 활성화
  • prod: 기본 프로파일 (dev, test가 아니면 prod)

커스텀 프로파일

기본 세 가지 외에 커스텀 프로파일도 만들 수 있습니다.

PROPERTIES
# staging 프로파일
%staging.quarkus.datasource.jdbc.url=jdbc:postgresql://staging-db:5432/stagingdb
%staging.quarkus.log.level=INFO

활성화 방법은 여러 가지입니다.

BASH
# 시스템 프로퍼티
java -Dquarkus.profile=staging -jar app.jar

# 환경 변수
QUARKUS_PROFILE=staging java -jar app.jar

# dev 모드에서
quarkus dev -Dquarkus.profile=staging

Spring Boot의 spring.profiles.active와 비슷하지만, Quarkus는 properties 파일 안에서 %프로파일.키=값 형태로 한 파일에 모든 프로파일 설정을 넣을 수 있어서 파일이 분산되지 않습니다. 물론 application-{profile}.properties처럼 파일을 분리하는 것도 가능합니다.


환경 변수 매핑 규칙

Quarkus는 12-Factor App 원칙에 따라 환경 변수를 최우선으로 지원합니다. 매핑 규칙은 이렇습니다.

PLAINTEXT
properties 키                    → 환경 변수
quarkus.http.port               → QUARKUS_HTTP_PORT
quarkus.datasource.jdbc.url     → QUARKUS_DATASOURCE_JDBC_URL
app.database.pool.max-size      → APP_DATABASE_POOL_MAX_SIZE

변환 규칙을 정리하면 다음과 같습니다.

  1. 모든 문자를 ** 대문자 **로 변환
  2. 점(.)과 하이픈(-)을 ** 언더스코어(_)**로 변환
  3. 그게 끝입니다
BASH
# Docker나 Kubernetes에서 이렇게 설정하면 됨
export QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://prod-db:5432/proddb
export QUARKUS_DATASOURCE_USERNAME=prod_user
export QUARKUS_DATASOURCE_PASSWORD=secret123
export APP_DATABASE_POOL_MAX_SIZE=100

SmallRye Config Source 우선순위

여러 소스에서 같은 키가 정의되면 **ordinal이 높은 쪽이 이깁니다 **.

순위Config SourceOrdinal
1시스템 프로퍼티 (-Dkey=value)400
2환경 변수300
3.env 파일 (프로젝트 루트)295
4application.properties (classpath)250
5MicroProfile Config 기본값100

이 우선순위가 중요한 이유는, ** 코드나 설정 파일을 수정하지 않고도 환경 변수만으로 설정을 오버라이드할 수 있다 **는 점입니다.

PROPERTIES
# application.properties에 기본값 설정
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/devdb
BASH
# 운영 환경에서는 환경 변수가 덮어쓴다
export QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://prod-db:5432/proddb

Spring Boot도 비슷한 우선순위를 가지고 있지만, Quarkus의 우선순위 체계는 MicroProfile Config 표준을 따르기 때문에 다른 MicroProfile 구현체로 갈아타더라도 동일한 방식으로 동작합니다.


12-Factor App과 Quarkus 설정 전략

12-Factor App의 세 번째 원칙은 "설정을 환경에 저장하라(Store config in the environment)" 입니다. Quarkus의 설정 시스템이 이 원칙과 어떻게 맞닿아 있는지 정리해보겠습니다.

코드와 설정의 완전한 분리

JAVA
// 코드에는 설정값이 하드코딩되어 있지 않음
@ConfigMapping(prefix = "app.external-api")
public interface ExternalApiConfig {
    String baseUrl();
    String apiKey();
    @WithDefault("PT5S")
    Duration timeout();
}
PROPERTIES
# 개발 환경: application.properties
app.external-api.base-url=http://localhost:8081
app.external-api.api-key=dev-key-12345
BASH
# 운영 환경: 환경 변수로 주입
APP_EXTERNAL_API_BASE_URL=https://api.production.com
APP_EXTERNAL_API_API_KEY=prod-secret-key
APP_EXTERNAL_API_TIMEOUT=PT10S

시크릿 관리

민감한 정보는 properties 파일에 넣으면 안 됩니다. Quarkus는 여러 시크릿 관리 방법을 지원합니다.

PROPERTIES
# 환경 변수 참조 (가장 간단)
quarkus.datasource.password=${DB_PASSWORD}

# Vault 연동 (quarkus-vault 익스텐션)
quarkus.vault.url=https://vault.example.com
quarkus.datasource.password=${vault:secret/data/myapp#db-password}

# Kubernetes Secret 매핑
# configmap/secret을 환경 변수로 마운트하면 자동으로 읽힘

공부하면서 12-Factor 원칙이 Quarkus 설정 구조에 거의 그대로 반영되어 있다는 걸 느꼈습니다. "설정 파일에 기본값을 넣고, 환경 변수로 오버라이드한다"는 패턴만 기억하면 대부분의 환경 분리가 해결됩니다.


실전 예제: 다중 환경 설정 구성

실제 프로젝트에서 자주 쓰는 패턴을 종합적으로 정리합니다.

PROPERTIES
# === 공통 설정 ===
quarkus.application.name=my-service
quarkus.http.port=8080

# === 데이터소스 ===
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydb
quarkus.datasource.username=app_user
quarkus.datasource.password=${DB_PASSWORD:devpass}

# === 외부 API ===
app.payment.base-url=http://localhost:9090
app.payment.api-key=${PAYMENT_API_KEY:test-key}
app.payment.timeout=PT5S

# === 개발 환경 ===
%dev.quarkus.hibernate-orm.database.generation=drop-and-create
%dev.quarkus.log.category."io.quarkus".level=DEBUG
%dev.quarkus.devservices.enabled=true

# === 테스트 환경 ===
%test.quarkus.datasource.jdbc.url=jdbc:h2:mem:testdb
%test.quarkus.datasource.db-kind=h2
%test.app.payment.base-url=http://localhost:${quarkus.http.test-port}/mock-payment

# === 운영 환경 ===
%prod.quarkus.datasource.jdbc.min-size=10
%prod.quarkus.datasource.jdbc.max-size=50
%prod.quarkus.http.port=80
%prod.quarkus.log.level=WARN

이 설정을 읽는 Config Mapping 인터페이스는 이렇게 작성합니다.

JAVA
@ConfigMapping(prefix = "app.payment")
public interface PaymentConfig {
    @WithName("base-url")
    String baseUrl();

    @WithName("api-key")
    String apiKey();

    @WithDefault("PT5S")
    Duration timeout();
}
JAVA
@ApplicationScoped
public class PaymentClient {

    @Inject
    PaymentConfig config;

    @Inject
    @RestClient
    PaymentApi paymentApi;

    public PaymentResponse charge(PaymentRequest request) {
        Log.infof("결제 API 호출: %s (타임아웃: %s)",
            config.baseUrl(), config.timeout());
        return paymentApi.charge(request);
    }
}

정리

Quarkus 설정 시스템의 핵심을 요약하면 다음과 같습니다.

  • @ConfigProperty 는 개별 값 주입에, @ConfigMapping 은 구조화된 설정 그룹에 사용
  • %dev, %test, %prod 접두사로 한 파일에서 환경별 설정을 분리할 수 있음
  • 환경 변수는 점 → 언더스코어, 소문자 → 대문자로 매핑되며, 설정 파일보다 우선순위가 높음
  • **12-Factor 원칙 **: 기본값은 properties에, 환경별 차이는 환경 변수로 오버라이드
  • 시크릿은 절대 코드에 넣지 말고, 환경 변수 또는 Vault 같은 시크릿 관리 도구를 사용

Quarkus의 설정 시스템은 Spring Boot와 개념적으로 매우 유사합니다. 가장 큰 차이는 MicroProfile Config 표준을 따른다는 점과, 빌드 타임에 설정값 누락을 잡아준다는 점입니다. 이 두 가지만 기억하면 기존 Spring 경험을 그대로 활용할 수 있습니다.

댓글 로딩 중...