먼저 깃허브에서 코드를 받아올 수 있게 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 파일이다. 해당 파일을 직접 이용하기 때문에 별도로 설치할 필요가 없다.
작성한 코드를 실제 서버에 반영하는 것으 배포라고 한다. 이 책에서 배포라 하면 아래 모두를 포괄한다.
앞선 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편함이 많다. 그래서 이를 쉘 스크립트로 작성해 스크립트만 작성하면 앞의 과정이 차례로 진행되도록 하곘다. 참고로 쉘 스크립트와 빔은 서로 다른 역햘을 한다. 쉘 스크립트는 리눅스에서 기본적으로 사용할 수 있는 스크립트 파일이다. 빔은 리눅스 환경과 같이 GUI가 아닌 환경에서 사용할 수 있는 편집 도구다.
vim ~/app/step1/deploy.sh
#!/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 &
REPOSITORY=/home/ec2-user/app/step1
- 프로젝트 디렉토리 주소는 스크립트 내에서 자주 사용하는 값이기 때문에 이를 변수로 저장한다.
- 마찬가지로 PROJECT_NAME=freelec-springboot2-webservice도 동일하게 변수로 저장한다.
- 쉘에서는 타입 없이 선언하여 저장한다.
- 쉘에서는
$변수명
으로 변수를 사용할 수 있다.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 ~ fi
- 현재 구동 중인 프로세스가 있는지 없는지를 판단해서 기능 수행
- process id 값을 보고 프로세스가 있으면 해당 프로세스를 종료한다.
JAR_NAME=$(ls -tr $REPOSITORY/|grep jar | tail -n 1)
- 새로 실행할 jar 파일명을 찾는다.
- 여러 jar 파일이 생기기 때문에 tail -n로 가장 나중의 jar 파일(최신 파일)을 변수에 저장한다.
nohup java -jar $REPOSITORY/$JAR_NAME2>&1 &
- 찾은 jar 파일명으로 해당 jar 파일을 nohup으로 실행한다.
- 스프링 부트의 장점으로 특별히 외장 톰캣을 설치할 필요가 없다.
- 내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있다.
- 일반적으로 자바를 실행할 때는 java -jar라는 명령어를 사용하지만, 이렇게 하면 사용자가 터미널 접속을 끊을 때 애플리케이션도 같이 종료된다.
- 애플리케이션 실행자가 터미널을 종료해도 애플리케이션을 계속 구동할 수 있도록 nohup 명령어를 사용한다.
chmod +x ./deploy.sh
스크립트 실행 권한 추가
잘 실행되었으니 nohup.out 파일을 열어 로그를 보자. nohup.out 은 실행되는 애플리케이션에서 출력되는 모든 내용을 갖고 있다.
맨 아래에 가면 ClientRegistrationRepository
를 찾을 수 없다는 에러를 알 수 있다.
이유는 다음과 같다. ClientRegistrationRepository
를 생성하려면 clientId
와 clientSecret
가 필수다. 로컬 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는 MariaDB를 사용중이다. MariaDB에서 스프링부트 프로젝트를 실행하기 위해서는 몇 가지 작업이 필요하다.
테스트 코드스 로그로 생성되는 코드가 있다.
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
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
가 아니라
- 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
curl localhost:8080을 하면 html을 가져온다
curl 명령어를 통해 EC2에 서비스가 잘 배포된 것을 확인했다. 이제 브라우저 에서 확인해보자
EC2에 스프링 부트 프로젝트가 8080포트로 배포되었으니, 8080포트가 보안 그룹에 열려있는지 확인한다.
잘 열려있다.
EC2에서 퍼블릭 DNS를 확인한다.
이 퍼블릭 DNS에 :8080
을 붙여서 브라우저에 입력하면 서비스가 나온다.
현재 상태에서는 해당 서비스에 EC2의 도메인을 등록하지 않았기 때문에 구글과 네이버 로그인이 작동하지 않는다.
https://console.cloud.google.com/home/dashboard
API 및 서비스 -> 사용자 인증 정보 -> OAuth 동의화면
http:// 없이 EC2 퍼블릭 DNS 등록
사용자 인증 정보 -> 서비스 이름 클릭
https://developers.naver.com/apps/#/myapps
api 설정 -> pc 웹 -> 서비스 URL과 Callback URL 수정
이제 깃허브에 푸시를 하면 자동으로 Test & Build & Deploy가 되게 해보자.