Spring Boot Profiles

Profile이 해결하는 문제: 환경별 설정 분리

개발, 스테이징, 프로덕션 환경은 데이터베이스 주소, 로그 레벨, 외부 API 엔드포인트가 다르다. 코드를 환경마다 수정하거나 if-else로 분기하면 배포 실수와 설정 누락이 발생한다. Spring Boot의 Profile은 환경별 설정을 파일 단위로 분리하고, 활성화된 Profile에 따라 자동으로 올바른 설정을 로드하는 메커니즘이다.

Spring Boot 공식 문서는 Profile을 “애플리케이션 구성의 일부를 분리하여 특정 환경에서만 사용할 수 있게 하는 방법”으로 정의한다. 이 글에서는 Profile 활성화 방법, 설정 파일 로딩 순서, @Profile 조건부 빈, 그리고 Kubernetes 환경에서의 운영 패턴까지 공식 문서 기반으로 정리한다.

Profile 활성화: 5가지 방법과 우선순위

Spring Boot에서 Profile을 활성화하는 방법은 여러 가지다. 공식 문서에 따르면 우선순위가 높은 것이 낮은 것을 덮어쓴다.

우선순위 방법 예시 사용 시점
1 (최고) 커맨드라인 인자 --spring.profiles.active=prod 로컬 테스트, 디버깅
2 환경 변수 SPRING_PROFILES_ACTIVE=prod Docker, Kubernetes
3 JVM 시스템 프로퍼티 -Dspring.profiles.active=prod IDE 실행 설정
4 application.properties spring.profiles.active=dev 기본 개발 프로파일
5 @SpringBootApplication 코드 SpringApplication.setAdditionalProfiles() 테스트, 특수 부트스트랩
# 환경 변수로 활성화 (Docker/Kubernetes에서 가장 흔한 방식)
SPRING_PROFILES_ACTIVE=prod java -jar app.jar

# 커맨드라인 인자로 활성화
java -jar app.jar --spring.profiles.active=prod

# 복수 Profile 활성화 (쉼표 구분)
SPRING_PROFILES_ACTIVE=prod,mysql,aws java -jar app.jar

Profile별 설정 파일: 네이밍 규칙과 로딩 순서

Spring Boot는 application-{profile}.properties(또는 .yml) 패턴으로 Profile별 설정 파일을 자동 로드한다.

# 파일 구조 예시
src/main/resources/
├── application.properties           # 공통 설정 (항상 로드)
├── application-dev.properties       # dev Profile
├── application-staging.properties   # staging Profile
├── application-prod.properties      # prod Profile
└── application-mysql.properties     # 기능별 Profile
# application.properties — 공통 설정
spring.application.name=my-service
server.shutdown=graceful
management.endpoints.web.exposure.include=health,info

# application-dev.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.jpa.show-sql=true
logging.level.root=DEBUG

# application-prod.properties
spring.datasource.url=jdbc:mysql://prod-db:3306/mydb
spring.jpa.show-sql=false
logging.level.root=WARN
server.port=8080

로딩 순서와 덮어쓰기 규칙

공식 문서에 따르면 설정은 다음 순서로 로드되며, 나중에 로드된 값이 이전 값을 덮어쓴다.

  1. application.properties (공통)
  2. application-{profile}.properties (Profile별)
  3. 환경 변수
  4. 커맨드라인 인자

복수 Profile이 활성화된 경우(prod,mysql), 마지막에 선언된 Profile이 이긴다. 예를 들어 application-prod.propertiesapplication-mysql.properties에 같은 키가 있으면 mysql의 값이 사용된다.

YAML 멀티 도큐먼트: 단일 파일에서 Profile 분리

Spring Boot 2.4+부터 application.yml 하나에서 --- 구분자와 spring.config.activate.on-profile로 Profile별 설정을 분리할 수 있다.

# application.yml — 멀티 도큐먼트 방식
spring:
  application:
    name: my-service

server:
  shutdown: graceful

---
# dev Profile
spring:
  config:
    activate:
      on-profile: dev
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
  jpa:
    show-sql: true

logging:
  level:
    root: DEBUG

---
# prod Profile
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://prod-db:3306/mydb
  jpa:
    show-sql: false

logging:
  level:
    root: WARN

