Post

(Spring) profile로 alpha, beta, real 빌드 구분하기

[Spring] 외부 상수 넣어주기

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

Spring profile VS Gradle(Maven) profile

Gradle(Maven) profile?
  • 빌드 시 어떤 profile에 속하는 리소스 파일을 빌드 결과물에 포함 시킬지 말지를 결정
  • 빌드타임 프로파일 설정
  • 즉, Gradle(Maven) profile이 더 큰 파일 개념이고, 선택한 파일(e.g., 리소스 파일)을 jar에 포함하거나 포함하지 않거나를 결정하는데 사용함.
    • 예를 들어 alpha/resources.yml 파일을 패키징 시 jar에 포함 할거냐 말거냐.
Spring profile? :: @Profile(), spring.profiles.active
  • Spring profile은 빌드 결과물 내에서 스프링이 어떤 profile을 사용할지를 결정
  • 런타임 프로파일 설정
  • 공통으로 사용할 application.yml을 하나 두고, 빌드 환경에 따라 추가적으로 application-${profile}.yml을 불러오도록 구성하려면 Spring profile 세팅이 꼭 필요하다.
  • 빌드시점에 Gradle profile로 application-${profile}.yml을 포함하도록 하더라도, 스프링에서 여기있는 변수를 불러오는건 별개의 문제이기 때문
  • spring.profiles.default도 있는데, 그냥 .active로 설정하는 것이 나아보임. (.default로 세팅하면 @Value(“spring.profiles.active”)로 안가져와지기 때문.)

크게 gradle/maven 컴파일 시 환경 별 resource를 모두 포함할지 말지 / SpringBoot 실행 시 profile을 명시할지 말지에 따라 크게 아래 3가지 방법으로 나눠볼 수 있음.

  1. 컴파일 할 때 alpha 전용으로 컴파일하고, SpringBoot 실행 시에도 spring.profiles.active 명시해야 하는 방법
  2. 컴파일 할 때 alpha 전용으로 컴파일하고, SpringBoot 실행 시 spring.profiles.active 명시안해도 되는 방법
  3. 컴파일 할 때 (alpha, beta, real) resources를 모두 포함해 컴파일하고, SpringBoot 실행 시 spring.profiles.active 명시해서 구분하는 방법
1번 방법 : alpha 전용으로 컴파일, 런타임에 spring.profiles.active 명시
  • alpha 전용으로 컴파일 하기 때문에 다른 환경(real)의 resources가 jar에 포함되지 않음. alpha jar가 유출되어도 리얼 key 등은 안전.
    • 하지만 별도 key 저장소를 사용하는 경우 리얼 key hash가 유출되어도 그 자체만으로는 의미가 없어 안전하긴 함.
  • 환경 별로 바이너리를 따로 컴파일 해야 하는 것은 보통 별로 문제되는 부분은 아님. (알파 베타는 테스트용도 로 소스 브랜치가 달라 어차피 빈번히 빌드해야 하기 때문에)
  • SpringBoot 실행 시 profile 명시해야 하는데, 다른 profile로 실행하면 에러 발생. (resource 찾을 수 없음)
2번 방법 : alpha 전용으로 컴파일, 런타임에 spring.profiles.active 명시안함
  • alpha 전용으로 컴파일. 1번과 동일.
  • application.properties의 spring.profiles.active의 값을 compile time에 확정해서 넣어주는 방식 이기 때문에, SpringBoot 실행 시 profile 명시할 필요 없음.
  • 실수 방지 차원에서 1번 방법 보다 나아보임. 어차피 다른 profile로 실행하면 에러날 것이기 때문에.
3번 방법 : 모든 profile resources를 포함해 컴파일, 런타임에 spring.profiles.active 명시
  • alpha, beta, real에서 모두 같은 바이너리 사용 가능. SpringBoot 실행 시 profile 명시해서 모드 구분.
    • 프로파일 변경 시 굳이 빌드를 다시 할 필요가 없음.
  • profile 구분 없이, 어떤 프로파일이든 모든 리소스 파일이 jar 패키징 시 포함되도록 구성하면 되기 때문에 컴파일 타임의 Gradle/Maven profile은 신경쓰지 않아도 됨.
  • 테스트 시 리소스 문제로 신경써야 하는 상황이 적음.
    • 빌드 시 @ActiveProfiles로 테스트만 다른 프로파일 적용해서 돌린다거나 하는게 가능.
  • alpha jar가 유출되면 리얼 key도 유출되는게 단점.
    • 별도 key 저장소 사용하지 않고 resouces에 평문으로 key가 저장되어 있는 경우 크리티컬.
  • 제일 심플한 방법. gradle에 profile 관련 세팅이 들어갈 필요가 없고, 컴파일 타임에 profile을 신경 쓸 필요 없음.
    • 별도 key 저장소 사용하거나 그렇게까지 보안을 신경쓰지 않아도 되는 환경이라면 이렇게 구성하는게 제일 간단.

