[Spring Boot Core] 2.2. Externalized Configuration

2024. 12. 9. 16:48Spring/Spring Docs

반응형
  1. spring.config.name
  2. spring.config.location
  3. optional location
  4. wildcard location
  5. profile별 구성 파일
    1. 특정 프로파일 활성화
    2. Last wins 전략
  6. 추가 데이터 import
    1. Profipe-specific varinants 가져오기
    2. 확장자 없는 파일 가져오기
  7. configtree
  8. property placeholders
  9. 다중 문서 파일 작업
  10. 활성화 프로퍼티

Spring Boot 는 애플리케이션이 시작될 때, application.propertiesapplication.yml 파일을 아래의 경로로부터 자동으로 찾아서 로드합니다.

  1. classpath로 부터 찾기
    1. classpath의 root
    2. classpath 하위의 /config 패키지
  2. 현재 디렉토리에서 찾기
    1. 현재 디렉토리
    2. 현재 디렉토리의 ./config/ 디렉토리
    3. ./config 디렉토리의 직계 하위 디렉토리

1. spring.config.name

기본적으로 application이라는 config 파일명을 로드하지만 (예: application.yml)
만일 config 파일명을 변경하고 싶다면, spring.config.name 환경 프로퍼티로 관련 설정을 추가할 수 있습니다.

예를들어 demo.jar 애플리케이션을 싫행하는데, demo.yml 또는 demo.properties 구성파일을 읽고 싶다면, 아래와 같이 애플리케이션을 실행하면 됩니다.

java -jar demo.jar --spring.config.name=demo

2. spring.config.location

하나 이상의 구성 파일을 로드하고 싶다면, spring.config.location 속성을 사용하면됩니다.
쉼표를 통해 여러개를 참조할 수도 있으며, 파일명 또는 디렉토리 위치를 직접적으로 설정하는 방법입니다.

나중에 읽어들인 구성파일은 이전에 불려진 구성파일을 override 합니다.

나중에 읽어들인 구성파일을 우선한다

java -jar demo.jar --spring.config.location=\
  optional:classpath:/default.yml,\
  optional:classpath:/override.yml

spring.config.name, spring.config.location, spring.config.additional-location 구성설정은 어떤 구성 파일을 읽을 건지를 판단하는 설정으로 애플리케이션 구동 중에 매우 일찍 읽어들이는 속성으로,
환경변수(OS 환경변수, 시스템 속성, command line 인수)로 정의해야 정상적으로 작동됩니다.


디렉토리와 파일의 위치값은 profile 별로 체크하여 확장할 수 있습니다.
spring.config.location=classpath:demo.yml 로 설정되어있을 때, profile 설정에 따라 특정 구성설정을 읽어들일 수 있습니다. (classpath:demo-.yml)

예를들어, profile이 dev 인 경우라면 classpath:demo-dev.yml 을 찾아서 읽는 것입니다.

spring.config.location에 아래와 같이 위치를 설정할 경우, 아래와 같은 순서로 구성파일을 읽어들입니다.

  1. optional:classpath:/custom-config/
  2. optional:file:./custom-config/
--spring.config.location=optional:classpath:/custom-config/,optional:file:./custom-config/

additional-location 에 설정할 경우, 기본 설정값을 읽어들인 후 추가적인 구성파일 설정을 override 하여 구성파일을 읽어들입니다.

  1. optional:classpath:/;optional:classpath:/config/
  2. optional:classpath:file:./;optional:classpath:file:./config/;optional:classpath:file:./config/*/;
  3. optional:classpath:/custom-config/
  4. optional:file:./custom-config/
--spring.config.additional-location=optional:classpath:/custom-config/,optional:file:./custom-config/

3. optional location

3.1. optional:

기본적으로 설정한 위치에 구성파일이 없을 경우 ConfigDataLocationNotFoundException를 발생시키면서 애플리케이션이 시작되지 않습니다.
만일, 특정 구성파일을 읽어들이는 것이 필수가 아니라면 optional: 을 붙여주면 됩니다.

위에서 예시에서 봤던것 처럼 spring.config.location 설정에도 사용할 수 있고, (ex. --spring.config.location=optional:classpath:/demo.yml)
spring.config.import 에도 설정할 수 있습니다.


3.2. on-not-found

만일 애플리케이션 시작중 config 파일 위치 찾기에 대한 오류(ConfigDataLocationNotFoundExceptions)를 모두 무시하고, 구성 파일가 없더라도 무조건 애플리케이션을 실행시키고 싶다면 시스템/환경 변수로 spring.config.on-not-found 프로퍼티 값을 ignore 로 설정하면 됩니다.

@SpringBootApplication
public class SpringBootApp {

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(SpringBootApp.class);
		app.setDefaultProperties(Map.of("spring.config.on-not-found", "ignore"));
		app.run(args);
	}

}

4. wildcard location

config/*/ 와 같이 location 끝에 *를 포함할 경우, 구성이 로드 될때 하위 디렉터리 까지 확장하여 검색합니다.
(구성 속성이 여러개인 k8s 환경에 유용합니다.)

