[스프링] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - EC2 서버에 프로젝트를 배포해 보자

June·2021년 10월 16일
0

EC2 프로젝트 Clone 받기

먼저 깃허브에서 코드를 받아올 수 있게 EC2에 깃을 설치한다.

sudo yum install git

설치가 완료되면

git --version

git clone으로 프로젝트를 저장할 디렉토리 생성

mkdir ~/app && mkdir ~/app/step1
cd ~/app/step1

생성된 디렉토리로 이동

git clone https://github.com/injoon2019/freelec-springboot2-webservice.git
cd freelec-springboot2-webservice/
ll

파일이 잘 복사되었는지 확인

chmod +x ./gradlew
./gradlew test

권한을 주고 테스트를 실행하는 것이다.

현재 EC2엔 Gradle을 설치하지 않았다. 하지만 Gradle Task(ex.test)를 수행할 수 있다. 이는 프로젝트 내부에 포함된 gradlew 파일 때문이다. 그레읻르이 설치되지 않은 환경 혹은 버전이 다른 상황에서도 해당 프로젝트에 한해서 그레이들을 쓸 수 있도록 지원하는 Wrapper 파일이다. 해당 파일을 직접 이용하기 때문에 별도로 설치할 필요가 없다.

배포 스크립트 만들기

작성한 코드를 실제 서버에 반영하는 것으 배포라고 한다. 이 책에서 배포라 하면 아래 모두를 포괄한다.

  • git clone 혹은 git pull을 통해 새 버전의 프로젝트를 받음
  • Gradle이나 Maven을 통해 프로젝트 테스트와 빌드
  • EC2 서버에서 해당 프로젝트 실행 및 재실행

앞선 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편함이 많다. 그래서 이를 쉘 스크립트로 작성해 스크립트만 작성하면 앞의 과정이 차례로 진행되도록 하곘다. 참고로 쉘 스크립트와 빔은 서로 다른 역햘을 한다. 쉘 스크립트는 리눅스에서 기본적으로 사용할 수 있는 스크립트 파일이다. 빔은 리눅스 환경과 같이 GUI가 아닌 환경에서 사용할 수 있는 편집 도구다.

vim ~/app/step1/deploy.sh

Vim 가이드

#!/bin/bash

