안녕하세요. 방금 우당탕탕 어찌저찌 무중단 배포로 CICD 세팅 완료하고 왔읍니다 !
지난 번 CICD에는 성공했지만,, 매번 코드가 바뀌고 배포할 때마다 그 배포 중은 서버가 502로 내려가는 것이 조금 불편쓰 했다.
그래서 이번에는 Nginx를 이용하여 무중단 배포 CICD를 세팅하기로 했다! 우분투 20.04 기준입니다!
플로우는 Spring Boot로 CICD 세팅하기 포스팅(https://soso-hyeon.tistory.com/53)와 비슷하게 흘러가되, Nginx와 몇 가지 파일을 추가함으로써 무중단 특성을 추가한다. (즉, 몇 가지 과정은 생략될 것이라는 마...ㄹ..)
전체적인 흐름도는 다음과 같다.
(1) 빌드된 JAR 파일을 S3에 업로드 한다.
(2) S3에 업로드 된 파일을 가지고 Code Deploy에 전달한다.
(3) Code Deploy에서 Code Deploy Agent에게 배포 명령을 내리고,
(4) S3에서 JAR 파일을 받아와 EC2에 배포한다.
(1)~(4) 과정은 CICD 과정과 같다. 여기서 Nginx를 사용하여 포트를 2개로 나눠 번갈아가며 WAS를 띄움으로써 배포 중 서버가 끊기지 않도록 한다. 처음에 8081 포트로 배포했다면, 다음 배포에서는 8082로 배포한다. 8082번이 배포될 동안은 8081번 배포는 살아있으니 배포 중에도 서버가 끊기지 않을 수 있다. 또한, proxy_pass을 사용하여 특정 도메인으로 요청을 날렸을 때 현재 배포된 포트 번호로 redirect 되도록 함으로써 무중단 배포를 설정할 수 있었다.
이제 직접 해보자!
CI, jdk17, codedeploy-agent, S3, IAM 설정 등등 과정은 생략하도록 하겠다. 해당 과정은 아래 포스팅을 참고해주세요!
jar 파일을 압축하여 S3 업로드까지는 필수로 해주세요!
Nginx 설치
아래와 같이 설치해주세요!
AWS ~ deploy.yml
AWS 설정부터 deploy.yml 파일까지는 위 포스팅에서 확인할 수 있는 과정이므로 생략하겠다.
appspec.yml 파일
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/smeme/
overwrite: yes
permissions:
- object: /home/ubuntu
pattern: '**'
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: scripts/run_new_was.sh
timeout: 180
runas: ubuntu
- location: scripts/health_check.sh
timeout: 180
runas: ubuntu
- location: scripts/switch.sh
timeout: 180
runas: ubuntu
본 CICD 과정에서 hooks 과정이 다르다. 무중단 배포에서는 3가지 스크립트 파일을 실행시킨다.
run_new_was.sh 파일
scripts 파일 아래에 run_new_was.sh 파일을 추가하자.
#!/bin/bash
CURRENT_PORT=$(cat /etc/nginx/conf.d/service-url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0
echo "> Current port of running WAS is ${CURRENT_PORT}."
if [ ${CURRENT_PORT} -eq 8081 ]; then
TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
TARGET_PORT=8081
else
echo "> No WAS is connected to nginx"
fi
TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')
if [ ! -z ${TARGET_PID} ]; then
echo "> Kill WAS running at ${TARGET_PORT}."
sudo kill ${TARGET_PID}
fi
nohup java -jar -Dserver.port=${TARGET_PORT} -Dspring.profiles.active=prod /home/ubuntu/smeme/build/libs/server-0.0.1-SNAPSHOT.jar > /dev/null 2> /dev/null < /dev/null &
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0
- 현재 배포 중인 포트가 8081이라면 8082를, 반대라면 8081을 TARGET_PORT로 지정한다. (그 외는 연결된 것이 없음)
- TARGET_PORT를 사용 중인 PID가 있다면 kill 한다.
- nohup으로 TARGET_PORT로 실행시킨다. (TARGET_PORT로 배포)
-Dspring.profiles.active는 프로필 지정으로, 낯설다면 아래 포스팅을 참고해주세요!
health_check.sh 파일
파일명 그대로 잘 배포되었는지 heath check 하는 용도의 파일이다. 마찬가지로 scripts 폴더 내부에 추가한다.
#!/bin/bash
# Crawl current connected port of WAS
CURRENT_PORT=$(cat /etc/nginx/conf.d/service-url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0
# Toggle port Number
if [ ${CURRENT_PORT} -eq 8081 ]; then
TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
TARGET_PORT=8081
else
echo "> No WAS is connected to nginx"
exit 1
fi
echo "> Start health check of WAS at 'http://127.0.0.1:${TARGET_PORT}' ..."
for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10
do
echo "> #${RETRY_COUNT} trying..."
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${TARGET_PORT}/)
if [ ${RESPONSE_CODE} -eq 200 ]; then
echo "> New WAS successfully running"
exit 0
elif [ ${RETRY_COUNT} -eq 10 ]; then
echo "> Health check failed."
exit 1
fi
sleep 10
done
- 이전 yml 파일에서와 같이 TARTGET_PORT를 가져온다.
- 10번의 카운트 동안 테스트 경로 요청에서 200을 받으면 패스한다. (카운트 안에 요청 못하면 실패)
그러므로 health check 할 API 경로를 미리 만들어주도록 하자.
@GetMapping("/")
public String test() {
return "CD TEST V5";
}
"/" 경로에 요청 테스트 API를 추가했다. (Spring Security를 사용한다면 permitAll() 해주는 것도 잊지 말아야 한다.)
switch.sh
EC2 내에 TARGET_PORT를 저장해주는 용도이다. (CURRENT_PORT > TARGET_PORT) 역시 scripts 폴더 내에 추가한다.
#!/bin/bash
# Crawl current connected port of WAS
CURRENT_PORT=$(cat /etc/nginx/conf.d/service-url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0
echo "> Nginx currently proxies to ${CURRENT_PORT}."
# Toggle port number
if [ ${CURRENT_PORT} -eq 8081 ]; then
TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
TARGET_PORT=8081
else
echo "> No WAS is connected to nginx"
exit 1
fi
# Change proxying port into target port
echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" |sudo tee /etc/nginx/conf.d/service-url.inc
echo "> Now Nginx proxies to ${TARGET_PORT}."
# Reload nginx
sudo service nginx reload
echo "> Nginx reloaded."
CURRENT_PID=$(lsof -Fp -i TCP:${CURRENT_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')
sudo kill ${CURRENT_PID}
- TARGET_PORT를 구한다.
- service_url 환경변수 값의 포트번호를 TARGET_PORT로 바꾼다.
- nginx를 재시작 시킨다.
- 재배포 전에 사용하던 포트번호는 해당 PID를 찾아서 kill 한다.
EC2 설정
sudo vi /etc/nginx/sites-available/default 경로로 들어가서 파일을 편집해야 한다.
location / 부분을 찾아서 위에 include 부분과, 아래 proxy_set_header ~ send_timeout 까지 7줄 추가해준다.
해당 ip로 API 요청이 들어왔을 때, include된 파일에서 설정한 환경변수 $service_url로 redirect 된다.
이제 sudo vi /etc/nginx/conf.d/service-url.inc 경로로 들어가서 환경변수를 설정해주어야 한다.
set $service_url http://127.0.0.1:8081;
처음에 8081번으로 설정했다.
배포를 시킬 때, 이미 8081번 8082번 중 하나로 배포가 되어있는 상태여야 하므로 처음에 수동으로 한 번 배포시켜주어야 한다.
nohup java -jar -Dserver.port=8081 -Dspring.profiles.active=prod /home/ubuntu/smeme/build/libs/server-0.0.1-SNAPSHOT.jar &
위 명령어를 EC2 내에서 실행하여 백그라운드로 jar 파일을 배포시켜주어야 한다.
배포
그럼 이제 health check 할 부분의 API를 수정하여 develop에 push 해보자.
1. 깃허브 액션 성공
2. CodeDeploy 배포 성공
3. 위에서 설정한 service-url.inc 파일을 확인하면 포트번호가 8082로 변경되었음을 확인할 수 있다.
4. 포트번호 없이 ip 주소(또는 도메인)으로 접속하면 API가 정상적으로 호출되는 것을 확인할 수 있다.
무중단 배포를 설정해준 후에는 배포 중에도 서버가 502로 내려가지 않았다. 끊기지 말고 편하게 개발하자! 야호~!
Refereces
https://mumomu.tistory.com/126
https://velog.io/@dirn0568/NGINX-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC