이 글은 이동욱 님의 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책 내용을 정리한 것입니다.
http://www.yes24.com/Product/Goods/83849117
EC2에 프로젝트 Clone 받기
깃허브 코드를 받아오게 EC2에 git 설치하기
sudo yum install git
설치가 완료되면 설치 상태 확인하기
git -- version
깃이 성공적으로 설치되면 git clone으로 프로젝트를 저장할 디렉토리를 생성하기
mkdir ~/app && mkdir ~/app/step1
본인의 깃허브 레포지토리에 가서 [Code]버튼 클릭 → HTTPS 주소를 복사한다. 복사한 주소를 가지고, 방금 생성한 디렉토리로 이동하고 다음 명령어로 클론한다.
git clone 복사한 깃 주소
이제 코드들이 잘 수행되는지 테스트로 검증해보자.
./gradlew test
Gradle을 EC2에 설치하지 않아도 Gradle을 설치할 수 있는 이유는 프로젝트 내부에 있는 gradlew 파일 때문이다. 이것은 해당 프로젝트에 한해서 그레이들이 설치되어 있지 않은 환경에서도 그레이들을 쓸 수 있도록 지원하는 Wrapper 파일이다.
배포 스크립트 만들기
작성한 코드를 실제 서버에 반영하는 것을 배포라고 한다. 이 책에서 배포는 아래의 과정을 모두 포괄하는 의미이다.
- git clone 혹은 git pull을 통해 새 버전의 프로젝트를 받음
- Gradle이나 Maven을 통해 프로젝트 테스트와 빌드
- EC2 서버에서 해당 프로젝트 실행 및 재실행
앞선 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편함이 많다. 그래서 이를 쉘 스크립트로 작성해 스크립트만 실행하면 앞의 과정이 차례로 진행되도록 한다.
참고로, 쉘 스크립트와 vim은 서로 다른 역할을 한다. 쉘 스크립트는. sh라는 파일 확장자를 가진 파일이다. 리눅스에서 기본적으로 사용할 수 있는 스크립트 파일의 한 종류이다.
vim은 리눅스 환경과 같이 GUI가 아닌 환경에서 사용할 수 있는 편집 도구이다.
~/app/step1/에 deploy.sh 파일 하나를 생성하자.
vim ~/app/step1/deploy.sh
deploy.sh
#!/bin/bash
REPOSITORY=/home/ec2-user/app/step1 //(1)
PROJECT_NAME=springbootWebservice
cd $REPOSITORY/$PROJECT_NAME/ //(2)
echo "> Git Pull" //(3)
git pull
echo "> 프로젝트 Build 시작"
./gradlew build //(4)
echo ">step1 디렉토리로 이동"
cd $REPOSITORY
echo "> Build 파일복사"
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/ //(5)
echo "> 현재 구동중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) //(6)
echo "현재 구동중인 애플리케이션 pid 확인"
if [ -z "$CURRENT_PID" ]; then //(7)
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) //(8)
echo "> JAR Name: $JAR_NAME"
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 & //(9)
REPOSITORY=/home/ec2-user/app/step1
- 프로젝트 디렉토리 주소는 스크립트 내에서 자주 사용하는 값이기 때문에 이를 변수로 저장
- 마찬가지로
PROJECT_NAME=springbootWebservice
도 동일하게 변수로 저장한다. - 쉘에서는 타입 없이 선언하여 저장한다.
- 쉘에서는 $ 변수명으로 변수를 사용할 수 있다.
cd Repository/PROJECT_NAME/
- 제일 처음 git clone 받았던 디렉토리로 이동한다.
- 바로 위의 쉘 변수 설명을 따라 /home/ec2-user/app/step1/springbootWebservice 주소로 이동한다.
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-fi
- 현재 구동 중인 프로세스가 있는지 없는지를 판단해서 기능을 수행한다.
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으로 실행한다.
- 스프링 부트의 장점으로 특별히 외장 톰캣을 설치할 필요가 없다.
- 내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있다.
- 일반적으로 자바를 실행할 때는 java -jar라는 명령어를 사용하지만, 이렇게 하면 사용자가 터미널 접속을 끊을 때 애플리케이션도 같이 종료된다.
- 애플리케이션 실행자가 터미널을 종료해도 애플리케이션은 계속 구동될 수 있도록 nohup 명령어를 사용한다.
이렇게 생성한 스크립트에 실행 권한을 추가한다.
chmod +x ./deploy.sh
아래 명령어로 실행한다.
./deploy.sh
아래 명령어로 로그를 확인한다. (애플리케이션에서 출력되는 모든 내용을 가지고 있다.)
vim nohup.out
외부 Security 파일 등록하기
nohup.out 제일 아래로 가면 ClientRegistrationRepository를 찾을 수 없다는 에러가 발생하면 애플리케이션 실행에 실패했다는 것을 알 수 있다.
❓왜 그럴까?
이유는 다음과 같다. ClientRegistrationRepository를 생성하려면 clientId와 clientSecret가 필수이다. 로컬 PC에서 실행할 때는 application-oauth.properties가 있어 문제가 없었다. 하지만, 이 파일은 .gitignore로 git에서 제외 대상이라 깃허브에는 올라가 있지 않는다.
애플리케이션을 실행하기 위해 공개된 저장소에 ClientId와 ClientSecret을 올릴 수는 없으니 서버에서 직접 이 설정들을 가지고 있게 해 보자.
먼저 step1이 아닌 app 디렉토리에 properties 파일을 생성한다.
vim /home/ec2-user/app/application-oauth.properties
그리고 로컬에 있는 application-oauth.properties의 내용을 그대로 붙여 넣기를 하고 해당 파일을 저장하고 종료한다.(:wq) 그리고 방금 생성한 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 &
-Dsrping.config.location
- 스프링 설정 파일 위치를 지정한다.
- 기본 옵션들을 담고 있는 application.properties과 OAuth 설정들을 담고 있는 application-oauth.properties의 위치를 지정한다.
- classpath가 붙으면 jar 안에 있는 resources 디렉토리를 기준으로 경로가 생성된다.
- application-oauth.properties 는 절대 경로를 사용한다. 외부에 파일이 있기 때문이다.
스프링 부트 프로젝트로 RDS 접근하기
MariaDB에서 스프링부트 프로젝트를 실행하기 위해서는 몇 가지 작업이 필요하다.
- 테이블 생성
- H2에서 자동 생성해주던 테이블들을 MariaDB에선 직접 쿼리를 이용해 생성한다.
- 프로젝트 생성
- 자바 프로젝트가 MariaDB에 접근하려면 데이터베이스 드라이버가 필요하다. MariaDB에서 사용 가능한 드라이버를 프로젝트에 추가한다.
- EC2 설정
- 데이터베이스의 접속 정보는 중요하게 보호해야 할 정보이다. 공개되면 외부에서 데이터를 모두 가져갈 수 있기 때문이다.
- 프로젝트 안에 접속 정보를 갖고 있다면 깃허브와 같이 오픈된 공간에선 누구나 해킹할 위험이 있다.
- EC2 서버 내부에서 접속 정보를 관리하도록 한다.
RDS 테이블 생성
먼저, RDS에 테이블을 생성하자.
- JPA가 사용될 Entity 테이블
- 스프링 세션이 사용될 테이블
스프링 세션 테이블은 schema-mysql.sql 파일에서 확인할 수 있다.
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;
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;
프로젝트 설정
build.gradle
compile("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 설정
OAuth와 마찬가지로 RDS 접속 정보도 보호해야 할 정보이니 EC2 서버에 직접 설정 파일을 둔다. app 디렉토리에 application-real-db.properties 파일을 생성한다.
vim ~/app/application-real-db.properties
application-real-db.properties
spring.jpa.hiberante.ddl-auto=none
spring.datasource.url=jdbc:mariadb://rds주소:포트명/database이름
spring.datasource.username = db계정
spring.datasource.password = db계정 비밀번호
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.hiberante.ddl-auto=none
- jpa로 테이블이 자동 생성되는 옵션을 none으로 지정한다.
- rds에는 실제 운영으로 사영될 테이블이니 절대 스프링 부트에서 새로 만들지 않도록 해야 함
- 주의해야 하는 옵션이다
마지막으로 deploy.sh가 real profile을 쓸 수 있도록 다음과 같이 개선한다.
...
nohup java -jar \ -Dspring.config.location=classpath:/application.properties, /home/ec2-user/app/application-oauth.properties, /home/ec2-user/app/application-real-db.properties, classpath:/application-real.properties \
-Dspring.profiles.active=real \
$REPOSITORY/$JAR_NAME 2>&1 &
-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
마지막으로 실제 브라우저에서 로그인을 시도해보자.
EC2에서 소셜 로그인하기
curl 명령어를 통해 EC2에 서비스가 잘 배포된 것은 확인하였다. 이제 브라우저에서 확인해볼 텐데, 그전에 다음과 같은 몇 가지 작업을 해보겠다.
AWS 보안 그룹 변경
먼저 EC2에 스프링 부트 프로젝트가 8080 포트로 배포되었으니, 8080 포트가 보안 그룹에 열려있는지 확인한다.
AWS EC2 도메인으로 접속
왼쪽 사이드바의 [인스턴스] 메뉴를 클릭한다. 본인이 생성한 EC2 인스턴스를 선택하면 다음과 같이 상세 정보에서 퍼블릭 DNS를 확인할 수 있다.
이 주소가 EC2에 자동으로 할당된 도메인이다. 인터넷이 되는 장소 어디나 이 주소를 입력하면 우리의 EC2 서버에 접근할 수 있다.
이제 이 도메인 주소에 8080 포트를 붙여 브라우저에 입력하자.
우리 서비스가 이제 도메인을 가진 서비스가 되었다..! 하지만, 현재 상태에서는 해당 서비스에 EC2의 도메인을 등록하지 않았기 때문에 구글과 네이버 로그인이 작동하지 않는다.
그래서 차례로 서비스에 등록해보자. 먼저 구글에 등록해보자.
구글에 EC2 주소 등록
- 구글 웹 콘솔(https://console.cloud.google.com/home/dashboard)로 접속하여 본인의 프로젝트로 이동한 다음 [API 및 서비스 ⇨ OAuth 동의 화면]으로 이동한다.
- [Edit App]을 선택하고 아래에서 승인된 도메인에 'http://' 없이 EC2의 퍼블릭 DNS를 등록한다.
- [사용자 인증 정보] 탭을 클릭해서 본인이 등록한 서비스의 이름을 클릭한다.
- 퍼블릭 DNS 주소에 :8080/login/oauth2/code/google 주소를 추가하여 승인된 리디렉션 URI에 등록한다.
이렇게 해주면 모든 작업이 끝난다.
EC2 DNS 주소로 이동해서 다시 구글 로그인을 시도해 보면 다음과 같이 로그인이 정상적으로 수행되는 것을 확인할 수 있다.
네이버에 EC2 등록
- 네이버 개발자 센터(https://developers.naver.com/apps/#/list)로 접속해서 본인의 프로젝트로 이동한다.
- 아래로 내려가다 보면 PC 웹 항목이 있는데 여기서 서비스 URL과 Callback URL 2개를 수정한다.
- 서비스 URL
- 로그인을 시도하는 서비스가 네이버에 등록된 서비스인지 판단하기 위한 항목
- 8080은 제외하고 실제 도메인 주소만 입력 한다.
- 네이버에서 아직 지원되지 않아 하나만 등록이 가능하다.
⇒ EC2의 주소를 등록하면 localhost가 안된다. - 개발 단계에서는 등록하지 않는 것을 추천한다.
- localhost도 테스트하고 싶으면 네이버 서비스를 하나 더 생성해서 키를 발급받으면 된다.
- CallbackURL
- 전체 주소를 등록한다. (EC2 퍼블릭 DNS:8080/login/oauth2/code/naver)
- 서비스 URL
두 개의 항목을 모두 수정/추가하였다면, 구글과 마찬가지로 네이버 로그인을 시도한다. 그럼 다음과 같이 로그인이 정상적으로 수행되는 것을 확인할 수 있다.