콘텐츠로 이동

운영 배포

Production은 레거시 tax-gpt 스택(2023년 7월부터 EC2 + ALB + RDS 운영)이 실 사용자 트래픽을 받는 동안, semugpt-2026 신규 스택new.semugpt.co.kr / api-new.semugpt.co.kr 서브도메인에 soft launch하는 단계다. Hard cutover(apex/www를 신규 스택으로 전환 + 레거시 폐기)는 무기한 연기된 상태이며, 양쪽 frontend에 서로 이동하는 nav 버튼만 추가해 사용자가 opt-in으로 신버전을 시험한다.

1차 출처: docs/superpowers/specs/2026-05-03-production-deployment.md + 레포 루트 CLAUDE.md의 "Production Infrastructure" 섹션. 본 페이지는 그 핵심만 추렸으며 변경 사항은 그쪽을 먼저 갱신할 것.

토폴로지 한눈에

flowchart LR
    subgraph LEGACY["레거시 (default — 변경 없음)"]
        L_USERS["기존 사용자"]
        L_DNS["semugpt.co.kr
www / api / pro"] L_CF["CloudFront × 2"] L_S3["S3 (legacy frontend)"] L_ALB1["ALB semu-gpt-alb
(default rule)"] L_EC2["EC2 tax-gpt
t2.medium"] L_USERS --> L_DNS L_DNS --> L_CF L_CF --> L_S3 L_DNS --> L_ALB1 L_ALB1 --> L_EC2 end subgraph NEW["신규 (soft launch)"] N_USERS["opt-in 사용자"] N_DNS["new.semugpt.co.kr
api-new.semugpt.co.kr"] N_ALB["ALB semu-gpt-alb
(host-rule priority 100/110)"] N_LS["Lightsail semugpt-prod
large_3_0 8GB"] N_BE["systemd: semugpt-backend (:8080)"] N_FE["systemd: semugpt-frontend (:3000)"] N_DOCK["docker: ES + Redis"] N_USERS --> N_DNS N_DNS --> N_ALB N_ALB --> N_LS N_LS --> N_BE N_LS --> N_FE N_LS --> N_DOCK end RDS[("RDS tax-gpt instance
tax_gpt DB (legacy)
semugpt_2026 DB (new)")] L_EC2 --> RDS N_BE --> RDS

ALB(semu-gpt-alb)는 양쪽이 공유한다 — host header 기반 listener rule이 legacy/new를 분기. RDS instance도 공유, database 이름만 다르다.

1. 인프라 인벤토리 (verified 2026-05-11)

계층 리소스 식별자 / 상세
AWS account 023888247019 프로파일 alias ob
Region ap-northeast-2 (Seoul) ACM은 CloudFront용만 us-east-1
Default VPC vpc-0be51da1347eed536 CIDR 172.31.0.0/16 (legacy EC2 + ALB)
Lightsail VPC (region default) CIDR 172.26.0.0/16 (peering active ✅, pcx-0449043e7e8170851)
DNS Route 53 hosted zone semugpt.co.kr. (zone ID Z0917222THSORQW5ECCW)
TLS (ALB) ACM ap-northeast-2 *.semugpt.co.kr (와일드카드 only, apex 미포함), 만료 2026-08-13 (ff97682a-fcbf-...)
TLS (CloudFront) ACM us-east-1 semugpt.co.kr + *.semugpt.co.kr, 만료 2026-08-07
CDN (legacy) CloudFront × 2 apex: EQH9FKLG1LISZ → S3 semugpt.co.kr / www: EMX0TAZMBZFP1 → S3 semugpt-hosting
S3 (legacy) 3 buckets semugpt.co.kr, semugpt-hosting, semuchat.co.kr (2026-04-22 생성)
ALB semu-gpt-alb Internet-facing, 443 HTTPS forward + 80→443 redirect. idle_timeout = 300s
Target group (legacy) semu-gpt-instance HTTP:80, instance type, EC2 i-07aea223b0818ab0a 등록
EC2 (legacy) i-07aea223b0818ab0a "tax-gpt" Ubuntu 22.04, t2.medium, Public IP 3.39.210.101, key tax-gpt, SG tax-gpt
RDS tax-gpt MySQL 8.0.44, db.t3.micro, 20GB allocated → autoscale 1000GB. 엔드포인트 tax-gpt.cl2zydns6yrm.ap-northeast-2.rds.amazonaws.com. ⚠️ PubliclyAccessible=true
RDS databases 4개 tax_gpt (legacy, live), tax_gpt_v2 / tax-gpt-v3 (미사용), semugpt_2026 (account 324 + phone_auth 351 + membership 642 — 부분 migration 2026-05-11)
Lightsail prod (예정/진행 중) semugpt-prod, bundle large_3_0 ($44/mo, 8GB/2vCPU/160GB/5TB), ap-northeast-2a
ElastiCache (Redis) ❌ 미사용 Docker로 Lightsail box 안에서 실행
OpenSearch ❌ 미사용 Docker로 Lightsail box 안에서 실행 (dev와 동일 패턴)

2. 비용 비교

항목 현재(레거시 단독) Cutover 후 비고
EC2 t2.medium (legacy) ~$33 $0 (Phase 12에서 terminate, 무기한 연기)
Lightsail prod large_3_0 $44 aws lightsail get-bundles 검증
RDS db.t3.micro 20GB ~$13 ~$13 동일 instance, 새 database
ALB ~$22 ~$22 공유
CloudFront × 2 ~$2-5 $0 (Phase 12)
Route 53 + S3 + ACM ~$2 ~$3
Lightsail dev $24 $24 dev/prod 분리 유지
합계 ~$96-100 ~$106-116 net ≈ +$10/mo

⚠️ 스펙 초안의 $250-300은 ElastiCache + OpenSearch managed service 가정 (Docker 대체로 무산). 스펙의 ~$100은 Cloudflare Workers/Tunnel ($0) 가정 (AWS-native 전환으로 무산).

3. Soft launch 핵심 결정

결정 상태
사용자 데이터 partial migration (account/phone/membership, all FREE) ✅ 확정 — 2026-05-11 적용 (infra/sql/2026-05-11-legacy-data-migration.sql, PR #166)
Soft launch via subdomain (new + api-new) ✅ 확정 (hard cutover 무기한 연기)
ES volume tar (re-index 안 함) ✅ 확정 — Phase 5.5
Lightsail SSH key 재사용 (semugpt-aws) ✅ 확정
Cloudflare 전면 도입 ❌ 무산 — Workers Custom Domain + Tunnel 모두 zone-on-Cloudflare 필수 (Route 53 유지 결정)
Toss live API keys ⚠️ user 제공 대기
Backend secret rotation ⚠️ Hard cutover 시점에 (현재 legacy + new 동일 secret)
RDS SG 0.0.0.0/0:3306 잠그기 ⚠️ Hard cutover 후
Manual schema migration ⚠️ Flyway 없음 — infra/sql/2026-05-11-prod-init.sql 수동 적용

⚠️ Cloudflare Tunnel은 prod 도메인(Route 53)에 사용 불가. dev URL(*.bootalk.co.kr)은 Cloudflare zone이라 Tunnel/Workers 가능하지만, prod(semugpt.co.kr)는 Route 53이라 동일 패턴 적용 불가. ALB + Lightsail로 AWS-native 운영.

4. Pre-cutover 코드 수정 체크리스트

새 prod 배포가 동작하려면 다음 변경이 main에 먼저 머지되어야 한다. (Section 4.5 — spec 참조)

# 변경 파일 상태
4.5a CORS allowed origins에 semugpt.co.kr + www.semugpt.co.kr 추가 SecurityConfig.kt:200-204 ✅ PR #162
4.5b Langfuse prod 활성화 (env-var refs) application-prod.yml ✅ PR #162
4.5c DB URL tax_gptsemugpt_2026 + Hikari pool 10 application-prod.yml:11-16 ✅ PR #162
4.5d sentry.client.config.ts 추가 apps/frontend/ ✅ PR #162
4.5e 옛 Pages-Router deploy 스크립트 제거 apps/frontend/package.json ✅ PR #162
4.5f CORS에 https://new.semugpt.co.kr 추가 (soft launch) SecurityConfig.kt:203 ✅ verified (PR #151 4.5f, line 203에 존재)
4.5g 양쪽 frontend nav 버튼 (legacy → 신버전, new → 기본 링크) semugpt-frontend + apps/frontend/ ⏳ Phase 10.5 pending

5. Pre-cutover 데이터 복사 절차

MySQL (부분 migration — 완료)

account 324건 + phone_auth 351건 + membership 642건이 tax_gptsemugpt_2026으로 복사됐다 (2026-05-11 13:11 KST, PR #166). 전 사용자 FREE membership으로 변환. Coupon / payment / payment_method / chat history는 skip — 양쪽 DB 독립 진행.

스크립트: infra/sql/2026-05-11-legacy-data-migration.sql.

⚠️ Hard cutover 시점에 추가 migration 정책 재결정 필요 (예: legacy 결제 사용자에게 어떤 보상을 줄지).

ES (volume tar — Phase 5.5)

OpenAI 임베딩 재계산을 피하려면 dev semugpt-2026_es-data Docker volume을 prod box에 tar로 복사. ES 8.17.0 + analysis-nori plugin + UID 1000 매핑이 dev/prod 동일하다는 점이 검증됨.

# Dev에서 ES 정지 → tar → 즉시 재기동 (downtime ~5분)
ssh semugpt-aws 'cd /opt/semugpt-2026 && sudo docker compose -f docker-compose.dev.yml stop elasticsearch'
ssh semugpt-aws 'sudo tar czf /tmp/es-data-$(date +%Y%m%d).tar.gz -C /var/lib/docker/volumes/semugpt-2026_es-data _data'
ssh semugpt-aws 'cd /opt/semugpt-2026 && sudo docker compose -f docker-compose.dev.yml start elasticsearch'

# 전송 (~7GB compressed)
scp semugpt-aws:/tmp/es-data-*.tar.gz /tmp/
scp /tmp/es-data-*.tar.gz semugpt-prod:/tmp/

# Prod에서 확장 + 권한 fix
ssh semugpt-prod 'sudo docker compose -f /opt/semugpt-2026/docker-compose.dev.yml stop elasticsearch'
ssh semugpt-prod 'sudo rm -rf /var/lib/docker/volumes/semugpt-2026_es-data/_data/*'
ssh semugpt-prod 'sudo tar xzf /tmp/es-data-*.tar.gz -C /var/lib/docker/volumes/semugpt-2026_es-data/'
ssh semugpt-prod 'sudo chown -R 1000:0 /var/lib/docker/volumes/semugpt-2026_es-data/_data/'
ssh semugpt-prod 'cd /opt/semugpt-2026 && sudo docker compose -f docker-compose.dev.yml start elasticsearch'

⚠️ Fallback(volume copy 실패 시): uv run semugpt-index bulk --index ... --embed 재인덱싱 — 6-24시간.

Redis (cold start)

캐시 only이므로 복사하지 않는다. 기동 후 자연스럽게 채워짐.

6. Phase 진행 상태 (Issue #151)

Phase 작업 상태
0 Lightsail VPC peering ✅ done (pcx-0449043e7e8170851 active)
1 Schema prep — infra/sql/2026-05-11-prod-init.sql 생성 ✅ done
2 semugpt_2026 DB 생성 + 스키마 apply ✅ done
3 Lightsail prod 프로비저닝 ⏳ 진행 중
4 ACM cert 재발급 (apex 포함) DEFERRED — soft launch는 wildcard로 충분
5 Backend systemd 배포
5.5 ES volume tar 복사
6 Frontend systemd 배포 (--keepAliveTimeout 360000)
7 ALB Target Groups 생성 + Lightsail private IP 등록
8 ALB host rule api-new (priority 100)
9 ALB host rule new (priority 110)
10 Route 53 alias new + api-new
10.5 양쪽 frontend nav 버튼 (4.5g)
11 검증 + 보안 hardening (secret rotation, RDS SG 잠금)
12 레거시 폐기 DEFERRED indefinitely

상세 명령은 spec의 해당 Phase 섹션 참조.

7. 결정 트리: 진단

flowchart TD
    A["api.semugpt.co.kr 503/502"] --> B{"레거시?
(host=api)"} B -->|"yes"| C["EC2 i-07aea223b0818ab0a
+ legacy TG semu-gpt-instance"] C --> D["aws elbv2 describe-target-health
--target-group-arn ..."] A2["api-new.semugpt.co.kr 503/502"] --> E{"신규?
(host=api-new)"} E -->|"yes"| F["Lightsail semugpt-prod
+ TG-backend-2026"] F --> G["ssh semugpt-prod
sudo systemctl status semugpt-backend"] G --> H{"backend UP?"} H -->|"no"| I["sudo systemctl restart semugpt-backend"] H -->|"yes"| J["aws elbv2 describe-target-health
(VPC peering / private IP 확인)"]

8. 자주 쓰는 진단 명령

# 외부 헬스 체크 (인증 불필요)
curl -s https://api.semugpt.co.kr/actuator/health      # legacy
curl -s https://api-new.semugpt.co.kr/actuator/health  # new (DNS 추가 후)

# SSO 토큰 갱신
aws sso login --profile ob

# Legacy EC2 상태
aws --profile ob ec2 describe-instances --instance-ids i-07aea223b0818ab0a \
  --query 'Reservations[0].Instances[0].{State:State.Name,IP:PublicIpAddress}' --output table

# ALB target health (legacy TG)
aws --profile ob elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:023888247019:targetgroup/semu-gpt-instance/c290ed2d2bcca56a

# RDS 상태 + storage
aws --profile ob rds describe-db-instances --db-instance-identifier tax-gpt \
  --query 'DBInstances[0].{Status:DBInstanceStatus,Public:PubliclyAccessible,Storage:AllocatedStorage,MaxStorage:MaxAllocatedStorage}' --output table

# RDS 직접 접속 (현재 0.0.0.0/0 개방)
MYSQL_PWD='selim3400!!' mysql -h tax-gpt.cl2zydns6yrm.ap-northeast-2.rds.amazonaws.com -u admin

# DNS 레코드 조회
aws --profile ob route53 list-resource-record-sets --hosted-zone-id Z0917222THSORQW5ECCW

# Lightsail VPC peering 상태
aws --profile ob ec2 describe-vpc-peering-connections \
  --filters "Name=accepter-vpc-info.vpc-id,Values=vpc-0be51da1347eed536"

9. ⚠️ Destructive 명령 가드레일

다음 명령은 사용자 명시 승인 없이 절대 실행 금지:

명령 영향
aws ec2 terminate-instances --instance-ids i-07aea223b0818ab0a 레거시 EC2 영구 삭제 — Phase 12 무기한 연기 상태
aws cloudfront delete-distribution --id EQH9FKLG1LISZ apex CloudFront 삭제 — 레거시 frontend 다운
aws s3 rb s3://semugpt.co.kr --force S3 버킷 비우기 + 삭제
aws elbv2 delete-load-balancer --load-balancer-arn ... ALB 삭제 — 양쪽 스택 모두 다운
aws rds delete-db-instance --db-instance-identifier tax-gpt --skip-final-snapshot RDS instance 삭제 — 전 데이터 손실
aws route53 change-resource-record-sets ... Action: DELETE (legacy records) apex/www/api/pro DNS 제거 — 레거시 다운

레거시 폐기(Phase 12)는 무기한 연기되어 있으며, 재개 시 spec과 별도 stakeholder 결정이 필요하다.

10. 검증된 사실 (2026-05-11 audit)

  • ✅ Legacy tax_gpt DB는 실 사용자 트래픽 받는 중: 최근 30일간 ~11명 신규 가입, 마지막 활동 2026-05-11 09:00.
  • ⚠️ RDS Security Group sg-09b20a06663fcfa010.0.0.0/0:3306을 인터넷에 개방 — 보안 우려. Hard cutover 후 잠글 예정.
  • ✅ RDS max_connections=60, 현재 ~13 사용 중. 신규 backend Hikari pool 10으로 설정됨.
  • ✅ RDS storage: 20 GB allocated, 17 GB free. Autoscaling to 1000 GB enabled.
  • ✅ Cloudflare Workers/Tunnel 둘 다 zone-on-Cloudflare 필수 — Route 53 유지 결정으로 둘 다 사용 불가.
  • ✅ ALB target type ip172.16.0.0/12 CIDR을 VPC peering 경유로 지원 — Lightsail의 172.26.x.x 포함.
  • ✅ 기존 ACM cert는 와일드카드 only(apex 미포함) — soft launch에는 충분, hard cutover 시 재발급 필요.
  • ✅ Lightsail large_3_0 실제 가격 = $44/mo (spec 초안 $40은 IPv6 변종).
  • ✅ ALB idle_timeout 이미 300s (verified).
  • AsyncConfig.kt 이미 bounded ThreadPoolTaskExecutor 적용 (Issue #148 carryover 해결).

알려진 한계 / 개선 예정

  • Flyway 부재 — schema migration이 수동 (infra/sql/*.sql). 지난 15개 마이그레이션 동일 — 외부 도구로 수동 적용되는 듯
  • Toss webhook URL 미등록 — Toss console에서 https://api-new.semugpt.co.kr/... 등록 필요 (외부 dashboard 작업)
  • Backend Sentry 미설치 — frontend는 있으나 backend는 없음. 모니터링 갭, blocker는 아님
  • Lightsail SPOF — backend + frontend가 단일 box. 레거시도 동일 패턴이라 회귀 아님
  • intent-router.unified.enabled prod 미설정 — 기본값 동작 확인 필요
  • Phase 12 무기한 연기 — 레거시 + 신규 동시 운영 중. 비용 ~$10/mo 증가는 의도된 trade-off

관련 문서

  • 개발 환경 — dev 운영 및 ES tar 추출 절차의 dev 측면
  • 시크릿 관리 — pre-cutover에 필요한 secret(Toss live keys, JWT 회전 등)
  • 복구 절차 — 503/디스크 풀/인증서 만료 등 시나리오별 대응
  • 1차 출처: docs/superpowers/specs/2026-05-03-production-deployment.md (레포 내 markdown, mkdocs nav 외부)
  • 1차 출처: 레포 루트 CLAUDE.md의 "Production Infrastructure" 섹션
  • GitHub Issue #151 — 진행 상태 트래킹