'스프링 부트와 AWS로 혼자 구현하는 웹 서비스'를 읽고 정리한 글입니다.
8.1 EC2에 프로젝트 Clone 받기
EC2에 Git을 설치하고 프로젝트를 Clone
$ sudo yum install git
$ git --version
$ mkdir ~/app && mkdir ~/app/step1
$ cd ~/app/step1
$ git clone [Git 저장소]
$ cd [Git 저장소]
$ chmod 755 gradlew // gradlew에 실행 권한 부여
$ ./gradlew test
8.2 배포 스크립트 만들기
배포란 작성한 코드를 실제 서버에 반영하는 것을 말한다.
- git clone 혹은 git pull을 통해 새 버전의 프로젝트 받음
- Gradle이나 Maven을 통해 프로젝트 테스트와 빌드
- EC2 서버에서 해당 프로젝트 실행 및 재실행
이 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편하다.
그래서 쉘 스크립트를 작성해 스크립트만 실행하면 위 과정이 차례로 진행되도록 하자.
deploy.sh
step1 디렉토리에 deploy.sh 파일 생성
#!/bin/bash
REPOSITORY=/home/ec2-user/app/step1
PROJECT_NAME=springboot2-aws-webservice
cd $REPOSITORY/$PROJECT_NAME/
echo "> Git Pull"
git pull
echo "> 프로젝트 Build 시작"
./gradlew build
echo "> step1 디렉토리로 이동"
cd $REPOSITORY
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=...
- 프로젝트 디렉토리 주소를 변수로 저장한다.
- 프로젝트 이름도 변수로 저장한다.
- 쉘에서는 타입 없이 선언하여 저장한다.
- $ 변수명으로 변수를 사용할 수 있다.
cd $REPOSITORY/$PROJECT_NAME/
- git clone 받았던 디렉토리로 이동
git pull
- 디렉토리 이동 후, master 브랜치의 최신 내용을 받는다.
./gradlew build
- 프로젝트 내부의 gradlew로 build를 수행한다.
cp ./build/libs/*.jar $REPOSITORY/
- build의 결과물인 jar 파일을 복사해 jar 파일을 모아둔 위치로 복사한다.
CURRENT_PID=$(pgrep -f springboot-webservice)
- 수행 중이던 스프링 부트 애플리케이션을 종료한다.
- pgrep는 process id만 추출하는 명령어다.
- -f 옵션은 프로세스 이름으로 찾는다.
if ~ else if
- 현재 구동 중인 프로세스가 있는지 없는지를 판단해서 기능을 수행한다.
- process id 값을 보고 프로세스가 있으면 해당 프로세스를 종료한다.
JAR_NAME=$(ls -tr $REPOSITORY/|grep jar| tail -n 1)
- 새로 실행할 jar 파일명을 찾는다.
- 여러 jar 파일이 생기기 때문에 tail -n로 가장 나중의 jar 파일을 변수에 저장한다.
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
- 찾은 jar 파일명으로 해당 jar 파일을 nohup으로 실행한다.
- 스프링 부트의 장점으로 WAS(외장 톰캣)을 설치할 필요가 없다.
- 내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있다.
- 일반적으로 자바를 실행할 때는 java -jar 명령어를 사용하지만
- 이렇게 하면 사용자가 터미널 접속을 끊을 때 애플리케이션도 같이 종료된다.
- 애플리케이션 실행자가 터미널을 종료해도 애플리케이션은 계속 구동될 수 있도록 nohup 명령어를 사용한다.
생성한 스크립트에 실행 권한을 추가한다.
$ chmod +x ./deploy.sh
스크립트 실행
$ ./deploy.sh
nohup.out파일에 애플리케이션에서 출력되는 내용을 갖고 있다.
가장 하단에 보면 에러가 발생하여 애플리케이션 실행 실패했다는 것을 알 수 있다.
8.3 외부 Security 파일 등록하기
ClientRegistrationRepository를 생성하려면 CliendId와 clientSecret이 필수다.
그런데 ClientId와 clientSecret정보가 담긴 application-oauth.properties를 .gitignore에 등록해놓았기 때문에 git에 올라가 있지 않은 상태다. 애플리케이션을 실행하기 위해 서버에서 직접 이 설정들을 가지고 있게 해야 한다.
먼저 properties파일을 생성한다.
$ vim /home/ec2-user/app/application-oauth.properties
로컬에 있는 application-oauth.properties 파일 내용을 그대로 붙여넣기 한다.
그 다음 deploy.sh 파일을 수정한다.
nohup java -jar \-Dspring.config.location=classpath:/application.properties,
/home/ec2-user/app/application-oauth.properties $REPOSITORY/$JAR_NAME 2>&1 &
-Dspring.config.location
- 스프링 설정 파일 위치를 지정한다.
- 기본 옵션들을 담고 있는 application.properties과 OAuth 설정들을 담고 있는 application-oauth.properties의 위치를 지정한다.
- classpath가 붙으면 jar 안에 있는 resources 디렉토리를 기준으로 경로가 생성된다.
- appplication-oauth.properties은 외부에 파일이 있기 때문에 절대경로를 사용한다.
다시 deploy.sh를 실행하면 정상적으로 실행된 것을 확인할 수 있다.
8.4 스프링 부트 프로젝트로 RDS 접근하기
MariaDB에서 스프링 부트 프로젝트를 실행하기 위해서 몇 가지 작업이 필요하다.
- 테이블 생성
- H2에서 자동 생성해주던 테이블들을 MariaDB에선 직접 쿼리를 이용해 생성한다.
- 프로젝트 설정
- 자바 프로젝트가 MariaDB에 접근하려면 데이터베이스 드라이버가 필요하다.
- MariaDB에서 사용 가능한 드라이버를 프로젝트에 추가한다.
- EC2(리눅스 서버) 설정
- 데이터베이스의 접속 정보는 중요하게 보호해야할 정보다.
- 공개되면 외부에서 데이터를 모두 가져갈 수 있기 때문이다.
RDS 테이블 생성
여기선 JPA가 사용될 엔티티 테이블과 스프링 세션이 사용될 테이블 2가지 종류를 생성한다.
JPA가 사용할 테이블은 테스트 코드 수행 시 로그로 생성되는 쿼리를 사용하면 된다.
테스트 코드를 수행하면 다음과 같이 로그가 발생하니 create table부터 복사하여 RDS에 반영하자.
use springboot2_aws_webservice;
create table posts (
id bigint not null auto_increment,
created_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,
created_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
스프링 세션 테이블은 schema-mysql.sql 파일에서 확인할 수 있다.
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;
SPRING_SESSION_ATTRIBUTES 테이블 생성하는데 문법 에러가 자꾸 발생해서 오랫동안 해결 방법을 찾았다.
Error executing SQL statement. You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 6 - Connection: sg: 24ms
일일히 타이핑도 해보고 Git의 이슈에도 해결 방법이 없었고 구글링해도 답이 없는 상황이라 어찌할지 막막했다...
쿼리를 실행할 때 ctrl + enter로 한줄씩 실행해보고 전체 실행도 해보고 전혀 답이 나오지 않았다.
그런데 너무도 어처구니 없게 해결됐다.
해당 쿼리를 드래그하고 실행버튼을 눌렀는데 테이블이 생성되었다.
이런 문제가 발생한 원인이 무엇이며 해결된 원인 또한 모르겠다.
인텔리제이 커뮤니티 버전에서 SQL을 지원하지 않아 구문 분석을 할 수 없어 발생한 invalid or incomplete statement가 문제라고 생각했지만, 문법 체크만 지원하지 않을 뿐 다른 쿼리들은 정상적으로 동작한다.
어이없게.. 해결했다.
프로젝트 설정
MariaDB 드라이버를 build.gradle에 등록한다.
implementation('org.mariadb.jdbc:mariadb-java-client')
서버에서 구동될 환경을 구성한다.
src/main/resources/에 application-real.properties 파일을 추가한다.
spring.profiles.include=oauth,real-db
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.session.store-type=jdbc
- application-real.properties로 파일을 만들면 profile=real인 환경이 구성된다.
- 실제 운영될 환경이기 때문에 보안/로그상 이슈가 될 만한 설정들을 모두 제거하며 RDS profile 설정이 추가된다.
이제 깃허브로 푸시하여 최신화하자.
EC2 설정
RDS 정보도 보호해야 할 정보이니 EC2 서버에 직접 설정 파일을 두자.
$ vim ~/app/application-real-db.properties
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show_sql=false
spring.datasource.hikari.jdbc-url=jdbc:mariadb://rds주소:포트명(3306)/database이름
spring.datasource.hikari.username=db계정
spring.datasource.hikari.password=db계정 비밀번호
spring.datasource.hikari.driver-class-name=org.mariadb.jdbc.Driver
rds주소: rds 엔드포인트
database이름: _로 연결되는 이름
spring.jpa.hibernate.ddl-auto=none
- JPA로는 테이블이 자동 생성되는 옵션을 none으로 지정한다.
- RDS에는 실제 운영으로 사용될 테이블이니 절대 스프링 부트에서 새로 만들지 않도록 해야 한다.
- 이 옵션을 하지 않으면 테이블이 모두 새로 생성될 수 있다.
다음으로 deploy.sh가 real profile을 쓸 수 있도록 변경하자.
-Dspring.profiles.active=real
- application-real.properties를 활성화시킨다.
- application-real.properties의 spring.profiles.include=oauth,real-db 옵션 때문에 real-db 역시 대상에 포함된다.
이제 deploy.sh를 실행하여 nohup.out 하단에 다음과 같은 로그가 보인다면 성공적으로 수행된 것이다.
curl 명령어로 html 코드가 정상적으로 보인다면 성공이다.
curl localhost:8080
8.5 EC2에서 소셜 로그인하기
EC2에 서비스가 잘 배포된 것을 확인하였다.
이제 브라우저에서 확인해야 하는데 그 전에 몇 가지 작업을 해야 한다.
AWS 보안 그룹 변경
EC2에 스프링 부트 프로젝트가 8080 포트로 배포되었으니 8080포트가 보안 그룹에 열려 있는지 확인한다.
AWS EC2 도메인으로 접속
EC2 인스턴스를 선택하면 상세 정보에서 퍼블릭 DNS를 확인할 수 있다.
- 이 주소가 EC2에 자동으로 할당된 도메인이다.
- 이 주소를 입력하면 EC2 서버에 접근할 수 있다.
이 도메인 주소에 8080 포트를 붙여 브라우저에 입력해보자.
현재 상태에서는 해당 서비스에 EC2의 도메인을 등록하지 않았기 때문에 구글과 네이버 로그인이 작동하지 않는다.
구글에 EC2 주소 등록
구글 웹 콘솔에 접속하여 사용자 인증 정보로 이동한다.
OAuth 동의 화면 탭으로 이동해서 앱 등록 수정에 들어간다.
승인된 도메인에 EC2의 퍼블릭 DNS 주소를 등록한다.
사용자 인증 정보 탭에서 서비스의 이름을 클릭한다.
승인된 리디렉션 URI에 EC2 퍼블릭 DNS 주소에 :8080/login/oauth2/code/google 주소를 추가하여 등록한다.
이제 구글 로그인이 정상적으로 동작한다.
네이버에 EC2 주소 등록
네이버 개발자 센터로 접속하여 프로젝트로 이동한다.
PC 웹 항목의 서비스 URL과 Callback URL에 EC2 퍼블릭 DNS 주소를 등록한다.
서비스 URL
- 로그인을 시도하는 서비스가 네이버에 등록된 서브스인지 판단하기 위한 항목이다.
- 8080 포트는 제외하고 실제 도메인 주소만 입력한다.
- 네이버에서 아직 지원하지 않아 하나만 등록 가능하다.
- 즉, EC2의 주소를 등록하면 localhost가 안된다.
- 개발 단계에서는 등록하지 않는 것을 추천한다.
- localhost도 테스트하고 싶으면 네이버 서비스를 하나 더 생성해서 키를 발급받으면 된다.
Callback URL
- 전체 주소를 등록한다(EC2 퍼블릭 DNS:8080/login/oauth2/code/naver)
이제 구글과 네이버 로그인도 EC2와 연동이 되었다.
현재 방식은 몇 가지 문제가 있다.
수동 실행되는 Test
- 본인이 짠 코드가 다른 개발자의 코드에 영향을 끼치지 않는지 확인하기 위해 전체 테스트를 수행해야 한다.
- 현재 상태에선 항상 개발자가 작업을 진행할 때마다 수동으로 전체 테스트를 수행해야만 한다.
수동 Build
- 다른 사람이 작성한 브랜치와 본인이 작성한 브랜치가 합쳐졌을 때(Merge) 이상이 없는지는 Build를 수행해야만 알 수 있다.
- 이를 매번 개발자가 직접 실행해봐야만 한다.
다음 장에서 이런 수동 Test & Build를 자동화시키는 작업을 한다.
깃허브에 푸시를 하면 자동으로 Test & Build & Deploy가 진행되도록 개선한다.