[SYNAPSE 개발일지 #5] 테스트 코드 도입과 GitHub Actions를 활용한 CI 파이프라인 구축
[SYNAPSE 개발일지 #5] 테스트 코드 도입과 GitHub Actions를 활용한 CI 파이프라인 구축
[SYNAPSE 개발일지 #4] Refresh Token 적용을 위한 API 구조 개선과 Axios 마이그레이션 [SYNAPSE 개발일지 #4] Refresh Token 적용을 위한 API 구조 개선과 Axios 마이그레이션[SYNAPSE 개발일지 #3] GraphRAG 파이프라인
blog.juyear.dev
이전 글 읽으러 가기!
👋 소개 및 회고
안녕하세요. 대학생 개발자 주이어입니다.
저번 글에서는 Git Actions을 사용하여 Unit Test CI 구축 과정을 정리했었습니다.
오늘은 저번 글에 이어서 실제로 인프라를 설계하고 구축까지 해보려고 합니다.
- 인프라 설계 Trade-off
- 인프라 구축 중 만난 문제
위 2가지를 중심으로 작성할 계획입니다.
🔐 인프라 설계 Trade-off

우선 인프라 설계에는 정말 다양한 의견이 있다고 생각하고 제가 한 고민과 결정도 확실하다고 할 수 없습니다.
또한 AWS 인프라 설계의 경우 저도 익숙한 편이 아니라는 점을 알아주시면 좋을 것 같고, 혹시나 글을 보시고 피드백 있으신 분들은 댓글에 남겨주시면 감사하겠습니다.
본론으로 돌아오자면, 인프라 설계는 위 설계도와 같 구축을 하였습니다.
이제 고민했던 부분들을 하나씩 짧게 정리해보겠습니다.
[Lambda vs EC2]

저는 처음에 Lambda를 사용해서 서버를 띄우려고 했었습니다. 초기에는 사용량이 적기 때문에 사용한 만큼만 지불하는 것이 비용면에서 유리하다고 생각했습니다.
하지만 현재 서비스는 Redis Stream 기반 Worker 구조를 사용하고 있었고, AI 작업을 Backend와 분리하여 비동기로 처리하고 있었습니다. 따라서 Lambda를 사용하려면 Redis Stream을 Lambda와 연결하기 위한 별도의 이벤트 처리 구조를 구성하거나, SQS 같은 Lambda의 Event Source를 사용하는 방향으로 변경해야 했습니다.
현재는 이러한 새로운 구조로 변경하는 것 보다 EC2를 사용하여 Worker를 지속적으로 실행하는 것이 더 낫겠다고 생각하여 EC2를 선택하였습니다.
[EC2 vs ECS Fargate]

그 다음 고민했던 부분은 EC2를 사용할지와 ECS Fargate를 사용할지 였습니다.
우선 저는 Backend 서버와 AI 서버를 분리할 생각이었습니다.
- 현재 구조 자체가 Redis Stream을 사용한 이벤트 기반 비동기 처리이며, 이 구조를 사용했던 이유가 AI 서버의 리소스 사용을 Backend 서버와 분리하기 위해서였음
- 직접적으로 클라이언트와 통신해야하는 Backend 서버는 Public Subnet으로, Backend 서버와만 통신하면 되는 AI 서버는 Private Subnet으로 분리하고 싶었음
즉 EC2를 사용하든 ECS Fargate를 사용하든 2개의 서버를 운영할 계획이었습니다.
하지만 ECS Fargate의 경우 초기 설정(Task Definition, Service, 배포 파이프라인 등..)이 복잡하고, 사용량이 적은 초기 서비스인 것에 비해 오버 엔지니어링 + 비용 문제가 있다고 판단했습니다.
물론 초기 서비스에 EC2 인스턴스 2개를 사용하는 것도 오버 엔지니어링으로 볼 수 있지만, 저는 서버를 Public Subnet과 Private Subnet으로 분리하는 설계를 경험해보고, Security Group을 통해 서버 간 통신을 제어하는 구조도 직접 설계하고 운영해보고 싶어 이렇게 설계하였습니다.
따라서 최종적으로 Backend와 AI를 각각 EC2에 배치하고, Backend는 Public Subnet에서, AI는 Private Subnet에서 운영하는 구조를 선택하였습니다.
*추가로 이후 이미지 기반 배포 파이프라인을 구축하기 위해 ECR을 사용한 이미지 저장소도 구축하였습니다.
[Redis Container vs ElastiCache]

또 다른 고민은 Redis를 Backend EC2에 Docker 기반으로 띄울지, ElastiCache를 사용하여 별도의 AWS 서비스를 이용할지 였습니다.
처음에는 ElastiCache를 사용해야겠다고 자연스럽게 생각했고, EC2에 같이 띄운다는 생각은 못 했었습니다.
이미 역할에 맞게 EC2 배정이 깔끔하게 완료 되었다고 생각했기 때문입니다.
하지만 EC2 인스턴스를 2개 띄우고 보니 ElastiCache까지 띄운다면 토이 프로젝트 치고 비용이 너무 커질 것 같았습니다.
그래서 여기서 어떤 방법을 도입할 수 있을까 고민하던 중에 기존에 만들어둔 Backend EC2에 Redis Container를 같이 띄운다면 비용 문제를 해결할 수 있겠다고 생각했습니다.
물론 Backend EC2에 Redis를 같이 띄운다는 설계가 왜인지 모르겠으나 마음에 들지는 않았습니다.
하지만 현재 상황에서는 가장 현실적인 방법이라고 생각했고, 해당 설계를 선택하였습니다.
[기타]
추가로 DB의 경우 RDS PG를 사용하여 구축하였습니다.
초기 프로젝트, 비용 문제 등을 고려하여 멀티 AZ는 설계하지 않았습니다.
RDS 연결을 위한 username과 password의 경우 env에 직접 설정하는 것과 Secrets Manager를 사용하는 것 중 고민을 했고, Secrets Manager의 경우 비용이 거의 나오지 않는다고 해서 Secrets Manager를 사용하는 쪽으로 설계하였습니다.

현재 비용이 EC2에서 거의 대부분을 차지하고 있어서 이 비용을 어떻게 줄일 수 있을지 고민 중이고, 설계가 살짝 바뀔 수 있습니다. (현재 EC2 인스턴스 2개 모두 t3.small을 사용 중 입니다.)
🔐 인프라 구축 중 만난 문제
[로컬에서 ECR 이미지 배포 권한 문제]
제가 만났던 문제는 대부분 실제 배포를 해보는 과정에서 많이 만났습니다.
그 중 첫 번째 문제가 로컬에서 이미지를 배포하기 위한 문제였습니다.
EC2의 경우 서비스에 권한을 설정하면 권한 문제가 해결됐지만, 로컬은 AWS 서비스가 아니기에 별도의 사용자 인증 작업을 거쳐야했습니다.

이를 해결하기 위해 IAM에서 ECR 권한을 가진 사용자를 생성했고, 해당 사용자의 Key를 이용해서 권한을 가진 로컬이라는 것을 증명했습니다.
이후 ECR에 로컬 이미지를 배포할 수 있었습니다.
[EC2에서 Secrets Manager 읽기 권한 문제]
두 번째 문제는 첫 번째와 비슷한 권한 문제였습니다.
EC2에서 Secrets Manager 값을 읽어와 DB에 연결해야 하는데 읽으려고 하면 권한이 없어서 읽을 수 없었습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": [
"arn"
]
}
]
}
이를 해결하기 위해 IAM에서 Secrets Manager 읽기 권한만을 주도록 설정하여 해결할 수 있었습니다.
[Private Subnet SSH 접속 문제]
세 번째 문제는 Private Subnet에 있는 AI EC2에 접속하는 문제였습니다.
Public Subnet에 있는 Backend EC2의 경우 public ip가 있기 때문에 로컬에서 ssh key를 이용해서 접속할 수 있었습니다.
반면 Private Subnet에 있는 AI EC2의 경우 Backend EC2에서 오는 요청만 허용하도록 Security Group을 설정해둔 상황이었습니다. 즉 AI EC2에 접속하기 위해서는 로컬 - Backend EC2 - AI EC2 와 같은 경로로 접속해야 했습니다.
가장 쉬운 방법은 AI EC2에 접속하기 위한 ssh key를 Backend EC2에 다운받는 것 이었습니다.
하지만 이는 보안상 적절한 방법이 아니라고 생각했습니다. 단순 테스트를 위해서 잠깐 다운받는 것은 괜찮지만, 앞으로도 지속적으로 AI EC2에 접속해야 하는 상황에서, ssh key를 Backend EC2에 계속 보관해두는 것은 적절하지 않다고 생각했습니다.
그래서 알게된 방법이 바로 ssh-add 기능이었습니다. 로컬에서 Backend EC2에 접속할 때 AI EC2 연결에 필요한 ssh key를 들고 가는 것입니다. 즉, 로컬에 저장해둔 ssh key를 가지고 Backend EC2에 접속하는 것 입니다. 해당 방법은 로컬에만 ssh key를 저장하고 Backend EC2에는 저장하지 않을 수 있었습니다.
ssh-add "ssh key 파일 위치"
이를 설정하기 위해서는 ssh-add 명령어를 사용해 key를 저장해 두어야 합니다.
ssh -A ec2-user@BACKEND_IP
그 후 ssh -A를 사용하여 Backend EC2에 접속합니다.
ssh ec2-user@AI_PRIVATE_IP
이후에는 해당 key를 사용하여 AI EC2에 접속할 수 있습니다.
즉 로컬에서 가져온 ssh key를 사용하여 AI EC2까지 접속할 수 있게 됩니다.
[HTTPS 인증 문제]
인프라 구축이 완료된 이후 바로 테스트를 진행해봤습니다.
역시나 한 번에 실행되지 않았습니다.
오류 문구를 확인해보니 대충 요청을 보내는 클라이언트는 HTTPS를 사용하는데 서버는 HTTP라는 보안 문제였습니다.
이를 해결하기 위해 HTTPS 인증 방식을 찾아보았고 AWS의 Certificate Manager를 먼저 고려하였습니다.
그래서 저는 바로 해당 서비스를 사용하여 인증 설계를 시작했습니다.
그런데 설계를 하다 보니 ACM을 활용하기 위해서는 ALB나 CloudFront와 같은 다른 AWS 서비스와 함께 구성해야 했습니다. 물론 ACM을 단독으로 사용하는 방법도 있지만, 결국 EC2에서 별도의 환경 구축이 필요했습니다.
즉, 새로운 서비스를 추가하거나 그게 싫다면 EC2에서 별도의 환경 구축을 해야 했습니다.
그래서 저는 새로운 서비스를 추가하지 않기로 결정했고, 어차피 EC2에서 설정이 필요하다면, Nginx를 사용하는 것이 좋겠다고 판단했습니다.
따라서 저는 Nginx를 사용하는 방법으로 구축하기로 결정했고, Backend EC2에서 Nginx를 설정해주었습니다.