2번 방법 구성안

Gradle Profile 구성하기

build.gradle.kts
1
2
3
4
5
6
7
8
9
10
11
val profile = if (project.hasProperty("profile"))
    project.property("profile").toString() else "local"
sourceSets {
    main {
        resources {
            srcDirs(listOf("src/main/resources", "src/main/resources-$profile"))
        }
    }
}
// spring으로 넘어기는 프로파일은 $profile (@Profile 애너테이션 등에서 사용하게 될)
// 리소스를 불러오는 것은 resources-$environment 로 나누는 것도 필요하다면 가능하다.
  • 여기서 주의할 점
    • 같은 파일명의 application.yml이 각 dir 마다 존재한다면, 가장 마지막에 명시된 dir의 application.yml만을 사용하게 됨.
    • 그래서 폴더를 resources, resources-alpha 등으로 나누더라도 내부에 들어가는 파일 이름을 다르게 지정해 주어야 한다. (application-real.yml)
  • 폴더를 resources(공통리소스), resources-alpha(환경 별 리소스)로 나누는 이유?
    • 폴더 분리 안하고 resources/ 하위에 application-beta.yml 등을 모두 함께 넣어버리면, 해당 dir의 모든 리소스가 같이 포함되어 jar로 묶인다.
    • alpha jar가 유출되면 뜯어서 리얼 서버에서 사용하는 Key 등등을 알아낼 수 있음.
    • 따라서 폴더를 분리하여 필요한 리소스만 포함해서 빌드하도록 하는 것이 좋다.
    • 또, 폴더 분리를 하지 않는 경우 common.yml 같은 별도 프로퍼티 파일을 환경 별로 두기 어려워진다.
      • 별도 파일로 두지 못하니…
      • application-{profile}.yml에 {profile}용 common.yml의 모든 설정을 넣어 합치거나
      • 환경 별로 따로 존재하던 common.yml을 하나로 합치고 파일 내에서 activate.on-profile 사용해서 구분해야 한다.
      • 아니면 환경변수로 gradle로부터 {profile}을 넘겨받아 import=common-${profile}.yml 이 가능하도록 만들거나.
      • common.yml은 어찌 처리한다 쳐도, logback.xml 같은 환경 별로 이름이 동일한 파일은 어쨌든 폴더 구조로 관리하는게 편하다.

srcDirs, include, exclude 설정을 통해서 모두 resources 하위에 두는 것도 가능은 하지만 조금 번잡스럽다.

  • 이런 식으로 위치시키고 빌드하면, resources/application.yml, resources-local/application-local.yml이 포함되어 빌드된다
  • 근데 막상 런타임에는 application-local.yml에 있는 상수 이름은 못찾는다고 에러가 발생한다.
  • Spring Profile을 설정해줘야 여기 있는 상수도 찾을 수 있음!

  • 어떤 Spring profile을 빌드 시 포함하도록 할 것인지는 다음 옵션으로 지정
    • gradle clean build -Pprofile=beta
  • intelliJ 에서는 Run Configurations에서
    • Before launch - Run Gralde task
      • Task : clean build
      • Arguments : -Pprofile=$profile

Spring Profile 구성하기

https://www.baeldung.com/spring-profiles

application.yml
1
2
3
spring:
  profiles:
    active: @env@
  • 컴파일 타임에 maven profile이 local이었다면, sping profile도 local이 되도록 maven properties @env@를 가져와 세팅
    • 빌드 결과물 보면 local로 치환되어 있다.
    • 런타임에 어떤 Spring profile을 사용할 것인지 명시 할 필요 없음. (application.yml에 active로 명시되어 있기 때문에)
  • 이렇게 설정해줘야 application-local.yml에 있는 상수도 불러올 수 있다.
  • 스프링은 프로필로 설정된 문자열에 해당하는 application-${profile}.yml가 있는지 뒤져보고, 있다면 불러온다. (기본 설정)
    • 이 경우 application-local.yml이 있는지 찾아보고, 없으면 넘어가고 있다면 불러옴.
  • 이렇게 profile base로 추가적인 리소스를 가져오는 것 이외에, 아예 다른 이름의 프로퍼티를 가져오고 싶다면https://www.baeldung.com/properties-with-spring4.7 참고
