-
GitHub-Actions로 CI/CD 구축하기 (2)Lesson-Learned/tech 2023. 12. 17. 17:51
GitHub-Actions로 CI/CD 구축하기 (2) (AWS S3 + AWS CodeDeploy + Spring Boot)
안녕하세요, MADII의 Server 개발자 하노입니다 🍀
오늘은 지난 포스팅에 이어 마디의 서버 아키텍처와 CI/CD 과정을 마저 소개해 보겠습니다!
유중단 배포까지 성공했으니, 이어서 Nginx를 활용하여 무중단 배포 과정을 진행해 보겠습니다.
CI/CD 구축 과정 2) Nginx로 무중단 배포하기
[레퍼런스 2], [레퍼런스 3]을 참고하기도 하고 구글링도 많이 하면서 진행했습니다.
전체적인 무중단 배포 과정을 먼저 설명드리겠습니다.
- appspec.yml 에 정의된 대로 Code Deploy가 새 애플리케이션 파일을 EC2 인스턴스에 배포합니다.
- run_new.sh 스크립트가 실행되어 새로운 WAS 인스턴스를 시작합니다.
- health_check.sh 스크립트가 새 WAS 인스턴스의 정상 작동 여부를 확인합니다.
- 모든 것이 정상이면, switch.sh 스크립트가 실행되어 Nginx가 새 WAS 인스턴스로 트래픽을 전환합니다.
- 이 과정을 통해 기존 WAS 인스턴스에서 새로운 WAS 인스턴스로의 전환이 서비스 중단 없이 이루어집니다.
먼저 Ubuntu 환경에서 Nginx를 설치하기 위해서는 apt 패키지 관리 도구를 사용해야 합니다.
# 패키지 리스트 업데이트 sudo apt update # nginx 설치 sudo apt install -y nginx # nginx 실행 확인 sudo systemctl status nginx
http(80) 로 접속하면 Nginx 에서 요청을 다른 포트로 전달할 수 있도록 아래 설정을 진행합니다.
1. service-url.inc 파일 생성
sudo vim /etc/nginx/conf.d/service-url.inc
# service-url.inc # 아래 파일로 nginx가 바라볼 포트를 지정한다. # 내용은 배포시 마다 실행되는 scripts로 동적으로 변경된다. set $service_url <http://127.0.0.1:8081>;
2. nginx.conf 로 설정 파일 수정
*sudo vim /etc/nginx/nginx.conf*
server { listen 80; listen [::]:80; server_name _; include /etc/nginx/conf.d/service-url.inc; location / { # service_url로 요청 전달! 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.conf 일부
3. appspec.yml 추가
AWS CodeDeploy에 의해 사용되며, 배포할 파일의 위치, 권한 설정, 그리고 배포 중 실행할 스크립트에 대한 정보를 담고 있습니다.
- files 섹션에서는 소스 파일을 EC2 인스턴스의 /home/ubuntu/seesaw-app/ 경로에 배포하도록 지정합니다.
- permissions 섹션은 배포된 파일에 대한 사용자(ubuntu) 및 그룹(ubuntu) 권한을 설정합니다.
- hooks 섹션은 배포 생명주기의 특정 단계에서 실행할 스크립트를 지정합니다. 여기서는 ApplicationStart 단계에서 run_new_was.sh, health_check.sh, switch.sh 스크립트를 순서대로 실행합니다.
version: 0.0 os: linux files: - source: / destination: /home/ubuntu/seesaw-app/ # 파일들을 EC2 인스턴스에서 어디에 배포할지를 나타내는 경로 overwrite: yes permissions: - object: / 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
4. scripts 디렉토리 생성
4-1. run_new_was.sh
새로운 웹 애플리케이션 서버(WAS) 인스턴스를 시작하며, 스크립트는 다음과 같은 순서로 동작합니다.
- 현재 WAS가 실행 중인 포트 번호를 가져옵니다.
- 타겟 포트 번호를 설정합니다.
- 타겟 포트에서 실행 중인 WAS의 PID를 가져옵니다.
- 해당 PID가 존재하면 프로세스를 종료합니다.
- 새 WAS 인스턴스를 타겟 포트에서 실행합니다.
#!/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} /home/ubuntu/seesaw-app/build/libs/SeeSaw-0.0.1-SNAPSHOT.jar > /home/ubuntu/nohup.out 2>&1 & echo "> Now new WAS runs at ${TARGET_PORT}." exit 0
스크립트 실행 전에 다음 사항들을 확인한 후 동작하도록 합니다.
4-2. health_check.sh
새로 시작된 WAS 인스턴스가 정상적으로 작동하는지 확인하는 스크립트입니다.
- **curl**을 사용하여 새 WAS 인스턴스의 건강 상태를 확인합니다. 여기서 HTTP 상태 코드 200이 응답되면 정상으로 간주합니다.
- 여러 번 시도 후에도 정상적인 응답이 없으면 스크립트는 실패로 종료됩니다.
@RestController public class HealthController { @GetMapping("/health") public String checkHealth() { return "healthy"; } }
#!/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}/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
스크립트 실행 전에 curl 명령어가 시스템에 설치되어 있는지 확인합니다. 만약 설치되어 있지 않다면 **sudo apt install curl**로 설치할 수 있습니다.
4-3. switch.sh
Nginx 설정을 변경하여 새로운 WAS 인스턴스로 트래픽을 전환하는 스크립트입니다.
- Nginx 의 service-url.inc 파일을 수정하여 프록시가 새 WAS 인스턴스로 트래픽을 전달하도록 설정합니다.
- Nginx 를 리로드하여 변경 사항을 적용합니다.
#!/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 systemctl reload nginx echo "> Nginx reloaded."
5. 무중단 배포 전 WAS 띄우기
netstat -tuln | grep 8081
처음에는 어떤 WAS도 실행되지 않고 있기 때문에 초기에 WAS를 하나 실행합니다. EC2에 접속해서 해당 프로젝트 디렉터리로 이동한 후 JAR 파일을 Java로 실행하여 애플리케이션을 시작합니다.
보통 Spring Boot 프로젝트를 빌드할 때 *.jar와 *-plain.jar 두 가지 버전의 JAR 파일이 생성될 수 있습니다. 이 두 JAR 파일의 차이점은 다음과 같습니다:
- -plain.jar: 이것은 일반적인 JAR 파일로서, 내장 Tomcat과 같은 내장 웹 서버 없이 생성됩니다.
- .jar: 이것은 실행 가능한 JAR 파일로, 내장 웹 서버(Tomcat, Jetty 등)를 포함하고 있어 java -jar 명령어로 직접 실행할 수 있습니다.
무중단 배포나 다른 용도로 Spring Boot 애플리케이션을 실행하려면 내장 웹 서버를 포함하는 실행 가능한 *.jar 파일을 사용해야 합니다.
java -jar -Dserver.port=8081 /home/ubuntu/seesaw-app/build/libs/SeeSaw-0.0.1-SNAPSHOT.jar &
-> &로 백그라운드 실행!
무중단 배포 성공
CI/CD 구축하면서 만난 Trouble Shooting은 크게 2가지가 있습니다.
Trouble Shooting 1. 유중단 배포 확인 중 Code Deploy 배포 실패
원인
- AWS S3에서 특정 객체를 찾을 수 없을 때 발생하는 오류입니다. AWS Code Deploy가 배포 프로젝트를 찾을 수 없을 때 발생한다고 합니다.
- AWS S3에 압축 파일의 형태로 업로드는 잘 되고 있지만, 원인은 S3에 업로드 되어 있는 위치와 Code Deploy에서 참조하는 위치가 달랐기 때문이었습니다.
해결
- name: Upload to S3 run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip # run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
- 경로 수정하니 배포 성공하였고, Code Deploy가 참조하길 기대하는 위치에 압축 파일의 형태로 프로젝트가 올라가 있는 것을 확인할 수 있었습니다.
Trouble Shooting 2. CI/CD는 성공했는데, Spring Boot Application 연결 오류
기대하는 바로는 퍼블릭 ip 접속 시 Nginx 화면이 뜨고 Spring Boot Application의 Whitelabel이 뜨지 않았습니다. 프로젝트 연결이 잘 안 되었나 생각이 들어 Nginx 설정 파일을 살펴 보았습니다.
원인
- 80번 포트로 들어온 요청을 처리하는 설정 파일이 충돌하는 문제였습니다.
- 무중단 블루-그린 배포를 위한 Nginx 설정의 기본 개념은 ‘Nginx가 들어오는 모든 요청을 현재 활성화된 서버(블루 혹은 그린)로 프록시하는 것’입니다.
- 기본적으로 Nginx 설정 파일은 /etc/nginx/nginx.conf 또는 /etc/nginx/sites-available/default 등의 경로에 위치하게 됩니다.
user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { server { listen 80; listen [::]:80; server_name _; include /etc/nginx/conf.d/service-url.inc; location / { # service_url로 요청 전달! 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; } }
# Default server configuration # server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; # Add index.php to the list if you are using PHP index index.html index.htm index.nginx-debian.html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; }
- default 설정은 여러 가지 용도로 사용할 수 있지만, 현재 상황에서는 **nginx.conf**의 설정과 충돌이 발생할 수 있습니다. 그 이유는 두 설정 모두 80 포트에서 동작하고 있기 때문입니다.
- 현재 제시된 설정에 따르면 **nginx.conf**에서는 80 포트로 들어온 요청을 service-url.inc에 정의된 서비스 URL 즉, http://127.0.0.1:8081로 프록시하도록 설정되어 있습니다. 반면 default 설정에서는 80 포트로 들어온 요청을 기본 웹 루트 (/var/www/html)에서 처리하도록 설정되어 있습니다
- 두 설정이 동시에 활성화된 상태에서는 80 포트로 들어온 요청이 어느 설정에 따라 처리될지 명확하지 않습니다. 따라서 default 설정을 비활성화하여 이 충돌을 해결할 필요가 있습니다.
해결
- default 설정 비활성화: sites-enabled 디렉터리의 default 심볼릭 링크를 제거하여 default 설정을 비활성화합니다.이렇게 하면 **/etc/nginx/sites-available/default**의 설정은 더 이상 활성화되지 않습니다.
sudo rm /etc/nginx/sites-enabled/default
- Nginx 재시작: 설정 변경 사항을 적용하기 위해 Nginx를 재시작합니다.
sudo systemctl restart nginx
원하는 Whitelabel 화면이 보이고 해결 완료했습니다!
이렇게 마디의 CI/CD 구축 과정을 적어 보았는데요, 아직 공부하면서 배우고 있는 중이라 부족한 점이 많을 수 있으니 참고해 주세요. 처음 도전한 무중단 블루-그린 배포 방식, 처음 사용해본 Code Deploy로 새로운 인프라 구축을 해볼 수 있어 유의미한 시간이었습니다.
그럼 이만 포스팅을 마치도록 하겠습니다. 마디와 구떠리에 많은 관심 가져 주시고 다음 포스팅에서 만나요 :)
긴 글 읽어 주셔서 감사합니다 ❤️
'Lesson-Learned > tech' 카테고리의 다른 글
MADII의 도메인을 모델링해보자! (1) (1) 2024.01.04 iOS 프로젝트 세팅을 해보자..! (feat. SwiftLint) (1) 2023.12.20 GitHub-Actions로 CI/CD 구축하기 (1) (1) 2023.12.17