운영 배포¶
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_gpt → semugpt_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_gpt → semugpt_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_gptDB는 실 사용자 트래픽 받는 중: 최근 30일간 ~11명 신규 가입, 마지막 활동 2026-05-11 09:00. - ⚠️ RDS Security Group
sg-09b20a06663fcfa01이0.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
ip가172.16.0.0/12CIDR을 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이미 boundedThreadPoolTaskExecutor적용 (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.enabledprod 미설정 — 기본값 동작 확인 필요- 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 — 진행 상태 트래킹