Post

Weasel 프로젝트를 마치며

2024년 8월 21일, Weasel 프로젝트를 성공적으로 마무리했습니다.

GitHub: https://github.com/Team-S5T1

Weasel 프로젝트는 Bedrock Claude Sonnet 3.5 Model을 사용한 문제풀이 서비스로, Frontend 2명, Backend 2명, Infra 2명, 총 6명으로 진행하였고, 저는 인프라를 담당했습니다. 처음 팀장으로서 프로젝트를 진행하며 내가 잘할 수 있을지 걱정이 되었지만, 팀원들 덕분에 직면한 문제를 수월하게 해결할 수 있었습니다. 이번 포스트에서는 Weasel 프로젝트에 대해 소개하고, 트러블슈팅과 느낀 점을 다뤄보겠습니다.

주요 기능

  • 로그인, 회원가입 (Spring Security, OAuth)
  • 문제에 대한 정답, 해설 제공
  • 프롬프트 히스토리
  • 응답 스트리밍

한글 문제 풀이

영어 문제 풀이

기술 스택

Backend - Spring Boot
Frontend - React
IaC - Terraform
Deploy - Amazon EKS, S3, Route53, Cloudfront
AWS - VPC, ECR, Secrets Manager, WAF
CI/CD - Jenkins, ArgoCD
GenAI Model - AWS Bedrock(Claude Sonnet 3.5)
Monitoring - Prometheus, Grafana

인프라 아키텍처

인프라는 AWS 클라우드를 기반으로 구축했습니다. Terraform을 모듈을 생성하고 이를 사용해 인프라를 구축하면서 자동화, 버전 관리, 일관성을 추구했습니다. 고가용성(HA)과 내결함성(FT)을 위해 EKS를 사용했으며 HPA를 사용해 Pod Autoscaling과 Karpenter를 사용해 Node Autoscaling을 구현했습니다.

보안을 위해 Secrets Manager를 사용해 중요 환경변수의 노출을 방지하였습니다. 네트워크 측면에서 Public과 Private Subnet을 나눠 외부에서 내부 서비스에 직접 접근하지 못하도록 하였으며 3306, 8080, 22와 같은 Well-Known 포트 피하려 노력했습니다.

인프라 아키텍처

Bedrock Workflow

사용자에게서 문제를 전달 받고 문제에 대한 답과 해설을 리턴하는 절차를 최대한 간단히 하려고 노력했습니다.

Bedrock Workflow

CI/CD Workflow

CI/CD Workflow

Backend CI/CD Workflow

GitHub 코드 저장소에 변경이 발생했을 때 GitHub WebhookJenkins로 전달되고, CI 파이프라인이 실행되도록 하였습니다. CI 과정에서 갱신된 이미지를 ArgoCD를 사용해 EKS Deployment에 반영되도록 하였습니다. Backend CI/CD 파이프라인 구성은 다음과 같습니다.

  1. Slack 채널에 파이프라인 실행 알림
  2. AWS cli를 통해 ECR_Repository에 접근하기 위한 토큰 발급 후 로그인
  3. GitHub Repository에서 Backend 코드 Clone
  4. Spring Project를 Docker 이미지로 빌드 후 ECR에 이미지 업로드
  5. Kubernetes Deployment Manifest 파일 이미지 갱신
  6. Slack 채널에 CI 파이프라인 결과 알림
  7. Kubernetes Manifest를 구독하고 있는 ArgoCD가 변경 감지 후 클러스터에 반영

Frontend CI/CD Workflow

GitHub WebhookJenkins로 전달되는 과정은 Backend CI/CD와 동일하며, CI 과정에서 산출된 HTML, CSS, JavaScript 정적 파일을 정적 웹호스팅 기능이 허용된 S3에 반영되도록 하였습니다. Frontend CI/CD 파이프라인 구성은 다음과 같습니다.

  1. Slack 채널에 파이프라인 실행 알림
  2. AWS cli를 통해 ECR_Repository에 접근하기 위한 토큰 발급 후 로그인
  3. GitHub Repository에서 Frontend 코드 Clone
  4. React Project 빌드
  5. 빌드 산출물인 HTML, CSS, JavaScript 정적 파일로 S3에 반영
  6. Cloudfront 캐시 무효화
  7. Slack 채널에 CI/CD 파이프라인 결과 알림

Trouble Shooting

Trouble 1. Terraform State 충돌

인프라 팀이 IaC 도구로 Terraform을 사용해 협업하면서 서로 tfstate 파일을 참조하게 되어 인프라의 실제 상태와 정의된 상태 간의 차이가 발생했습니다. 이로 인해 리소스가 중복으로 생성되거나 의도하지 않은 리소스 삭제되는 등의 문제가 발생했습니다.

이를 해결하기 위해 AWS S3를 백엔드로 사용하여 tfstate 파일을 중앙에서 관리하도록 설정했습니다. 그 결과 동일한 상태 파일을 공유할 수 있었고, 앞선 문제를 해결할 수 있었습니다.

1
2
3
4
5
6
7
8
terraform {
  backend "s3" {
    bucket  = "terraform-state-weasel"
    key     = "persistent/terraform.tfstate"
    region  = "us-east-1"
    encrypt = true
  }
}

Trouble 2. Jenkins 저장 공간 부족

개발 과정에서 코드 추가, 수정이 자주 발생하면서 Jenkins 가상 머신의 저장 공간이 부족한 현상과 컨테이너 이미지 생성 및 업로드시간이 오래 걸리는 문제를 직면했습니다. 원인은 추적하니 컨테이너 이미지 파일의 용량이 너무 컸고, 파이프라인이 동작할 때마다 컨테이너 이미지가 가상 머신에 축적되었다는 것을 파악했습니다.

alt text alt text

이러한 현상이 일어날 때마다 가상 머신의 저장 공간을 확장시켜주었습니다. 하지만 해당 방법으로 문제의 원인을 근본적으로 해결하지 못했습니다. 4번의 시행착오을 거쳐 이를 해결했으며, 시도한 방법은 아래와 같습니다.

  1. (초기) 컨테이너 이미지에 코드 삽입 -> 컨테이너 이미지를 생성하며 코드 빌드 후 산출된 바이너리 파일을 사용하는 이미지 생성 -> ECR Push
  2. Jenkins 가상 머신에서 코드 빌드 -> 빌드 산출물을 기반으로 동작하는 컨테이너 이미지 생성 -> ECR Push
  3. 컨테이너 이미지에 프로젝트 코드 삽입 -> Multi-Stage Build를 사용해 최종 산출물에 이전 단계에서 산출된 바이너리 파일만 포함하는 이미지 생성 -> ECR Push
  4. Jenkins 가상 머신에서 코드 빌드 -> 빌드 산출물을 기반으로 동작하는 컨테이너 이미지 생성 -> ECR Push -> 컨테이너 이미지 삭제

1번 방법

CI/CD 파이프라인은 정상적으로 동작하지만 컨테이너 이미지의 크기가 크며 빌드 시간이 오래 걸렸습니다.

2번 방법

빌드 산출물만을 포함한 작은 컨테이너 이미지를 생성할 수 있었고, 이미지 빌드 시간이 크게 단축되었습니다.

3번 방법

1번 문제를 해결하기 위해 고민했던 방법들 중 하나입니다. 빌드 작업이 Docker 컨테이너 내에서 진행되기 때문에 종속성을 설치하거나 코드를 컴파일하는 단계에서 시간이 소요되어 2번 방법보다 이미지 빌드에 걸리는 시간이 오래걸렸습니다.

4번 방법

2, 3번 방법을 통해 이미지 빌드 시간과 이미지의 크기를 줄일 수 있었습니다. 그러나 이는 컨테이너 이미지의 크기를 줄여 Jenkins 가상 머신의 저장 공간이 가득 차게 되는 시기를 늦추었을 뿐 컨테이너 이미지가 Jenkins 가상 머신에 계속 쌓인다는 문제는 해결하지 못했습니다. 해당 문제를 해결하기 위해 ECR에 컨테이너 이미지 Push 후 해당 이미지를 삭제해주는 단계를 추가해 주었습니다.

