프로덕션 수준 CI/CD 파이프라인 구축: Terraform, GitHub Actions, Ansible을 활용한 인프라 자동화
Source: Dev.to
프로젝트 목표 및 개요
이 프로젝트의 주요 목표는 다중 서비스 TODO 애플리케이션에 대해 완전 자동화된 배포 파이프라인을 구축하고 인프라 자동화를 전면에 구현하는 것이었습니다. 솔루션은 다음과 같은 핵심 요구 사항을 충족해야 했습니다:
핵심 요구 사항
- 코드 커밋부터 프로덕션 배포까지 전 과정 자동화
- 선언적 구성을 이용한 인프라 프로비저닝
- 일관된 서버 설정을 위한 자동화된 구성 관리
- SSL/TLS 종료를 통한 무중단 배포
- 인프라 일관성을 유지하기 위한 드리프트 감지
- 마이크로서비스 간 상호작용 디버깅을 위한 분산 추적
- 암호화된 비밀 관리와 최소 공격 표면을 갖춘 보안 우선 접근 방식
선택된 기술 스택
- Infrastructure as Code: AWS 리소스 프로비저닝을 위한 Terraform
- Configuration Management: 서버 구성 및 애플리케이션 배포를 위한 Ansible
- CI/CD Orchestration: 워크플로 자동화를 위한 GitHub Actions
- Containerization: 서비스 격리를 위한 Docker 및 Docker Compose
- Reverse Proxy: 라우팅, 로드 밸런싱 및 자동 SSL을 위한 Traefik
- Observability: 분산 요청 추적을 위한 Zipkin
- Message Queue: 비동기 로그 처리를 위한 Redis
최종 목표는 인프라 변경 및 애플리케이션 업데이트를 git push 하나로 배포할 수 있는 시스템을 구축하는 것이었으며, 드리프트 감지, 이메일 알림, 프로덕션 환경에 대한 수동 승인 게이트와 같은 안전 메커니즘을 내장했습니다.
시스템 아키텍처 및 설계
애플리케이션은 각각의 책임에 가장 적합한 언어로 구현된 7개의 독립 서비스로 구성된 마이크로서비스 패턴을 따릅니다.
아키텍처 다이어그램

그림 1: 모든 서비스, 기술 스택 및 데이터 흐름을 보여주는 전체 마이크로서비스 아키텍처.
서비스 책임
| # | 서비스 | 언어 / 플랫폼 | 주요 책임 |
|---|---|---|---|
| 1 | 프론트엔드 서비스 | Vue.js | SPA UI, 백엔드 API와 통신, Zipkin 클라이언트, 정적 자산 제공 |
| 2 | Auth API | Go | 인증 및 인가, JWT 처리, Users API를 통한 자격 증명 검증 |
| 3 | Todos API | Node.js | TODO 항목 CRUD, Redis에 이벤트 발행, JWT 검증 |
| 4 | Users API | Spring Boot (Java) | 사용자 프로필 관리, Auth 서비스용 읽기 전용 조회 |
| 5 | 로그 메시지 프로세서 | Python | Redis 메시지 소비, 모니터링을 위한 이벤트 로그 |
| 6 | Redis | — | 인메모리 메시지 큐 (pub/sub) |
| 7 | Zipkin | — | 분산 추적 수집기 및 UI |
| 8 | Traefik | — | 리버스 프록시, 자동 서비스 디스커버리, Let’s Encrypt SSL, 라우팅 및 대시보드 |
네트워크 아키텍처

그림 2: Traefik만 외부 게이트웨이로 두고 app-network라는 격리된 Docker 네트워크를 보여줌.
모든 서비스는 app-network라는 전용 Docker 브리지 네트워크를 통해 통신하며, 다음을 제공합니다:
- 호스트 시스템으로부터 격리
- 컨테이너 이름(DNS)을 통한 서비스 간 통신
- Traefik을 통한 포트만 외부에 노출
- 외부 클라이언트와 Traefik 사이의 트래픽 암호화
Terraform을 이용한 인프라스트럭처 코드
Terraform은 선언적 구성, 상태 관리 및 성숙한 AWS 프로바이더 덕분에 선택되었습니다.
프로비저닝된 AWS 리소스
1. EC2 인스턴스
# Data source ensures we always use the latest Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical's AWS account
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# EC2 Instance resource definition
resource "aws_instance" "todo_app" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
key_name = aws_key_pair.deployer.key_name
vpc_security_group_ids = [aws_security_group.todo_app.id]
tags = {
Name = "todo-app-server-v2"
Environment = "production"
Project = "hngi13-stage6"
}
}
2. 보안 그룹
resource "aws_security_group" "todo_app" {
name = "todo-app-sg"
description = "Security group for TODO application"
# HTTP access for initial Let's Encrypt challenges
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# HTTPS access
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# SSH access (restricted)
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR.TRUSTED.IP/32"]
}
# Allow all outbound traffic
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}