Github Actions + Code Deploy + S3 + NginX 로 Spring Boot 블루/그린 무중단 배포 구현하기 (1)
시스템 아키텍쳐 해당 프로젝트에서 사용한 시스템 아키텍쳐는 다음과 같다. 1. 개발자가 코드를 수정하고, 배포를 원하는 브랜치로 Github에 Push한다. 2. Github Actions에서 정해진 워크플로우에 따
mingyum119.tistory.com
Github Actions + Code Deploy + S3 + NginX로 Spring Boot 블루/그린 무중단 배포 구현하기 (2)
2023.08.27 - [Server/Architecture] - Github Actions + Code Deploy + S3 + NginX 로 Spring Boot 블루/그린 무중단 배포 구현하기 (1) Github Actions + Code Deploy + S3 + NginX 로 Spring Boot 블루/그린 무중단 배포 구현하기 (1) 시스
mingyum119.tistory.com
첫 번째 포스팅에서는 EC2 인스턴스와 CodeDeploy, S3 등의 AWS 서비스를 생성하고 접근 권한을 부여하는 실습을 하였다. 두 번째 포스팅에서는 Github Actions 워크플로우를 작성하고 CodeDeploy Agent에 배포 요청을 보내 인스턴스 내에서 배포를 수행하는 appspec.yml까지 작성 후 여러 필요한 패키지를 설치하였다.
이어서 마지막 포스팅에는 배포 스크립트를 작성하여 Nginx 무중단 배포를 구현하고, 도메인을 구입한 후 인증서를 등록해 최종 배포 테스트를 진행해보자.
1. 배포 스크립트 작성
이전에 작성한 appspec.yml
을 다시 보자. appspec.yml
은 CodeDeploy Agent가 배포를 실행할 때의 가이드 문서같은 것인데, 아래 사진을 보면 세 가지 배포 스크립트가 차례대로 실행되는 것을 알 수 있다.
이 배포 스크립트에서 8081, 8082 포트를 스위칭하며 원하는 포트에 프로젝트를 배포할 수 있다.
배포 스크립트를 작성하기 전에, Ubuntu 서버에 아래와 같은 파일을 만들어서 현재 구동 중인 포트의 번호를 기록한다.
cd /home/ubuntu
vi service_url.inc
## 아래 내용을 기입한다.
set $service_url http://127.0.0.1:8081;
배포 스크립트에서 이 파일을 참조하여 현재 구동 중인 포트의 번호를 확인하고, 포트를 스위치할 때 파일을 수정하여 8082 포트로 변경하게 된다.
- run_new_was.sh
- 현재 실행 중인 포트를 확인하고, 실행 중이지 않은 포트를 대상으로 새로운 버전을 배포한다.
- health_check.sh
- run_new_was.sh에서 새로운 포트로 서버가 잘 작동 중인지 확인하기 위한 스크립트이다.
- switch.sh
- 새로운 버전이 배포된 포트로 nginx의 80번 포트를 프록시한다.
run_new_was.sh
#!/bin/bash
echo "> Start run_new_was.sh"
# Parse port number from 'service_url.inc'
CURRENT_PORT=$(cat /home/ubuntu/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0
echo "> Current port of running WAS is ${CURRENT_PORT}."
# Find target port to switch
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
# query pid using the TCP protocol and using the port 'TARGET_PORT'
# TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')
echo "> Kill WAS running at ${TARGET_PORT}."
sudo kill $(sudo lsof -t -i:${TARGET_PORT})
# run jar file in background
nohup sudo java -jar -Dspring.profiles.active=dev -Dserver.port=${TARGET_PORT} /home/ubuntu/app/build/libs/backend-0.0.1-SNAPSHOT.jar > /home/ubuntu/nohup.out 2>&1 &
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0
health_check.sh
#!/bin/bash
# Crawl current connected port of WAS
CURRENT_PORT=$(cat /home/ubuntu/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}' ..."
# Check 10 times to see if the current port is available
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}/health)
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
switch.sh
#!/bin/bash
# Crawl current connected port of WAS
CURRENT_PORT=$(cat /home/ubuntu/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};" | tee /home/ubuntu/service_url.inc
echo "> Now nginx proxies to ${TARGET_PORT}."
# Reload nginx
sudo service nginx reload
echo "> Nginx reloaded."
2. 도메인 구입
나는 '가비아'라는 사이트에서 도메인을 구입하였다.
도메인은 보통 한 번 구매하면 연장할 때 상당히 큰 금액을 내야하기 때문에 자신의 도메인 사용 기간과 네이밍을 고려하여 결정하기를 바란다.
결제 후 My가비아 > 도메인 > 관리 탭에 들어가서 도메인 설정 창에 진입한다.
아래로 살짝 내리면, DNS 정보 메뉴에서 도메인 연결 설정을 할 수 있다. 설정 버튼을 눌러 도메인 연결 창에 들어가보자 .
DNS 설정 > 레코드 수정을 누른다.
레코드 추가 버튼을 누르고 (처음 접속하는 사람이라면 레코드가 텅 비어있을 것이다.) 아래 사진에 나온 것처럼 총 두개의 레코드를 입력하자.
타입 : A
호스트 : www
값 : EC2 인스턴스의 탄력적 IP
타입 : A
호스트 : @
값 : EC2 인스턴스의 탄력적 IP
이 과정을 거치면 도메인 설정은 완료다.
3. HTTPS 인증서 등록
웹을 배포하기 위해서는 HTTPS 인증서를 서버에 등록해야한다.
HTTPS란?
HTTPS는 기본 프로토콜인 HTTP의 보안 버전으로, 데이터 전송의 보안을 강화하기 위해 등장하였다. 인증서에는 신뢰할 수 있는 제 3자인 CA(Certificate Authority)에 의해 검증되며, 검색 엔진에 의해 SEO(검색 엔진 최적화)에 도움을 준다.
따라서 서버에 인증서를 등록하여, 80번 포트를 자동으로 443 포트(HTTPS 프로토콜이 접근하는 포트)로 연결하여 서비스를 사용할 수 있도록 한다.
Let's Encrypt 인증서 생성
인증서를 생성하는 것에는 다양한 방법이 있지만, 나는 간단히 발급할 수 있는 Let's Encrypt 인증 기관을 사용하였다. 인증서 발급 시 유효기간 (90일)이 존재한다.
1. 저장소 업데이트
sudo apt-get update
sudo apt-get upgrade
2. Let's Encrypt 설치
sudo apt-get install letsencrypt -y
3. nginx 웹서버 용 Certbot을 설치
sudo apt install certbot python3-certbot-nginx
4. SSL 인증서 발급
sudo certbot --nginx -d {도메인주소}
발급이 완료되면 아래와 같이 메시지가 뜨면서 /etc/letsencrypt/live/example.com/fullchain.pem
, /etc/letsencrypt/live/example.com/privkey.pem
두 가지 파일이 생성된다.
Output
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2020-08-18. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
이 두 파일의 경로를 기억해두고, Nginx의 설정으로 넘어가자.
4. Nginx 설정
현재에는 웹 서버로 Nginx를 사용 중이고, 80번 포트로 접근하였을 때 자동으로 443 포트로 넘어가도록 하기 위해 Nginx 설정 파일을 변경한다.
Nginx 설정 파일은 기본적으로 /etc/nginx/nginx.conf
이다. Nginx의 설정 파일이 분리되면서 /etc/nginx/sites-avilable
폴더에 서버와 관련된 설정 파일을 별도로 두기는 하나, 나는 /etc/nginx/nginx.conf 파일에 관련 설정 정보를 모두 작성하였다.
기본적으로 주어져 있는 /etc/nginx/nginx.conf
파일에 아래와 같은 server 블록만 두 가지를 추가한다.
server {
server_name example.com;
root /usr/share/nginx/html;
include /home/ubuntu/service_url.inc;
location / {
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;
proxy_pass $service_url;
}
include /etc/nginx/default.d/*.conf;
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
return 301 https://$host$request_uri;
listen 80;
listen [::]:80;
server_name example.com;
return 404;
}
5. 배포 테스트
여기까지 따라왔다면, 배포 요청이 들어와 배포 스크립트가 실행되면 현재 Spring Boot가 구동 중인 포트를 찾고, 새로운 포트로 새 버전을 배포한 후 스위칭 하는 일련의 작업들이 벌어진다.
사용자 입장에서는, nginx 웹서버인 80번 포트로 항상 접근하기 때문에 8081 포트로 구동 중이던, 8082 포트로 구동 중이던 80번 포트에서 자동으로 변경되기 때문에 불편함 없이 계속 무중단 서비스를 이용할 수 있는 것이다.
배포를 테스트하기 위해 github에 살짝 수정된 파일을 push한다.
그럼 이렇게 모든 작업들이 성공적으로 완료되고, Amazon S3 버킷을 확인하면 위 워크플로우에서 만든 zip 파일이 정상적으로 업로드 된 것을 확인할 수 있다.
'Request to CodeDeploy` step에 의해서 CodeDeploy도 실행되었다. AWS의 CodeDeploy에 들어가서 배포 진행 과정을 확인해보자.
이렇게 S3 버킷에 올려놓은 객체와 잘 연동되어 배포가 성공한 것을 확인할 수 있다. 아래 '배포 수명 주기 이벤트'에서 "View events"를 누르면 단계별로 이벤트가 잘 작동되었는 지, 실패하였다면 어느 부분에서 실패하였는 지 확인할 수 있다.
만약 서비스가 잘 동작하지 않는다면, ubuntu 서버의 루트에서 nohup.out
파일을 열어보자. Spring Boot Application이 실행되면서 어느 부분에서 실패하였는지 애플리케이션 차원에서 오류를 뱉을 수도 있다.
혹은 CodeDeploy 쪽에서 문제가 생겼다면 /var/log/codedeploy-agent/codedeploy-agent.log
파일을 확인하자.
이렇게 모든 배포 과정이 완료되었다.
기술 스택이 간단한 만큼 소규모 프로젝트에 적용하기 좋은 과정이라고 생각하고, 추후 Docker와 Docker-compose를 활용한 CI/CD 과정도 배워서 포스팅해보겠다.
포스팅에 잘못 기입된 부분이 있다면 댓글로 알려주시면 감사하겠습니다 ! 😊
'Server > Architecture' 카테고리의 다른 글
Github Actions + Code Deploy + S3 + NginX로 Spring Boot 블루/그린 무중단 배포 구현하기 (2) (0) | 2023.08.27 |
---|---|
Github Actions + Code Deploy + S3 + NginX 로 Spring Boot 블루/그린 무중단 배포 구현하기 (1) (0) | 2023.08.27 |
[MSA] 터빈 서버 (Turbine Server)로 Hystrix Client 메시지 수집하기 (0) | 2023.03.28 |
[MSA] Spring Cloud Config Server 구축 및 profiles 설정 (2) | 2023.03.27 |
[MSA] 유레카(Eureka)서버, 줄(Zuul) 서버 설정 및 구동하기 (0) | 2023.03.24 |