그동안 나는 간단한 CRUD 기능을 가지는 어플리캐이션을 개발했고
이를 서버환경에 올리기 위해 AWS 환경에서 EC2 서버와 RDS 환경을 구축했다.
이제 어플리케이션을 AWS 환경에서 배포하는 작업을 시작하도록 하자.
그동안 개발한 어플리케이션은 git을 통해서 형상관리를 하고 있었다.
이제 git에서 EC2서버로 프로잭트 clone을 받도록 하자.
우선 git을 EC2 서버에 설치해야한다.
sudo yum install git
설치가 완료되면 설치상태를 확인해보자.
git --version
정상적으로 설치된 것을 확인했으니 이제 clone을 받을 저장소 디렉토리를 생성하자.
생성한 뒤에 해당경로로 이동하도록 한다.
mkdir ~/app && mkdir ~/app/step1
cd ~/app/step1
내 github 사이트에서 URL을 복사한다.
copy 버튼을 클릭해서 복사한 뒤 clone을 진행한다.
이제 코드 수행 검증을 해보도록 하자.
나같은 경우는 권한에 대한 이슈가 발생해서 권한부터 부여하고 시작했다.
chmod +x ./gradlew
./gradlew test
만약 실패했다면 수정사항을 수정해서 git에 반영하고 이를 다시 pull해야 한다.
git pull
배포란 작성된 코드를 실제 서버에 반영하는 것을 말한다.
배포의 과정은 다음과 같이 요약이 가능하다.
이 과정을 수행하면서 개발자가 직접 명령어를 실행을 해야 하는데,
이러한 반복작업으로 발생하는 손실을 줄이기 위해서 배포 스크립트를 작성할 것이다.
vim을 이용해서 배포 스크립트 쉘을 작성하도록 하자.
vim ~/app/step1/deploy.sh
작성한 스크립트 의 권한을 주자.
chmod +x ./deploy.sh
완성된 스크립트를 실행해보자.
./deploy.sh
오류가 발생했다.
42라인을 가보니 $CURRENT_PID뒤에 " 가 빠져있었다.
(인텔리제이가 몹시 그립다. 나처럼 무식하게 vim에서 직접 작성하지 않기를 권한다. )
수정후 다시 실행하보자.
이제 배포가 잘되었으니 nohup.out 파일을 열어 로그를 확인해보자.
vim nohup.out
에러 내용을 확인하자.
no main manifest attribute, in /home/ec2-user/app/step1/spring-aws-study-0.0.1-SNAPSHOT-plain.jar
요약하자면 'spring-aws-study-0.0.1-SNAPSHOT-plain.jar'를 실행하기 위한 메인메소드를 찾지 못했다는 뜻이다.
보통 프로잭트를 빌드하면 MANIFEST.MF 라는 파일이 작성되고 여기에 메인메소드에 대한 정보가 담겨있다.
지금 에러가 발생한 'spring-aws-study-0.0.1-SNAPSHOT-plain.jar'는 이 정보가 결손이 발생한 것이다.
결손이 난 이유는 spring boot 2.5.0버전 이상부터는 gradle로 빌드를 할때 jar파일이 2개 생성하는데 하나는 앱이름.jar -> bootJar Task로 생성되고 또 다른 하나는 앱이름-plain.jar -> build Task로 생성된다.
여기서 'spring-aws-study-0.0.1-SNAPSHOT-plain.jar'는 의존성을 제외하고 딱 프로젝트에 있는 자원들만 jar로 만들어진다.
그래서 spring 관련 의존성이 빠져 MANIFEST.MF에 Main메소드의 위치가 나오지 않는다.
해당 jar를 생성하지 않도록 bundle.gralde에 설정을 추가하자.
jar {
enabled = false
}
실행을 해보니 정상적으로 서버가 올라갔다.
이러면 안되는데 왜 정상적으로 올라간 것인가!
nohup.out에서 출력된 메세지들을 쭉 살펴보고 생각을 잠시 해봤다.
왜 정상으로 작동했는가?
이유를 알았다.
그 동안 개발할때 나는 master에 직접 commit하지 않고 별도의 branch를 생성하여 작업물을 commit했다.
그리고서 master에 PR을 한번도 하지 않았던 것이다.
작업물이 Merge되지 않은 상태이기 때문에 기본적인 프로잭트 상태가 서버에 배포된 것이고 이상태라면 에러가 안나는것이 정상이다.
빠르게 git에서 branch에 접속하여 PR을 했다.
이제 다시 배포 스크립트를 실행하보자.
콘솔에서 그동안 반영하지 않았던 작업 소스들이 반영되는 모습이다.
그런데 빌드를 시작하는 시점에서 자바메모리로 인한 에러가 발생했다.
배포스크립트에서 java 실행시 메모리 설정하는 부분을 추가하자.
나는 t2.micro 타입에 메모리가 1GB짜리 서버를 사용중이다.
그래서 256~512로 메모리를 설정해주려고 한다.
다시 배포 스크립트를 실행해보자.
에러가 없이 배포는 잘 진행 되었다.
다시 nohup.out을 확인해보자.
2021-11-12 15:43:07.435 WARN 25635 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' available
스프링 시큐리티 관련 에러가 발생했다.
우리는 oauth관련 별도의 properties를 git에 반영하지 않았음을 상기해야 한다.
에러가 나야 정상인 상태이다.
서버에서 사용할 application-oauth.properties를 생성하자.
vim /home/ec2-user/app/application-oauth.properties
그리고 로컬에 있는 application-oauth.properties 내용을 복사해서 붙인다.
이제 배포 스크립트에 application-oauth.properties를 쓰도록 수정해주자.
nohup java -Xms256m -Xmx512m -jar \ -Dspring.config.location=classpath:/application.properties, /home/ec2-user/app/application-oauth.properties \ JAR_NAME 2>&1 &
이제 부트 프로젝트에서 RDS로 접근해야 한다.
접근을 위한 3가지 설정을 해야한다.
DB 테이블을 생성하자.
JPA에서 만들어주는 쿼리를 이용해서 편리하게 할 수 있고
약간 조심해서 사용해야하는 자동생성 방법을 이용할 수도 있다.
(하지만 자동생성 방식을 사용하면 대형사고가 발생할 수 있다.
create-drop으로 설정하면 무조건 기존 테이블들을 전부 날리고 시작하기 때문이다.
절대 비추천한다. sql로 생성하길 권하고 로컬에서 H2 DB로 만들어지는 쿼리를 다른 DB로 설정을 바꿔서 생성하게하면 쉽게 쿼리문을 생성할 수 있다.)
create table posts
(
id bigint not null auto_increment,
create_date datetime(6),
modified_date datetime(6),
author varchar(255),
content TEXT not null,
title varchar(500) not null,
primary key (id)
) engine = InnoDB;
create table user
(
id bigint not null auto_increment,
create_date datetime(6),
modified_date datetime(6),
email varchar(255) not null,
name varchar(255) not null,
picture varchar(255),
role varchar(255) not null,
primary key (id)
) engine = InnoDB;
CREATE TABLE SPRING_SESSION
(
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE = InnoDB
ROW_FORMAT = DYNAMIC;
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES
(
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION (PRIMARY_ID) ON DELETE CASCADE
) ENGINE = InnoDB
ROW_FORMAT = DYNAMIC;
프로젝트에 추가할 MariaDB 의존성을 bundle.gradle에 추가하자.
서버에서 사용할 application-real.properties를 생성하자.
수정된 파일들을 git에 push하자.
그리고 나같은 경우는 branch를 사용하고 있기 때문에 반드시 PR을 해서 master에 반영해야 한다.
EC2서버에서 사용해야할 DB properties를 생성하자.
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mariadb://RDS주소:3306/데이터베이스이름
spring.datasource.username=계정명
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
vim ~/app/application-real-db.properties
이제 새롭게 추가된 properties들을 deploy.sh에서 사용할 수 있도록 수정하자.
nohup java -Xms256m -Xmx512m -jar \
-Dspring.config.location=classpath:/application.properties,classpath:/application-real.properties,/home/ec2-user/app/application-oauth.properties,/home/ec2-user/app/application-real-db.properties \
-Dspring.profiles.active=real \
$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &
이제 브라우저에서 퍼블릭 DNS주소:8080 으로 접근해보도록 하자.
http://ec2-3-37-37-4.ap-northeast-2.compute.amazonaws.com:8080/
드디어 메인화면에 접근이 가능해졌다.
이제 추가적으로 각 소셜 api에서 EC2서버 도메인 주소를 추가해주면 된다.
사용자 인증 정보에 들어가서 기존의 OAuth 2.0 클라이언트 ID를 클릭하고
승인된 리디렉션 URI를 작성하자.
http://ec2-3-37-37-4.ap-northeast-2.compute.amazonaws.com:8080/login/oauth2/code/google
이제 구글 로그인을 하면
정상적으로 로그인에 성공했다.
Naver에서는 두가지를 수정해야한다.
기존의 로컬 정보로 되어있던 서비스 URL과 리디렉션 URI를 작성하자.
서비스 URL : http://ec2-3-37-37-4.ap-northeast-2.compute.amazonaws.com/
리디렉션 URI : http://ec2-3-37-37-4.ap-northeast-2.compute.amazonaws.com:8080/login/oauth2/code/naver
여기서 한가지 큰 실수를 했었는데,
서버에 올라간 application-oauth.properties에서 naver에 사용할 redirect-uri값을 수정해주지 않고 네이버에서만 수정을 해서 원인을 찾는데 한참 고생했다.
잊지말고 꼭 같이 수정하자.
이제 네이버로 로그인하면
정상으로 로그인된다.
드디어 EC2 환경에서 내가 만든 어플리케이션을 배포하는데 성공했다.
아직은 배포관련된 환경이 완성된 것은 아니지만 이제 하나하나 추가를 해야 할 것 같다.
배포스크립트를 작성하면서 몇가지 고생한 부분과 인상적인 내용들을 적어봤다.
가장 인상적인 부분은 테스트코드가 빌드에 포함되서 수행된다는 점이다.
나는 그간 대기업 협력사에서 SI/SM를 근무해왔는데 요즘 같은 TDD기반, 테스트코드를 작성하는 환경에서 일을 해본 경험이 없다.
그래서 단순히 젠킨스 파이프라인을 이용한 빌드 및 배포환경에만 익숙했고
spring profile을 이용한 선택적 properties 적용정도말고는 경험해보지 못했다.
그런 나에게 빌드와 테스트가 함께 진행되는 이러한 방식은 상당한 신뢰감을 가지게 만들었다.
그다음으로 하면서 가장 고생했던 부분들을 정리해봤다.
환경을 구축하면서 가장 힘들었던 부분은 각 파트별 설정이 연동이 되는데 이 부분에서 실수가 발생하는 것에 대해서 수정포인트를 찾는게 어려웠다.
특히 oauth 관련된 내용이 properties와 sns 사이트간에 통신에 중요한 포인트가 되는데, 네이버 같은 경우는 oauth2 지원이 안되서 properties에 기입했던 정보가 별도로 있었다보니 해당 정보를 처음에 생성할때 실수가 발생하면서 원인을 찾는데 많은 시간이 들어갔다.
그리고 스프링부트 2.5이상 버젼에서는 jar가 두개가 생성된다는 것도 이번기회에 처음 알게 되었고 *-plain.jar가 일반 snapshot이랑 머가 다른지에 대한 차이점도 알 수 있었다.
책에 쓰인 시점이 19년도인 만큼 그 동안 바뀐 부분을 하나하나 수정하면서 진행하는 소소한 재미가 있어서 좋은 것 같다.