[V1 V2] IaC 및 마이그레이션 - 100-hours-a-week/KTB4-15th-CLOUD GitHub Wiki

목차


1. V1 인프라 import

작업 순서

  1. 이슈 생성(리소스)
  2. AWS CLI로 리소스 ID 조회
  3. imports.tf에 import 블록 작성 → terraform plan -generate-config-out으로 코드 자동 생성
  4. 생성된 코드 정리 (기본값·충돌 속성 제거, 하드코딩 ID → 참조로 변경)
  5. 로컬 plan으로 "N to import, 0 to add, 0 to destroy" 확인 (태그 추가만 change로 나옴)
  6. PR → CI plan 확인 → merge → CI가 apply (실제 import는 CI에서 실행)
  7. 정리 PR: imports.tf 삭제 + 미사용 리소스 있으면 코드에서 지워서 삭제

1-1. 최종 파일 구조

KTB4-15th-CLOUD/
├── .github/
│   └── workflows/
│       ├── terraform-pr.yaml            # 모든 PR: fmt·validate·tflint·plan (필수 체크 terraform)
│       └── terraform-apply.yaml         # main push: plan → apply
│
├── infra/
│   ├── environments/                    # Terraform Root (State 1개)
│   │   ├── backend.tf                   # S3 Backend
│   │   ├── providers.tf                 # Terraform/AWS/Archive Provider, default_tags
│   │   ├── main.tf                      # shared · prod · dev V1 모듈 호출
│   │   ├── dev-v2.tf                    # dev V2 모듈 호출 (main.tf와 분리)
│   │   ├── .terraform.lock.hcl
│   │   ├── .tflint.hcl
│   │   │
│   │   ├── shared/
│   │   │   └── core/
│   │   │       ├── ecr.tf               # prod ECR: Next.js, Spring, FastAPI
│   │   │       ├── route53.tf           # Hosted Zone, MX, SPF
│   │   │       └── outputs.tf
│   │   │
│   │   ├── prod/
│   │   │   ├── parameter-store/         # V1/V2 공용 MySQL String 설정
│   │   │   ├── s3/                      # prod 이미지 버킷
│   │   │   ├── v1/
│   │   │   │   ├── network/             # VPC, Subnet, IGW, Route Table, App/DB SG
│   │   │   │   ├── compute/             # EC2, EIP, EIP Association
│   │   │   │   ├── database/            # MySQL RDS (modules/rds-mysql 사용)
│   │   │   │   ├── iam/                 # ec2.tf, cicd.tf, discord-alert.tf, eventbridge.tf
│   │   │   │   ├── cicd-deploy-policy/  # 앱 CD 배포 Role 정책 (prod EC2만)
│   │   │   │   ├── edge/                # lookddak.com, www A 레코드
│   │   │   │   ├── monitoring/          # Logs, Alarm, EventBridge, SQS, SNS, Discord Lambda
│   │   │   │   │   └── lambda/discord_alert/lambda_function.py
│   │   │   │   └── parameter-store/     # V1 EC2 PostgreSQL String 설정
│   │   │   └── v2/                      # database/, ecs/, edge/ (Placeholder)
│   │   │
│   │   └── dev/
│   │       ├── ecr/                     # dev ECR: nextjs, spring, fastapi, crawler (V1·V2 공용)
│   │       ├── parameter-store/         # dev MySQL String 설정
│   │       ├── s3/                      # dev 이미지 버킷
│   │       ├── database/                # Placeholder (.gitkeep)
│   │       ├── v1/
│   │       │   ├── network/             # dev VPC 10.30.0.0/16, App/DB SG
│   │       │   ├── database/            # dev MySQL RDS
│   │       │   ├── parameter-store/     # dev EC2 PostgreSQL String 설정
│   │       │   ├── iam/                 # dev EC2 Role, 앱 dev CD Role
│   │       │   ├── compute/             # dev EC2, EIP
│   │       │   ├── cicd-deploy-policy/  # 앱 dev CD 배포 Role 정책 (dev EC2만)
│   │       │   ├── edge/                # dev.lookddak.com A 레코드
│   │       │   └── logging/             # dev 로그 그룹 (알림 없음)
│   │       └── v2/
│   │           ├── network/             # Public 2c, Private App 2a/2c, NAT, V2 SG
│   │           ├── alb/                 # ALB, ACM, Listener, Target Group
│   │           ├── ecs-cluster/         # lookddak-dev-v2
│   │           ├── ecs-services/        # ECS Service, Service Connect, Auto Scaling
│   │           ├── iam/                 # Task Execution/Task/Scheduler Role
│   │           ├── database/            # PostgreSQL RDS + Parameter Store
│   │           ├── crawler/             # EventBridge Scheduler
│   │           ├── logging/             # V2 CloudWatch Log Group
│   │           ├── sqs/                 # 메인 Queue·DLQ·CloudWatch Alarm
│   │           ├── cicd-deploy-policy/  # V2 ECS 배포 권한
│   │           └── task-definitions/    # Task Definition 기본 정의
│   │
│   └── modules/
│       ├── rds-mysql/                   # prod·dev V1 MySQL
│       ├── rds-postgres/                # dev V2 PostgreSQL
│       ├── ecs-service/                 # dev V2 ECS Service
│       └── network/                     # 비어 있음
│
└── scripts/
    └── v1/
        ├── README.md
        ├── docker-compose.yaml
        ├── docker-compose.dev.yaml      # dev 서버 덮어쓰기 (로그 그룹, Nginx 설정)
        ├── nginx/
        │   ├── nginx.conf
        │   ├── conf.d/default.conf      # prod
        │   └── conf.d.dev/default.conf  # dev
        ├── scripts/
        │   ├── bootstrap.sh
        │   ├── deploy.sh
        │   ├── load-parameters.sh       # prod
        │   ├── load-parameters-dev.sh   # dev (서버에는 load-parameters.sh 이름으로 배치)
        │   ├── container-metric.sh
        │   └── docker-alert.sh
        └── systemd/
            └── docker-alert.service
  1. Terraform Root 단일화

    infra/environments만 Terraform root로 사용하고 모든 리소스가 S3 State 하나를 공유한다. Child Module에는 별도 Backend, Provider, versions.tf를 두지 않는다. (infra/modules의 재사용 모듈만 versions.tf를 가진다.)

  2. 환경 분리와 V1/V2 분리

    VPC는 환경마다 하나씩 둔다. 처음에는 VPC를 shared/core에 두었지만 dev VPC를 prod와 분리하면서 prod/v1/network로 옮겼다. (moved 블록으로 이동, 리소스 재생성 없음)

    환경 VPC V1 V2
    prod 10.20.0.0/16 prod/v1 prod/v2 (예정, 같은 VPC 재사용)
    dev 10.30.0.0/16 dev/v1 dev/v2 (같은 VPC 재사용)

    shared/core에는 환경과 무관한 Route53 Hosted Zone과 prod ECR만 남는다. V1/V2와 무관하게 계속 쓰는 리소스(S3, ECR, 공용 Parameter Store)는 prod/, dev/ 바로 아래에 둔다.

  3. 공용 설정과 V1 전용 설정 분리

    MySQL 및 공용 설정은 {prod,dev}/parameter-store, EC2 내부 PostgreSQL 컨테이너 설정은 {prod,dev}/v1/parameter-store, V2 PostgreSQL RDS 설정은 dev/v2/database에서 관리한다.

  4. 운영 서버 파일의 기준선 보관

    scripts/v1은 EC2의 /opt/lookddak 파일을 복구하거나 변경 이력을 확인하기 위한 기준본이다. Terraform 관리 대상이 아니며 자동 배포되지 않는다. prod와 dev는 같은 파일을 쓰고, dev 차이는 dev 전용 파일(docker-compose.dev.yaml, conf.d.dev, load-parameters-dev.sh)로 분리한다.

  5. V2 코드 분리

    dev V2 코드는 dev/v2/에만 두고 루트 연결은 dev-v2.tf에서 한다. V1 파일과 충돌하지 않게 하기 위함이다. prod/v2, dev/database, modules/network는 아직 비어 있다.

  6. 순환 참조 회피용 분리

    cicd-deploy-policy는 배포 정책에 EC2 ARN이 필요하고, EC2는 iam의 인스턴스 프로파일이 있어야 만들어지므로 iam과 별도 폴더로 둔다.

