스프링 부트와 AWS로 혼자 구현하는 웹서비스 (프리렉, 이동욱 지음) 책에서 공부한 내용을 정리한 게시글입니다.

해당 시리즈의 소스코드는 이곳에서 확인할 수 있습니다.


목차

  1. 프로젝트 설정 - MariaDB 드라이버 등록(build.gradle)
  2. RDB에 프로젝트에 사용되는 테이블 생성
  3. EC2 서버에 프로젝트 clone 받기
  4. EC2 서버에 배포 스크립트 작성
  5. EC2 설정 - RDS 접속 정보 설정




chap8 ~ 10프로젝트 배포와 관련된 내용 을 포스팅 할 예정입니다.

전체적인 흐름을 단계별로 설명 하겠습니다. 각 단계는 배포 방식을 개선하는 것으로 총 3단계로 진행됩니다.

  • Chap 8: Step 1. 스크립트를 실행하여 수동 으로 프로젝트 Test & build 하기
    • 프로젝트 설정 - MariaDB 드라이버 등록(build.gradle)
    • RDB에 프로젝트에 사용되는 테이블 생성
    • 외부 Security 파일 등록
    • 배포 스크립트 작성
    • EC2 설정 - RDS 접속 정보 설정
    • EC2에서 소셜 로그인
  • Chap 9: Step 2. 깃허브에 Push 하면 자동 Test & Build & Deploy
    • Github와 Travis CI 연동
    • 프로젝트 Travis CI(.travis.yml) 설정
    • Travis CI와 AWS S3 연동
      • AWS Key(IAM, identity and Access Management) 발급
      • Travis CI에 IAM키 등록
      • AWS S3 버킷 생성
      • Travis CI의 빌드내용(Jar)을 S3에 올리기 위해 프로젝트(.travis.yml)에 설정 추가
    • Travis CI와 AWS S3, CodeDeploy 연동하기
      • EC2와 CodeDeploy 연동
      • CodeDeploy 연동을 위해 EC2에서 사용할 IAM 역할 생성
      • EC2 서버에 CodeDeploy 에이전트 설치
      • CodeDeploy -> EC2 접근을 위해 CodeDeploy에서 사용할 IAM 역할 생성
      • CodeDeploy 생성
      • CodeDeploy 관련 설정을 appspec.yml에 추가
      • Travis CI 설정 파일(.travis.yml)에 CodeDeploy 내용을 추가
    • 배포 자동화 구성(스크립트 파일(.sh) 작성)
      • 배포를 위한 스크립트(Jar, appspec.yml)가 아닌 것을 제외하기 위해 .travis.yml 내용 수정
      • Codedeploy 명령을 담당할 appspec.yml 파일 수정
    • CodeDeploy 로그 확인
  • Chap 10: Step 3. Nginx 무중단 배포
    • EC2 서버에 Nginx 설치 -> 서비스 시작
    • EC2 보안 그룹 추가 : 80 포트
    • 구글, 네이버 리디렉션 URI 추가
    • 프로젝트와 Nginx 연동
    • 무중단 배포 스크립트 작성
      • 8001, 8002 어느 포트를 사용할지 판단하는 API 작성(/profile-real: TravisCI 배포 자동화를 위한 profile 입니다.)
      • 무중단 배포를 위한 profile 2개(real1, real2) 추가(application-real1,2.properties 파일 생성)
      • EC2 서버의 Nginx 설정 수정
      • 배포 장소 변경/ 배포 스크립트 사용할 수 있도록 appspec.yml 내용 수정
      • 프로젝트에 배포 스크립트 작성(5개, profile, start, stop, health, switch)

Step 1에서 배포과정 요약

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

프로젝트 설정 - MariaDB 드라이버 등록(build.gradle)

먼저 RDS에 생성한 DB 유형인 MariaDB와 맞춰서 프로젝트에 MariaDB 드라이버를 build.gradle에 등록 해 줍니다.

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

그리고 서버에서 구동될 환경(스프링의 profile)을 하나 구성합니다. src/main/resources/ 에 application-real.properties 파일을 추가 합니다. 앞에서 이야기한 대로 application-real.properties로 파일을 만들면 profile=real인 환경이 구성된다고 보면 됩니다. 실제 운영될 환경이기 때문에 보안/로그상 이슈가 될 만한 설정들을 모두 제거하며 RDS환경 profile 설정이 추가됩니다.

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

RDB에 프로젝트에 사용되는 테이블 생성