주의: spring.config.activate.on-profile은 Spring Boot 2.4에서 도입되었다. 이전 버전의 spring.profiles 키는 2.4부터 deprecated되었고 Spring Boot 3.x에서는 에러를 발생시킨다.

spring.profiles.group: Profile 그룹화

Spring Boot 2.4+에서 도입된 spring.profiles.group은 하나의 Profile을 활성화하면 여러 하위 Profile을 자동으로 함께 활성화하는 기능이다.

# application.properties
spring.profiles.group.prod=proddb,prodcloud,prodsecurity
spring.profiles.group.dev=devdb,devtools

# prod를 활성화하면 proddb, prodcloud, prodsecurity도 함께 활성화
# SPRING_PROFILES_ACTIVE=prod → 실제 활성: prod, proddb, prodcloud, prodsecurity

이 기능으로 관심사별(DB, 클라우드, 보안) 설정 파일을 분리하면서도 운영자는 prod 하나만 지정하면 된다.

@Profile 어노테이션: 조건부 빈 등록

설정 값뿐 아니라 특정 Profile에서만 등록되는 빈을 만들 수 있다. @Profile 어노테이션은 @Component, @Configuration, @Bean 메서드에 사용할 수 있다.

// 인터페이스
public interface StorageService {
    void store(byte[] data, String key);
}

// dev: 로컬 파일시스템 저장
@Service
@Profile("dev")
public class LocalStorageService implements StorageService {
    @Override
    public void store(byte[] data, String key) {
        // /tmp에 파일 저장
    }
}

// prod: S3 저장
@Service
@Profile("prod")
public class S3StorageService implements StorageService {
    private final S3Client s3Client;

    public S3StorageService(S3Client s3Client) {
        this.s3Client = s3Client;
    }

    @Override
    public void store(byte[] data, String key) {
        // S3 업로드
    }
}

@Profile의 논리 연산

// AND: prod이면서 mysql일 때만 등록
// → 두 Profile 모두 활성화 필요
@Profile({"prod & mysql"})

// OR: dev 또는 staging일 때 등록
@Profile({"dev", "staging"})

// NOT: prod가 아닐 때 등록
@Profile("!prod")

// 복합: prod가 아니면서 test도 아닐 때
@Profile({"!prod", "!test"})  // !prod OR !test (주의: AND가 아님)
@Profile("!prod & !test")     // !prod AND !test
표현식 의미 주의사항
@Profile("dev") dev 활성 시 등록
@Profile({"dev", "test"}) dev 또는 test (OR) 배열 요소는 OR
@Profile("prod & mysql") prod 그리고 mysql (AND) 단일 문자열 내 &
@Profile("!prod") prod가 아닐 때 기본 Profile(아무것도 안 설정)에도 매칭

spring.profiles.default: 아무 Profile도 없을 때

# 아무 Profile도 활성화하지 않으면 "default" Profile이 활성화됨
# application-default.properties가 로드됨

# 기본 Profile 이름을 변경하려면:
spring.profiles.default=local

# 또는 환경 변수로:
SPRING_PROFILES_DEFAULT=local

공식 문서에 따르면, spring.profiles.active가 하나라도 설정되면 default Profile은 비활성화된다. 즉 defaultprod가 동시에 활성화되지 않는다.

Kubernetes 환경에서의 Profile 운영 패턴

# Kubernetes Deployment에서 환경 변수로 Profile 주입
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
spec:
  template:
    spec:
      containers:
      - name: app
        image: my-service:1.0
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod,k8s"
        # 또는 ConfigMap에서 주입
        envFrom:
        - configMapRef:
            name: app-config
# ConfigMap으로 Profile별 설정 외부화
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  SPRING_PROFILES_ACTIVE: "prod"
  SPRING_DATASOURCE_URL: "jdbc:mysql://mysql-service:3306/mydb"
전략 설정 위치 장점 단점
JAR 내장 Profile 파일 application-prod.yml 단순, 버전 관리됨 비밀 정보 포함 불가, 변경 시 재배포
환경 변수 (ConfigMap) K8s ConfigMap 이미지 불변, 환경별 독립 키 이름 규칙 준수 필요
환경 변수 (Secret) K8s Secret 비밀 정보 분리 base64일 뿐 암호화 아님
볼륨 마운트 외부 파일 ConfigMap → 파일 복잡한 YAML 구조 가능 spring.config.import 설정 필요

