이제 완성된 프로젝트와 배포 환경을 구축했습니다. 이들을 조합해 실제로 서비스를 한번 배포해봅시다.

 

 

 

EC2에 프로젝트 Clone 받기

EC2에 깃 설치

sudo yum install git

깃 설치 상태 확인

git --version

git clone으로 프로젝트를 저장할 디렉토리를 생성해줍시다.

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

생성한 디렉토리로 이동합시다.

cd ~/app/step1

본인의 https 주소를 복사합니다. git clone을 해줍시다.

git clone (복사한 주소)

파일이 잘 복사 되었는지 확인합시다.

cd (프로젝트명)
ls

코드들이 잘 수행되는지 테스트로 검증해봅시다.

./gradlew test

5장까지 잘 적용했다면 정상적으로 테스트를 통과합니다.

만약 테스트가 실패해서 수정하고 깃허브에 푸시를 했다면 프로젝트 폴더 안에서 다음 명령어를 사용하면 됩니다.

git pull

만약 아래와 같이 gradlew 실행 권한이 없다는 메시지가 뜬다면

-bash: ./gradlew: Permission denied

아래 명령어로 실행 권한을 추가한 뒤 다시 테스트를 수행해주면 됩니다.

chmod +x ./gradlew

 

 

 

배포 스크립트 만들기

작성한 코드를 실제 서버에 반영하는 것을 배포라고 합니다. 이 책에서 배포라 하면 아래의 과정을 모두 포괄하는 의미라고 보면 됩니다.

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

앞선 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편함이 많습니다. 그래서 이를 쉘 스크립트로 작성해 스크립트만 실행하면 앞의 과정이 차례로 진행되도록 하겠습니다.

~/app/step1에 deploy.sh 파일을 하나 생성합니다.

vim ~/app/step1/deploy.sh

빔은 기타 에디터와 다르게 빔만의 사용법이 있다고 합니다. 아래의 코드를 추가해줍시다. (PHP코드와 비슷합니다.)

#!/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도 마찬가지입니다.
    • $변수명으로 사용가능합니다.
  • 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
    • 현재 구동 중인 프로세스가 있는지 없는지를 판단해서 기능을 수행합니다.
  • 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으로 실행합니다.
    • 스프링 부트의 장점으로 특별히 외장 톰캣을 설치할 필요가 없습니다.

이렇게 생성한 스크립트에 실행 권한을 추가합니다.

chmod +x ./deploy.sh

아래 명령어로 확인해보면 x 권한이 추가된 것을 확인할 수 있습니다.

ll

이제 이 스크립트를 다음 명령어로 실행합니다.

./deploy.sh

잘 실행되었으면 nohup.out 파일으르 열어 로그를 보겠습니다.

vim nohup.out

로그를 보니, ClientRegistrationRepository를 찾을 수 없다는 에러가 발생하면서 애플리케이션 실행에 실패하는 것을 볼 수 있습니다.

***************************

APPLICATION FAILED TO START

***************************



Description:



Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' that could not be found.



The following candidates were found but could not be injected:

        - Bean method 'clientRegistrationRepository' in 'OAuth2ClientRegistrationRepositoryConfiguration' not loaded because OAuth2 Clients Configured Condition registered clients is not available





Action:



Consider revisiting the entries above or defining a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' in your configuration.

이유는 ClientRegistrationRepository를 생성하려면 clientId와 clientSecret가 필수입니다. 로컬 PC에서는 application-oauth.properties가 있어 문제가 되지 않았지만, 해당 파일은 .gitignore로 제외 대상이기 때문에 깃허브에 올라가지 않습니다.

 

 

 

외부 Security 파일 등록하기

애플리케이션을 실행하기 위해 공개된 저장소에 clientId와 clientSecret을 올릴 수는 없습니다. 서버에서 직접 이 설정들을 갖고 있게 해줍시다.

 

먼저 step1이 아닌 app 디렉토리에 properties파일을 생성합니다.

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

로컬에 있는 application-oauth.properties 파일 내용을 그대로 붙여넣기 합시다. 붙여넣기 한 뒤, :wq를 누르면 저장됩니다.

방금 생성한 application-oauth.properties를 쓰도록 deploy.sh 파일을 수정합니다.