예를들어 redis와 mysql 구성이 있는 경우, 구성을 그대로 유지하면서 각각 별도 파일로 구성하고 싶다면 config/*/ 로 설정해두면 아래와 같은 위치에 존재하는 구성 파일들을 모두 찾아 로드할 수 있습니다.

  • /config/redis/application.yml
  • /config/mysql/application.yml

config/*/ 는 Spring Boot에서 포함하고 있는 기본 검색 위치이기 때문여, 기본적으로 내 jar 프로젝트의 외부 config 디렉토리의 하위 디렉토리를 모두 검색합니다.

물론 탐색하고 싶은 위치를 변경하고 싶다면 spring.config.location 이나 spring.config.additional-location
* 를 포함하여 변경할 수 있습니다.

Note

wildcare location은 *를 단하나만 포함시켜야 하고, 와일드카드를 포함하여 찾고자하는 디렉토리 위치를 설정하고자한다면 */로 끝내야 하고, 와일드카드 디렉토리 하위의 특정 파일명을 지정하고 싶다면 */<filename> 같은 형식으로 정의해야 합니다.
와일드카드가 있는 위치 파일 이름은 알파벳 오름차순 순으로 정렬되어 로드됩니다.

wildcard location은 외부 디렉토리에서만 작동 합니다. classpath: 위치에는 와일드카드를 사용할 수 없습니다.


5. profile별 구성 파일

5.1. 특정 프로파일 활성화

-Dspring.profiles.active=dev 와 같은 방식으로 활성화할 프로파일을 정의하면, 1순위로 application.yml 파일을 로드하고 그 후에 application-dev.yml 구성파일을 로드합니다.

나중에 로드된 application-dev.yml 구성파일이 이전에 읽은 구성 값을 override 합니다.


구성파일

spring:
  application:
    name: spring-boot

custom:
  name: jini
  group: jiniworld

application.yml


spring:
  application:
    name: spring-boot-dev

custom:
  name: jini-dev

application-dev.yml


애플리케이션 실행

활성화할 프로파일을 지정하여 애플리케이션 실행합니다.

java -jar demo.jar -Dspring.profiles.active=dev

결과

나중에 불려지는 application-dev.yml 에 설정한 값이 최종적으로 덮어써지기 때문에, spring.application.name 값은 spring-boot-dev가 됩니다.
(이는, 애플리케이션 실행시 로그 내에서 확인할 수 있습니다.)

2024-11-15T16:44:43.572+09:00 DEBUG 14254 --- [spring-boot-dev] [  restartedMain] o.s.boot.SpringApplication               : Loading source class me.jiniworld.springboot.SpringBootApp

spring.application.name 과 마찬가지로 custom.name 값은 application-dev.yml 에서 override 된 "jini-dev"가 설정되고, dev profile에는 정의하지 않은 custom.group 값은 기본 application.yml 에 정의한 jiniworld 가 출력되는 것을 확인할 수 있습니다.


5.2. Last wins 전략

만일, spring.profiles.active 값에 "prod,live" 와 같이 여러개를 설정할 경우
동일한 프로퍼티 값에 대해 가장 나중에 호출된 application-live.yml 파일에 설정한 값으로 override 합니다.

여러개의 profile이 지정된 경우, 마지막에 설정한 전략이 override 되는 Last wins 전략이 적용됩니다.

Last wins 전략은 위치 그룹 수준에서 적용되는데,

spring.config.active=prod,live 인 상황에서 아래와 같은 구조로 구성파일이 있다고 할때,

/cfg
  application-live.yml
/ext
  application-live.yml
  application-prod.yml

spring.config.location=classpath:/cfg/,classpath:/ext/
spring.config.location=classpath:/cfg/;classpath:/ext/ 는 override 규칙이 다릅니다.


5.2.1. classpath:/cfg/,classpath:/ext/

location을 쉼표로 구분하는 것은 (활성화할 profile 순서를 정의한것 처럼) 각 location 디렉토리별로 순서를 지정하는 것과 같습니다.

따라서, cfg 디렉토리 하위에 있는 파일들이 가장 우선순위가 높고 그다음에 ext 디렉토리 하위의 파일을 로드합니다.

  1. /cfg/application-live.yml
  2. /ext/application-prod.yml
  3. /ext/application-live.yml

5.2.1. classpath:/cfg/;classpath:/ext/