실무 권장: JAR 안에는 공통 설정과 개발용 기본값만 넣고, 프로덕션 민감 정보(DB 비밀번호, API 키)는 Kubernetes Secret으로 환경 변수 주입한다. Spring Boot의 Relaxed Binding 덕분에 SPRING_DATASOURCE_PASSWORD 환경 변수가 spring.datasource.password로 자동 매핑된다.

테스트에서의 Profile: @ActiveProfiles

// 통합 테스트에서 test Profile 활성화
@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Test
    void shouldCreateUser() {
        // application-test.properties의 설정(H2 DB 등)이 적용됨
    }
}

// 복수 Profile
@SpringBootTest
@ActiveProfiles({"test", "mock-external"})
class ExternalApiTest {
    // test + mock-external 모두 활성화
}
# application-test.properties — 테스트 전용 설정
spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG

실전 체크리스트: Profile 설계 7단계

  1. 환경 식별자 통일 — dev/staging/prod 이름을 팀 전체에서 통일하고 README에 문서화
  2. 공통 설정은 application.properties에 — Profile 파일에는 환경마다 다른 값만 넣는다
  3. 민감 정보는 설정 파일에 넣지 않기 — K8s Secret, Vault 등 외부에서 환경 변수로 주입
  4. Profile 그룹으로 관심사 분리spring.profiles.group.prod=proddb,prodsecurity
  5. 기본 Profile 설정application-default.properties로 Profile 미지정 시 안전한 기본값 제공
  6. @Profile 빈은 인터페이스 기반 — 구현체를 Profile로 교체하되 주입 측은 인터페이스만 의존
  7. 시작 시 활성 Profile 로그 확인The following profiles are active: prod 메시지를 모니터링

흔한 실수 4가지와 방지법

실수 1: application.properties에 spring.profiles.active=dev를 하드코딩

증상: 프로덕션에서 SPRING_PROFILES_ACTIVE=prod를 설정했는데도 dev 설정이 섞여 들어온다. spring.profiles.active가 파일에 있으면 active가 아닌 include처럼 동작하는 경우가 있다.

방지: spring.profiles.active는 외부(환경 변수, 커맨드라인)에서만 설정한다. 파일에 기본값이 필요하면 spring.profiles.default를 사용한다.

실수 2: 복수 Profile의 덮어쓰기 순서를 모름

증상: SPRING_PROFILES_ACTIVE=prod,mysql로 설정했는데 prod의 datasource URL 대신 mysql의 URL이 적용된다.

방지: 마지막 Profile이 이긴다는 규칙을 이해한다. 의도한 우선순위 순서로 나열하되, 충돌하는 키가 없도록 Profile별 관심사를 분리한다.

실수 3: @Profile 배열의 OR 동작을 AND로 오해

증상: @Profile({"prod", "mysql"})로 “prod이면서 mysql일 때”를 의도했지만, 실제로는 prod 또는 mysql이면 빈이 등록된다.

방지: AND가 필요하면 단일 문자열에 & 연산자를 사용한다: @Profile("prod & mysql"). 배열 요소는 항상 OR이다.

실수 4: Profile별 @Configuration을 만들었는데 빈 충돌

증상: 같은 타입의 빈이 두 Profile에 정의되어 있고, 하나의 Profile만 활성화했는데 NoUniqueBeanDefinitionException이 발생한다.

방지: @Profile이 실제로 붙어 있는지 확인한다. @Configuration 클래스에 @Profile을 붙이면 내부의 모든 @Bean에 적용되지만, @Component 스캔되는 클래스에는 각각 붙여야 한다.

마무리

Spring Boot Profile은 환경별 설정 분리의 표준 메커니즘이다. 설정 파일 네이밍(application-{profile}), 5가지 활성화 방법과 우선순위, @Profile의 OR/AND/NOT 논리, 그리고 Profile 그룹을 이해하면 환경 관리가 명확해진다. Kubernetes 환경에서는 환경 변수(SPRING_PROFILES_ACTIVE)로 Profile을 주입하고, 민감 정보는 Secret으로 분리하는 것이 공식 문서에서도 권장하는 패턴이다. 이 글의 모든 내용은 Spring Boot 공식 문서(Profiles, Externalized Configuration)를 근거로 한다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux