어디까지 갈 수 있을까?

EC2 서버에 프로젝트를 배포해보자 &코드가 푸시되면 자동으로 배포해 보자 본문

책/스프링부트와 AWS로 혼자 구현하는 웹 서비스

EC2 서버에 프로젝트를 배포해보자 &코드가 푸시되면 자동으로 배포해 보자

_Min 2021. 4. 12. 16:45

EC2에 프로젝트 Clone 받기

EC2에 접속해서 깃 설치
sudo yum install git
git --version

프로젝트를 저장할 디렉터리 생성
mkdir ~/app && mkdir ~/app/step1
cd ~/app/step1

git clone 깃허브 주소
cd 프로젝트명
ll
프로젝트 코드들이 모두 있으면 성공
코드들이 잘 수행되는지 테스트로 검증

./gradlew test

gradlew 실행 권한이 없다고 뜨면
chmod +x ./gradlew

 

배포 스크립트 만들기

배포 : 작성한 코드를 실제 서버에 반영하는 것

vim ~/app/step1/deploy.sh

#!/bin/bash 

REPOSITORY=/home/ec2-user/app/step1 
PROJECT_NAME=spring_web_service

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=spring_web_service

쉘에서는 $ 변수명으로 변수를 사용할 수 있습니다.

 

git pull

master 브랜치의 최신 내용을 받습니다.

 

./gradlew build

프로젝트 내부의 gradlew로 build를 수행합니다.

 

CURRENT_PID=$(pgrep -f ${PROJECT_NAME}*.jar)

기존에 수행 중이던 스프링 부트 process id 추출 명령어

-f 옵션은 프로세스 이름으로 찻습니다.

 

JAR_NAME=$(ls -tr $REPOSITORY/ | grep *.jar | tail -n 1)

새로 실행할 jar 파일명을 찾습니다.

여러 jar 파일이 생기기 때문에 tail -n로 가장 나중의 jar파일을 변수에 저장합니다.

 

nohup java -jar JAR_NAME 2>&1 &

찾은 jar 파일명으로 해당 jar파일을 nohup으로 실행합니다.

내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있습니다.

nohup으로 실행을 시키려면 실행파일 권한이 755이상으로 되어있어야 함

명령어 뒤에 '&'를 추가하면 백그라운드로 실행됨

nohup 을 통해 프로그램을 실행시키면 nohup.log 라는 로그 파일 생성

 


cd spring_web_service

 



cannot allocate memory 오류 :
AWS (무료)에서는 초기에 SWAP메모리를 디폴트 0으로 잡아주어 발생하는 문제

free -h
swap 메모리가 출력되지 않거나 0으로 뜨면 swap 가상메모리 설정이 되어있지 않은 것

aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-swap-file/

나머지는 이 링크를 따라한다

could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarExcept 오류 :

RDS의 데이터 베이스 이름 or 테이블 이름 or 컬럼 이름이 Entity의 이름과 다르진 않은지 확인

 

외부 Security 파일 등록

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

-Dspring.config.location

스프링 설정 파일 위치를 지정합니다.

classpath가 붙으면 jar 안에 있는 resources 디렉트리 기준으로 경로가 생성됩니다.

application-oauth.properties은 절대경로를 사용합니다.

 

 

스프링 부트 프로젝트로 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;

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;

테스트 코드 수행 시 생성되는 로그 쿼리 사용

+

ctrl+shift+n 에서 schema-mysql.sql 의 세션 테이블 생성 쿼리 사용

 

프로젝트 설정 & EC2 설정

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

src/main/resource/에 application-real.properties 작성

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

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

deploy.sh 수정
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 재실행
./deploy.sh

curl localhost:8080

curl: (7) Failed to connect to localhost port 8080: Connection refused 오류

netstat -ln | grep 8080

 

EC2에서 소셜 로그인하기

EC2 보안 그룹 확인

8080포트가 열려 있어야 함


EC2퍼블릭DNS:8080 주소로 접속한다

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

google Login 클릭시 오류가 난다

 

 

구글에 EC2 주소 등록

console.cloud.google.com/home/dashboard

 

 

이름 클릭


퍼블릭DNS주소:8080/login/oauth2/code/google

 

http://없이 퍼블릭 DNS 등록

 

네이버에 EC2 주소 등록

developers.naver.com/apps/#/myapps


내 애플리케이션 이름 선택 후 API 설정 클릭

서비스 URL
EC2퍼블릭DNS 등록
로그인을 시도하는 서비스가 네이버에 등록된 서비스인지 판단하기 위한 항목

Callback URL
EC2퍼블릭DNS:8080/login/oauth2/code/naver 등록

 

현재 문제점

수동 실행 Test, 수동 Build

 

깃허브에 푸시를 하면 자동으로 Test&Build&Deploy가 진행되도록 개선 필요

 

 

코드가 푸시되면 자동으로 배포해 보자 - Travis CI 배포 자동화

CI

Continuous Integration, 지속적인 통합

Git에 코드가 push되면 자동으로 테스트와 빌드가 수행되어 안정적인 배포 파일을 만드는 과정

 

CD