EC2 혹은 IntelliJ에서 RDS 서버에 접속하여 이후 배포될 프로젝트에서 사용되는 테이블들을 생성 해 줍니다.

create table posts (
 id bigint not null auto_increment,
 created_date datetime,
 modified_date datetime,
 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,
 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;
/* 
* 스프링 세션 테이블은 -> schema-mysql.sql 에서 확인 가능합니다.
* File 검색(win: Ctrl+Shift+N)으로 검색
*/
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;

외부 Security 파일 등록

외부에 Security 파일을 등록을 해주어야 합니다. 이유는 기존에 프로젝트에는 application-oauth.properties가 있어 clientId와 clientSecret으로 ClientRegistrationRepository를 생성 하였지만 해당 파일이 .gitignore로 등록하여 깃 허브에는 올라가지 않기 때문입니다. 애플리케이션을 실행하기 위해 공개된 저장소에 clientId와 clientSecret을 올릴 수는 없으니 서버에서 직접 이 설정들을 가지고 있게 하겠습니다.

vim /home/ec2-user/app/application-oauth.properties
spring.security.oauth2.client.registration.google.client-id=클라이언트ID
spring.security.oauth2.client.registration.google.client-secret=클라이언트 비밀
spring.security.oauth2.client.registration.google.scope=profile,email

# registration
spring.security.oauth2.client.registration.naver.client-id=클라이언트ID
spring.security.oauth2.client.registration.naver.client-secret=클라이언트 비밀
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.client-name=Naver

# provider -> https://developers.naver.com/apps/#/myapps/eR05U0GAJw6PSXCVMKSs/overview
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response

EC2 서버에 프로젝트 clone 받기

프로젝트 설정과 DB 테이블을 생성 하였으니 깃 허브 저장소에 있는 프로젝트를 EC2 서버에 clone 합니다. 그 전에 EC2 서버에 git을 설치가 필요합니다. 아래의 명령어를 순차적으로 수행해주세요.

sudo yum install git // git 설치

git --version // 설치 확인

mkdir ~/app && mkdir ~/app/step1 // 디렉토리 생성

cd ~/app/step1

git clone 깃 허브 URL

cd 프로젝트명 // 프로젝트로 이동

프로젝트 clone이 완료 되었으면 코드들이 잘 수행되는지 테스트 검증하겠습니다. chap 5. 스프링 시큐리티 내용을 잘 따라왔다면 테스트는 정상 수행이 될 것입니다.

./gradlew test // 테스트 수행

// 만약 권한이 없다는 메시지가 뜰때 아래 명령어를 수행해주세요
chmod +x ./gradlew // gradlew 파일에 실행 권한 부여

EC2 서버에 배포 스크립트 작성

EC2 서버에 프로젝트를 가져왔습니다. 이제 본격적으로 배포하는 과정을 설명하겠습니다.

배포 란?

작성한 코드를 실제 서버에 반영하는 것을 말합니다.

step 1 에서 수행할 배포 는 아래의 과정을 포괄하는 의미를 가집니다.

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

위의 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편함이 많습니다. 그래서 이를 쉘 스크립트로 작성해 스크립트만 실행하면 앞의 과정이 차례로 진행되도록 하겠습니다. 쉘 스크립트 는 리눅스에서 기본적으로 사용할 수 있는 스크립트 파일의 한종류입니다. 노드JS가 .js라는 파일을 통해 서버에서 작동하는 것과 같은 것이라고 생각하면됩니다.

~/app/step1 에 deploy 라는 이름의 쉘 스크립트 파일을 하나 생성 합니다.

vim ~/app/step1/deploy.sh
  • deploy
#!/bin/bash

REPOSITORY=/home/ec2-user/app/step1 # (1)
PROJECT_NAME=freelec-springboot2-webservice

cd $REPOSITORY/$PROJECT_NAME/ # (2)

echo "> Git Pull" # (3)

git pull

echo "> 프로젝트 Buile 시작"

./gradlew build # (4)

echo "> step 1 디렉토리로 이동"

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: $CURRENT_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)