maven pom.xml의 변수를 application.yml에 전달
1
2
3
4
5
6
7
8
9
10
11
12
<profile>
    <id>alpha</id>
    <properties>
        <env>alpha</env>
    </properties>
</profile>
...
<resource>
    <filtering>true</filtering> <-- 이 설정이 없으면 @env@가 치환되지 않는다.
    <directory>src/main/resources</directory>
</resource>

build.gradle.kts의 profile 변수를, gradle Task 실행 시 전달
  • gradle_system_properties 란?
  • 위에서 [argument로 전달:JAVA_OPTS] 부분을, build.gradle.kts에 명시해서 자동으로 넣어준다고 보면 될 것 같다.
1
2
3
4
// Test task를 실행할 때 profile 전달
tasks.getByName<Test>("test") {
    systemProperty("spring.profiles.active", profile)
}
build.gradle의 변수를 application.yml에 전달

https://www.baeldung.com/spring-boot-auto-property-expansion

1
2
3
4
## 변환 전
spring:
  profiles.active: ${profile} # <- gradle에서 가져올 property
  batch.job.name: \${job.name:NONE} # <- gradle에서 가져오지 않을 property
1
2
3
4
5
tasks.processResources {
    filesMatching("application.yml") {
        expand("profile" to profile)
    }
}
1
2
3
4
## 변환 후 (빌드 결과)
spring:
  profiles.active: alpha # <- gradle에서 가져올 property
  batch.job.name: ${job.name:NONE} # <- gradle에서 가져오지 않을 property
  • 주의 할 점!
1
2
3
> Failed to parse template script (your template may contain an error or be trying to use expressions not currently supported): startup failed:
     SimpleTemplateScript23.groovy: 1: Unexpected input: '(' @ line 1, column 10.
        out.print("""spring:

IntelliJ에서 실행할 때?

  • intelliJ에서는 Run Configurations에서 설정해줄 것.
    • Gradle task(bootRun, Test, …)를 실행할 때 선택할 profile 지정
      • 방법1 ) 위의 build.gradle.kts의 profile 변수를 gradle Task 실행 시 전달
      • 방법2 ) Before launch: Gradle task에 환경변수 명시
      • Gradle task 실행시 선택할 profile을 명시 안하면 Test 돌릴 때 Profile이 default로 설정돼서 실패뜬다.

분명 모든 설정을 잘 했는데 안될 때… 빌드 결과물 resource 확인해보면 프로파일이 제대로 적용되지 않은 경우가 종종 있음
IntelliJ 오류로 언제는 되고 언제는 안되고 그럴 수 있어서 확인 필요.

반대로 application-core.yml을 구성하는 방법은?

  • [1] 위 방식은 공통으로 사용할 resources/application.yml과 선택에 따라 resources-${profile}/application-${profile}.yml을 불러오는 방법.
  • [2] 반대로 공통으로 사용할 resources/application-core.yml을 두고, 선택에 따라 resources-${profile}/application.yml을 불러오도록 하는 방법도 있다.
  • 어느 쪽이 되었건 application.yml이 먼저 로드되고, application-xxx.yml이 더 늦게 로드 된다.
  • 같은 이름 상수가 정의되어 있는 경우,늦게 로드 되는 쪽이 overwrite 한다!
  • 그래서 [1] 방식을 사용하는 편이 좋아 보인다. 실수로 같은 이름 상수를 정의한 경우에도, 더 specific한 설정으로 덮어 써지므로.
test property 설정 예제
  • test/resources에 있는 것은 이걸 사용하고, 없는 것은 main/resources 사용하고 싶은 경우?
    • 별도 설정 필요 없음.자동으로 이렇게 되어 있음.
1
2
3
target  // 빌드 결과 dir
ㄴ  classes       // 2. 1번에서 없으면 여기 리소스를 뒤진다.
ㄴ  test-classes  // 1. 테스트 시 여기 리소스를 먼저 뒤지고
1
2
3
4
5
6
// 실행 커맨드에 spring profile 옵션도 주어 application.yml의 active profile을 덮어쓰도록 한다.
$ mvn clean test -P test -Dspring.profiles.active=test


// 참고로 install 시 테스트 스킵하는 옵션은
$ mvn clean install -P beta -Dmaven.test.skip=true
참고

https://garyj.tistory.com/9

https://perfectacle.github.io/2017/09/23/Spring-boot-gradle-profile/

This post is licensed under CC BY 4.0 by the author.