Spring Boot 설정 우선순위 (외부 yml, launch.sh, systemctl)

Spring Boot 프로젝트를 처음 운영할 때 저도 application.yml을 소스 내부에 그냥 넣어서 관리했습니다.

처음엔 단순하고 편했는데, 운영 서버가 네다섯 개로 늘어나면서 생각지도 못한 장애가 연달아 터졌습니다.

그때 겪은 시행착오와 결국 자리잡게 된 운영 전략을 솔직하게 풀어보겠습니다.

처음엔 몰랐던 설정 우선순위의 함정

Spring Boot는 설정 파일을 여러 위치에서 읽을 수 있고, 동일한 키가 여러 곳에 존재하면 우선순위가 높은 쪽이 최종 적용됩니다.

이걸 처음부터 제대로 이해하는 개발자가 생각보다 많지 않습니다. 저도 한동안은 그냥 application.yml 하나면 되는 거 아닌가 싶었으니까요.

Externalized Configuration이란 애플리케이션 외부에서 설정값을 주입하는 방식을 뜻합니다.

쉽게 말해, JAR 파일 안에 설정을 묶어넣지 않고 서버의 특정 경로나 환경 변수로 설정을 분리하는 것입니다.

Spring Boot는 이 방식을 공식적으로 권장하고 있고, 실제로 Spring 공식 문서에도 Profiles 및 외부 설정 사용 전략이 상세히 안내되어 있습니다.

우선순위를 간단히 정리하면, Command Line Arguments가 가장 강하고, 그 아래로 JVM System Properties, OS 환경 변수, 외부 application.yml, 그리고 JAR 내부 application.yml 순으로 낮아집니다.

즉, 내부 설정 파일은 우선순위가 가장 낮습니다. 직접 겪어보니 이 순서를 모르는 상태에서 운영하면 “왜 이 설정이 안 먹히지?”라는 상황이 반드시 한 번은 터집니다.

내부 application.yml만 믿다가 터진 일들

문제가 본격적으로 드러난 건 운영 서버가 개발, QA, 운영, 고객사 서버로 나뉘면서부터였습니다. 환경마다 DB 접속 정보도 다르고, API URL도 다르고, 로그 레벨도 달랐습니다.

그때 저희 팀이 쓰던 방식은 배포 전에 application.yml을 해당 환경에 맞게 직접 수정하고 다시 빌드하는 것이었습니다.

Spring Profiles란 환경별로 다른 설정을 활성화할 수 있는 기능입니다. 예를 들어 spring.profiles.active 값을 dev로 지정하면 application-dev.yml이 함께 로드됩니다. 이 기능 자체는 편리하지만, 수동으로 값을 바꿔가며 빌드하는 구조에서는 결국 사람이 실수할 여지가 계속 생깁니다.

실제로 제가 경험했던 사고들을 떠올려보면 지금도 아찔합니다.

  1. 운영 서버에 dev profile이 그대로 배포되어 개발 DB에 실 트래픽이 붙은 일
  2. 로그 레벨이 DEBUG인 채로 운영 배포가 나가서 디스크가 순식간에 꽉 찬 일
  3. 야간 긴급 배포 중 고객사 전용 설정이 누락되어 다음날 아침에야 발견된 일
  4. profile 오타로 인해 서버가 기동 자체를 실패했는데 알림이 늦게 온 일

특히 profile 관련 오류는 배포 직후 바로 발견되지 않는 경우도 많았습니다.

서버는 멀쩡히 뜨는데 내부적으로 엉뚱한 환경으로 돌아가고 있는 거라 한참 후에야 이상함을 눈치채는 경우가 종종 있었습니다.

그 느낌이 얼마나 당혹스러운지는 한 번 겪어본 사람만 압니다.

외부 application.yml 전략으로 바꾸고 나서

그래서 운영 구조 자체를 바꿨습니다. 핵심은 간단합니다.

JAR 내부에는 공통 설정만 두고, 환경별 설정은 배포 경로에 별도로 꺼내두는 것입니다. 예를 들면 서버의 ./ 경로 아래에 application.yml과 service.jar를 나란히 두는 구조입니다.

Spring Boot는 JAR 실행 시 현재 작업 디렉토리에 application.yml이 있으면 내부 파일보다 그것을 먼저 읽습니다.

이 동작 덕분에 JAR를 새 버전으로 교체해서 재배포하더라도 외부에 두었던 설정 파일은 그대로 유지됩니다. 빌드와 설정이 완전히 분리되는 셈입니다.

