Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

외부 설정과 프로필 본문

Spring/Spring boot - 핵심 원리와 활용

외부 설정과 프로필

studyHub 2025. 1. 29. 23:53

외부 설정

외부 설정이 필요한 이유

하나의 애플리케이션을 로컬/개발/운영 환경과 같이 여러 다른 환경에서 사용해야 할 때가 있다.

문제는 각각의 환경에 따라서 서로 다른 설정값이 존재한다는 점이다. (로컬/개발/운영 DB 별 url 정보 등)

 

그래서 보통 빌드는 한번만 하고 각 환경에 맞추어 실행 시점에 외부 설정값을 주입한다.

환경에 따라 변하는 설정 값을 실행 시점에 주입

 

☆유지보수하기 좋은 애플리케이션 개발의 가장 기본 원칙은 변하는 것과 변하지 않는 것을 분리하는 것이다.

각 환경에 따라 변하는 외부 설정값은 분리하고, 변하지 않는 코드와 빌드 결과물은 유지했다.

덕분에 빌드 과정을 줄이고, 환경에 따른 유연성을 확보하게 되었다.

 

외부 설정 방법

  1. OS 환경 변수: OS에서 지원하는 외부 설정, 해당 OS를 사용하는 모든 프로세스에서 사용
  2. 자바 시스템 속성: 자바에서 지원하는 외부 설정, 해당 JVM안에서 사용
  3. 자바 커맨드 라인 인수: 커맨드 라인에서 전달하는 외부 설정, 실행시 main(args) 메서드에서 사용
  4. 외부 파일(설정 데이터): 프로그램에서 외부 파일을 직접 읽어서 사용 (각 서버마다 해당 파일안에 다른 설정 정보)

 

1. OS 환경 변수

해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값이다. 다른 외부 설정과 비교해서 사용 범위가 가장 넓다.

아래와 같이 애플리케이션에서 OS 환경 변수 값을 읽을 수 있다.

//DB_URL=dev.db.com  개발 서버
//DB_URL=prod.db.com 운영 서버
String dbUrl = System.getenv("DB_URL");

 

하지만 OS 환경 변수는 이 프로그램 뿐만 아니라 다른 프로그램에서도 사용할 수 있다. 

해당 애플리케이션을 사용하는 자바 프로그램 안에서만 사용되는 외부 설정값을 사용하고 싶을 때도 있다.

 

2. 자바 시스템 속성

Java System properties는 실행한 JVM 안에서 접근 가능한 외부 설정이다. 자바 프로그램을 실행할 때 사용한다.

-D VM 옵션을 통해서 key=value 형식을 주면 된다.

예) java -Durl=dev -jar app.jar

IDE에 실행 옵션으로 추가할 수도 있다.

Properties properties = System.getProperties();
for (Object key : properties.keySet()) {
    log.info("prop {}={}", key, System.getProperty(String.valueOf(key)));
}

위 코드를 실행하면 자바가 기본으로 제공하는 수 많은 속성들이 추가되어 있는 것을 확인할 수 있다. 자바는 내부에서 필요할 때 이런 속성들을 사용하는데, 예를 들어 file.encoding=UTF-8 를 통해 기본적인 파일 인코딩 정보 등으로 사용한다.

 

3. 커맨드 라인 인수

애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법이다.

필요한 데이터를 마지막 위치에 스페이스로 구분해서 전달하면 된다.

예) java -jar app.jar dataA dataB

IDE에서 실행시 커맨드 라인 인수 추가

 

그런데 애플리케이션을 개발할 때는 보통 key=value 형식으로 데이터를 받는 것이 편리하다.

커맨드 라인 인수를 url=devdb 로 입력하고 실행하면, 결국 arg를 개발자가 파싱해야 하는 번거로움이 있다.

for (String arg : args) {
    log.info("arg {}", arg); //url=devdb
}

 

커맨드 라인 옵션 인수(command line option arguments)

커맨드 라인 인수를 key=value 형식으로 구분하는 방법이 필요하다. 그래서 스프링에서는 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록 스프링 만의 표준 방식을 정의했다.

커맨드 라인에 - (dash) 2개( -- )를 연결해서 시작하면 key=value 형식으로 정하고 이것을 커맨드 라인 옵션 인수라 한다.

--url=devdb --username=dev_user --password=dev_pw mode=on

스프링이 제공하는 ApplicationArguments 인터페이스와 DefaultApplicationArguments 구현체를 사용하면 커맨드 라인 옵션 인수를 규격대로 파싱해서 편리하게 사용할 수 있다.

ApplicationArguments appArgs = new DefaultApplicationArguments(args);

for (String optionName : appArgs.getOptionNames()) {
    log.info("option args {}={}", optionName, appArgs.getOptionValues(optionName));
}

 

 

커맨드 라인 옵션 인수와 스프링 부트

