github actions, EC2, nginx를 통한 무중단 배포
지금은 익숙한 프로세스이지만 누구나 처음 배포하는 과정은 매우 어려울 것 같습니다.
그렇지만 미래를 상상하면서 지금의 과정을 즐기는 것도 나쁘지 않지 않을까요?
IAM 설정
본격적으로 sh파일과 yml 파일을 작성 전에 해야할 단계가 있습니다.
바로 aws의 IAM 설정과 보안그룹 설정입니다.
위처럼 사용자를 만들 때 생성되는 secret key와 access key를 잘 보관해놔야합니다.
이후에 아래의 권한정책에서 4개를 추가해주면 됩니다.
각각 배포, ec2접근, S3 사용에 관한 권한입니다.
사실 제 코드에서는 S3를 사용하진 않는데 지금 보니 GPT가 참 멍청하네요.
github secret repository 설정
setting에 들어간 뒤
secrets 탭에서 actions에 들어간 뒤에
위처럼 키를 만들어주면 됩니다.
access키와 secret키에는 아까 iam에서 생성할 때 저장해 둔 키를 사용하면 되고
보통 ec2를 사용하면 인스턴스 서버에 접속할 때,
ssh -i ???.pem USERNAME@HOST
이러한 양식으로 접속하게 되는데 이 양식에서
EC2_USERNAME = USERNAME
EC2_HOST = HOST
SSH_PRIVATE_KEY = pem키를 cat ???.pem 했을 경우 pem키의 내용을 복붙
nginx 설정
맨 처음에 nginx를 왜 이용하지? 라는 생각이 들었습니다.
지피티와 사투끝에 내제가 깨닳은 바는 우리가 배포할 때 기존의 포트를 kill하고 새로 업데이트한 jar파일을 배포하게 되는데
이 경우, 기존의 서비스를 이용하던 사용자들은 갑자기 서버가 끊기는 동안 서비스를 못 이용하는 불편함을 겪게 됩니다.
그렇기에 그렇다면 사용자가 서버가 끊기는 것을 모르도록 배포하면 되지 않을까?의 해결책으로 나온게 nginx입니다.
nginx 다운로드
sudo apt update
sudo apt install nginx
nginx 시작
sudo systemctl start nginx
sudo systemctl enable nginx
방화벽 설정
sudo ufw allow 22
sudo ufw allow 22/tcp
sudo ufw allow 8080/tcp
sudo ufw allow 80/tcp
sudo ufw allow 'Nginx Full'
sudo ufw enable
22 포트를 방화벽을 활성화 전에 열어주는 이유는 ssh -i 이렇게 접속할 때 22포트로 요청을 하기에 22포트를 허가 안 하고 enable을 하면 인스턴스 서버를 원격에서 접속 못 하는 대참사가 일어납니다.
필자는 이거를 한번 겪고 터미널에서 접속을 못하게 돼서 aws 사이트에서 따로 들어가서 22포트를 다시 열어주는 수고를 겪었습니다.
nginx 기본 설정 변경
sudo nano /etc/nginx/sites-available/default
파일을 열어서 안의 내용을 다 지운 뒤에
upstream backend {
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
server {
listen 80;
server_name your_domain_or_ip;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
이렇게 설정해 주면 됩니다.
여기서는 로드밸런싱이라는 기법을 사용하는데 이게 무엇이냐?
코드를 수정하고 다시 배포를 할 때 그냥 kill PID 하고 다시 배포를 하면 기존의 유저가 잘 사용하던 서비스가 갑자기 끊기면 문제가 생길 거에요.
이거를 방지하기위해 기존에 8081을 사용하고 있는 경우에는 8082포트에 새로운 배포버전을 올려서 기존의 8081 이용자들을 스무스하게 옮기는 방법을 로드 밸런싱이라고 합니다.
nginx 업데이트 및 재시작
sudo nginx -t
sudo systemctl restart nginx
deploy_nginx.sh 설정
#!/bin/bash
APP_NAME=""
JAR_NAME="$APP_NAME.jar"
DEPLOY_DIR="/home/ubuntu"
CURRENT_PORT=$(sudo lsof -i -P -n | grep LISTEN | grep 808 | awk '{print $9}' | sed 's/[^0-9]*//g')
TARGET_PORT=0
echo "> 현재 구동중인 애플리케이션 포트: $CURRENT_PORT"
if [ "$CURRENT_PORT" == "8081" ]; then
TARGET_PORT=8082
elif [ "$CURRENT_PORT" == "8082" ]; then
TARGET_PORT=8081
else
TARGET_PORT=8081
fi
echo "> 배포할 포트: $TARGET_PORT"
# 새로운 애플리케이션 배포
nohup java -jar $DEPLOY_DIR/$JAR_NAME --server.port=$TARGET_PORT > $DEPLOY_DIR/nohup_$TARGET_PORT.out 2>&1 &
# 새로운 애플리케이션이 구동될 때까지 대기
sleep 10
# 새로운 애플리케이션이 정상적으로 구동되었는지 확인
echo "> 새로운 애플리케이션 구동 확인"
NEW_PID=$(lsof -t -i:$TARGET_PORT)
echo "> Nginx 설정 변경"
sudo sed -i "s/proxy_pass http:\/\/localhost:808[0-9];/proxy_pass http:\/\/localhost:$TARGET_PORT;/" /etc/nginx/sites-available/default
echo "> Nginx 리로드"
sudo systemctl reload nginx
echo "> 기존 애플리케이션 종료"
if [ "$CURRENT_PORT" != "0" ]; then
sudo fuser -k -n tcp $CURRENT_PORT
fi
echo "> 배포 완료"
github actions 작성
여기서 새로운 workflows를 만들고 이름은 임시로 ci-cd.yml으로 만든 다음에
name: CI/CD Pipeline
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '17'
- name: Grant execute permission for gradlew
run: cd runtale && chmod +x gradlew
- name: Build with Gradle
run: cd runtale && ./gradlew clean -x test build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: runtale/build/libs/runtale-0.0.1-SNAPSHOT.jar
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
path: runtale/build/libs
- name: Deploy to AWS EC2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'ap-northeast-2'
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
echo "${SSH_PRIVATE_KEY}" > private_key.pem
chmod 600 private_key.pem
scp -i private_key.pem -o StrictHostKeyChecking=no runtale/build/libs/runtale-0.0.1-SNAPSHOT.jar ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }}:/home/${{ secrets.EC2_USERNAME }}/api/runtale.jar
ssh -i private_key.pem -o StrictHostKeyChecking=no ${{ secrets.EC2_USERNAME }}@${{ secrets.EC2_HOST }} "chmod +x /home/${{ secrets.EC2_USERNAME }}/api/deploy_nginx.sh && /home/${{ secrets.EC2_USERNAME }}/api/deploy_nginx.sh"
rm -f private_key.pem
위처럼 복붙을 해주면 됩니다.
필자의 코드에서 주의할 점은 루트 디렉토리 위에 api라는 폴더가 하나 더 있어서 디렉토리 경로를 독자의 기준으로 수정해주면 좋을 것 같습니다.
간략히 코드 설명을 하자면 git의 가상서버에 나의 최신의 프로젝트 코드를 bulid하여서 임시로 저장한 후에 깃의 가상서버에서
scp 명령어를 이용해서 빌드한 jar파일을 가져온 뒤, ssh로 인스턴스 서버를 접속한 뒤에 가져온 jar파일을 통해서 내가 미리 서버에 작성해둔 sh파일과 함께 배포하는 과정이라고 생각하면 됩니다.파일 내용
ec2 보안 그룹 설정
모든 설정을 거의 다 마쳤습니다.
우리는 사용자가 들어올 경우 제가 열어둔 포트로 리버스 프록싱이 될 것입니다.
우리는 8081과 8082 포트를 사용하는데 그러기 위해서는 ec2에서 80포트를 열어둬야 합니다.
'개발' 카테고리의 다른 글
[spring mvc] 스프링 mvc 꼭 알아야 하는 필수 지식 (0) | 2024.10.15 |
---|---|
[java] JVM 개념 및 기능 : 왜 쓰는 걸까? (3) | 2024.10.10 |
[spring] spring security 완전 정복 (4) | 2024.10.09 |
[JAVA] 자바의 가비지컬렉션 개념과 동작원리 (0) | 2024.10.08 |
[spring mvc] 서블릿 컨테이너 너는 누구냐 (1) | 2024.10.06 |