spring boot 프로젝트를 EC2에 배포하면서 알게된 내용을 요약 정리하는 글입니다.
application.yml에는 데이터베이스 커넥션을 위한 DB정보들이 담깁니다.
실제 운영서버에서는 어떻게든 이 DB 정보들을 외부로 노출시키지 않아야합니다.
그러기 위해서 Jasypt를 이용해서 application.yml의 주요 DB 정보들을 암호화시켰습니다.
레포지토리를 private으로 바꾸는 방법도 있었지만 단편적인 방법이란 생각과 함께 인적 실수로 인해 레포지토리를 public으로 돌렸을 경우를 대비할 수 없기에 해당 방법은 사용하지 않았습니다.
EC2에 버전에 맞는 Java와 Git이 설치되어 있다는 가정하에 기록합니다.
git을 이용해 프로젝트 레포지토리를 클론합니다.
./gradlew build
를 이용해 빌드를 하려고 했는데 아래의 이미지와 같이 빌드가 되지 않았습니다.
일단 해당 명령어가 테스트를 수행하는지도 처음 알게 됐습니다.
테스트를 수행한다면 DB 연결을 시도할테고 해당 DB에 접근하는 DB 정보가 암호화되어 있고 그 정보를 복호화할 수 없으니 해당 테스트가 실패하는 것으로 확인했습니다.
따라서 Jasypt를 이용해서 암호화한 DB 정보를 복호화할 key를 명령어들에 입력해서 해당 문제를 해결했습니다.
두 개의 단계에서 복호화할 수 있는 key가 필요했습니다.
$ ./gradlew build -Pjasypt.encryptor.password='jasypt_password'
$ java -jar ./api/build/libs/api-0.0.1-SNAPSHOT.jar --jasypt.encryptor.password='jasypt_password'
추가적으로 jasypt에 해당 시스템 변수를 사용하는 모듈의 build.gradle 파일에 다음 설정을 추가해야 합니다.
test {
useJUnitPlatform()
systemProperty 'jasypt.encryptor.password', findProperty("jasypt.encryptor.password")
}
여기까지만 봐도 해당 문제는 해결이 됐습니다.
하지만 gradle build시 어떤 task들이 같이 실행되고 여러가지 해결 방법이 있는지 공부하기 위해서 추가적인 자료 조사를 실시했습니다.
아래의 명령어를 실행해보면 그래들 빌드 시 어떠한 태스크를 수행하는지 확인할 수 있었습니다.
--console=plain
옵션이 실행되는 태스크를 콘솔에 프린트해줍니다.
$ ./gradlew build -Pjasypt.encryptor.password='jasypt_password' --console=plain
:api:test 태스크가 수행되는 과정에서 DB 커넥션이 발생하는 것을 확인했습니다.
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :jar UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE
> Task :core:compileJava UP-TO-DATE
> Task :api:compileJava UP-TO-DATE
> Task :api:processResources UP-TO-DATE
> Task :api:classes UP-TO-DATE
> Task :core:processResources UP-TO-DATE
> Task :core:classes UP-TO-DATE
> Task :core:jar UP-TO-DATE
> Task :api:bootJarMainClassName UP-TO-DATE
> Task :api:bootJar UP-TO-DATE
> Task :api:jar UP-TO-DATE
> Task :api:assemble UP-TO-DATE
> Task :api:compileTestJava UP-TO-DATE
> Task :api:processTestResources NO-SOURCE
> Task :api:testClasses UP-TO-DATE
> Task :api:test
2023-07-25 23:56:37.538 INFO 43162 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-07-25 23:56:37.540 INFO 43162 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2023-07-25 23:56:37.542 DEBUG 43162 --- [ionShutdownHook] org.hibernate.SQL :
alter table expense_transaction
drop
foreign key FKhtc5v7xro83h7vt6i0qx3cbir
2023-07-25 23:56:37.587 DEBUG 43162 --- [ionShutdownHook] org.hibernate.SQL :
alter table income_transaction
drop
foreign key FKqy4sbxsch0jfsj8m5qgmobk8w
2023-07-25 23:56:37.615 DEBUG 43162 --- [ionShutdownHook] org.hibernate.SQL :
drop table if exists expense_transaction
2023-07-25 23:56:37.664 DEBUG 43162 --- [ionShutdownHook] org.hibernate.SQL :
drop table if exists income_transaction
2023-07-25 23:56:37.705 DEBUG 43162 --- [ionShutdownHook] org.hibernate.SQL :
drop table if exists transaction
2023-07-25 23:56:37.748 INFO 43162 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2023-07-25 23:56:37.823 INFO 43162 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
> Task :api:check
> Task :api:build
> Task :core:bootJarMainClassName
> Task :core:bootJar SKIPPED
> Task :core:assemble UP-TO-DATE
> Task :core:compileTestJava
> Task :core:processTestResources NO-SOURCE
> Task :core:testClasses
> Task :core:test
> Task :core:check
> Task :core:build
대략적으로 살펴보면 아래와 같은 build task 실행 시 아래의 sub task들이 수행됩니다.
compileJava
processResources
classes
compileJava
태스크와 processResources
태스크에 의존합니다. 즉 classes
태스크를 실행하면 compileJava
, processResources
태스크가 실행됩니다.jar
classes
태스크에 의존합니다.assemble
compileTestJava
compileJava
와 같습니다processTestResources
processResources
와 같습니다testClasses
classes
와 같습니다test
check
test
태스크에 의존적입니다.build
check
, assemble
태스크에 의존적입니다.bootJarMainClassName
bootJar
bootJarMainClassName
태스크에 의존적입니다.bootJar { false }
라는 옵션을 추가하면 해당 모듈은 실행가능하지 않은 xxx-plain.jar 파일만 libs 폴더에 빌드됩니다.bootJar 태스크와 bootJarMainClassName 태스크는 그래들에서 기본제공하는 태스크가 아니라 스프링부트에서 제공하는 태스크입니다.
id 'org.springframework.boot' version '2.7.14' 적용에 따라서 추가되는 task 입니다.
이 링크를 참조하시면 됩니다. https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#packaging-executable.jars
위의 문서에 따르면 bootJar 태스크는 assemble 태스크 실행시 자동 실행되도록 설정되어 있다고 설명하고 있습니다.
어느 블로그에서 공통으로 사용할 모듈은 실행할 모듈이 아니므로 bootJar 옵션을 false로 설정하는 것이 좋은 방향이라는 글을 읽었습니다.
bootJar를 false 설정 할 시 jar { enabled = true } 옵션을 줘야한다는 글을 읽고 확인 없이 해당 옵션들을 적용했습니다.
이번 기회에 공부를 하면서 bootJar 옵션을 꺼도 xxx-plain.jar 파일이 빌드된다는 사실을 알게 되었고, 공통 모듈인 core 모듈에 적용되어 있는 jar { enabled = true } 옵션을 삭제한다면 어떻게 동작할까 라는 궁금증이 들었습니다.
실제 테스트를 해보니 bootJar를 { enabled = false }로 설정해도 xxx-plain.jar 파일이 생성되고 정상적으로 서버가 동작하는 것을 확인했습니다.
조금 더 확인해보니 스프링부트 2.4.11 버전 이전에는 따로 설정하지 않는 한 jar task가 SKIP되어 xxx-plain.jar 파일이 생성되지 않았고, bootJar를 false 설정했으므로 jar task가 실행이 안될 것이기에 의도적으로 jar { enabled = true} 설정을 줘야한다는 의도의 글이었던 것 같습니다.
이 프로젝트는 2.7이상의 버전이므로 core 모듈의 jar { enabled = true} 옵션은 전혀 필요없을 것으로 판단했고, 실제 테스트 해보니 해당 옵션을 지워도 서버가 잘 동작하는 것을 확인했습니다.
// 공통 모듈인 core모듈의 build.gradle
// 가장 아래쪽에 적용된 jar {enabled = true} 옵션이 필요없는 것으로 확인
plugins {
id 'java-library'
}
dependencies {
// spring-data-jpa
api 'org.springframework.boot:spring-boot-starter-data-jpa'
// h2
testRuntimeOnly 'com.h2database:h2'
// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
bootJar {
enabled = false
}
// jar {
// enabled = true
// }
build과정에서 테스트를 수행하므로 스프링부트를 구동시켜서 DB 커넥션이 발생하는 것이 빌드가 실패한 주요 원인이었습니다.
특정 상황에서는 테스트만 건너뛰고 빌드를 할 수 없을까라는 생각이 들었고 그래들을 조금 더 찾아보았고 아래와 같은 추가적인 옵션들이 있는 것을 발견했습니다.
./gradlew build --exclude-task test
./gradlew build --continue
위의 옵션들은 정말 필요에 따라 사용 여부를 판단할 수 있을 것 같습니다.
많은 도움이 되었습니다, 감사합니다.