최근에 스프링 설정 파일 구조를 개선하다가 위험한 상황을 마주쳤는데, 같은 실수를 반복하지 않고자 글을 적어보려 합니다.
프로젝트는 크게 api, application, rdb 모듈로 이루어져있습니다.
api 모듈은 application 모듈에 의존하고, application 모듈은 rdb 모듈에 의존하고 있습니다.
rdb 모듈에 데이터베이스 관련 정보를 적어놨고, 상위인 api 모듈에서 active profile을 설정할 수 있도록 구성했습니다.
rdb 모듈의 설정 파일은 아래와 같습니다.
server:
shutdown: graceful
spring:
jpa:
open-in-view: false
---
spring:
config:
import:
- optional:application-secret-rdb
activate:
on-profile: prod
jpa:
...
---
spring:
config:
activate:
on-profile: local
datasource:
url: jdbc:mysql://localhost:3307/nottodo?allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=Asia/Seoul
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
...
---
spring:
config:
activate:
on-profile: test
datasource:
hikari:
driver-class-name: org.h2.Driver
jdbc-url: jdbc:h2:mem://localhost/~/nottodo;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;DB_CLOSE_ON_EXIT=false;TIME ZONE=Asia/Seoul
username: sa
password:
jpa:
...
prod 환경인 경우 github에 데이터베이스 정보가 유출되면 안되기 때문에 서브 모듈을 두어 prod profile 인 경우에만 데이터베이스 정보를 받을 수 있도록 설정했습니다.
yml 파일을 작성하면서 optional 이라는 것을 처음 사용해보았는데요.
optional은 말그대로 있으면 사용하고, 없으면 사용하지 않는다는 뜻입니다.
위에 작성한 yml 파일을 예로들면 applicaiton-secret-rdb.yml
파일이 있으면 해당 환경변수 파일을 import 하고, 없는 경우 import 하지 않습니다.
그런데, 이 기능을 설정하면서 prod의 데이터베이스를 초기화시켰습니다..
개발할때는 active profile을 local로 설정했는데 어떻게 날릴 수 있었을까요?
환경 설정 파일의 우선순위 때문입니다.
optional의 경우 있으면 해당 환경 설정 파일을 사용한다 정도가 아니라 해당 환경 설정으로 덮어씌웁니다. active profile에 상관없이 application-secret-rdb.yml
파일이 존재하면 해당 파일을 import하여 기존 datasource를 덮어씌웁니다.
active profile의 우선순위가 optional의 우선순위보다 낮다는 뜻입니다.
첫 실행을 했을 때 "왜 local DB가 안보이지? 분명 잘 설정했는데...". 원래는 ddl-auto
옵션을 update
로 설정하는데, 잘 되지 않아 local profile의 ddl-auto
옵션을 create
로 바꾸어보았습니다. 그렇게 prod DB의 데이터가 초기화되었고 복구하는데 고생했습니다.
처음에 제가 optional을 사용한 의도는 application-secret-rdb.yml
파일이 없어도 FileNotFoundException과 같은 오류를 내지 않기 위해서입니다. 동료 개발자가 prod의 데이터베이스 정보를 알지 않아도 개발에 지장없게끔 하고 싶었습니다.
기존에는 어플리케이션 실행 시에 gradle에서 서브모듈 파일을 복사하고, 복사본을 읽을 수 있도록 했습니다. 이렇게 하는 경우 실행할 때마다 서브모듈의 파일이 계속 복사되기 때문에 yml의 optional 옵션에 걸리게 되고, local profile 이더라도 prod db를 읽어오는 참사가 일어납니다.
저는 prod profile 인 경우에는 서브모듈의 파일을 복사하고, 빌드하고 나선 복사본을 제거하는 간단한 방법으로 문제를 해결했습니다.
gradle 코드는 아래와 같습니다.
val copyYml = tasks.register<Copy>("copyYml") {
from("../../server-secret/application-secret-rdb.yml")
into("src/main/resources/")
doLast {
val file = file("src/main/resources/application-secret-rdb.yml")
if (file.exists()) {
file.delete()
}
}
}
tasks.processResources {
if (project.hasProperty("spring.profiles.active") && project.property("spring.profiles.active") == "prod") {
dependsOn(copyYml)
}
}
반년만에 글을 써봤습니다.
첫 시작이 어렵다는데, 일단 어려운 고비 다시 넘겼으니 계속 글을 남겨보겠습니다.
짧은 글이지만 읽어주셔서 감사합니다.