본문 바로가기

Study

[내일배움캠프 TIL] Terraform으로 AWS EC2 배포 자동화 완성

728x90
반응형

📌 한 줄 요약

Terraform으로 AWS EC2 인스턴스를 코드 한 줄로 프로비저닝하고, GitHub Actions CD 파이프라인과 연결해 develop 브랜치 push만으로 전체 마이크로서비스가 EC2에 자동 배포되도록 구성하는 데 성공했다.


🗺️ 전체 인프라 구성

[개발자 로컬]
    │
    ├─ terraform apply
    │       ↓
    │   [AWS ap-northeast-2 (서울)]
    │   ├── EC2 t3.xlarge (Ubuntu 22.04 LTS)
    │   │   └── Docker + Docker Compose 자동 설치
    │   ├── Security Group (22, 80, 443, 8080 개방)
    │   ├── Key Pair (RSA 4096, omc-key.pem 로컬 저장)
    │   └── Elastic IP (고정 IP → GitHub Secrets에 등록)
    │
    └─ git push origin develop
            ↓
    [GitHub Actions CD]
    ├── 1. Gradle 빌드 (Java 21)
    ├── 2. Docker 이미지 빌드 & ghcr.io 푸시 (5개 서비스)
    └── 3. SSH → EC2에서 docker compose pull & up -d

🏗️ STEP 1 — Terraform으로 AWS 인프라 프로비저닝

1-1. Provider & 버전 고정

terraform {
  required_providers {
    aws   = { source = "hashicorp/aws",   version = "~> 5.0" }
    tls   = { source = "hashicorp/tls",   version = "~> 4.0" }
    local = { source = "hashicorp/local", version = "~> 2.0" }
  }
}

provider "aws" {
  region  = "ap-northeast-2"   # 서울 리전
  profile = "terraform-user-my" # ~/.aws/credentials 프로파일
}

포인트: profile을 명시해서 실수로 다른 계정에 배포하는 사고를 방지했다.

AWS 자격증명 저장 위치

Terraform이 AWS에 접근할 때 내 PC의 ~/.aws/credentials 파일을 읽는다.

# ~/.aws/credentials
[terraform-user-my]
aws_access_key_id     = AKIA...
aws_secret_access_key = xxxxxx
# ~/.aws/config
[profile terraform-user-my]
region = ap-northeast-2

이 파일은 AWS IAM 콘솔에서 terraform-user-my 사용자의 액세스 키를 발급받아 직접 저장해 둔 것이다. Terraform은 코드에 키를 하드코딩하지 않고 이 로컬 파일을 참조한다.

1-2. SSH Key 자동 생성 — terraform apply 한 번으로 모두 자동 처리

# 1. RSA 4096 키 쌍 생성
resource "tls_private_key" "omc_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

# 2. 공개키 → AWS Key Pair 등록
resource "aws_key_pair" "omc_key" {
  key_name   = "omc-key"
  public_key = tls_private_key.omc_key.public_key_openssh
}

# 3. 비밀키 → 로컬 .pem 파일로 저장 (권한 0400)
resource "local_file" "omc_key_pem" {
  content         = tls_private_key.omc_key.private_key_pem
  filename        = "${path.module}/omc-key.pem"
  file_permission = "0400"
}

terraform apply 명령을 실행하면 아래 과정이 자동으로 실행된다. 키를 직접 만들거나 AWS 콘솔에서 수동 등록할 필요가 없다.

  1. RSA 4096 키 쌍 생성
  1. 비밀키 → 내 PC infra/terraform/omc-key.pem으로 자동 저장 (권한 0400)
  1. 공개키 → AWS Key Pair omc-key로 자동 등록
  1. 이후 EC2 SSH 접속 시 내 PC의 .pem 파일 사용

`bash

ssh -i ./omc-key.pem ubuntu@<Elastic IP>

`

.pem 파일과 terraform.tfstate(비밀키가 평문으로 포함됨)는 절대 git 커밋 금지. 이미 .gitignore에 추가되어 있다.

1-3. Security Group

포트 용도

22 SSH 접속
8080 Spring Cloud Gateway API
80 HTTP (Nginx 대비)
443 HTTPS (Nginx 대비)

모든 outbound는 전체 허용 (0.0.0.0/0).

1-4. EC2 인스턴스

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical 공식 계정

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "omc" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.xlarge"   # vCPU 4, RAM 16GB
  key_name      = aws_key_pair.omc_key.key_name

  root_block_device {
    volume_size = 30  # GB
  }

  user_data = <<-EOF
    #!/bin/bash
    # Docker CE 공식 설치 스크립트
    apt-get update && apt-get install -y ca-certificates curl gnupg
    # ... (Docker 저장소 추가 → apt install)
    usermod -aG docker ubuntu
    systemctl enable docker && systemctl start docker
  EOF
}

포인트: user_data로 EC2 최초 부팅 시 Docker를 자동 설치한다. 인스턴스에 직접 SSH해서 수동 설치할 필요가 없다.

