1. 무중단 배포를 위한 Nginx
이전에 github actions를 이용해서 자동배포를 만들었다
하지만 자동배포를 할때 다운타임 이 발생한다
여기서 다운타임이란 기존에있던 서버가 내려가고 새로운 서버가 올라갈때
그때 생기는 잠깐의 시간에 클라이언트가 사이트를 이용하지 못하게 되는데
이를 지칭한다고 생각하면 된다
이러한 다운타임이 생기지 않게 하는것이 무중단 배포
우리가 Nginx를 이용하는 이유이다
무중단 설정은 Nginx의 reverse proxy 기능을 사용했고
Rolling 배포방식을 이용했으며 인스턴스를 1개 사용하고
포트를 8081, 8082로 두개를 이용해서 구현했다
2. Nginx 설정하기
1) 가장 먼저 EC2에 접속해서 보안 인바운드규칙에 80포트를 추가한다

2) EC2에서 Nginx를 설치한다
$ sudo apt install nginx
설치를 마쳤으면 시작해본다
$ sudo service nginx start
nginx가 실행되고 있는지 확인해본다
$ ps -ef | grep nginx
3) http(80), https(443) 으로 접속하면 nginx에서 서비스가 올라간
8081포트로 서비스를 전달하도록 하기위해 아래와 같이 설정한다
설정을 위한 파일 생성
$ sudo vim /etc/nginx/conf.d/service-url.inc
service-url.inc 첫줄에 아래와같이 설정한다
set $service_url http://127.0.0.1:8081;
이렇게 설정해두면 nginx가 아래 코드로 포트를 바라보고
나중에 switch.sh 스크립트 파일로 인해 동적으로 변경된다
4) nginx.conf 파일을 수정해준다
많은 블로그에서 sudo vim /etc/nginx/nginx.conf 에서 설정 파일을 수정하라는데
나같은 경우 들어오니 아래와 같이 2개의 코드가 적혀있다
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
저 파일을 포함하고 있다는 뜻이고
우리가 지금 수정하려는 nginx.conf는 2번째 줄의
sites-enabled 아래에 default 파일로 존재한다
default파일로 접근해서 아래와같이 코드를 추가해주자
server {
include /etc/nginx/conf.d/service-url.inc;
location / {
proxy_pass $service_url;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
}
설정을 마쳤으면 아래 코드로 nginx를 재시작 해준다
$ sudo service nginx start
3. 프로젝트 설정하기
1) nginx 컨트롤러 만들기
@RestController
@RequiredArgsConstructor
public class NginxController {
private final String HEALTH = "up";
private final Environment env;
@GetMapping("/api/nginx/profile")
public String getProfile() {
return Arrays.stream(env.getActiveProfiles()).findFirst().orElse("");
}
@GetMapping("/api/nginx/health")
public String getHealth() {
return HEALTH;
}
}
2) 변경 할 포드 yml 2개 만들기
application-port1.yml
server:
port: 8081
spring:
config:
import: classpath:application.yml
application-port2.yml
server:
port: 8082
spring:
config:
import: classpath:application.yml
3) 배포 스크립트 작성하기
deploy.sh
#!/bin/bash
BUILD_JAR=$(ls /home/ubuntu/app/deploy/mountainz-*.jar)
JAR_NAME=$(basename $BUILD_JAR)
echo "> build 파일명: $JAR_NAME" >> /home/ubuntu/app/deploy/deploy.log
echo "> build 파일 복사" >> /home/ubuntu/app/deploy/deploy.log
DEPLOY_PATH=/home/ubuntu/app/deploy/
cp $BUILD_JAR $DEPLOY_PATH
RESPONSE_CODE=${curl -s -o /dev/null -w "%http_code" http://localhost/api/nginx/profile}
echo "> $RESPONSE_CODE response code"
if [ ${RESPONSE_CODE} -ge 400 ]
then
echo "> NO RUNNING APP"
CURRENT_PROFILE=port2
else
CURRENT_PROFILE=$(curl -s http://localhost/api/nginx/profile)
fi
echo "> $CURRENT_PROFILE current profile"
if [ $CURRENT_PROFILE == port1 ]
then
IDLE_PROFILE=port2
IDLE_PORT=8082
elif [ $CURRENT_PROFILE == port2 ]
then
IDLE_PROFILE=port1
IDLE_PORT=8081
else
echo "> no coincidence profile: $CURRENT_PROFILE"
echo "> set profile: port1"
IDLE_PROFILE=port1
IDLE_PORT=8081
fi
IDLE_APPLICATION=$IDLE_PROFILE-$JAR_NAME
IDLE_APPLICATION_PATH=$DEPLOY_PATH$IDLE_APPLICATION
ln -Tfs $DEPLOY_PATH$JAR_NAME $IDLE_APPLICATION_PATH
echo "> 현재 실행중인 애플리케이션 pid 확인" >> /home/ubuntu/app/deploy/deploy.log
IDLE_PID=$(pgrep -f $IDLE_APPLICATION)
if [ -z $IDLE_PID ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." >> /home/ubuntu/app/deploy/deploy.log
else
echo "> kill -15 $IDLE_PID"
kill -15 $IDLE_PID
sleep 10
fi
echo "> $IDLE_PROFILE 배포" >> /home/ubuntu/app/deploy/deploy.log
nohup java -jar -Duser.timezone=GMT+9 -Dspring.profiles.active=$IDLE_PROFILE $IDLE_APPLICATION_PATH >> /home/ubuntu/app/deploy/deploy.log 2>/home/ubuntu/app/deploy/deploy_err.log &
for RETRY in {1...10}
do
RESPONSE=$(curl -s http://localhost:$IDLE_PORT/api/nginx/health)
HEALTH_WORD_COUNT=$(echo $RESPONSE | grep 'up' | wc -l)
if [ ${HEALTH_WORD_COUNT} -ge 1 ]
then
echo "> health check done"
break
else
echo "> health check fail"
echo "> $RESPONSE"
fi
if [ ${RETRY} -eq 10 ]
then
echo "> health check loop fail"
echo "> NGINX Switching fail"
exit 1
fi
echo "> health check retrying"
sleep 10
done
echo "> Profile Switch"
sleep 10
sudo sh /home/ubuntu/app/deploy/switch.sh
switch.sh
echo "> 현재 구동중인 Port 확인"
CURRENT_PROFILE=$(curl -s http://localhost/api/nginx/profile)
# 쉬고 있는 set 찾기: port1이 사용중이면 port2가 쉬고 있고, 반대면 port1이 쉬고 있음
if [ $CURRENT_PROFILE == port1 ]
then
IDLE_PORT=8082
elif [ $CURRENT_PROFILE == port2 ]
then
IDLE_PORT=8081
else
echo "> 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE"
echo "> 8081을 할당합니다."
IDLE_PORT=8081
fi
echo "> 전환할 Port: $IDLE_PORT"
echo "> Port 전환"
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" |sudo tee /etc/nginx/conf.d/service-url.inc
PROXY_PORT=$(curl -s http://localhost/api/nginx/profile)
echo "> Nginx Current Proxy Port: $PROXY_PORT"
echo "> Nginx Reload"
sudo nginx -s reload
appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app/deploy
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: deploy.sh
timeout: 60
runas: ubuntu
main.yml
name: Java CI TEST with Gradle
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
env:
RESOURCE_PATH: ./src/main/resources/application.yml
PROJECT_NAME: mountainz
# Database
DB_URL: ${{ secrets.DB_URL }}
DB_USERNAME: ${{ secrets.DB_USERNAME }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
# JWT Secret
JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
# AWS S3
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
S3_REGION: ${{ secrets.S3_REGION }}
AWS_REGION: ap-northeast-2
# CODE_DEPLOY
APPLICATION_NAME: ${{ secrets.APPLICATION_NAME }}
DEPLOY_GROUP_NAME: ${{ secrets.DEPLOY_GROUP_NAME }}
# SLACK WEBHOOK
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Generate Environment Variables File for Properties
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.RESOURCE_PATH }}
env:
spring.datasource.url: ${{ env.DB_URL }}
spring.datasource.username: ${{ env.DB_USERNAME }}
spring.datasource.password: ${{ env.DB_PASSWORD }}
jwt.secretKey: ${{ env.JWT_SECRET_KEY }}
cloud.aws.credentials.access-key: ${{ env.AWS_ACCESS_KEY_ID }}
cloud.aws.credentials.secret-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
cloud.aws.s3.bucket: ${{ env.S3_BUCKET_NAME }}
cloud.aws.region.static: ${{ env.S3_REGION }}
logging.slack.webhook-uri: ${{ env.SLACK_WEBHOOK_URL }}
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# Build
- name: Build with Gradle
run: ./gradlew clean build
# 전송할 파일을 담을 디렉토리 생성
- name: Make Directory for deliver
run: mkdir deploy
# Jar 파일 Copy
- name: Copy Jar
run: cp ./build/libs/*.jar ./deploy/
# appspec.yml Copy
- name: Copy appspec
run: cp ./appspec.yml ./deploy/
# script file Copy
- name: Copy shell
run: cp ./scripts/* ./deploy/
# 압축파일 형태로 전달
- name: Make zip file
run: zip -r -qq -j ./$PROJECT_NAME.zip ./deploy
# S3 Bucket으로 copy
- name: Deliver to AWS S3
env:
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
run: aws s3 cp --region $S3_REGION --acl private ./$PROJECT_NAME.zip s3://$S3_BUCKET_NAME/
# Deploy
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ env.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.AWS_SECRET_ACCESS_KEY }}
run: aws deploy create-deployment --application-name $APPLICATION_NAME --deployment-group-name $DEPLOY_GROUP_NAME --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME.zip --region $S3_REGION'DevOps' 카테고리의 다른 글
| [DevOps] OAuth 2.0 (생활코딩) (0) | 2022.12.01 |
|---|---|
| [DevOps] Forward Proxy와 Reverse Proxy 차이점 (0) | 2022.12.01 |
| [DevOps] 우분투(ubuntu) 기본 쉘 dash 에서 bash로 변경하기 (0) | 2022.11.29 |
| [DevOps] 도메인 구매 + Route53 + ELB(HTTPS) (0) | 2022.11.27 |
| [DevOps] Route 53 (생활코딩) (0) | 2022.11.27 |