created at 2023-10-07
Table of contents
본 포스팅은 제가 아래의 이슈에서 이야기한 내용을 정리한 글이에요.
CI/CD 자동화 성공 그리고 실패
기존에 CI/CD 플로우는 하나의 머신에 자동으로 업로드하는 플로우를 가지고 있습니다(기존의 CI/CD 방법). 이 방법은 매우 성공적으로 끝났어요. 모든게 잘 수행되었고 완벽히 자동화되어있었어요. 하지만 이 방법에는 큰 단점이 있었습니다. 만약 컴퓨팅 머신의 리소스가 과부화된다면 수직확장을 하거나 다른 방법을 찾아야했거든요.
그리고 우려하던 일이 실제로 발생했습니다 😅😅. 바로 제 머신인
AWS-EC2 t2.micro
의 CPU 사용률이 100% 를 찍어버렸기 때문이죠. 아래의 그래프처럼요!그렇다면 어떻게 해결할 수 있을까요? 크게는 수직확장, 수평확장, 리소스 할당감소, 성능 향상 이 네 가지를 볼 수 있습니다. 저는 현재 수직확장과 리소스 할당 감소를 수행해보았으며, 수평확장을 수행하는 중입니다.
이제부터 아래의 순서에 맞춰 설명하겠습니다.
- 기존의 CI/CD 방법
- 수직확장과 리소스 할당 감소 시 결과
- 수평확장 플랜
1. 기존의 CI/CD 플로우
저는 통합배포과정을 Git-Actions + Gradle + Docker + Shell-Script + 기타 들로 수행합니다. 먼저 이해를 돕기위해 아래의 그림을 볼까요?
제가 표기한 번호에 맞추어서 각각의 과정을 설명할게요.
- 최신 태그버전 가져오기
- Deploy 워크플로우가 최신 태그 버전을 가져옵니다.
- 버전 파싱 및 업데이트
- 태그에서 “v” 접두사를 제거하고, major, minor, patch 버전을 분리합니다.
- Pull-Request 제목과 일치하는 버전을 업데이트하고 이를 GitHub 에 푸시합니다.
- 예를 들어,
v5.0.1-abf2154
이 최신 태그이고 PR 제목에**[patch]**
가 포함되어 있다면, 이 작업은v5.0.2-b321ac
버전을 생성하여 GitHub에 푸시합니다. 새로운 태그 끝에는 언제나 커밋 해시를 추가합니다.
- 프로젝트 빌드
./gradlew build --build-cache --parallel -Pversion=${new version}
명령을 실행하여.jar
파일을 빌드합니다..jar
파일 이름 끝에${new version}
을 추가합니다..jar
파일과 함께 Dockerfile 을 자동으로 생성합니다.예를 들어,
v5.0.2-b321ac
가 새 버전 태그라면, Gradle은 새 버전을 읽어들이고**_v5.0.2-b321ac.jar
를 빌드합니다..jar
파일 빌드 이후에 Gradle 스크립트는.jar
파일을 복사하여 Dockerfile 을 생성합니다 (예:COPY /build/libs/shop-user-service-v5.0.2-b321ac.jar /null/app.jar
).
- Docker 이미지 빌드 및 ECR 푸시
- 모든 Dockerfile 을 빌드하여 생성된 모든 이미지를 ECR에 푸시합니다.
- AWS-ECR 은 여러 리포지토리를 만들 경우 상당히 비용이 들 수 있기 때문에 하나의 리포지토리만 생성하고,
${service_name}_${new_version}
로 태그된 모든 이미지를 식별하기 위해 푸시합니다.
- EC2에서 이미지 풀
- AWS-EC2에 액세스하여 AWS-ECR 에서 모든 이미지를 가져옵니다.
- 모든 이미지를 가져올 때, 이미지 이름을 가져오고 접미사 이름을 필터링해야 합니다. 예를 들어, ECR에
A-service_v5.0.2
,A-service_v5.0.1
,B-service_v5.0.2
,C-service_v5.0.2
와 같은 이미지가 있다면, 각 이름을_
구분자로 분리하고 인덱스가v5.0.2
와 정확히 일치하는지 확인하여 for 루프를 사용하여 새 태그를 생성합니다.
- Docker Compose 배포
run.sh
를 실행하여 docker-compose-prod.yaml`를 백그라운드에서 실행합니다.remove_dangling_image.sh
를 실행하여 dangling 된 이미지들을 제거합니다.
요약하면 아래와 같이 진행되요
- 새로운 태그를 Git 에 푸쉬
- 프로젝트 빌드
- 이미지 빌드
- AWS-ECR 에 이미지 푸쉬
- AWS-EC2 에서 AWS-ECR 이미지 풀
- 컴포즈로 컨테이너 일괄 실행
이렇게 기존에 “하나의 컴퓨팅 머신” 에 배포하는 CI/CD 워크플로우를 완성하고 동작을 확인했습니다.
Originally posted by @ghkdqhrbals in #78
이렇게 완성시키고 실제로 배포해보니… CPU 가 100% 를 찍어버렸습니다. 그래서 “수직확장” 을 하거나 다른 방법을 찾아야했죠.
2. “수직확장” 과 “리소스 할당 감소” 시 결과
그래서 저는 AWS-EC2 t2.micro
에서 AWS-EC2 t2.medium
으로의 “수직확장” 을 먼저 수행해보았습니다. 또한 Kafka 브로커를 한 대만 설정해서 장점 중 하나인 안전성을 버렸어요. 추가적으로 spring 내장 tomcat 의 스레드풀 사이즈들을 전부 낮추었습니다.
결과적으로 CPU 평균 usage 는 약 50% 를 보여주었어요. 이는 잘 된 일이죠! 하지만 불안하지 않나요? 만약 더 큰 리소스가 필요하다면? Kafka 브로커를 안전성을 위해 2n+1
(n은 자연수) 으로 설정한다면? 만약 더 많은 서버가 필요하다면? 그럴 때마다 수직확장을 해야하나요? 끝까지 간다면 분명 리소스 확장 비용이 기하급수적으로 증가할텐데요😱.
3. 수평확장 플랜
그래서 저는 수직확장이 아닌 수평확장을 위해 여러 대의 컴퓨팅 머신에서 자동으로 일을 할당해줄 수 있는 Kubernetes 를 추가하여 아래와 같은 플랜을 세웠습니다.
- 사전준비
- AWS-EC2 t2.medium 인스턴스를 3대 준비합니다.
- 쿠버네티스 및 도커를 설치합니다.
- 하나를 master, 나머지를 worker 노드로 설정합니다.
- 기존과 같은 플로우
- 최신 태그버전 가져오기
- 버전 파싱
- 버전 업데이트
- 프로젝트 빌드
- Docker 이미지 빌드 및 ECR 푸시
- 이 후 추가되는 새로운 플로우
- AWS-EC2 Master 노드에서 git 을 pull 받습니다.
- sed 명령어를 통해
*-deployment.yaml
파일들을 읽고 AWS-ECR 이 가지고 있는 최신 이미지로 PULL 할 이미지들을 교체합니다.ECR 의 tag:
A-service_v5.0.1
는${ECR_URL}_A-service:latest
으로 yaml 파일을 변경합니다. - 이 후 namespace, PV, PVC, Service, Deploy, Pods 들을 순서대로 apply 합니다.
이러한 새로운 플로우는 test/only-chat, test/only-chat-cicd 브랜치에서 진행하고있어요. 아직 쿠버네티스는 익숙하지 않아서 시간이 걸리고 있습니다. 하지만 이 플로우가 완성되면, 수평확장이 가능한 CI/CD 플로우를 완성할 수 있을 것 같아요!
현재는 마지막 페이즈인 namespace, PV, PVC, Service, Deploy, Pods 들을 구현하고있어요!