Continuous Deployment, 지속적인 배포

이 빌드 결과를 자동으로 운영 서버에 무중단 배포까지 진행되는 과정

 

 

 

Travis CI 연동하기 & 프로젝트 설정

 

Travis CI

깃허브에서 제공하는 무료 CI 서비스

 

.yml

야믈이라고 부름

JSON에서 괄호를 제거한 것

이념은 기계에서 파싱하기 쉽게 사람이 다루기 쉽게

travis-ci.com/ 접속
책에는 travis-ci.org/ 라고 돼 있지만 페이지를 이전했다

깃허브 로그인

activate 클릭

프로젝트 이름을 클릭하면 빌드 히스토리 페이지로 이동한다

build.gralde과 같은 위치에 .travis.yml 생성



language: java

jdk:
  - openjdk8

branches:
  only:
    - master

# Travis CI 서버의 Home
cache:
  directories:
    - '$HOME/.m2/repository'
    - '$HOME/.gradle'

before_install:
  - chmod +x gradlew

script: "./gradlew clean build"

# CI 실행 완료 시 메일로 알람
notifications:
  email:
    recipients:
      - 본인 메일 주소

branches
Travis CI를 어느 브랜치가 푸시될 때 수행할지 지정
현재 옵션은 오직 master 브랜치에 push될 때만 수행합니다.

cache
그레이들을 통해 의존성을 받게 되면 이를 해당 디렉토리에 캐시하여, 같은 의존성은 다음 배포 때부터 다시 받지 않도록 설정

script
master 브랜치에 푸시되었을 때 수행하는 명령어입니다.
여기서는 프로젝트 내부에 둔 gradlew를 통해 clean&build를 수행2에 접속해서 깃 설치

 

계속 오류가 나서 2시간 쯤 후에 재시도 했는데 갑자기 됐다..! 
코드 상 고친 건 없었다. 안 되면 시간을 두고 나중에 다시 시도해보는 것도 좋을 듯 하다

 

Travis CI와 AWS S3 연동하기

S3란 AWS에서 제공하는 일종의 파일 서버

이미지 파일을 비롯한 정적 파일들을 관리하거나 배포 파일들을 관리하는 기능 지원

CodeDeploy는 저장 기능이 없어 Travis CI가 빌드한 결과물 받아 CodeDeploy가 가져갈 수 있도록 보관하는 공간 : AWS S3

*빌드와 배포가 분리되어 있으면 예전에 빌드한 Jar를 재사용하면 되지만, CodeDeploy만 사용하면 항상 빌드하게 되니 확장성이 떨어짐 => 빌드와 배포 분리

 

AWS에는 외부 서비스가 접근할 수 없어 접근 가능한 권한을 가진 Key를 생성해서 사용 -> IAM(Identity and Access Management)

 


 

Travis CI에서 사용될 키

Travis CI에 키 등록

 

이제 등록된 값들을 .travis.yml에서
$AWS_ACCESS_KEY, $AWS_SECRET_KEY란 이름으로 사용할 수 있음

S3 버킷 생성
Simple Storage Service
파일을 저장하고 접근 권한을 관리, 검색 등을 지원하는 파일 서버의 역할

 

.travis.yml 추가

Travis CI에서 빌드하여 만든 Jar 파일을 s3에 올릴 수 있도록 코드 추가

language: java
jdk:
 - openjdk8

branches:
 only:
   - master

# Travis CI 서버의 Home
cache:
 directories:
   - '$HOME/.m2/repository'
   - '$HOME/.gradle'

before_install:
 - chmod +x gradlew

script: "./gradlew clean build"