data source 활용: AMI ID를 하드코딩하지 않고 data.aws_ami로 항상 최신 Ubuntu 22.04 LTS AMI를 동적으로 조회한다. 리전마다 AMI ID가 다르기 때문에 이 방식이 이식성이 높다.

1-5. Elastic IP

resource "aws_eip" "omc_eip" {
  instance = aws_instance.omc.id
  domain   = "vpc"
}

output "elastic_ip" {
  value = aws_eip.omc_eip.public_ip
}

이유: EC2를 중지/시작하면 Public IP가 바뀐다. Elastic IP를 붙이면 고정 IP가 유지되어 GitHub Secrets의 EC2_HOST를 수정할 필요가 없다.

1-6. terraform apply 실행 순서

# 1. 초기화 (프로바이더 다운로드)
terraform init

# 2. 변경 사항 미리보기
terraform plan

# 3. 실제 프로비저닝
terraform apply

# 완료 후 출력 예시:
# elastic_ip  = "x.x.x.x"
# ssh_command = "ssh -i omc-key.pem ubuntu@x.x.x.x"

🐳 STEP 2 — 배포 대상 서비스 구성 (docker-compose.prod.yml)

구분 서비스 이미지 출처 메모리 제한

인프라 PostgreSQL 18 Docker Hub 512 MB
인프라 Redis 7.2 Docker Hub 256 MB
인프라 Zookeeper 7.5.0 Confluent 256 MB
인프라 Kafka 7.5.0 Confluent 512 MB
인프라 Keycloak 26.2 Quay.io 1 GB
eureka-server ghcr.io 512 MB
config-server ghcr.io 512 MB
gateway (8080) ghcr.io 512 MB
user-service ghcr.io 512 MB
drop-service ghcr.io 512 MB

healthcheck 기반 의존성 체인:

postgres healthy → keycloak healthy
postgres healthy → user-service, drop-service
eureka-server healthy → config-server healthy
config-server healthy → gateway, user-service, drop-service
keycloak healthy → gateway, user-service

⚙️ STEP 3 — GitHub Actions CD 파이프라인

트리거

on:
  push:
    branches:
      - develop

develop 브랜치에 push하면 자동으로 CD가 실행된다.

Job 1: Build & Push Images

1. Checkout 코드
2. Java 21 (Temurin) 세팅
3. Gradle 빌드 (5개 서비스 bootJar, 테스트 제외)
   - eureka-server
   - config-server
   - gateway
   - user-service
   - drop-service
4. ghcr.io 로그인 (GITHUB_TOKEN 사용)
5. 각 서비스 Docker 이미지 빌드 & 푸시
   → ghcr.io/wansix/omc-{서비스명}:latest

Job 2: Deploy to EC2

1. SSH로 EC2 접속 (appleboy/ssh-action)
   - host : $EC2_HOST (Elastic IP)
   - key  : $EC2_SSH_KEY (omc-key.pem 내용)
2. EC2에서 실행:
   cd ~/omc
   docker login ghcr.io   # 프라이빗 이미지 pull 권한
   docker compose -f docker-compose.prod.yml pull
   docker compose -f docker-compose.prod.yml up -d
   docker image prune -f  # 미사용 이미지 정리

GitHub Secrets 목록

시크릿 키 값

EC2_HOST Terraform output의 Elastic IP
EC2_USER ubuntu
EC2_SSH_KEY omc-key.pem 파일 내용 (-----BEGIN RSA...)

💡 핵심 배운 점

  1. IaC(Infrastructure as Code)의 가치: 인프라 설정이 코드로 관리되므로 팀원 누구나 terraform apply 한 번으로 동일한 환경을 재현할 수 있다. 수동 콘솔 클릭 작업이 사라진다.
  2. tls 프로바이더로 키 관리 자동화: 수동으로 AWS 콘솔에서 Key Pair를 만들고 다운로드하는 번거로운 과정이 없어졌다. Terraform 상태 파일(terraform.tfstate)에 비밀키가 저장되므로 .gitignore에 추가해 절대 커밋하면 안 된다.
  3. data source로 AMI 동적 조회: AMI ID는 리전마다 다르고 주기적으로 업데이트된다. data.aws_ami를 쓰면 항상 최신 공식 이미지를 사용할 수 있다.
  4. user_data로 부트스트랩 자동화: EC2 생성과 동시에 Docker 설치까지 완료된다. 별도 Ansible, 수동 SSH 설정 없이도 바로 컨테이너를 실행할 수 있는 상태가 된다.
  5. Elastic IP의 필요성: 재시작할 때마다 IP가 바뀌면 GitHub Secrets를 매번 수정해야 한다. Elastic IP로 고정하는 것이 CI/CD 운영의 기본이다.
  6. healthcheck 기반 의존성 설정: depends_on: condition: service_healthy를 설정하지 않으면, 예컨대 Keycloak이 아직 뜨지 않았는데 Gateway가 먼저 시작되어 연결 실패 에러가 발생한다. 특히 Keycloak처럼 초기 기동이 오래 걸리는 서비스는 start_period를 넉넉히(90초) 주는 것이 중요하다.
반응형