스프링 부트는 커맨드 라인을 포함해서 커맨드 라인 옵션 인수를 활용할 수 있는 ApplicationArguments 를 스프링 빈으로 등록해둔다. 그리고 그 안에 입력한 커맨드 라인을 저장해둔다. 그래서 해당 빈을 주입 받으면 main 메서드가 아니더라도! 커맨드 라인으로 입력한 값을 어디서든 사용할 수 있다.

@Component
@RequiredArgsConstructor
public class CommandLineBean {

    private final ApplicationArguments applicationArguments;

    @PostConstruct
    public void init() {
        log.info("source {}", List.of(applicationArguments.getSourceArgs()));
        log.info("optionNames {}", List.of(applicationArguments.getOptionNames()));

        for(String optionName : applicationArguments.getOptionNames()) {
            log.info("option args {}={}", optionName, applicationArguments.getOptionValues(optionName));
        }
    }
}

 

외부 설정 - 스프링 통합

커맨드 라인 옵션 인수, 자바 시스템 속성, OS 환경변수는 모두 외부 설정을 key=value 형식으로 사용할 수 있는 방법이다. 그런데 어디에 있는 외부 설정값을 읽어야 하는지에 따라서 각각 읽는 방법이 다르다는 단점이 있다. 만약 환경 변수 방식을 바꾸면 해당 코드들을 모두 변경해야 한다..

 

외부 설정값이 어디에 위치하든 상관없이 일관성 있고, 편리하게 key=value 형식의 외부 설정값을 읽을 수 있으면 사용하는 개발자 입장에서 더 편리하고 또 외부 설정값을 설정하는 방법도 더 유연해질 수 있다.

 

이럴 때 사용하는게 추상화다.

스프링은 이 문제를 Environment 와 PropertySource 라는 추상화를 통해서 해결한다.

스프링의 외부 설정 통합

PropertySource

스프링은 PropertySource 라는 추상 클래스를 제공하고, 각각의 외부 설정를 조회하는 XxxPropertySource 구현체를 만들어두었다.

예) CommandLinePropertySource, SystemEnvironmentPropertySource

스프링은 로딩 시점에 필요한 PropertySource 들을 생성하고, Environment 에서 사용할 수 있게 연결해둔다.

 

설정 데이터(파일)

application.properties , application.yml 도 PropertySource 에 추가된다. 따라서 Environment 를 통해서 접근할 수 있다.

 

Environment

Environment 를 통해서 특정 외부 설정에 종속되지 않고, 일관성 있게 key=value 형식의 외부 설정에 접근할 수 있다.

Environment 는 내부에서 여러 과정을 거쳐서 PropertySource 들에 접근한다. (getProperty(key) 를 통해 값을 조회)

모든 외부 설정은 이제 Environment 를 통해서 조회하면 된다.

@Component
@RequiredArgsConstructor
public class EnvironmentCheck {

    private final Environment environment;

    @PostConstruct
    public void init() {
        String url = environment.getProperty("url");
        String username = environment.getProperty("username");
        String password = environment.getProperty("password");
        log.info("url: {}, username: {}, password: {}", url, username, password);
    }
}

 

우선순위

같은 값이 있을 경우를 대비해서 스프링은 미리 우선순위를 정해두었다.

우선순위는 상식 선에서 딱 2가지만 기억하면 된다.

  • 더 유연한 것이 우선권을 가진다.
    (변경하기 어려운 파일 보다 실행시 원하는 값을 줄 수 있는 자바 시스템 속성이 더 우선권을 가진다.)
  • 범위가 넒은 것보다 좁은 것이 우선권을 가진다.
    (자바 시스템 속성은 해당 JVM 안에서 모두 접근할 수 있다. 반면 커맨드 라인 옵션 인수는 main 의 arg를 통해서 들어오기 때문에 접근 범위가 더 좁다.)

 

설정 데이터 

외부 파일

OS 환경 변수, 자바 시스템 속성, 커맨드 라인 옵션 인수는 사용해야 하는 값이 늘어날 수록 사용하기가 불편해진다.

대안으로는 설정값을 파일에 넣어서 관리하는 방법이 있다. 애플리케이션 로딩 시점에 해당 파일을 읽어들이면 된다.

 

설정 데이터(Config data)

application.properties 과 같은 파일을 각각 개발/운영 서버의 자바를 실행하는 위치(build/libs)에 만들어 두면 된다.

그러면 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource 의 구현체를 제공한다.

스프링에서는 이러한 application.properties 파일을 설정 데이터(Config data)라 한다.

당연히 설정 데이터도 Environment 를 통해서 조회할 수 있다.  (application.yml 에도 동일하게 적용된다)

 

 

내부 파일 분리

설정 파일을 외부에 관리하는 것은 상당히 번거로운 일이다. 설정을 변경할 때 마다 서버에 들어가서 각각의 변경 사항을 수정해두어야 한다.

 

이 문제를 해결하는 간단한 방법은 설정 파일을 프로젝트 내부에 포함해서 관리하는 것이다. 그리고 빌드 시점에 함께 빌드되게 하는 것이다. 이렇게 하면 애플리케이션을 배포할 때 설정 파일의 변경 사항도 함께 배포할 수 있다.