1-2. Secret 관리

1. 문제 상황

현재 운영 환경에서는 일반 설정값(String) 7개와 민감정보(SecureString) 12개를 AWS Systems Manager Parameter Store에서 관리하고 있다.

  • 일반 설정값(String): 7개
  • 민감정보(SecureString): 12개

일반 설정값은 Terraform 코드와 State에 저장되어도 문제가 크지 않지만, SecureString을 Terraform으로 관리하면 다음 문제가 발생할 수 있다.

  1. Terraform은 Parameter Store의 값 입력을 요구한다.

    aws_ssm_parameter 리소스를 생성하려면 value가 필요하다. 따라서 Parameter Store에서는 이름과 설명 같은 “보관 공간”만 Terraform으로 만들고, 실제 값은 콘솔에서 별도로 입력하는 방식이 어렵다.

  2. 민감정보가 Terraform State에 남을 수 있다.

    비밀번호와 API Key를 Terraform 코드에 직접 작성하지 않더라도 Terraform이 값을 읽으면 State에 저장될 가능성이 있다.

    State를 S3에서 안전하게 관리하더라도 민감정보가 State에 들어가는 것 자체가 보안 범위와 접근 권한을 불필요하게 넓힌다.

  3. ignore_changes도 완전한 해결책이 되지는 못한다.

    다음과 같이 임시 값을 넣고 실제 값 변경을 무시하는 방법도 있다.

    resource "aws_ssm_parameter" "example" {
      name  = "/lookddak/prod/example"
      type  = "SecureString"
      value = "CHANGE_ME"
    
      lifecycle {
        ignore_changes = [value]
      }
    }
    

    하지만 이 방식에는 다음 위험이 있다.

    • 처음 생성될 때 CHANGE_ME가 실제 값으로 등록된다.
    • Terraform이 실제 값을 조회하면서 State에 저장할 수 있다.
    • 실수로 리소스를 재생성하면 임시 값으로 덮어쓸 수 있다.
    • 실제 값의 변경 여부를 Terraform이 관리하거나 검증하지 못한다.

    따라서 민감정보 관리 방식으로는 사용하지 않기로 한다.

2. 해결 방안

장기적으로는 민감정보를 Secrets Manager로 이전하는 것이 적절하지만, V1 Import 단계에서 바로 이전하면 운영 방식이 함께 변경된다. Secrets Manager로 이전하려면 다음 항목을 함께 변경해야 한다.

  • load-parameters.sh
  • EC2 IAM 권한
  • Lambda IAM 권한과 조회 코드
  • 애플리케이션 환경변수 생성 과정
  • 장애 복구 및 배포 절차
  • 운영 서버에 있는 실제 스크립트
  • 롤백 절차

이를 V1 Import와 동시에 진행하면 인프라 기준선 등록과 운영 애플리케이션 동작 변경이 하나의 작업에 섞인다. 배포 실패 또는 Secret 조회 실패가 발생하면 원인이 Import인지 Secret 전환인지 구분하기 어려워진다.

따라서 V1 Import와 V1/V2 무중단 마이그레이션 기간에는 기존 운영 방식을 유지한다. 민감하지 않은 String 7개는 Terraform으로 관리한다. 민감정보(SecureString) 12개는 terraform으로 Import 하지 않고 Parameter Store Console에서 수동 관리한다.

관리 대상 현재 관리 방법
비민감 String 7개 Terraform + Parameter Store
민감 SecureString 12개 Parameter Store Console
실제 민감값 Terraform 코드·State에 저장하지 않음
EC2의 Parameter 조회 권한 IAM + Terraform
Lambda의 Discord Parameter 조회 권한 IAM + Terraform
V1 운영 스크립트 scripts/v1에 기준본 보관, 서버 반영은 수동

3. 마이그레이션 기간의 운영 방식

V1과 V2가 동시에 동작하는 동안에는 기존 Parameter Store 값을 유지한다.

필요한 경우 V2 애플리케이션도 마이그레이션 기간에는 기존 SecureString을 읽도록 구성한다. 이를 통해 V2 장애 시 V1으로 다시 트래픽을 돌릴 수 있도록 한다.

이 기간에는 다음 작업을 하지 않는다.

  • 기존 SecureString 삭제
  • Secret 이름 변경
  • V1 load-parameters.sh의 Secrets Manager 전환
  • Terraform을 통한 실제 Secret 값 관리
  • 검증되지 않은 Secret 자동 Rotation 적용

4. 최종 전환 방안

V1/V2 무중단 마이그레이션과 V2 안정화가 완료된 후 다음 구조로 변경한다.

  • 비민감 설정값: Parameter Store
  • 민감정보: Secrets Manager

Terraform에서는 Secrets Manager의 보관함과 IAM 권한만 관리한다.

관리 대상 최종 관리 방법
Secret 이름, 설명, 태그 Terraform
Secret 보관함 생성 및 삭제 방지 Terraform
실제 비밀번호 및 API Key 입력 AWS Console
Secret 값과 버전 Secrets Manager
애플리케이션 접근 권한 IAM + Terraform
비민감 설정값 Parameter Store + Terraform

전환은 다음 순서로 진행한다.

  1. V2 트래픽 전환 및 안정화 완료
  2. Secrets Manager 보관함 생성
  3. 기존 SecureString 값을 Console에서 수동 복사
  4. V2 IAM에 secretsmanager:GetSecretValue 권한 추가
  5. V2 애플리케이션의 Secret 조회 방식 변경
  6. 조회 및 장애 복구 테스트
  7. 일정 기간 기존 Parameter Store 값과 병행 유지
  8. 롤백 필요성이 사라진 후 기존 SecureString 정리
  9. V1 폐기

