스프링 부트와 AWS로 혼자 구현하는 웹 서비스 를 공부하고 정리한 내용입니다.
1 ~ 5장까지는 스프링 부트로 서비스 코드를 개발했고, 6 ~ 7장까지는 배포 환경을 구성했다!
이제 이들을 조합해 실제로 서비스를 배포해보자!
책에서는 MariaDB를 사용했지만, Intellij Ultimate에서 사용하기 좋은 것 같다.
나는 Intellij Community을 사용해서 MYSQL을 사용했다.
먼저, EC2로 접속한다.
sudo yum install git : git 설치
mkdir ~/app && mkdir ~/app/step1 : 저장할 디렉터리 생성
cd ~/app/step1 : step1으로 이동
git clone 레포지토리주소
# 프로젝트 디렉터리로 이동한 후
./gradlew test
을 실행한다.
💡 참고
- EC2엔 그레이들(Gradle)을 설치하지 않았다.
- 하지만, Gradle Task(ex: test)를 수행할 수 있다.
- 이는 프로젝트 내부에 포함된
gradlew 파일
때문이다.- 그레이들이 설치되지 않은 환경 혹은 버전이 다른 상황에서도 해당 프로젝트에 한해서 그레이들을 쓸 수 있도록 지원하는 Wrapper 파일이다.
- 해당 파일을 직접 이용하기 때문에 별도로 설치할 필요가 없다.
ec2에서 프로젝트 clone 후, ./gradlew build
를 한다. (위에서는 ./gradlew test
)
그리고 나서 잘실행되는지 테스트를 해본다.
java -jar springawsbook-0.0.1-SNAPSHOT.jar &
실행을 하니
bash 출력된 내용
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Connection is broken: "java.net.ConnectException: Connection refused (Connection refused): localhost" [90067-200]
~
2022-06-04 13:25:52.940 ERROR 12540 --- [ main] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name 'sessionRepositoryFilterRegistration' defined in class path resource [org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.class]: Unsatisfied dependency expressed through method 'sessionRepositoryFilterRegistration' parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.session.JdbcSessionConfiguration$SpringBootJdbcHttpSessionConfiguration': Unsatisfied dependency expressed through method 'setTransactionManager' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcSessionDataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.session.JdbcSessionDataSourceScriptDatabaseInitializer]: Factory method 'jdbcSessionDataSourceScriptDatabaseInitializer' threw exception; nested exception is java.lang.IllegalStateException: Unable to detect database type
2022-06-04 13:25:53.009 INFO 12540 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2022-06-04 13:25:53.022 WARN 12540 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
2022-06-04 13:25:53.051 INFO 12540 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-06-04 13:25:53.102 ERROR 12540 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
~~
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sessionRepositoryFilterRegistration' defined in class path resource [org/springframework/boot/autoconfigure/session/SessionRepositoryFilterConfiguration.class]: Unsatisfied dependency expressed through method 'sessionRepositoryFilterRegistration' parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.session.JdbcSessionConfiguration$SpringBootJdbcHttpSessionConfiguration': Unsatisfied dependency expressed through method 'setTransactionManager' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcSessionDataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.session.JdbcSessionDataSourceScriptDatabaseInitializer]: Factory method 'jdbcSessionDataSourceScriptDatabaseInitializer' threw exception; nested exception is java.lang.IllegalStateException: Unable to detect database type
~~
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcSessionDataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.session.JdbcSessionDataSourceScriptDatabaseInitializer]: Factory method 'jdbcSessionDataSourceScriptDatabaseInitializer' threw exception; nested exception is java.lang.IllegalStateException: Unable to detect database type
~~
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jdbcSessionDataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/session/JdbcSessionConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.autoconfigure.session.JdbcSessionDataSourceScriptDatabaseInitializer]: Factory method 'jdbcSessionDataSourceScriptDatabaseInitializer' threw exception; nested exception is java.lang.IllegalStateException: Unable to detect database type
3시간정도 구글링을 했지만 찾을 수 없어 위 내용을 읽어봤다.
그런데? 위 출력 내용을 보면
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Connection is broken: "java.net.ConnectException: Connection refused (Connection refused): localhost" [90067-200]
Factory method 'jdbcSessionDataSourceScriptDatabaseInitializer' threw exception; nested exception is java.lang.IllegalStateException: Unable to detect database type
위 두 문장이 유독 반복되거나 이유를 말해주는 것 같았다.
localhost
를 연결할 수 없다.문뜩, 프로젝트 application.yml
에서 h2 database를 사용하기 위해
datasource
를 작성했던 게 기억이 나서 확인했다.
나는 ec2 가상 서버를 만든 후, 프로젝트 서버를 실행시킨 상태이다.
즉, 그림에서 보이는 url jdbc:h2:tcp//localhost//Users/leekyoungchang/~~~
경로가 ec2 가상서버에는 없다.
그래서, 위 두 문장 오류가 발생했다는 것을 알게 되었다.
datasource를 주석처리 git push 하여 ec2 가상서버에 전달한 후
./gradlew build
로 build를 수정한 후
libs
에서 실행하면, database에 접근도 가능하고, 책 배포 스크립트 만들기에서 말한 ClientRegistrationRepository
오류를 만나게 된다. (이 오류는 다음 3. 외부 Security 파일 등록하기를 통해 해결할 수 있다.)
🔔 배포란?
- git clone 혹은 git pull을 통해 새 버전의 프로젝트 받음
- Gradle이나 Maven을 통해 프로젝트 테스트와 빌드
- EC2 서버에서 해당 프로젝트 실행 및 재실행
이전까지 배포할 때마다 개발자가 하나하나 명령어를 실행했다.
이제 쉘 스크립트로 작성해 스크립트만 실행하면 앞의 과정이 차례로 진행되도록 하겠다.
💡 참고
쉘 스크립트 :.sh
라는 파일 확장자를 가진 파일이다.
vim : 리눅스 환경과 같이 GUI가 아닌 환경에서 사용할 수 있는 편집 도구
책에서는 빔으로 리눅스 환경에서의 편집을 진행한다.
vim ~/app/step1/deploy.sh
: deploy.sh 파일을 하나 생성한다.
#!/bin/bash
# 자주 사용하는 값 변수에 저장
REPOSITORY=/home/ec2-user/app/step1
PROJECT_NAME=springawsbook/springawsbook
# git clone 받은 위치로 이동
cd $REPOSITORY/$PROJECT_NAME/
# master 브랜치의 최신 내용 받기
echo "> Git Pull"
git pull
# build 수행
echo "> project build start"
./gradlew build
echo "> directory로 이동"
cd $REPOSITORY
# build의 결과물 (jar 파일) 특정 위치로 복사
echo "> build 파일 복사"
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/
echo "> 현재 구동중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)
echo "> 현재 구동중인 애플리케이션 pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -15 $CURRENT_PID"
kill -15 $CURRENT_PID
sleep 5
fi
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)
echo "> Jar Name: $JAR_NAME"
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
REPOSITORY=/home/ec2-user/app/step1
PROJECT_NAME=freelec-springboot2-webservice
도 동일하게 변수로 저장한다.$ 변수명
으로 변수를 사용할 수 있다.cd $REPOSITORY/$PROJECT_NAME/
/home/ec2-user/app/step1/freelec-springboot2-webservice
주소로 이동한다.git pull
./gradlew build
cp ./build/libs/*.jar $REPOSITORY/
CURRENT_PID=$(pgrep -f springboot-webservice)
-f 옵션
은 프로세스 이름으로 찾는다.if ~ else ~ fi
process id 값
을 보고 프로세스가 있으면 해당 프로세스를 종료한다.JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
chmod + x ./deploy.sh
: 이제 이렇게 생성한 스크립트에 실행 권한을 추가한다.
./deploy.sh
를 실행한다.
실행이 잘된다. 이제 nohup.out
파일을 열어서 로그를 보자.
nohup.out
: 실행되는 애플리케이션에서 출력되는 모든 내용을 갖고 있다.
nohup.out
제일 아래로 가면 ClientRegistrationRepository
를 찾을 수 없다는 에러가 발생하면서 애플리케이션 실행에 실패했다.
💡 참고
프로젝트 clone한 후, 간단한 실행테스트를 해본다.
build 디렉터리를 대상으로gradlew
을 해줘야 한다. (./gradlew build
)
cd build/libs
로 이동해java -jar springawsbook-0.0.1-SNAPSHOT.jar &
실행
➡ 아마,
application-oauth.yml
이 github repository에 없으면 (.gitignore
에 추가했을 경우) ClientRegistrationRepository를 찾을 수 없다는 에러가 발생할 것이다. (실행 실패, 이러면 된거임)
ClientRegistrationRepository
를 생성하려면 clientId
와 clientSecret
가 필수이다.
로컬에서는 실행할 때, application-oauth.yml
가 있어 문제가 없었다.
vim /home/ec2-user/app/application-oauth.yml
yml
파일을 생성한다.application-oauth.yml
파일 내용을 그대로 붙여넣는다.그리고 생성한 application-oauth.yml
을 쓰도록 deploy.sh
파일을 수정한다.
nohup java -jar \
-Dspring.config.location=classpath:/application.yml,/home/ec2-user/app/application-oauth.yml \
$REPOSITORY/$JAR_NAME 2>&1 &
Dspring.config.location
application.yml
과 OAuth 설정들을 담고 있는 application-oauth.yml
의 위치를 지정한다.application-oauth.yml
은 절대경로를 사용한다. 외부에 파일이 있기 때문이다.
수정이 다 되었다면 다시 deploy.sh
를 실행해본다.
실행 성공!