이렇게 하면 클라이언트 - DNS(가비아에서 설정) - Backend EC2 - Nginx - NestJS 흐름이 되고, Nginx에서 HTTPS 인증을 처리하게 됩니다.
또한 이제 외부에서 3000포트로 직접 요청을 보내지 않고, Nginx가 요청 인증을 처리한 후 3000포트로 보내주기 때문에 Security Group에서도 3000포트 외부 연결을 제거해주었습니다.

이후 다시 테스트를 해보았고, 드디어 제 서비스가 인프라 환경에서도 제대로 작동하는 것을 확인할 수 있었습니다.
😊 마무리
✅ 인프라 설계
✅ 인프라 구축
✅ 인프라 환경 테스트
➡️ 모니터링 및 로깅 설계
[진행 상황 정리]
이번 글에서는 인프라 설계 관점을 위주로 정리하며, 구축 중 만난 문제까지 정리해보았습니다.
제가 지금까지는 인프라와 관련된 글을 따로 작성한 적은 없었는데 이번에는 AWS를 사용하여 제대로 인프라를 구축하는 경험을 하게 되어서 한 번 정리해보았습니다.
평소에도 AWS 및 인프라와 관련된 특강을 많이 듣고 실습도 많이 해봤었는데, 실제로 서비스에 맞게 구축하고 이미지를 배포하는 경험을 해보니 생각했던 것 보다 좀 더 복잡하고 어려웠던 것 같습니다.
설계 부분에서는 비용과 오버 엔지니어링의 줄타기가 어려웠던 것 같습니다.
이렇게 설계하는게 더 좋은 것 같으면서도 "비용은?", "좀 과한거 아닌가?" 라는 생각을 멈출 수가 없었습니다.
사실 단순히 EC2 하나 띄워서 다 배포하는 방법도 충분히 가능했을 수도 있지만, 이번 프로젝트는 단순히 배포하는 것이 목적이 아니였기 때문에 좀 더 설계를 촘촘하게 했던 것 같습니다.
그럼 지금까지
읽어주셔서 감사드리며, 다음에는 '모니터링 및 로깅 설계' 글로 돌아오도록 하겠습니다.
by. 대학생 개발자 주이어
KYT CODING COMMUNITY Discord 서버에 가입하세요!
Discord에서 KYT CODING COMMUNITY 커뮤니티를 확인하세요. 27명과 어울리며 무료 음성 및 텍스트 채팅을 즐기세요.
discord.com
KYT CODING COMMUNITY 가입하기!