vim deploy.sh
nohup java -jar \
   -Dspring.config.location=classpath:/application.properties,/home/ec2-user/app/application-oauth.properties \
   $REPOSITORY/$JAR_NAME 2>&1 &
  • -Dspring.config.location
    • 스프링 설정 파일 위치를 지정합니다.
    • 기본 옵션들을 담고 있는 application.properties과 OAuth 설정들을 담고 있는 application-oauth.properties의 위치를 지정합니다.
    • classpath가 붙으면 jar 안에 있는 resources 디렉토리를 기준으로 경로가 생성됩니다.
    • application-oauth.properties는 절대경로를 사용합니다. 외부 파일이 있기 때문입니다.

이제 다시 deploy.sh를 실행해봅시다.

./deploy.sh

 

 

 

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

MariaDB에서 스프링부트 프로젝트를 실행하기 위해선 몇 가지 작업이 필요합니다.

  • 테이블 생성 : H2에서 자동 생성해주던 테이블들을 MariaDB에선 직접 쿼리를 이용해 생성합니다.
  • 프로젝트 설정 : 자바 프로젝트가 MariaDB에 접근하려면 데이터베이스 드라이버가 필요합니다.
  • EC2 설정 : 데이터베이스의 접속 정보는 중요하게 보호해야 할 정보입니다. 공개되면 외부에서 데이터를 모두 가져갈 수 있기 때문입니다. 프로젝트 안에 접속 정보를 갖고 있다면 깃허브와 같이 오픈된 공간에선 누구나 해킹할 위험이 있습니다.

 

 

RDS 테이블 생성

먼저 RDS 테이블을 생성해줍시다. 여기선 JPA가 사용될 엔티티 테이블과 스프링 세션이 사용될 테이블 2가지 종류를 생성합니다. JPA가 사용할 테이블은 테스트 코드 수행 시 로그로 생성되는 쿼리를 사용하면 됩니다.

 

복사하여 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;

 

 

프로젝트 설정

MariaDB 드라이버를 build.gradle에 등록합시다.