location을 세미콜론로 구분하는 것은 각 location 디렉토리를 동일한 수준으로 간주하는 것과 같습니다.
다만, 만일 두 디렉토리 내에 동일한 프로파일이 존재한다면 먼저 위치한 디렉토리를 먼저 로드합니다.

  1. /ext/application-prod.yml
  2. /cfg/application-live.yml
  3. /ext/application-live.yml

Note

만일 활성화할 프로파일을 명시적으로 지정해주지 않는다면 기본적으로 default 프로파일을 설정합니다.
즉, 프로파일 미지정시에는 application-default 구성파일 내에 정의된 프로파일들을 로드합니다.

아래는 활성화할 프로파일 미지정시 출력되는 로그화면의 일부입니다.

2024-12-04T14:05:35.451+09:00  INFO 32545 --- [spring-boot] [  restartedMain] me.jiniworld.springboot.SpringBootApp    : No active profile set, falling back to 1 default profile: "default"

6. 추가 데이터 import

애플리케이션 프로퍼티는 spring.config.import 프로퍼티를 이용하여 다른 위치로부터 config data를 가져올 수 있습니다.

아래와 같이 classpath 하위에 있는 파일을 추가로 가져올 수도 있고,

spring:
  config:
    import: optional:classpath:/demo.yml

프로퍼티 파일이 위치한 동일한 디렉토리에 있는 파일을 가져오는 것도 가능합니다.

spring:
  config:
    import: optional:./demo.yml

spring.config.import를 이용하여 가져온 값들을 가장 우선하며, 구성파일 내에서의 spring.config.import 프로퍼티의 위치와는 무관합니다.
(= import를 정의한 구성 파일의 맨 마지막에 작성하든 맨 위에 작성하든 프로퍼티 위치와는 무관하게 무조건 import한 파일을 맨 마지막으로 override 한다.)

즉, 아래 두 구성파일은 모두 동일하게 작동되며, demo.yml 에 작성된 것으로 덮어써집니다.

spring:
  config:
    import: "demo.yml"

custom:
  name: hello
custom:
  name: hello

spring:
  config:
    import: "demo.yml"

spring.config.import 에는 여러개의 위치를 포함시킬 수 있으며, 마찬가지로 맨 마지막에 정의한 위치를 우선합니다.
spring.config.import 프로퍼티 설정은 구성파일 내에 단 한번만 import 합니다. (여러개 선언하더라도)


6.1. Profipe-specific varinants 가져오기

또, application*.yml 이외에 변형된 profile 도 가져오는게 가능합니다.

예를들어, 아래와 같이 demo 라는 이름의 구성파일을 가져온다고 설정이 되어있으면서, qa 프로파일이 활성화 되어있을 경우에는 (= -Dspring.profiles.active=qa)

spring:
  config:
    import:
      - demo.yml

application.yml → application-qa.yml → demo.yml → demo-qa.yml 순으로 덮어써집니다. (demo-qa.yml 구성파일이 가장 높은 우선순위)


6.2. 확장자 없는 파일 가져오기

Spring Boot에는 다양한 위치주소를 지원하는 플러그형 API를 포함하고 있기 때문에, 기본적으로 java 클래스를 이용한 프로퍼티 속성과 YAML, 구성 트리를 통한 import가 가능합니다.
(Consul, Apache Zookeeper, Netflix Archaius 등과 같은 외부 저장소로부터 구성 데이터 로드)

일부 클라우드 플랫폼은 볼륨 마운트된 파일에 파일 확장자를 추가할 수 없습니다.
이러한 경우에 대한 구성파일 로드를 가능하게 하기 위해 Spring Boot는 확장자 hint를 제공합니다.

확장자 hint는 대괄호를 묶어서 실행하는데, 아래와 같이 작성하면 파일명에 확장자가 없어도 로드가 가능합니다.

spring:
  config:
    import:
      - "file:/etc/config/demo[.yml]"

7. configtree

Kubernetes 와 같은 클라우드 플랫폼에서 애플리케이션을 실행할 때, 플랫폼에서 제공하는 구성값을 읽어야하는 경우가 있습니다.
플랫폼에서 제공하는 구성값을 읽기 위하여 환경변수를 사용하는 것은 드문일은 아니지만, 값을 비밀로 해야하는 경우에는 보안상 취약하다는 단점이 있습니다.

그러한 이유로, 환경변수 대안으로 많은 클라우드 플랫폼에서 마운트된 데이터 볼륨에 구성을 매핑할 수 있습니다.

Kubernetes를 예로 들자면 ConfigMapsSecrets 둘다 볼륨 마운트 할 수 있습니다.