Trouble 3. MySQL 한글 인코딩 문제

개발이 마무리되고 테스트를 진행하며, 프롬프트에 영어가 아닌 한글을 입력할 시, 해당 프롬프트를 Database에 저장할 때 SQLException: Incorrect string value 문제가 발생했습니다. 문제의 원인은 MySQL의 문자열 인코딩 방식이 한글을 처리할 수 없는 latin1_general_ci: 인코딩 방식이었기 때문에 한글 인코딩에 실패한다는 것이었습니다.

이를 해결하기 위해 character-set-server, collation-server 파라미터의 값을 변경해 문자열 인코딩 방식을 latin1_general_ci:에서 utf8mb4_unicode_ci로 변경해주는 방식으로 해결했습니다.

Trouble 4. 비…비용 문제

클라우드 기반 어플리케이션에서 고가용성(HA)과 내결함성(FT)를 추구해 EKS를 사용하고 Public Subnet과 Private Subnet을 나눠 외부에서는 Public Subnet의 Load Balancer를 통해서만 어플리케이션에 접근이 가능하고, Private 내부의 인스턴스들은 NAT Gateway를 통해서만 외부와 통신할 수 있도록 구성했습니다.

하지만 EKS와 NAT Gateway에서 생각보다 많은 지출이 발생하였고.. 매일 아침, 저녁 다음과 같이 정해진 레벨의 Budget이 초과되었다는 알림을 받게 되었습니다..

alt text

비용 절감을 위해 Karpenter Node Pool 설정을 통해 자동으로 스케일링되는 EKS 노드 인스턴스를 Spot Instance로 변경하였으며, 개발 기간 동안에는 NAT Gateway를 NAT Instance로 교체하였습니다. 이를 위해 Terraform 설정에 enable_nat_instance라는 boolean 변수를 추가하여 NAT Gateway와 NAT Instance 간의 전환을 쉽게 관리할 수 있도록 설정하였습니다.

마무리

이번 프로젝트를 통해 성공적인 프로젝트 마무리를 위해서는 팀원 간의 원활한 소통과 체계적인 정리가 필수적이라는 중요한 교훈을 얻었습니다. Weasel 프로젝트에서도 이러한 중요성을 인식하여 Pull Request와 Branch 네이밍, Issue 컨벤션 등 명확한 규칙을 도입했습니다. 이를 통해 팀원 간의 협업 효율성을 높였으며, 진행 상황을 NotionSlack을 활용해 투명하게 공유했습니다. 또한, 매주 1회 오프라인 회의를 고정적으로 진행하여 프로젝트의 진행 상황을 점검하고, 다음 회의까지 목표를 명확히 하였습니다. 이를 통해 발생할 수 있는 문제를 조기에 식별하고 방지할 수 있었습니다. 이와 같은 노력이 프로젝트를 성공적으로 마무리하는 데 큰 역할을 했다고 생각합니다.

alt text alt text alt text

추가적으로, 클라우드를 인프라로 채택함으로써 높은 확장성과 유연성, 그리고 관리의 편리함이라는 큰 장점을 얻을 수 있었습니다. 그러나, 이러한 장점에도 불구하고 예상보다 높은 비용이 발생했다는 점은 고민할 부분이었습니다. 특히, 지속적인 리소스 사용과 트래픽 증가에 따라 비용이 가파르게 상승하는 것을 경험했습니다. 향후 프로젝트에서는 비용 효율성을 더욱 고려하여 클라우드 자원을 최적화하고, 필요에 따라 비용을 절감할 수 있는 방안을 마련해야겠다는 생각을 하게 되었습니다.


궁금하신 점이나 추가해야 할 부분은 댓글이나 아래의 링크를 통해 문의해주세요.
Written with KKam._.Ji

This post is licensed under CC BY 4.0 by the author.