implementation("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

이렇게 하면 profile=real인 환경이 구성된다고 보면됩니다. 실제 운영 될 환경이기 때문에 보안/로그상 이슈가 될만한 설정들을 모두 제거하며 RDS 환경 profile 설정이 추가됩니다.

github에 푸시합니다.

 

 

EC2 설정

OAuth와 마찬가지로 RDS 접속 정보도 보호해야할 정보이니 EC2 서버에 직접 설정 파일으 둡니다. app디렉토리에 application-real-db.properties 파일을 생성합니다. 아래의 코드를 추가한 뒤 :wq로 저장해줍니다.

spring.jpa.hibernate.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.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

아래의 명령어로 html 코드가 정상적으로 보인다면 성공입니다.

curl localhost:8080

저는 잘 되지 않았습니다. 

해결 방법은 일단 책 저자분의 블로그를 참고했습니다.

 

(2020.12.16) 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 최신 코드로 변경하기

작년 11월 말에 스프링 부트와 AWS로 혼자 구현하는 웹 서비스를 출판 하였습니다. Spring Boot가 2.1 -> 2.4로, IntelliJ IDEA가 2019 -> 2020으로 오면서 너무 많은 변화가 있다보니, 집필할 때와 비교해 실습

jojoldu.tistory.com

여기서 읽어 보시면 맨아래

이렇게 바꾸라고 되어있는데 이렇게 바꾸면

***************************

APPLICATION FAILED TO START

***************************



Description:



Failed to bind properties under 'spring.datasource.hikari' to com.zaxxer.hikari.HikariDataSource:



    Property: spring.datasource.hikari.driver-class-name

    Value: org.mariadb.jdbc.Driver

    Origin: URL [file:/home/ec2-user/app/application-real-db.properties]:7:44

    Reason: Failed to load driver class org.mariadb.jdbc.Driver in either of HikariConfig class loader or Thread context classloader



Action:



Update your application's configurat

위의 에러가 나오게 됩니다. application-real-db.properties 파일을  spring.datasource.hikari를 com.zaxxer.hikari.HikariDataSource로 전부 고쳐주니 정상적으로 돌아갔습니다.

spring.jpa.hibernate.ddl-auto=none
spring.jpa.show_sql=false

com.zaxxer.hikari.HikariDataSource.jdbc-url=jdbc:mariadb://(rds주소):(포트명)/(database명)
com.zaxxer.hikari.HikariDataSource.username=db 계정
com.zaxxer.hikari.HikariDataSource.password=db 비밀번호
com.zaxxer.hikari.HikariDataSource.driver-class-name=org.mariadb.jdbc.Driver

돌려 보니 SQL 테이블이 제대로 생성되지 않는 에러가 계속 발생해서 찾아보니 springboot 2.1.9가 되면서 기존에 사용중이던 MySQL5InnoDBDialect 이 Deprecated가 된것으로 보입니다. application.properties의 코드인 아래 코드를

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

2.1.10부터는 아래의 코드로 추가해줘야합니다.

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect spring.jpa.properties.hibernate.dialect.storage_engine=innodb spring.datasource.hikari.jdbc-url=jdbc:h2:mem://localhost/~/testdb;MODE=MYSQL
  • MySQL5Dialect 로 하게되면 auto innodb가 적용이 안된채로 작동되는것을 확인하였습니다.
    • 스프링부트 2.2.2가 되어도 MySQL57Dialect 를 사용해야될것 같습니다.
  • 2.1.10 이후에는 직접 jdbc-url을 선언하여 ;MODE=MYSQL가 h2주소 뒤에 붙어야만 mysql 테이블 쿼리가 정상 작동됩니다.

제가 잘 되지 않았던 부분은 application.properties에서 application-real.properties에 접근을 잘 못했던 것 같습니다. 아래의 코드로 테이블이 생성되면 안되는데

spring.jpa.hibernate.ddl-auto=none

계속 테이블이 생성되며 에러가 발생했던 것이였고, application.properties에서 application-real-db.properties로 직접 접근을 시켜주니 해결이 됐습니다.. (이것 때문에 EC2, RDS 두번 설치했습니다..ㅠㅠ)

application.properties를 아래와 같이 변경해주시면 됩니다!!

spring.profiles.include=oauth,real-db

만약 아래와 같이 입력했을 때,

curl localhost:8080

아래와 같이 나온다면

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

아래 명령어를 입력해줍시다!

netstat -ln | grep 8080

그 후 다시 curl localhost:8080을 해보시면  아래와 같은 html 코드를 볼 수 있습니다!!

추가적으로 저는 두번째에 할때는 DB Navigator가 잘 동작하지 않아 IntelliJ 우측 탭에있는 Database를 사용해서 쿼리문을 작성하였습니다.

 

 

 

 

EC2에 소셜 로그인하기

잘 배포된 것은 확인했습니다. 이제 브라우저를 확인해 볼텐데, 그 전에 몇 가지 작업을 해보겠습니다.

  • AWS 보안 그룹 변경
    • 먼저 EC2에 스프링 부트 프로젝트가 8080 포트로 배포되었으니 8080 포트가 보안 그룹에 열려 있는지 확인합니다.

저는 열려 있었습니다.

 

  • AWS EC2 도메인으로 접속

왼쪽 사이드 바의 [인스턴스] 메뉴를 클릭합니다. 본인이 생성한 EC2 인스턴스를 선택하면 상세 정보에서 퍼블릭 DNS를 확인할 수 있습니다. 해당 주소가 EC2에 자동으로 할당된 도메인 입니다. 

그럼 도메인 주소에 8080 포트를 붙여 브라우저에 입력합니다. 아래와 같이 localhost:8080을 입력할 때와 같은 화면을 볼 수 있습니다.

이제 도메인을 가진 서비스가 되었습니다. 

 

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

 

 

  • 구글에 EC2 주소 등록

구글 console에 들어가서 우측에 있는 API 및 서비스 -> 사용자 인증 정보를 눌러줍니다. OAuth 2.0 클라이언트 ID에 만들어져있는 ID를 눌러줍니다. 승인된 리디렉션 URI에 (퍼블릭DNS주소:8080/login/oauth2/code/google)를 추가해준 뒤 저장해줍니다.

 

  • 네이버에 EC2 주소 등록

네이버 개발자 센터로 접속해서 본인의 프로젝트로 이동해줍니다. API 설정에서 PC웹 항목에 있는 서비스 URL과 Callback URL 2개를 수정합니다.  서비스 URL은 포트 번호를 제외한 실제 도메인 주소, Callback URL에는 구글에서 등록한 주소와 비슷한 전체 주소인 (퍼블릭DNS주소:8080/login/oauth2/code/naver)를 입력한 뒤 수정버튼을 눌러줍니다.

 

구글, 네이버 둘 다 도메인 주소로 로그인을 테스트 해보면 정상적으로 로그인이 될 것 입니다.

 

 

 

 

 

 

출처

 

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

가장 빠르고 쉽게 웹 서비스의 모든 과정을 경험한다. 경험이 실력이 되는 순간!이 책은 제목 그대로 스프링 부트와 AWS로 웹 서비스를 구현한다. JPA와 JUnit 테스트, 그레이들, 머스테치, 스프링

www.yes24.com

 

 

 

+ Recent posts