2. V1 Dev 서버 구축

prod V1과 같은 구조의 dev 환경을 dev.lookddak.com에 구축했다. prod와 리소스·권한을 모두 분리한다.

2-1. 구성

영역 리소스 prod와 다른 점
네트워크 lookddak-dev-vpc 10.30.0.0/16, Public 2a 10.30.1.0/24, Private DB 2a/2c 10.30.11.0/24, 10.30.12.0/24, App/DB SG VPC 자체를 분리
DB RDS MySQL 8.4 lookddak-dev-mysql (db.t4g.micro, prod와 같은 파라미터) 삭제 보호 꺼짐, 최종 스냅샷 생략, 관리자 비밀번호 write-only
컴퓨팅 EC2 lookddak-dev-app-v1 (t4g.medium, Ubuntu 24.04 ARM, gp3 30GB), EIP 종료 보호 꺼짐
컨테이너 Nginx, Next.js, Spring, FastAPI, PostgreSQL(pgvector) /opt/lookddak, 데이터 /srv/lookddak/postgresql
이미지 dev ECR lookddak-dev-{nextjs,spring,fastapi} prod ECR과 분리, 최신 10개 유지
설정 /lookddak/dev/rds/*, /lookddak/dev/postgresql/* (String), dev SecureString 9개
IAM EC2 Role lookddak-dev-ec2 dev ECR·dev S3·/lookddak/dev/*·dev 로그 그룹만
도메인 dev.lookddak.com A → dev EIP, Let's Encrypt 인증서 www 없음
로그 /lookddak/dev/{nginx,spring,fastapi,nextjs,postgresql,deploy}, 30일 알림 없음 (메트릭 필터·알람·EventBridge·Discord 미구성)

dev ECR을 prod와 분리한 이유

  • ECR이 태그 덮어쓰기 금지(IMMUTABLE)라 같은 커밋 SHA 태그를 dev·prod가 함께 쓸 수 없다. FE는 NEXT_PUBLIC_API_BASE_URL이 빌드 시점에 이미지에 들어가 dev·prod 이미지 내용이 다르다.
  • dev 빌드가 prod 수명 주기(최신 35개)를 채워 prod 롤백용 이미지가 삭제되는 것을 막는다.

2-2. 서버 파일과 설정

  • 서버 파일은 scripts/v1과 같은 구조로 /opt/lookddak에 배치한다. dev는 load-parameters-dev.sh를 load-parameters.sh 이름으로 복사한다.

    COMPOSE_FILE=docker-compose.yaml:docker-compose.dev.yaml   # dev 덮어쓰기 적용
    ECR_REPOSITORY_PREFIX=lookddak-dev                          # dev ECR 사용 (없으면 lookddak)
    S3_BUCKET, CORS_ALLOWED_ORIGINS, NEXT_PUBLIC_API_BASE_URL   # dev 값
    NEXTJS_IMAGE_TAG / SPRING_IMAGE_TAG / FASTAPI_IMAGE_TAG     # deploy.sh가 갱신
    

2-3. 앱 dev CD

항목 내용
트리거 앱 레포 develop, hotfix/** push
GitHub Environment develop (변수 이름은 production과 같고 값만 dev)
AWS Role {fe,be,ai}-dev-cd-{build,deploy}-role (신뢰 조건: environment:develop, develop, hotfix/*)
권한 build: dev ECR push / deploy: dev ECR 조회, dev EC2에만 SSM Run Command
job 이름 dev-build, dev-deploy
배포 로그 /lookddak/dev/deploy
  • prod 배포 Role 정책에서 dev EC2를 제거했다. prod Role은 prod EC2에만 배포할 수 있다.
  • 앱 레포 브랜치 규칙: develop은 CI 필수, main은 CI + dev-deploy 필수 → dev 서버에 배포 성공한 커밋만 main에 merge된다.

3. V2 Dev 서버 구축

VPC

  • v1 dev - v2 dev 동일 (10.30.0.0/16)
  • v1 prod - v2 prod 동일 (10.20.0.0/16)

총 2개로 운영

작업 순서

숙지사항

  • 모든 PR에서 Prod 리소스 변경이 0인지 확인
  • 각 번호가 issue & pr 단위
  • V2 코드는 infra/environments/dev/v2/ 아래에 배치
  • 루트 모듈 연결은 기존 main.tf를 수정하지 않고 infra/environments/dev-v2.tf에서 처리
  • 기존 V1 Dev VPC 10.30.0.0/16와 DB subnet을 재사용
  • Dev는 운영과 같은 구성 요소·네트워크 경로·Task 크기를 사용 — 평상시 Task 수만 서비스별 1개로 축소
  • V2 Spring은 V2 Dev MySQL(lookddak-dev-v2-mysql)을 사용한다. 해당 MySQL Green 생성 과정은 현재 Terraform 관리 범위에서 제외한다.
  • V1 EC2 → PostgreSQL RDS 연결도 지금은 허용하지 않는다. 마이그레이션 단계에서 별도 SG 변경으로 연다
  • 평상시 개발 중에는 V2 PostgreSQL 쓰기를 허용한다. 리허설 시작 전에 데이터를 초기화하고 쓰기 주체를 통제
  • SQS는 Spring이 VTON 작업을 발행하고 FastAPI가 소비하기 위한 큐
  • Crawler는 EventBridge Scheduler로 실행되며 29CM 크롤링 후 PostgreSQL을 갱신

1. V2 네트워크

기존 Public 2a  10.30.1.0/24   (V1 dev)
신규 Public 2c  10.30.2.0/24   lookddak-dev-v2-public-c

Private App 2a  10.30.21.0/24  lookddak-dev-v2-private-app-a
Private App 2c  10.30.22.0/24  lookddak-dev-v2-private-app-c

기존 Private DB 2a  10.30.11.0/24  (V1 dev)
기존 Private DB 2c  10.30.12.0/24  (V1 dev)

추가로 아래 리소스 구축

  • Public 2c subnet
  • Public 2c route table (lookddak-dev-v2-public-c-rt)
  • 기존 Dev VPC의 Internet Gateway 조회 (data source)
  • NAT Gateway 1개 (lookddak-dev-v2-nat, Public 2c)
  • NAT용 EIP (lookddak-dev-v2-nat-eip)
  • Private App route table (lookddak-dev-v2-private-app-rt)
  • Private App 2a/2c route association
  • V2 전용 Security Group

NAT(1개)

Public 2c:
  Internet Gateway 연결
  NAT Gateway 배치

Private App 2a:
  0.0.0.0/0 → NAT Gateway

Private App 2c:
  0.0.0.0/0 → NAT Gateway

2. V2 보안그룹

lookddak-dev-v2-alb-sg
lookddak-dev-v2-nextjs-sg
lookddak-dev-v2-spring-sg
lookddak-dev-v2-fastapi-sg
lookddak-dev-v2-crawler-sg
lookddak-dev-v2-postgresql-sg

주요 통신 규칙

Internet:
  ├─ 80 → ALB  (HTTPS 리다이렉트용)
  └─ 443 → ALB

ALB:
  ├─ 3000 → Next.js
  └─ 8080 → Spring

Next.js:
  └─ 8080 → Spring (SSR, Service Connect)

Spring:
  ├─ 3306 → V2 Dev MySQL (해당 DB 보안 그룹에 Spring 허용 규칙 추가)
  ├─ 8000 → FastAPI
  └─ 443 → AWS API (SQS 등)

FastAPI:
  ├─ 5432 → PostgreSQL RDS
  └─ 443 → OpenAI·Runware 및 AWS API

Crawler:
  ├─ 5432 → PostgreSQL RDS
  └─ 443 → 29CM 크롤링 대상

PostgreSQL RDS:
  ├─ 5432 ← FastAPI
  └─ 5432 ← Crawler
  • ECS Task(Next.js·Spring·FastAPI·Crawler)는 443 아웃바운드를 공통으로 허용한다. SQS 접근 권한은 SG가 아니라 IAM으로 제어한다.

3. V2 IAM·ECS Cluster 기반 구성

  • ECS Cluster lookddak-dev-v2
  • Container Insights 활성화
  • ECS Task Execution Role
  • 서비스별 Task Role
  • CloudWatch Log Group 및 로그 전송 권한 구성(세부 내용은 11번 참고)

Role

Role 이름 권한
Execution Role lookddak-dev-v2-task-execution ECR pull (lookddak-* 저장소 패턴), /lookddak/dev/* Parameter 조회, SSM 경유 KMS decrypt
Next.js Task Role lookddak-dev-v2-nextjs-task 기본적으로 AWS 권한 없음. 서버 측 S3 접근이 확인될 때만 추가
Spring Task Role lookddak-dev-v2-spring-task Dev S3 읽기·쓰기, 필요한 SQS 발행·소비 권한(10번 참고), ECS Exec용 ssmmessages 권한
FastAPI Task Role lookddak-dev-v2-fastapi-task Dev S3 읽기·쓰기, 필요한 SQS 발행·소비 권한(10번 참고)
Crawler Task Role lookddak-dev-v2-crawler-task AWS 권한 없음. PostgreSQL 접속은 네트워크와 DB 계정으로 제어
Crawler Scheduler Role lookddak-dev-v2-crawler-scheduler Crawler Task에 대한 ecs:RunTask, Task/Execution Role에 대한 iam:PassRole
  • Execution Role의 ECR 권한이 lookddak-* 패턴이라 V1 dev와 같은 dev ECR(lookddak-dev-*)을 그대로 사용한다.

4. V2 ALB & DNS

  • Internet-facing ALB lookddak-dev-v2-alb (Public 2a/2c)
  • HTTP 80 Listener → HTTPS Redirect (301)
  • HTTPS 443 Listener
  • ACM 인증서 및 Route 53 DNS 검증
  • V2 Next.js Target Group lookddak-dev-v2-nextjs-tg (ip, 3000)
  • V2 Spring Target Group lookddak-dev-v2-spring-tg (ip, 8080)
  • CDN 제외
  • FastAPI는 외부 ALB에 연결하지 않고 Service Connect로만 접근
V2 Test Domain: v2-dev.lookddak.com

Listener Rule (HTTPS 443):
  priority 10: /api, /api/*  → V2 Spring Target Group : 8080
  default: 그 외 경로         → V2 Next.js Target Group : 3000

V1 Domain:
  dev.lookddak.com → V1 Dev EC2 유지
  • /v2 URL prefix는 사용하지 않는다.
  • V2 검증 완료 전까지 dev.lookddak.com은 V1을 유지한다.
  • 최종 헬스 체크와 테스트 도메인 정비 내용은 12번에서 다룬다.

5. RDS Postgres 생성

Identifier:              lookddak-dev-postgresql
Engine:                  PostgreSQL 16.15
Parameter family:        postgres16
Instance class:          db.t4g.small
Database:                lookddak

Storage type:            gp3
Allocated storage:       20GiB
Max allocated storage:   100GiB

Deployment:              Multi-AZ DB instance
Standby:                 RDS가 다른 AZ에 자동 생성
Public access:           false
Subnet:                  기존 Private DB 2a/2c
Security Group:          lookddak-dev-v2-postgresql-sg

Encryption:              true
Backup retention:        7일 (18:00-18:30 UTC)
Maintenance:             sun:19:00-19:30 UTC
Deletion protection:     true
Skip final snapshot:     false
Final snapshot:          lookddak-dev-postgresql-final

Logs:
  - postgresql
  - upgrade

Admin password:          SecureString → ephemeral → master_password_wo (State·plan에 남지 않음)

Extension:
  pgvector: 0.8.2 (수동 SQL로 활성화 필요)

Multi-AZ 구성

Primary:
  위치: Private DB subnet 중 한 AZ
  역할: 애플리케이션 연결 및 읽기·쓰기

Standby:
  위치: 다른 AZ
  역할: 동기식 복제 및 장애 시 승격
  직접 연결: 불가능
  읽기 분산: 불가능

Endpoint:
  애플리케이션은 하나의 RDS Endpoint 사용
  장애 조치 시 Endpoint가 새로운 Primary를 가리킴
  • Primary와 Standby를 Terraform 리소스 두 개로 생성하지 않는다.
  • aws_db_instance 하나에 multi_az = true를 지정한다. (modules/rds-postgres)
  • Multi-AZ는 가용성 구성이지 V1→V2 데이터 마이그레이션 수단이 아니다.
  • Multi-AZ 비용은 Primary와 Standby 두 인스턴스를 기준으로 발생한다.

Parameter group

rds.logical_replication           = 1      (pending-reboot)
max_replication_slots             = 10     (pending-reboot)
max_wal_senders                   = 10     (pending-reboot)
max_logical_replication_workers   = 4      (pending-reboot)
max_worker_processes              = 8      (pending-reboot)
max_slot_wal_keep_size            = 2048   (immediate)
파라미터 역할 이번 구성에서 필요한 이유
rds.logical_replication RDS의 논리 WAL 생성을 활성화 RDS가 역방향 복제의 Publisher가 되기 위해 필요
max_replication_slots 유지 가능한 Slot 및 Subscription 추적 수의 상한 정방향 Subscription과 역방향 Slot, 초기 테이블 동기화용 여유 확보
max_wal_senders WAL을 외부 Subscriber에 전달할 수 있는 연결 수 RDS → V1 역방향 복제 연결에 필요
max_logical_replication_workers Apply·초기 동기화·병렬 적용 Worker 상한 RDS가 정방향 Subscriber로 동작할 때 변경 사항 적용에 필요
max_worker_processes 논리 복제를 포함한 전체 Background Worker 상한 논리 복제 Worker가 실행될 상위 Worker Pool 제공
max_slot_wal_keep_size Slot으로 보존할 WAL 크기의 안전 상한 복제 중단 시 WAL이 계속 누적되어 스토리지를 소진하는 상황 방지

pgvector 설치

CREATE EXTENSION IF NOT EXISTS vector VERSION '0.8.2';
  • RDS 엔진이 pgvector를 지원한다고 해서 확장이 자동 활성화되지는 않는다.
  • RDS 생성 후 Migration 계정 또는 Admin 계정으로 대상 Database에 확장을 생성한다.
  • 논리 복제 초기화 전에 V1과 V2의 확장 및 스키마를 동일하게 준비한다.

Parameter Store

V1 dev EC2 PostgreSQL 컨테이너가 이미 /lookddak/dev/postgresql/*를 사용하므로, V2 RDS는 /lookddak/dev/v2/postgresql/* 경로를 사용한다.

# Terraform 관리: String (dev/v2/database/parameter-store.tf)
/lookddak/dev/v2/postgresql/admin/username       lookddak_admin
/lookddak/dev/v2/postgresql/host
/lookddak/dev/v2/postgresql/port
/lookddak/dev/v2/postgresql/database             lookddak
/lookddak/dev/v2/postgresql/app/username         lookddak_app
/lookddak/dev/v2/postgresql/migration/username   lookddak_migration

# 콘솔에서 수동 생성: SecureString
/lookddak/dev/v2/postgresql/admin/password
/lookddak/dev/v2/postgresql/app/password
/lookddak/dev/v2/postgresql/migration/password

원칙은 다음을 고수한다.

  • host, port, database, username은 Terraform이 String으로 생성한다.
  • 비밀번호는 Terraform 리소스로 생성하지 않는다.
  • Terraform 코드에는 SecureString의 경로만 locals, IAM 정책 또는 문서로 정의한다.
  • 빈 값이나 CHANGE_ME 같은 임시 값은 넣지 않는다.
  • Parameter Store는 값 없이 Parameter 이름만 생성할 수 없으므로, SecureString의 실제 리소스는 콘솔에서 값을 입력할 때 생성한다.
  • 전체 DATABASE_URL은 저장하지 않고 ECS Task 시작 시 개별 Parameter를 조합한다.
  • Admin 계정은 애플리케이션에서 사용하지 않는다.
  • App 계정과 Migration 계정을 분리한다.
  • App/Migration DB Role 생성은 별도 SQL 작업으로 수행한다.
  • Admin 비밀번호 SecureString이 없을 때 전체 plan을 보기 위한 plan 전용 입력(dev_v2_postgresql_admin_password_wo)이 있다. apply에는 사용하지 않는다.

6. Crawler 기반 구성

작업 범위

  • Crawler용 Dev ECR (dev/ecr에 crawler 추가)
  • Crawler ECS Task Definition JSON
  • EventBridge Scheduler Group
  • EventBridge Schedule
  • 기존 Scheduler 실행 Role 연결
  • 초기 Schedule 상태 DISABLED
  • SQS 및 Queue 관련 IAM 권한 제외
  • CloudWatch Log Group 및 로그 설정 제외

Crawler 실행 구조

EventBridge Scheduler:
  └─ ECS RunTask
      └─ Crawler Fargate Task
          ├─ HTTPS 443 → NAT Gateway → 29CM
          └─ PostgreSQL 5432 → 상품 데이터 INSERT/UPDATE
ECS Cluster:          lookddak-dev-v2
Task Execution Role:  lookddak-dev-v2-task-execution
Crawler Task Role:    lookddak-dev-v2-crawler-task
Scheduler Role:       lookddak-dev-v2-crawler-scheduler
Security Group:       lookddak-dev-v2-crawler-sg
Private App Subnet:   Private App 2a, Private App 2c
  • Crawler Task Role은 현재 AWS API 권한이 없다.
  • PostgreSQL 접속은 IAM이 아니라 Security Group과 DB 계정으로 제어한다.
  • 동일한 IAM Role과 Security Group을 중복 생성하지 않는다.

Crawler ECR

Name: lookddak-dev-crawler
Image tag mutability: IMMUTABLE
Image scanning: true
Encryption: AES256

Lifecycle:
  Untagged image: 1일 후 삭제
  Tagged image: 최신 10개 유지
  • V1 dev와 같은 dev/ecr 모듈에서 for_each로 함께 관리한다. (nextjs, spring, fastapi, crawler)
  • 운영 환경에서는 별도 lookddak-crawler를 생성하거나 이미지 승격 정책을 별도로 설계
  • Task Definition에는 latest가 아니라 Git SHA 태그의 Image Digest를 사용
  • Crawler 저장소용 GitHub Actions ECR Push 권한은 별도 CI/CD 작업에서 구성

EventBridge Scheduler

Group:                   lookddak-dev-v2
Schedule:                lookddak-dev-v2-crawler
Expression:              cron(0 4 * * ? *)  (Asia/Seoul, 매일 오전 4시)
State:                   DISABLED
Flexible time window:    OFF

Target:
  ECS Cluster:           lookddak-dev-v2
  Task Definition:       lookddak-dev-v2-crawler (family ARN, 최신 revision)
  Launch type:           FARGATE
  Platform version:      LATEST
  Task count:            1

Network:
  Private App 2a/2c
  lookddak-dev-v2-crawler-sg
  Assign public IP: false

Retry:
  maximum_event_age:     3600초
  maximum_retry:         2
  DLQ:                   제외
  • Schedule 이름은 기존 Scheduler Role의 IAM 조건과 일치하도록 lookddak-dev-v2-crawler 접두사를 사용한다.
  • Scheduler는 ECS Task 실행 요청 성공 여부만 확인하며, Crawler 프로세스의 최종 성공·실패까지 감시하지 않는다.
  • Task 종료 상태 모니터링은 이후 PLG 또는 ECS Task State Change 연동 단계에서 구성한다.
  • Crawler가 V2 PostgreSQL의 lookddak_app 계정을 재사용 (SELECT, INSERT, UPDATE)

Task Definition (dev/v2/task-definitions/crawler.json)

Family:              lookddak-dev-v2-crawler
Launch type:         FARGATE
Network mode:        awsvpc
OS / Arch:           LINUX / X86_64
CPU / Memory:        1024 / 2048
Ephemeral storage:   20GiB
Image:               lookddak-dev-crawler@sha256:REPLACED_BY_CI
Command:             이미지의 ENTRYPOINT/CMD
Port / Health check / Logging / Environment / Secrets: 미구성

배포 흐름

  1. Git SHA 태그로 Crawler 이미지 빌드
  2. lookddak-dev-crawler ECR에 Push
  3. Git SHA 태그의 Image Digest 조회
  4. Task Definition JSON의 REPLACED_BY_CI 교체
  5. 새 Task Definition Revision 등록
  6. Scheduler 활성화 전 수동 RunTask 검증
  7. 검증 완료 후 Schedule을 ENABLED로 변경

이 단계에서는 Crawler 로그·환경변수·시크릿을 제외했으며, 이후 11번 런타임 구성에서 보완했다.

7. V2 애플리케이션 Task Definition

Task CPU Memory Port (이름) 실행 형태
Next.js lookddak-dev-v2-nextjs 0.5 vCPU 1GiB 3000 (nextjs-http) ECS Service
Spring lookddak-dev-v2-spring 1 vCPU 2GiB 8080 (spring-http) ECS Service
FastAPI lookddak-dev-v2-fastapi 1 vCPU 2GiB 8000 (fastapi-http) ECS Service
Crawler lookddak-dev-v2-crawler 1 vCPU 2GiB 없음 Scheduler RunTask

공통 실행 설정

Launch type: FARGATE
Network mode: awsvpc
Operating system: LINUX
Container count: Task당 1개
Essential container: true
Public IP: false
Execution Role: lookddak-dev-v2-task-execution
Task Role: lookddak-dev-v2-{nextjs,spring,fastapi}-task
Image: lookddak-dev-{nextjs,spring,fastapi}@sha256:REPLACED_BY_CI
  • ECR 저장소는 V1 dev와 같은 lookddak-dev-{nextjs,spring,fastapi}를 사용한다. (V1 dev CD가 이미 이미지를 push 중)
  • latest와 Git SHA 태그만으로 실행하지 않고 Digest까지 고정

Port Mapping, Service Connect 기본 이름, 스토리지 설정, 관리 소유 형태는 이전 계획과 같다.

Service Connect:
  Namespace: lookddak-dev-v2
  Discovery name: nextjs / spring / fastapi
  Internal address: Spring → FastAPI http://fastapi:8000

Terraform 관리:
  ECS Cluster, IAM Role, Network, ECS Service, Service Connect, Auto Scaling

애플리케이션 GitHub Actions 관리 (예정):
  이미지 빌드 및 ECR Push → Digest 조회 → Task Definition Revision 등록 → ECS Service 배포

현재:
  Task Definition 기본 JSON만 준비 (AWS 등록 안 됨, Terraform apply 대상 아님)

위 내용은 최초 등록 전 상태를 설명한다. 실제 최초 Revision 등록 결과는 13번에서 다룬다.

8. ECS Service 생성(코드 완료, 생성 보류)

  • ECS Service Terraform 모듈(modules/ecs-service) 및 설정 작성
  • Service Connect Namespace 생성 (lookddak-dev-v2, Cloud Map HTTP Namespace)
  • 서비스별 네트워크·Security Group·Target Group 연결 구조 작성
  • Auto Scaling 설정 코드 작성
  • dev_v2_create_services = false (기본값) → 실제 ECS Service, Task, Auto Scaling 리소스 생성 보류
  • Service 생성 시 dev_v2_task_definition_arns에 등록된 Task Definition Revision ARN을 넘긴다.

ECS Service

Next.js: lookddak-dev-v2-nextjs-service  (Target Group: nextjs-tg, Service Connect: Client only)
Spring:  lookddak-dev-v2-spring-service  (Target Group: spring-tg, Service Connect: spring:8080)
FastAPI: lookddak-dev-v2-fastapi-service (Target Group 없음, Service Connect: fastapi:8000)

공통 설정: FARGATE, REPLICA, Private App 2a/2c, Public IP false, Desired count 1

Service Connect 통신 구조

외부 사용자
  ├─ ALB → Next.js:3000
  └─ ALB → Spring:8080

Next.js SSR
  └─ Service Connect → spring:8080

Spring
  └─ Service Connect → fastapi:8000

FastAPI
  └─ 5432 → PostgreSQL RDS

Auto Scaling

서비스 Min Max 지표 Scale-out Scale-in
Next.js 1 5 CPU 60% 180초 300초
Spring 1 5 CPU 60% 120초 300초
FastAPI 1 5 CPU 70%, Memory 70% 300초 300초

위 내용은 create_services=false 상태에서 인프라 코드를 먼저 준비한 단계다. 실제 Service 및 Task 활성화 결과는 14번에서 다룬다.

9. CI/CD 설정 (V2 미구축)

  • V1 dev CD는 완료 (2-3 참고). V2 CI/CD는 아래 순서로 별도 구성한다.
  • 앱 레포가 같은 dev ECR에 push하므로, V2 배포는 "이미지 Digest 조회 → Task Definition 등록 → ECS Service 배포" 단계만 추가하면 된다.
  • 필요 권한: ecs:RegisterTaskDefinition, ecs:UpdateService, ecs:DescribeServices, Task/Execution Role에 대한 iam:PassRole (dev CD Role 또는 V2 전용 Role)
  • Crawler 레포 CI/CD Role (dev ECR lookddak-dev-crawler push) 별도 구성

10. SQS 구성

용도 권장 실제 큐 이름 설정
BE → AI 요청 lookddak-dev-v2-be-fitting-request Visibility 120초, 보관 4일
요청 DLQ lookddak-dev-v2-be-fitting-request-dlq 보관 14일
AI 내부 후처리 lookddak-dev-v2-ai-fitting-response Visibility 300초, 보관 4일
후처리 DLQ lookddak-dev-v2-ai-fitting-response-dlq 보관 14일
AI → BE 결과 lookddak-dev-v2-be-fitting-response Visibility 60초, 보관 4일
결과 DLQ lookddak-dev-v2-be-fitting-response-dlq 보관 14일
  • Queue type: Standard
  • maxReceiveCount = 3
  • receive_wait_time_seconds = 20
    • Long Polling으로 빈 조회 요청과 비용을 줄임
  • delay_seconds = 0
  • 메시지 최대 크기: 256 KiB
    • 실제 이미지나 Base64를 넣지 않고 ID, S3 Key, 상태 정보만 전달
  • 암호화: SSE-SQS
    • sqs_managed_sse_enabled = true
    • 별도 KMS 키와 복호화 권한이 필요 없어 dev에 적합
  • 각 DLQ의 Redrive Allow Policy는 대응하는 메인 큐 하나만 허용
  • TLS가 아닌 요청을 거부하는 Queue Policy 적용
  • 태그:
    • Environment = dev
    • Version = v2
    • Component = sqs
    • Service = fitting

구현 방식

infra/environments/dev/v2/sqs/
├── main.tf
├── alarms.tf
├── variables.tf
└── outputs.tf
  • main.tf
    • 메인 큐 3개와 DLQ 3개 생성
    • 메인 큐와 DLQ 연결
    • Redrive Policy 및 Redrive Allow Policy 설정
    • 암호화 및 TLS Queue Policy 설정
  • alarms.tf
    • 메인 큐 지연 알람 3개 생성
    • DLQ 메시지 발생 알람 3개 생성
  • outputs.tf
    • 각 Queue URL과 ARN 출력
    • IAM과 ECS Task Definition에서 사용할 수 있도록 제공

루트 dev-v2.tf에서 SQS 모듈을 호출하고, 생성된 Queue ARN을 V2 IAM 모듈에 전달한다.

메시지 처리 방식

별도의 Worker ECS Service는 생성하지 않는다.

기존 Spring/FastAPI 애플리케이션이 실행될 때 코드에 구현된 SQS Consumer도 함께 실행되는 구조를 사용한다.

Spring ECS Service
├── 기존 HTTP API 처리
├── be-fitting-request 메시지 발행
└── be-fitting-response 메시지 수신 및 처리

FastAPI ECS Service
├── 기존 HTTP/Webhook API 처리
├── be-fitting-request 메시지 수신 및 처리
├── ai-fitting-response 메시지 발행 및 처리
└── be-fitting-response 메시지 발행

따라서 기존 Spring/FastAPI ECS Service와 Task Definition을 그대로 사용하며, 새로운 Worker용 ECS Service와 Task Definition은 생성하지 않는다

IAM 권한 설계

SQS 권한은 ECS Task Execution Role이 아니라 각 애플리케이션 Task Role에 부여한다. Execution Role은 ECS 에이전트가 이미지와 시크릿을 준비할 때 사용하고, 애플리케이션이 SQS API를 호출할 때는 Task Role을 사용한다.

  • spring(lookddak-dev-v2-spring-task)

    큐 권한
    be-fitting-request sqs:SendMessage
    be-fitting-response sqs:ReceiveMessage, sqs:DeleteMessage, sqs:ChangeMessageVisibility
  • fastapi(lookddak-dev-v2-fastapi-task)

    큐 권한
    be-fitting-request sqs:ReceiveMessage, sqs:DeleteMessage, sqs:ChangeMessageVisibility
    ai-fitting-response sqs:SendMessage, sqs:ReceiveMessage, sqs:DeleteMessage, sqs:ChangeMessageVisibility
    be-fitting-response sqs:SendMessage

권한은 Resource = "*"로 열지 않고 각 큐의 ARN으로 제한한다.

Queue URL을 환경변수로 직접 전달하기 때문에 sqs:GetQueueUrl은 기본 권한에 포함하지 않는다. 사용 중인 라이브러리가 큐 속성 조회를 요구하는 경우에만 sqs:GetQueueAttributes를 추가한다.

애플리케이션 Task Role에는 DLQ 조회, 삭제 및 Redrive 권한을 부여하지 않는다.

ECS 환경 변수

Queue URL은 민감정보가 아니므로 Parameter Store가 아닌 ECS Task Definition의 일반 환경변수로 전달한다. Spring

BE_FITTING_REQUEST_QUEUE_URL
BE_FITTING_RESPONSE_QUEUE_URL
AWS_REGION=ap-northeast-2

FastAPI

BE_FITTING_REQUEST_QUEUE_URL
AI_FITTING_RESPONSE_QUEUE_URL
BE_FITTING_RESPONSE_QUEUE_URL
AWS_REGION=ap-northeast-2

AWS Access Key와 Secret Key는 환경변수로 전달하지 않는다. Spring과 FastAPI는 ECS Task Role이 제공하는 임시 자격 증명을 사용한다.

CloudWatch 모니터링

  • 메인 queue 지연 알람

    큐 알람 조건
    be-fitting-request 가장 오래된 메시지가 180초를 초과한 상태가 2회 연속 발생
    ai-fitting-response 가장 오래된 메시지가 600초를 초과한 상태가 2회 연속 발생
    be-fitting-response 가장 오래된 메시지가 120초를 초과한 상태가 2회 연속 발생

공통 설정:

  • Metric: ApproximateAgeOfOldestMessage

  • Period: 1분

  • Statistic: Maximum

  • Missing data: 정상으로 처리

  • DLQ 알람

    각 DLQ에 메시지가 1개 이상 존재하면 알람을 발생시킨다.

    • Metric: ApproximateNumberOfMessagesVisible
    • Period: 1분
    • 평가 횟수: 1회
    • Missing data: 정상으로 처리

알람 이름은 lookddak- 접두사로 생성하여 기존 알림 파이프라인을 사용한다.

CloudWatch Alarm
→ 기존 EventBridge 규칙
→ lookddak-alert-queue
→ Discord 알림 Lambda
→ Discord

DLQ 운영 방식

메시지가 동일한 메인 큐에서 3회 처리되지 못하면 대응하는 DLQ로 이동한다.

처리 실패 3회
→ DLQ 이동
→ CloudWatch 알람
→ Discord 알림
→ 인프라 운영자가 원인 확인

DLQ 메시지는 자동으로 재처리하지 않는다. 원인을 수정하고 중복 처리 가능성을 확인한 후, 기존 AWS 운영 권한을 가진 인프라 담당자가 원래 메인 큐로 수동 Redrive한다.

작업 상황

영역 상태 설명
VPC·Subnet·NAT·보안 그룹 완료 ECS가 실행될 네트워크 준비 완료
ECS Cluster 완료 lookddak-dev-v2, Container Insights 활성화
ECS Task Definition 완료 Next.js·Spring·FastAPI 최초 Revision 등록 완료, Crawler 제외
ECS Service 완료 Next.js·Spring·FastAPI Service 활성화
실행 중인 ECS Task 완료 Next.js·Spring·FastAPI Task 실행 및 안정성 확인
ALB·HTTPS 인증서·Target Group 완료 v2-dev.lookddak.com, Next.js·Spring Target Healthy
Service Connect 완료 Spring 컨테이너에서 http://fastapi:8000 실제 호출 확인
PostgreSQL·MySQL 런타임 연결 확인 Spring의 V2 MySQL 연결과 FastAPI의 PostgreSQL 연결 확인
S3·ECR 부분 완료 앱 저장소에는 이미지가 있지만 Crawler 저장소는 비어 있음
SQS·DLQ·알람·IAM 완료 6개 큐와 6개 알람이 AWS에 실제 생성됨
SQS 애플리케이션 흐름 대기중 Producer/Consumer 코드와 관련 이미지가 준비된 뒤 전체 메시지 흐름 검증 필요
Crawler Scheduler 부분 완료 스케줄은 비활성화 상태이며 이미지·Task Definition 등록 및 수동 RunTask 검증 필요
CloudWatch 앱 로그 완료 V2 Log Group, awslogs, 로그 스트림 및 애플리케이션 로그 확인
V2 CI/CD 미구축 앱 빌드부터 ECS 재배포까지 자동화가 없음
Route 53 테스트 도메인 완료 v2-dev.lookddak.com은 V2 ALB, dev.lookddak.com은 V1 EC2 유지
PLG 모니터링 미구축 Prometheus·Loki·Grafana 미구축

11. V2 dev ECS 런타임 설정 및 CloudWatch Logs 구성(완료)

  1. CloudWatch Log Group 생성

    • V2 전용 로그 그룹을 생성

      /lookddak/dev/v2/nextjs
      /lookddak/dev/v2/spring
      /lookddak/dev/v2/fastapi
      /lookddak/dev/v2/crawler
      

      *보관 기간 14일

  2. Task Execution Role에 로그 전송 권한 추가

    logs:CreateLogStream
    logs:PutLogEvents
    

    권한 대상은 전체 Log Group이 아니라 위 V2 Log Group ARN으로 제한

  3. 기존 Task Definition JSON에 로그 설정 추가

    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/lookddak/dev/v2/spring",
        "awslogs-region": "ap-northeast-2",
        "awslogs-stream-prefix": "ecs"
      }
    }
    
  4. Spring 런타임 설정 보완

    일반 환경변수:

    SPRING_PROFILES_ACTIVE
    AWS_REGION
    S3_BUCKET_NAME
    AI_SERVER_URL
    BE_FITTING_REQUEST_QUEUE_URL
    BE_FITTING_RESPONSE_QUEUE_URL
    TZ
    

    Parameter Store에서 주입할 값:

    DB_URL
    DB_USERNAME
    DB_PASSWORD
    JWT_SECRET
    AI_INTERNAL_API_KEY
    SENTRY_DSN
    

    Spring에서 FastAPI를 호출하는 주소는 외부 주소가 아니라 Service Connect 내부 주소를 사용

    http://fastapi:8000
    
  5. FastAPI 런타임 설정 보완

    일반 환경변수:

    AWS_REGION
    S3_BUCKET_NAME
    BE_FITTING_REQUEST_QUEUE_URL
    AI_FITTING_RESPONSE_QUEUE_URL
    BE_FITTING_RESPONSE_QUEUE_URL
    TZ
    

    Parameter Store에서 주입할 값:

    DATABASE_URL 또는 PostgreSQL 개별 연결 정보
    CHECKPOINT_DSN
    INTERNAL_API_KEY
    OPENAI_API_KEY
    RUNWARE 관련 API KEY
    BODY_IMAGE_VALIDATION_URL
    BODY_IMAGE_VALIDATION_PROXY_KEY
    BODY_IMAGE_VALIDATION_PROXY_SECRET
    BODY_IMAGE_VALIDATION_API_KEY
    

    네 개의 BODY IMAGE 값은 앞서 구성한 공유 Parameter Store 경로를 그대로 사용

    비밀번호와 API Key는 Task Definition의 일반 environment에 넣지 않고 반드시 secrets로 연결

  6. Next.js 런타임 설정 보완

    • 서버 런타임 환경변수만 Task Definition에 추가
    • NEXT_PUBLIC_* 값은 향후 애플리케이션 CI/CD가 빌드 시 주입하도록 구분
    • /v2 base path를 사용하지 않도록 확인
    • CloudWatch Logs 설정 추가
  7. Crawler Task Definition 보완

    • CloudWatch Logs 설정
    • DB 연결 환경변수와 시크릿
    • AWS Region·S3 등 공통 설정
    • Task Role·Execution Role 연결

12. V2 ALB 배포 전제 정비(완료)

  • Spring Target Group 헬스 체크를 /actuator/health로 설정

  • Next.js Target Group은 우선 /로 헬스 체크

  • v2-dev.lookddak.com을 V2 ALB에 연결

  • /api/*는 Spring, 나머지는 Next.js로 전달

  • FastAPI는 외부 ALB에 연결하지 않고 Service Connect로만 접근

  • /v2 URL prefix는 사용하지 않음

  • V2 검증 완료 전까지 dev.lookddak.com은 V1 유지

13. Task Definition 최초 등록(완료)

  • Next.js, Spring, FastAPI의 사용할 ECR 이미지 digest 확인
  • 기존 JSON의 placeholder를 실제 digest로 치환
  • aws ecs register-task-definition으로 최초 revision 등록
  • Crawler는 제외

14. ECS Service 및 Task 활성화(완료)

  • 등록된 Task Definition ARN 입력
  • create_services = true
  • 로컬 terraform plan
  • PR 병합 후 기존 Terraform GitHub Actions로 적용

ECS Service 모듈은 Task Definition 변경을 Terraform이 계속 덮어쓰지 않도록 ignore_changes가 설정되어 있다. 따라서 이후 애플리케이션 CD가 새 Revision으로 서비스를 업데이트해도 충돌하지 않는다.

  • Terraform에서 Spring Service의 ECS Exec 활성화
  • Spring Task Role에 다음 ssmmessages 권한 추가
    • CreateControlChannel
    • CreateDataChannel
    • OpenControlChannel
    • OpenDataChannel
  • 적용 후 Spring Task 새로 배포

15. 런타임 검증(완료)

  • ECS Task가 재시작 없이 유지되는지
  • ALB Target이 Healthy인지
  • CloudWatch에 로그 스트림이 생성되는지
  • Spring/FastAPI DB 연결 여부
  • Service Connect 내부 통신
  • S3 및 SQS IAM 권한

애플리케이션에 SQS Producer/Consumer 코드가 아직 반영되지 않았다면 큐 자체와 IAM까지만 검증할 수 있다. 전체 메시지 흐름 검증은 관련 이미지가 나온 뒤 진행한다.

16. 테스트 도메인 연결 및 E2E 확인

  • v2-dev.lookddak.com으로 V2 테스트
  • 프론트 → Spring → FastAPI → DB 흐름 확인

V2 dev 인프라 구성을 완료했다. SQS 애플리케이션 메시지 흐름을 제외한 인프라 항목을 검증했다.

테스트 도메인 https://v2-dev.lookddak.com에서 프론트 로그인 화면이 표시되는 것을 확인했다.

현재 확인된 항목

  • Next.js, Spring, FastAPI ECS Service 실행
  • Next.js와 Spring ALB Target Healthy
  • CloudWatch Logs 스트림 생성 및 애플리케이션 로그 확인
  • Spring의 V2 MySQL 연결 확인
  • FastAPI의 PostgreSQL 연결 확인
  • Spring 컨테이너에서 http://fastapi:8000 호출 성공
    • FastAPI가 HTTP 404 응답을 반환해 Service Connect를 통한 실제 통신을 확인 완료
    • / 경로가 FastAPI의 유효 API가 아니라서 404가 나온 것이며, 통신 오류는 아님
  • Spring/FastAPI Task Role의 S3·SQS 권한을 정책 및 IAM 시뮬레이션으로 확인

따라서 인프라와 서비스 간 기본 연결은 확인됐다. 실제 기능 E2E 및 SQS 메시지 흐름 검증은 관련 애플리케이션 코드가 준비된 뒤 진행한다. CI/CD 구성은 별도 담당 범위로 제외했다.

17. 최종 컷오버

  • 검증 완료 후 dev.lookddak.com을 V1 EC2에서 V2 ALB로 전환
  • 문제 발생 시 V1로 되돌릴 절차 준비

18. Crawler 후속 작업

  • 실제 이미지와 Task Definition 실행 명령이 준비된 후 Scheduler 및 RunTask 검증