jar 하나로 설정 데이터까지 포함해서 관리하는 것이다.

설정 데이터 파일을 프로젝트 내부에서 관리
실행 시점에 내부 설정 파일 조회

실행할 때 외부 설정을 사용해서 개발 서버는 dev 라는 값을 제공하고, 운영 서버는 prod 라는 값을 제공하자. 편의상 이 값을 프로필이라 하자. 외부 설정으로 넘어온 프로필 값이 dev 라면 application-dev.properties 를 읽고, prod 라면 application-prod.properties 를 읽어서 사용하면 된다. 스프링은 이미 설정 데이터를 내부에 파일로 분리해 두고 외부 설정값(프로필)에 따라 각각 다른 파일을 읽는 방법을 다 구현해두었다.

 

프로필

스프링은 이런 곳에서 사용하기 위해 프로필이라는 개념을 지원한다. spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단한다. 그리고 프로필에 따라서 다음과 같은 규칙으로 해당 프로필에 맞는 내부 파일(설정 데이터)을 조회한다.

application-{profile}.properties

 

  • IDE에서 커맨드 라인 옵션 인수 실행
    --spring.profiles.active=dev
  • IDE에서 자바 시스템 속성 실행
    -Dspring.profiles.active=dev
  • Jar 실행
    java -Dspring.profiles.active=dev -jar external-0.0.1-SNAPSHOT.jar
    java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev

 

내부 파일 합체

설정 파일을 각각 분리해서 관리하면 한눈에 전체가 들어오지 않는 단점이 있다. 스프링은 이런 단점을 보완하기 위해 물리적인 하나의 파일 안에서 논리적으로 영역을 구분하는 방법을 제공한다.

 

application.properties 라는 하나의 파일 안에서 논리적으로 영역을 나눌 수 있다

  • spring.config.activate.on-profile 에 프로필 값 지정
  • application.properties 구분 방법 #--- 또는 !--- (dash 3) (위아래 주석이 있으면 안된다)
  • application.yml 구분 방법 --- (dash 3)

 

설정 데이터 - 하나의 파일로 통합

위와 같이 application.properties 라는 파일 하나에 통합해서 다양한 프로필의 설정 데이터를 관리할 수 있다

 


우선순위

설정 데이터

  1. 단순하게 문서를 위에서 아래로 순서대로 읽으면서 값을 설정한다. 이때 기존 데이터가 있으면 덮어쓴다.
  2. spring.config.activate.on-profile 옵션이 있으면 해당 프로필을 적용한다. 값이 없으면 default 프로필 적용.
    No active profile set, falling back to 1 default profile: "default"
  3. application.properties보다는 구체적이고 범위가 좁은 application-test.properties가 우선순위가 높아 덮어쓴다.
url=local.db.com
username=local_user
password=local_pw
#---
spring.config.activate.on-profile=dev
url=dev.db.com
username=dev_user
password=dev_pw
#---
spring.config.activate.on-profile=prod
url=prod.db.com
username=prod_user
password=prod_pw

 

가령 프로필이 dev라면, application.properties의 default를 dev로 덮어썼다가, application-dev.properties가 덮어쓰고 남는게 조회된다.

 

전체

원칙

  1. 더 유연한 것이 우선 순위가 높다. (파일보다는 실행 시 인자 전달, 내부 파일보단 빌드 다시할 필요없는 외부 파일)
  2. 범위가 좁은 구체적인 것이 우선 순위가 높다. (OS 환경변수 < 자바 시스템 속성 < 커맨드 라인 옵션 인수)

자주 사용하는 우선순위

  • 설정 데이터( application.properties ) //파일은 유연하지 않아서 우선 순위가 낮다
  • OS 환경변수
  • 자바 시스템 속성
  • 커맨드 라인 옵션 인수
  • @TestPropertySource (테스트에서 사용)

설정 데이터 우선순위

  • jar 내부 application.properties (내부 파일은 jar에 포함돼서 수정 시 다시 빌드해야한다)
  • jar 내부 프로필 적용 파일 application-{profile}.properties (더 구체적이다)
  • jar 외부 application.properties 
  • jar 외부 프로필 적용 파일 application-{profile}.properties

 

정리

이렇게 우선순위에 따라서 설정을 추가하거나 변경하는 방식은 상당히 편리하면서도 유연한 구조를 만들어준다.

실무에서 대부분의 개발자들은 applicaiton.properties 에 외부 설정값들을 보관한다. 이렇게 설정 데이터를 기본으로 사용하다가 일부 속성을 변경할 필요가 있다면 더 높은 우선순위를 가지는 자바 시스템 속성이나 커맨드 라인 옵션 인수를 사용하면 되는 것이다.

또는 기본적으로 application.properties 를 jar 내부에 내장하고 있다가, 특별한 환경에서는 application.properties 를 외부 파일로 새로 만들고 변경하고 싶은 일부 속성만 입력해서 변경하는 것도 가능하다.

 


참고 자료 & 이미지 출처
스프링 부트 - 핵심 원리와 활용 (김영한 님)