일반적인 볼륨 마운트 패턴

  1. 단일파일에는 전체 프로퍼티 세트를 포함하고 있다.
  2. 여러개의 파일은 파일이름은 'key'가 되고 내용은 'value'가 되어 디렉토리 트리에 기록됩니다.

K8s 가 아래와 같은 볼륨을 마운트했다고 해봅시다.
이 중, username은 config 값이고, password 값은 비밀입니다.

etc/
  config/
    myapp/
      username
      password

이 값을 읽기 위해서, 프로퍼티에 아래와 같이 설정할 수 있습니다.

spring:
  config:
    import: "optional:configtree:/etc/config/"

구성트리의 값은 예상되는 컨텐츠 내용에 따라 Stringbyte[] 유형 모두에 바인딩될 수 있습니다.

여러 구성트리 가져오기

아래와 같은 볼륨이 주어졌을 때에는,

etc/
  config/
    db/
      username
      password
    db/
      username
      password

아래와 같이 * 를 사용하면 여러개의 구성 트리를 가져올 수 있습니다.

spring:
  config:
    import: "optional:configtree:/etc/config/*/"

Note

다만, 여러구성트리를 import 할 경우, 알파벳 순으로 정렬하여 로드합니다.
만약 로드 순서를 직접 정의하고 싶다면 각 위치별로 별도로 import하여 가져와야합니다.


8. property placeholders

구성파일 내에서 "${}" 내에 property-placeholder 문법을 이용하면 기존에 정의한 프로퍼티나 시스템 속성, 환경변수를 참조할 수 있습니다.

${속성키:기본값} 과 같이 속성키가 없을 경우 할당할 기본값도 정의할 수 있습니다.

이때, 참조할 값은 소문자로 구성된 Kebab case 로 사용하는 표준 형식을 따라야 합니다.
이를 따를 때, Spring Boot는 기존 Relaxed Binding @ConfigurationProperties 를 사용할 때와 동일한 로직을 사용할 수 있습니다.

참고: [Spring Boot Core] Spring Boot Relaxed Binding using yaml


활용 방안: property placeholders를 활용한 명령어 인자 간소화

만일, 특정 애플리케이션을 실행할 때, 아래와 같이 포트를 지정한다고 할때,

java -jar demo.jar -Dserver.port=18080

property placeholders 를 활용하여 아래와 같이 정의해두면,

server:
  port: "${port:8080}"

아래와 같이 매우 간단하게 command line 인자를 사용할 수 있습니다.

java -jar demo.jar --port=18080

9. 다중 문서 파일 작업

하나로 구성된 구성파일을 여러 논리적 문서로 분할할 수 있습니다.

YAML 구성파일의 경우, 각 논리적 문서 구분을 --- 로 합니다.

.properties 파일의 경우에는 #--- 또는 !--- 로 합니다.


예를들어 아래와 같이 구성된 구성파일이 있다고 할때,

맨 위의 논리적 문서는 공통 속성이고,
활성화된 프로파일에 따라 특정 속성값을 재정의할 수 있습니다.

상세한 섦명은 활성화 프로퍼티 를 참고해주세요.

spring:
  datasource:
    hikari:
      connection-test-query: SELECT 1
      maximum-pool-size: 30
      auto-commit: false

management:
  endpoint:
    env:
      show-values: always
  endpoints:
    web:
      exposure:
        include: env

logging:
  level:
    root: info
    web: debug
    org.springframework:
      boot.autoconfigure: info
      web: debug

---

spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://상용서버:3306/jiniworld
    hikari:
      pool-name: pool-prod
      username: jini
      password: jiniProd

---

spring:
  config:
    activate:
      on-profile: local
  datasource:
    url: jdbc:mysql://localhost:3306/jiniworld
    hikari:
      pool-name: pool-local
      username: jini
      password: jiniLocal

Note

구성 파일 구분 기호 --- 앞에는 공백이 없어야합니다.


10. 활성화 프로퍼티

spring.config.activate 속성을 이용하여 조건부로 특정 프로퍼티 파일을 확성할 수 있습니다.

java -jar demo.jar -Dspring.config.activate=prod

위에서 소개했듯, 맨 첫번째 문단은 맨 처음으로 구성되는 공통 설정이고,
spring.config.activate.on-profile은 활성화되기 위해 일치해야할 profile 표현식을 정의합니다.

spring.config.activate.on-cloud-profile 라는 속성도 있는데, 이는 활성화되기 위해 탐지되어야하는 CloudPlatform을 정의합니다.
ex. spring.config.activate.on-cloud-profile=kubernetes

myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

++

  • 외부 애플리케이션 프로퍼티
  • file:./application-demo.yml 적용 안됨. 추후 알아볼 것.
  • configtree에 대해서도 직접적인 사용예제로 테스트해볼 것
728x90
반응형