nohup java -jar \
        -Dspring.config.location=classpath:/application.properties,/home/ec2-user/app/application-oauth.properties \
        $REPOSITORY/$JAR_NAME 2>&1 & # 시큐리티 설정 후 (10)
  • (1) REPOSITORY=/home/ec2-user/app/step1
    • 프로젝트디렉토리 주소는 스크립트 내에서 자주 사용하는 값이기 때문에 이를 변수로 저장합니다.
    • 마찬가지로 PROJECT_NAME=freelec-springboot2-webservice도 동일하게 변수로 저장합니다.
    • 쉘에서는 타입 없이 선언하여 저장합니다.
    • 쉘에서는 $ 변수명으로 변수를 사용할 수 있습니다.
  • (2) cd $REPOSITORY/$PROJECT_NAME/
    • 제일 처음 git clone 받았던 디렉토리로 이동합니다.
    • 바로 위의 쉘 변수 설명을 따라 /home/ec2-user/app/step1/freelec-springboot2-webservice 주소로 이동합니다.
  • (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*.jar)
    • 기존에 수행 중이던 스프링 부트 애플리케이션을 종료합니다.
    • 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_NAME 2>1 &
    • 찾은 jar 파일명으로 해당 jar파일을 nohup으로 실행합니다.
    • 스프링 부트의 장점으로 특별히 외장 톰캣을 설치할 필요가 없습니다.
    • 내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있습니다.
    • 일반적으로 자바를 실행할 때는 java -jar라는 명령어를 사용하지만, 이렇게 하면 사용자가 터미널 접속을 끊을 때 애플리케이션도 같이 종료됩니다.
    • 애플리케이션 실행자가 터미널을 종료해도 애플리케이션은 계속 구동될 수 있도록 nohup 명령어를 사용합니다.
  • (10) -Dspring.config.location
    • 스프링 설정 파일 위치를 지정합니다.
    • 기본 옵션들을 담고 있는 application.properties과 OAuth 설정들을 담고 있는 application-oauth.properties의 위치를 지정합니다.
    • classpath가 붙으면 jar 안에 있는 resources 디렉토리를 기준으로 경로가 생성됩니다.
    • application-oauth.properties 은 절대경로를 사용합니다. 외부에 파일이 있기 때문 입니다.

EC2 설정 - RDS 접속 정보 설정

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

vim ~/app/application-real-db.properties
  • application-real-db.properties
spring.jpa.hibernate.ddl-auto=none # (1)
spring.datasource.url=jdbc:mariadb://rds주소:포트명(기본 3306)/database이름
spring.datasource.username=db계정(RDS 인스턴스 생성시 입력한 마스터 계정을 말합니다.)
spring.datasource.password=db계정 비밀번호(RDS 인스턴스 생성시 입력한 마스터 계정 비밀번호을 말합니다.)
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
  • (1) spring.jpa.hibernate.ddl-auto=none
    • JPA로 테이블이 자동 생성되는 옵션을 None(생성하지 않음)으로 지정합니다.
    • RDS에는 실제 운영으로 사용될 테이블이니 절대 스프링 부트에서 새로 만들지 않도록 해야 합니다.
    • 이 옵션을 하지 않으면 자칫 테이블이 모두 새로 생성될 수 있습니다.
    • 주의해야 하는 옵션 입니다.
  • deploy 스크립트 파일 수정
nohup java -jar \
        -Dspring.config.location=classpath:/application.properties,/home/ec2-user/app/application-oauth.properties,/home/ec2-user/app/application-real-db.properties \
        -Dspring.profiles.active=real \
        $REPOSITORY/$JAR_NAME 2>&1 # (1)
  • (1) -Dspring.profiles.active=real
    • application-real.properties를 활성화시킵니다.
    • application-real.properties의 spring.profiles.include=oauth,real-db 옵션 때문에 real-db 역시 함께 활성화 대상에 포함됩니다.

EC2에서 소셜 로그인

기존 로컬 환경이 아닌 EC2 환경에서 소셜 로그인을 수행 하기 위해서 몇 가지 작업이 필요합니다.

  • AWS 보안 그룹 변경 -> 보안 그룹에 8080 포트를 등록합니다.
  • EC2 퍼블릭 DNS를 구글과 네이버 서비스에 추가해줍니다.
    • 구글 -> 승인된 리디렉션 URI
      • EC2 퍼블릭 DNS:8080/login/oauth2/code/google
    • 네이버 -> 서비스 URL, Callback URL 2개에 등록
      • EC2 퍼블릭 DNS:8080/login/oauth2/code/naver

소셜 로그인 설정을 끝으로 step 1은 끝이 났습니다. step 2는 깃 허브에 푸시를 하면 자동으로 Test & Build & Deploy가 진행되도록 개선하는 작업을 해보겠습니다.

Related Posts

References

  • 스프링 부트와 AWS로 혼자 구현하는 웹서비스 (프리렉, 이동욱 지음)