[AWS] 한 EC2 인스턴스에 frontend, backend 전부 배포하기! - React + Spring boot
오늘은 하나의 EC2 인스턴스에 2개의 프로젝트를 배포하는 법에 대해 설명하겠습니다.
저도 EC2에 프로젝트를 배포하는 방법을 공부하기 위해 검색을 하고, 책까지 구매하여 공부하였으나, 두 프로젝트를 하나의 인스턴스에 넣고 배포하는 법에 대한 자세한 정보를 얻기는 어려웠습니다. (보통은 한 가지 프로젝트를 올리는 방법만 적혀 있습니다 ㅠ.ㅠ)
그래서 여기 저기서 정보들을 얻고 취합하고.. EC2에 배포한다는 것의 실제적인 의미를 파악하는 과정들을 통해 성공할 수 있었습니다. 기본적으로 EC2 인스턴스를 만든 후, putty를 통해 접속한 상황이라고 가정하겠습니다.(windows) 만드는 과정과 접속하는 과정은 여러 좋은 글들이 많으니 참고하세요. 만들면서 체크해야 할 사항들은 따로 글을 작성하겠습니다. -> [바로가기]
1. 전체적인 설명
EC2는 Linux이고, frontend 프로젝트는 React를 이용하였고, backend 프로젝트는 Spring boot를 이용하였습니다. 그러므로, 배포나 패키지 관리 관련 언어는 프론트의 경우 JS, 백의 경우 JAVA가 되겠습니다. 배포와 관련된 공부가 주된 목적이였기 때문에, 기존에 만들다 말았던 익명 채팅 프로젝트를 작동할 만큼만 완성시켜 활용했습니다. 프로젝트에 대해 자세히 설명하지는 않겠습니다.
사진과 같이 localhost:3000에 프론트 프로젝트를 올려두고, 프론트단에서 SockJS를 이용해 Spring Stomp를 이용한 프로젝트가 올라가있는 localhost:8080으로 요청을 보내는 방식입니다. (tmi: 이름이랑 이름 배경 색은 접속시 랜덤으로 배정됩니다.)
2. 한 인스턴스에 두 프로젝트 배포하기
한 인스턴스에 두 프로젝트 배포하기는 생각보다 어렵지 않습니다. 큰 틀에서 이해하면 정말 쉽습니다.
일단 프로젝트를 EC2 서버에 배포하는 방법은, 프로젝트 빌드 -> 배포 입니다. 최대한 쉽게 말 해보자면, 빌드는 그냥 우리가 잘 아는 빌드이고, 우리가 만든 프로젝트를 실행 해볼 때 IDE에서 Run을 하거나 패키지 관리 툴로 직접 실행해봅니다. 이 과정이 배포에 해당합니다. 어렵지 않죠?
그냥 EC2는 서버로서, 하나의 컴퓨터에 해당하고, 거기서 프로젝트를 쉴 새 없이 돌려놓고 있는 것이나 마찬가지입니다. 위에서 보여준 사진과 같이 테스트 하는 경우도 React 프로젝트는 yarn start
로, Spring 프로젝트는 IDE상에서 Run으로 서버를 돌려 놓는데, 그냥 이걸 가상 컴퓨터가 24시간 종일 하고 있는 것이나 마찬가지입니다.
그럼 배포 과정은 아래와 같이 진행될 것입니다.
- Java Spring Boot 프로젝트 빌드 -> 배포
- React 프로젝트 빌드 -> 배포
이게 정말 끝입니다.
이런 배포 과정은 당연히 여러 작업들이 필요하겠죠? 그래서 deploy.sh
라는 파일 하나에 커맨드를 전부 입력하고, 한 번에 실행되도록 해주겠습니다.
일단 배포 파일을 작성해 주기 전에, 기본적으로 프로젝트들을 세팅해주겠습니다. 기본 경로에 자신의 EC2 운영체제에 따라 디렉토리를 파줍니다. 저는 이름을 app
으로 지었습니다.
이후 두 프로젝트의 github 레파지토리를 clone해왔습니다. git clone 경로
명령어이고, '경로'에는 해당 레파지토리의 메인 화면에 초록색으로 혼자 색칠된 Code
버튼을 눌러 http 링크를 따왔습니다.
이후 빌드 툴들에 권한을 줍니다. 예시로 Linux 기준 gradlew
에 실행 권한을 주는 커멘드는 chmod +x ./gradlew
입니다. spring 프로젝트는 빌드 툴 gradlew
로 빌드할 예정이니, 해당 프로젝트 경로에 가서 미리 권한을 주세요. 주는 김에 빌드 테스트도 해보면 좋습니다. ./gradlew test
로 테스트 해줍니다.
이후 deploy.sh를 함께 작성하면서 배워보겠습니다.
2.1 레포지토리 이름과 프로젝트 이름 지정
#!/bin/bash
REPOSITORY=/home/ec2-user/app
PROJECT_NAME=서버프로젝트이름
PROJECT_NAME2=클라이언트프로젝트이름
deploy.sh의 가장 윗 부분입니다. vim deploy.sh
명령어를 통해 vim으로 편집했습니다. 변수처럼 계속해서 재활용 하기 위해, 레포지토리 경로와, 프로젝트 이름들을 지정해줍니다. 저는 백앤드 프로젝트를 PROJECT_NAME
으로, 프론트 프로젝트를 PROJECT_NAME2
으로 두었습니다.
2.2 서버단 Git pull 이후 빌드하기
cd $REPOSITORY/$PROJECT_NAME/
echo "> Git Pull server project"
git pull
echo "> Build server project"
./gradlew build
일단 서버 프로젝트 부터 빌드해주겠습니다. cd
를 통해 해당 경로로 이동한 후, git pull
을 통해 레포지토리에 변경 내용들을 pull 해옵니다. 이후 ./gradlew build
를 통해 빌드해줍니다.
에코는 디버깅을 위해 두었습니다!
2.3 빌드 파일 복사
cd $REPOSITORY
echo "> copy Server Project Build file"
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/
이후 다시 레포지토리 경로로 나온 다음, 빌드 파일을 복사해줍니다.
2.4 작동 중인 프로세스 확인
echo "> check Server Application pid"
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)
echo "> Server Application Pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "> there is no running Application"
else
echo "> kill -15 $CURRENT_PID (safe kill)"
kill -15 $CURRENT_PID
sleep 5
CURRENT_PID_AFTER_KILL=$(pgrep -f ${PROJECT_NAME})
if [ -z $CURRENT_PID_AFTER_KILL ]; then
echo "> Application kill well"
else
echo "> Kill Application Forced"
kill -9 $CURRENT_PID_AFTER_KILL
sleep 5
fi
fi
일단 현재 작동중인 서버 Application이 있는지 확인합니다. PID를 가져와서 실행중인 경우 프로세스를 종료합니다. 아닌 경우 일단 프로세스 안전 종료 명령어인 kill -15
를 사용합니다. 이후 종료되지 않았다면 강제 종료인 kill -9
를 사용합니다.
2.5 빌드파일로 배포하기! :star:
echo "> deploy new Application"
cd $REPOSITORY
JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)
echo "> JAR Name: $JAR_NAME"
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
복사해 둔 빌드 파일로 배포를 진행합니다. JAR_NAME에 jar 파일 이름을 가져온 다음 nohup
을 이용해서 background에 배포합니다. background에 배포한다는 것이 무슨 의미일까요? 터미널을 통해 프로젝트를 Run 해본 경험이 있다면, 프로젝트 Running 중에는 해당 터미널로 다른 행위를 하지 못 한다는 것을 알 수 있습니다. 이런 상황은 foreground에 배포를 한 상황입니다. 이런 경우에는 해당 터미널을 종료하는 경우 배포도 종료됩니다. 따라서, 기왕 따로 서버를 샀으니, background에 돌리는게 좋겠죠? 프론트 프로젝트도 또 돌려줘야하기도 하구요.
nohup의 결과는 nohup.out
에서 확인 가능합니다.
6. 프론트 프로젝트도 똑같이 진행해주기
echo "> run client project"
cd $REPOSITORY/$PROJECT_NAME2
echo "> pm2 kill"
pm2 kill
echo "> git pull"
git pull
echo "> npm build"
npm install
npm run build
echo "> pm2 build"
pm2 serve build/ 3000 --spa
프론트 프로젝트의 background 배포는 pm2
로 진행하겠습니다. java에서의 nohup
과 같은 역할을 해줍니다. 일단 돌아가고 있는 pm2
가 있을 수 있으니, pm2 kill
을 해줍니다.
저는 종료되어도 상관 없어서 이렇게 짜 주었으나, 종료되면 안 되는 경우에는 pm2 kill
을 빼주시고, 클라이언트 프로젝트 빌드 이후 pm2 restart all
을 통해 재시작을 해주면 됩니다!
process kill을 진행한 이후, 빌드는 npm으로 해주겠습니다. npm install
과 npm run build
을 입력해줍니다. 사실 저는 yarn을 더 좋아해서 yarn을 사용했으나, 일반적인 경우를 적어 보았습니다.
이후, 원하는 포트 번호로 빌드 해줍니다. pm2 serve build/ 3000 --spa
를 통해 빌드해주었습니다. 뒤의 --spa
는 다른 페이지로의 이동이 있을 때 붙여주는 옵션이라고 합니다.
이렇게 해주면 빌드와 배포스크립트의 작성이 끝납니다! 프로젝트에 에러가 없을 경우 정상적으로 빌드-배포 됩니다.
메인 레포지토리 폴더에서 (REPOSITORY) `chmod +x deploy.sh`로 권한을 준 다음 `./deploy.sh`로 실행합니다.
위에서 말 했던 대로, 서버 빌드 => 배포, 클라이언트 빌드 => 배포의 순으로 이루어졌습니다. 분명 많은 고생을 했는데 쓰고 보니 간단해보이는군요.
완성된 전체 deploy.sh
의 스크립트는 아래와 같습니다.
#!/bin/bash
REPOSITORY=/home/ec2-user/app
PROJECT_NAME=서버프로젝트이름
PROJECT_NAME2=클라이언트프로젝트이름
cd $REPOSITORY/$PROJECT_NAME/
echo "> Git Pull server project"
git pull
echo "> Build server project"
./gradlew build
cd $REPOSITORY
echo "> copy Server Project Build file"
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/
echo "> check Server Application pid"
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)
echo "> Server Application Pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "> there is no running Application"
else
echo "> kill -15 $CURRENT_PID (safe kill)"
kill -15 $CURRENT_PID
sleep 5
CURRENT_PID_AFTER_KILL=$(pgrep -f ${PROJECT_NAME})
if [ -z $CURRENT_PID_AFTER_KILL ]; then
echo "> Application kill well"
else
echo "> Kill Application Forced"
kill -9 $CURRENT_PID_AFTER_KILL
sleep 5
fi
fi
echo "> deploy new Application"
cd $REPOSITORY
JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)
echo "> JAR Name: $JAR_NAME"
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
echo "> run client project"
cd $REPOSITORY/$PROJECT_NAME2
echo "> pm2 kill"
pm2 kill
echo "> git pull"
git pull
echo "> npm build"
npm install
npm run build
echo "> pm2 build"
pm2 serve build/ 3000 --spa
'🔥 Projects 🔥' 카테고리의 다른 글
[충격] S3 배포시 '이것' 설정 안 하자... 개인정보 '술술' (0) | 2023.06.02 |
---|---|
[Nginx] Don't try this at home - "IF" is Evil (0) | 2023.04.17 |
[Security] @WithSecurityContext를 이용해 커스텀 UserDetails SecurityContext Test 코드 작성하기 (0) | 2023.04.17 |
FaaS와 비동기 처리로 브라우저의 부담 줄여주기 (0) | 2022.08.26 |