이 구조로 바꾸고 나서 가장 크게 달라진 건 설정 변경에 재빌드가 필요 없어졌다는 점입니다. DB 접속 정보가 바뀌거나 API URL이 달라지더라도 외부 application.yml만 수정하고 서비스를 재시작하면 됩니다. 솔직히 이건 예상 밖으로 운영 편의성이 크게 올라간 부분이었습니다.

launch.sh란 java -jar 실행 명령어와 JVM 옵션을 셸 스크립트로 묶어둔 실행 스크립트입니다. 단순히 명령어를 모아둔 것 같지만, 이게 있고 없고의 차이가 상당합니다. 저는 아래와 같은 형태로 관리했습니다.

스크립트 안에서 -Dspring.profiles.active=dev-altibase처럼 JVM System Property로 profile을 명시하면, 누가 어떤 환경에서 실행해도 항상 같은 profile이 고정됩니다. 사람이 직접 yml을 건드리거나 기억에 의존할 필요가 없어지는 것입니다. 제 경험상 이것만으로 profile 관련 휴먼에러가 눈에 띄게 줄었습니다.

systemctl로 운영 프로세스까지 한 번에 잡기

외부 yml과 launch.sh 조합으로 설정 문제는 많이 잡혔는데, 또 다른 문제가 남아 있었습니다. 서버가 재부팅되거나 프로세스가 예기치 않게 죽었을 때 자동으로 복구가 안 된다는 점이었습니다. 새벽에 서버가 재시작된 후 아무도 모르게 서비스가 멈춰 있던 경험이 한두 번이 아니었습니다.

systemctl이란 Linux 시스템에서 서비스 프로세스를 등록하고 관리하는 데몬 제어 도구입니다. 쉽게 말해 애플리케이션을 OS 수준의 서비스로 등록해두면 서버가 켜질 때 자동으로 실행되고, 프로세스가 죽으면 자동으로 재시작됩니다.

서비스 파일 안에 WorkingDirectory를 JAR가 있는 경로로 지정하고, ExecStart에 launch.sh 경로를 연결하면 됩니다. Restart=always 옵션을 주면 비정상 종료 시 자동으로 다시 띄워줍니다. 이 설정을 마치고 나서는 새벽에 알람 울리는 횟수가 확실히 줄었습니다.

journald란 systemd와 연동되는 로그 수집 데몬입니다. systemctl로 관리되는 서비스는 journalctl 명령으로 로그를 한 번에 조회할 수 있어서 여러 서버의 로그를 추적할 때도 편리합니다. 별도 로그 설정을 하기 전에 기본으로 쓸 수 있다는 점에서 초반 운영 부담을 많이 줄여줬습니다. systemd 공식 문서에 서비스 파일 작성 방법이 상세히 나와 있으니, 처음 설정할 때 참고하면 도움이 됩니다.

정리하면, Spring Boot 설정 우선순위를 이해하는 것만큼 중요한 건 그 이해를 바탕으로 운영 구조를 어떻게 짜느냐입니다. 외부 application.yml로 설정을 분리하고, launch.sh로 JVM 옵션과 profile을 고정하고, systemctl로 프로세스를 관리하는 세 가지 조합이 제 경험상 가장 안정적인 구조였습니다. 운영 서버가 두 대만 넘어가도 이 구조를 초반에 잡아두는 게 훨씬 낫습니다. 나중에 바꾸려면 손이 두 배로 들어갑니다.

FAQ

Q. application.yml를 외부에 두는 이유는 무엇인가요?

운영 환경별 설정을 분리하고 재배포 없이 설정 변경이 가능하기 때문입니다.

또한 profile 관련 휴먼에러를 줄이는 데 도움이 됩니다.


Q. 내부 application.yml만 사용하면 안 되나요?

가능은 하지만 운영 환경이 많아질수록 설정 실수 가능성이 매우 높아집니다.

실무에서는 외부 설정 파일 사용을 권장합니다.


Q. launch.sh를 사용하는 이유는 무엇인가요?

운영 옵션을 명확하게 관리할 수 있기 때문입니다.

특히 JVM Option 및 profile 관리를 통합하기 편합니다.


Q. systemctl 서비스를 사용하는 이유는 무엇인가요?

운영 서버에서 프로세스를 안정적으로 관리하기 위해 사용합니다.

자동 재시작 및 서버 부팅 시 자동 실행 기능도 제공됩니다.

댓글 남기기