before_deploy:
 - zip -r spring_web_service ./*
 - mkdir -p deploy
 - mv spring_web_service.zip deploy/spring_web_service.zip

deploy:
 - provider: s3
   access_key_id: $AWS_ACCESS_KEY
   secret_access_key: $AWS_SECRET_KEY

   bucket: jmchoi-springboot-build #S3 버킷 이름
   region: ap-northeast-2
   skip_cleanup: true
   acl: private #zip 파일 접근 private으로 
   local_dir: deploy #before_deploy에서 생성한 디렉토리
   wait_until_deployed : true

# CI 실행 완료 시 메일로 알람
notifications:
 email:
   recipients:
     - chlwjdals98@gmail.com

before_deploy

deploy 명령어가 실행되기 전 수행

CodeDeploy는 Jar 파일은 인식하지 못하므로 Jar+ 기타 설정 파일들을 모아 압축(zip)

 

zip -r springboot-webservice

현재 위치의 모든 파일을 압축

 

deploy

외부 서비스와 연동될 행위들을 선언

 

local_dir: deploy

앞서 생성한 deploy 디렉터리를 지정

해당 위치의 파일들만 s3로 전송

 

Travis CI와 연동을 통해 S3에 자동으로 파일이 올라왔다

 

Travis CI와 AWS S3, CodeDeploy 연동하기

배포대상인 EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 생성

EC2에 IAM 역할 추가

 

역할을 EC2 서비스에 등록
 

 

CodeDeploy 에이전트 설치

aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2


실행 권한 추가
chmod +x ./install

install 파일 설치
sudo ./install auto

Agent 상태 검사
sudo service codedeploy-agent status

/usr/bin/env: ruby: No such file or directory 오류

sudo yum install ruby; 로 ruby 설치하면 해결

 

CodeDeploy를 위한 권한 생성 & CodeDeploy 생성

CodeDeploy에서 EC2에 접근하려면 권한이 필요하다

AWS의 서비스이니 IAM 역할을 생성한다

 

Code Deploy는 AWS의 배포 서비스이다.

현재 프로젝트에서 코드 저장소의 역할은 GitHub가, 빌드용 서비스는 Travis CI가, 배포는 CodeDepoy가 하고 있다.

 

Travis CI, S3, CodeDeploy 연동

S3에서 넘겨줄 zip 파일을 저장할 디렉토리 생성

EC2 디렉토리 생성
mkdir ~/app/step2 && mkdir ~/app/step2/zip

Travis CI의 Build가 끝나면 S3에 zip 파일이 전송되고, 이 zip파일은 /home/ec2-user/app/step2/zip로 복사되어 압축을 풀 예정

Travis CI의 설정은 travis.yml로 진행했음
AWS CodeDeploy의 설정은 appspec.yml로 진행

version: 0.0
os : linux
files :
source : /
destination: /home/ec2-user/app/step2/zip/
overwrite : yes

version: 0.0
CodeDeploy 버전
프로젝트 버전이 아니므로 0.0 외에 다른 버전을 사용하면 오류가 발생

source
CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정합니다.
루트 경로(/)를 지정하면 전체 파일을 이야기 합니다.

destination
source에서 지정된 파일을 받는 위치
이후 Jar를 실행하는 등은 destination에서 옮긴 파일들로 진행

overwrite
기존에 파일들이 있으면 덮어쓸지를 결정

 

.travis.yml에도 CodeDeploy 내용을 추가

deploy 아래에 추가하면 된다

- provider: codedeploy
    access_key_id: $AWS_ACCESS_KEY 
    secret_access_key: $AWS_SECRET_KEY

    bucket: jmchoi-springboot-build #S3 버킷 이름
    key : spring_web_service.zip #빌드 파일을 압축해서 전달

    bundle_type : zip
    application : spring_web_service #웹 콘솔에 등록한 CodeDeploy 애플리케이션

    deplyment_group : springboot-webservice-group #웹 콘솔에 등록한 CodeDeploy 배포 그룹
    region: ap-northeast-2
    wait-until-deployed : true

 

커밋하면 깃허브-> Travis CI -> CodeDeploy로 배포가 수행된다

 

cd /home/ec2-user/app/step2/zip

ll

파일이 잘 도착해 있다

 

 

배포 자동화 구성

Travis CI, S3, CodeDeploy 연동이 완료됐다.

이것을 기반으로 실제 Jar를 배포하여 실행까지 해보자

 

deploy.sh 파일 추가

#!/bin/bash

REPOSITORY=/home/ec2-user/app/step2
PROJECT_NAME=spring_web_service

echo "> Build 파일 복사"

cp $REPOSITORY/zip/*.jar $REPOSITORY/

echo "> 현재 구동중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -fl $PROJECT_NAME | grep jar | awk '{print $1}')

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/*.jar | tail -n 1)

echo "> JAR Name: $JAR_NAME"

echo "> $JAR_NAME 에 실행권한 추가"

chmod +x $JAR_NAME

echo "> $JAR_NAME 실행"

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 &

 

.travis.yml 파일 수정

before_deploy:
  - mkdir -p before-deploy
  - cp scripts/*.sh before-deploy/
  - cp appspec.yml before-deploy/
  - cp build/libs/*.jar before-deploy/
  - cd before-deploy && zip -r before-deploy *
  - cd ../ && mkdir -p deploy
  - mv before-deploy/before-deploy.zip deploy/spring_web_service.zip

Travis CI는 디렉토리 단위로만 업로드할 수 있기 때문에 디렉토리 생성

 

appspec.yml 파일 수정

version: 0.0
os : linux
files :
  - source : /
    destination: /home/ec2-user/app/step2/zip/
    overwrite : yes

permissions:
  - object: /
    pattern: "**"
    owner: ec2-user
    group: ec2-user

hooks:
  ApplicationStart:
    - location : deploy.sh
      timeout: 60
      runas: ec2-user

permissions

CodeDeploy에서 EC2 서버로 넘겨준 파일들을 모두 ec2-user 권한을 갖도록 한다.

 

hooks

CodeDeploy 배포 단계에서 실행할 명령어를 지정합니다.

ApplicationStart라는 단계에서 deploy.sh를 ec2-user 권한으로 실행하게 합니다.

timeout: 시간제한 60초로 설정(무한정 기다릴 수 없다)

 

 

다시 커밋하면

배포 성공!

 

 

하지만 이렇게 배포하면 배포하는 동안 스프링 부트 프로젝트는 종료 상태가 되어 서비스를 이용할 수 없다.

다음 장에서는 서비스 중단 없는 배포, 무중단 배포를 진행하자.

 

 

 

728x90
Comments