REPOSITORY=/home/ec2-user/app/step1
PROJECT_NAME=freelec-springboot2-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 &
  1. REPOSITORY=/home/ec2-user/app/step1
    • 프로젝트 디렉토리 주소는 스크립트 내에서 자주 사용하는 값이기 때문에 이를 변수로 저장한다.
    • 마찬가지로 PROJECT_NAME=freelec-springboot2-webservice도 동일하게 변수로 저장한다.
    • 쉘에서는 타입 없이 선언하여 저장한다.
    • 쉘에서는 $변수명으로 변수를 사용할 수 있다.
  2. cd $REPOSITORY/$PROJECT_NAME/
    • 제일 처음 git clone 받았던 디렉토리로 이동한다
    • 바로 위의 쉘 변수 명을 따라 이동한다.
  3. git pull
    • 디렉토리 이동 후, master 브랜치의 최신 내용을 받는다.
  4. ./gradlew build
    • 프로젝트 내부의 gradlew로 build를 수행한다.
  5. cp ./build/libs/*.jar $REPOSITORY/
    • build의 결과물인 jar 파일을 복사해 jar파일을 모아둔 위치로 복사한다.
  6. CURRENT_PID=$(pgrep -f springboot-webservice)
    • 기존에 수행중이던 스프링 부트 애플리케이션 종료
    • pgrep은 process id만 추출하는 명령어
    • -f 옵션은 프로세스 이름으로 차즌ㄴ다
  7. if ~ else ~ fi
    • 현재 구동 중인 프로세스가 있는지 없는지를 판단해서 기능 수행
    • process id 값을 보고 프로세스가 있으면 해당 프로세스를 종료한다.
  8. JAR_NAME=$(ls -tr $REPOSITORY/|grep jar | tail -n 1)
    • 새로 실행할 jar 파일명을 찾는다.
    • 여러 jar 파일이 생기기 때문에 tail -n로 가장 나중의 jar 파일(최신 파일)을 변수에 저장한다.
  9. nohup java -jar $REPOSITORY/$JAR_NAME2>&1 &
    • 찾은 jar 파일명으로 해당 jar 파일을 nohup으로 실행한다.
    • 스프링 부트의 장점으로 특별히 외장 톰캣을 설치할 필요가 없다.
    • 내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있다.
    • 일반적으로 자바를 실행할 때는 java -jar라는 명령어를 사용하지만, 이렇게 하면 사용자가 터미널 접속을 끊을 때 애플리케이션도 같이 종료된다.
    • 애플리케이션 실행자가 터미널을 종료해도 애플리케이션을 계속 구동할 수 있도록 nohup 명령어를 사용한다.
chmod +x ./deploy.sh

스크립트 실행 권한 추가

잘 실행되었으니 nohup.out 파일을 열어 로그를 보자. nohup.out 은 실행되는 애플리케이션에서 출력되는 모든 내용을 갖고 있다.

맨 아래에 가면 ClientRegistrationRepository를 찾을 수 없다는 에러를 알 수 있다.

외부 Security 파일 등록하기

이유는 다음과 같다. ClientRegistrationRepository를 생성하려면 clientIdclientSecret가 필수다. 로컬 PC에서 실행할 때는 application-oauth.properties가 있어 문제가 없었다. 하지만 이 파일은 .gitignore로 git에서 제외 대상이다.

애플리케이션을 싷행하기 위해 공개된 저장소에 CliendId와 ClientSecreet을 올리수 없으니 서버에서 직접 이 설정들을 가지고 있게하겠다.

vim /home/ec2-user/app/application-oauth.properties

로컬에 있는 application-oauth.properties를 복붙한다. 그리고 방금 생성한 application-oauth.properties를 쓰도록 deploy.sh를 수정한다.

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 \
    $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

1.-Dspring.confing.location

  • 스프링 설정 파일 위치를 지정한다.
  • 기본 옵션들을 담고 있는 application.properties와 OAuth 설정들을 담고 있는 application-oauth.properties의 위치를 지정한다.
  • classpath가 붙으면 jar 안에 있는 resources 디렉토리를 기준으로 경로가 생성된다.
  • application-oauth.properties은 절대경로를 사용한다. 외부에 파일이 있기 때문이다.

nohup.out을 보면 잘 실행된 것을 알 수 있다.

스프링 부트 프로젝트로 RDS 접근하기

RDS는 MariaDB를 사용중이다. MariaDB에서 스프링부트 프로젝트를 실행하기 위해서는 몇 가지 작업이 필요하다.

  • 테이블 생성: H2에서 자동 생성해주던 테이블들을 MariaDB에선 직접 쿼리를 이용해 생성한다.
  • 프로젝트 설정: 자바 프로젝트가 MariaDB로 접근하려면 데이터 베이스 드라이버가 필요하다.
  • EC2(리눅스 서버) 설정: 프로젝트 안에서 db 접속 정보를 갖고 있으면 해킹 위험이 있다. EC2 서버 내부에서 접속 서버를 관리하도록 설정한다.

RDS 테이블 생성

테스트 코드스 로그로 생성되는 코드가 있다.

create table posts (id bigint not null auto_increment, created_date datetime, modified_date datetime, author varchar(255), content varchar(255), title varchar(500) not null, primary key (id)) engine=InnoDB

create table user (id bigint not null auto_increment, created_date datetime, modified_date datetime, email varchar(255) not null, name varchar(255) not null, picture varchar(255), role varchar(255) not null, primary key (id)) engine=InnoDB

인텔리제이에서 Ctrl + Shift + N -> 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;

프로젝트 설정

compile("org.mariadb.jdbc:mariadb-java-client")

서버에서 구동될 환경을 하나 구성한다.
src/main/resources/application-real.properties파일을 추가한다.

application-real.properties

spring.profiles.include=oauth, real-db
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.session.store-type=jdbc

EC2 설정

OAuth와 마찬가지로 RDS 접속 정보도 보호해야 할 정보이니 EC2 서버에 직접 설정 파일을 둔다

app 디렉토리에 application-real-db.properties 파일 생성

vim ~/app/application-real-db.properties
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mariadb:/rds주소:포트명(기본은 3306)/database이름
spring.datasource.username=db계정
spring.datasource.password=db계정 비밀번호
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

여기서 db이름이 freelec_springboot2_webservice였다. freelec-springboot2-webservice가 아니라

  1. spring.jpa.hibernate.ddl-auto=none
    • JPA로 테이블이 자동 생성되는 옵션을 None
    • RDS에는 실제 운영으로 사용될 테이블이니 절대 스프링 부트에서 새로 만들지 않게 해야 한다.
    • 이 옵션을 하지 않은 자칫 테이블이 모두 새로 생성될 수 있다.

마지막으로 deploy.sh가 real profile을 쓸 수 있게 개선한다.

...
nohup java -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 &

deploy.sh 재실행 후 정상 작동되었느지 보자

메모리 부족 오류가 발생했다.
https://github.com/jojoldu/freelec-springboot2-webservice/issues/415#issuecomment-641697799

AWS 메모리 부족 문제 해결

curl localhost:8080을 하면 html을 가져온다

EC2에서 소셜 로그인하기

curl 명령어를 통해 EC2에 서비스가 잘 배포된 것을 확인했다. 이제 브라우저 에서 확인해보자

AWs 보안 그룹 변경

EC2에 스프링 부트 프로젝트가 8080포트로 배포되었으니, 8080포트가 보안 그룹에 열려있는지 확인한다.

잘 열려있다.

AWS EC2 도메인으로 접속

EC2에서 퍼블릭 DNS를 확인한다.

이 퍼블릭 DNS에 :8080을 붙여서 브라우저에 입력하면 서비스가 나온다.

현재 상태에서는 해당 서비스에 EC2의 도메인을 등록하지 않았기 때문에 구글과 네이버 로그인이 작동하지 않는다.

구글에 EC2 주소 등록

https://console.cloud.google.com/home/dashboard

API 및 서비스 -> 사용자 인증 정보 -> OAuth 동의화면
http:// 없이 EC2 퍼블릭 DNS 등록

사용자 인증 정보 -> 서비스 이름 클릭

책이 오래돼서 막힌 부분 같다.

네이버에 EC2 주소 등록

https://developers.naver.com/apps/#/myapps

api 설정 -> pc 웹 -> 서비스 URL과 Callback URL 수정

  1. 서비스 URL
  • 로그인을 시도하는 서비스가 네이버에 등록된 서비스인지 판단하기 위한 항목
  • 8080포트 제외하고 실제 도메인 주소만 입력
  • EC2를 입력하면 localhost가 안된다
  • 개발 단게에서는 등록하지 않는 것 추천
  1. Callback URL
    • 전체 주소 등록(EC2 퍼블릭 DNS:8080/login/oauth2/code/naver)

이제 깃허브에 푸시를 하면 자동으로 Test & Build & Deploy가 되게 해보자.

0개의 댓글