개발 환경¶
로컬 개발 환경(개발자 PC)과 공유 dev URL(semu-gpt-dev.bootalk.co.kr) 두 가지로 구성된다.
로컬은 docker-compose로 MySQL / Elasticsearch / Redis를 띄우고 Spring Boot + Next.js를 함께 실행, dev URL은 AWS Lightsail box(semugpt-backend)에서 24/7 운영되며 클라이언트 데모와 통합 테스트에 사용된다.
토폴로지 한눈에¶
flowchart LR
subgraph LOCAL["로컬 (개발자 Mac)"]
FE_L["Next.js
(:3010)"]
BE_L["Spring Boot
(:8080)"]
DC["docker-compose.dev.yml
(MySQL :3306
ES :9200
Redis :6379)"]
FE_L --> BE_L
BE_L --> DC
end
subgraph DEV["공유 dev URL"]
CF["Cloudflare Tunnel
semugpt-backend"]
LS["AWS Lightsail
semugpt-backend
(3.39.17.132)"]
BE_D["systemd: semugpt-backend
(:8080)"]
DC_D["docker compose
mysql / es / redis"]
CF --> LS
LS --> BE_D
BE_D --> DC_D
end
USER1["로컬 개발자"] --> FE_L
USER2["외부 사용자/클라이언트"] -->|HTTPS| CF_DNS["semu-gpt-dev.bootalk.co.kr"]
CF_DNS --> CF
USER3["Cloudflare Workers
semugpt-frontend-dev"] --> CF_DNS
USER4["semu-chat-dev.bootalk.co.kr"] --> USER3
1. 로컬 셋업¶
사전 요구사항¶
| 도구 | 버전 | 용도 |
|---|---|---|
| pnpm | 최신 | 프론트엔드 패키지 매니저 (yarn 아님) |
| uv | 최신 | Python 파이프라인/평가 패키지 매니저 |
| Java | 17 | 백엔드 (Gradle wrapper가 다운로드하기도 함) |
| Docker + Docker Compose plugin | 최신 | MySQL / ES / Redis 컨테이너 |
통합 실행 (권장)¶
스크립트는 docker-compose.dev.yml의 MySQL / ES / Redis가 떠 있는 것을 전제한다. 처음 1회 컨테이너 부팅:
개별 실행¶
# Backend (local 프로파일, 포트 8080)
cd apps/backend
./gradlew bootRun --args='--server.port=8080 --spring.profiles.active=local'
# Frontend (기본 포트 3010)
cd apps/frontend
pnpm install # 최초 1회
pnpm dev
설정 파일은 apps/backend/src/main/resources/application-local.yml — 로컬 MySQL(localhost:3306/tax_gpt, root/selim3400)과 로컬 ES(localhost:9200, elastic / uiti0701!)를 사용한다.
Dev 자동 로그인¶
NODE_ENV=development일 때 프론트엔드 userContext.tsx의 하드코딩된 JWT로 자동 로그인된다.
테스트 계정은 정정일(04aff73d-3eac-41f5-862c-9aa0fd12acc4, ADMIN). 본인(주우철) 계정이 아니라는 점 주의.
패키지 매니저 / 빌드 주의¶
- 프론트엔드는 pnpm 전용 — yarn 금지
- 빌드 명령은
pnpm build(Next.js 16에서 webpack 모드 자동 선택) - 타입 체크:
pnpm type-check, 테스트:pnpm test:run .env.local변경 시 dev 서버 재시작 필요
2. Dev URL 아키텍처¶
semu-gpt-dev.bootalk.co.kr은 AWS Lightsail box에서 운영되며 Mac에서 분리됐다(2026-04-27 이후).
semu-gpt-dev.bootalk.co.kr
→ Cloudflare Tunnel `semugpt-backend` (Lightsail box, systemd: cloudflared)
→ http://localhost:8080 (Spring Boot — systemd: semugpt-backend)
→ docker-compose.dev.yml (mysql / es / redis on the same box)
Frontend는 별도 인프라(Cloudflare Workers, semu-chat-dev.bootalk.co.kr)에서 호스팅되며 fetch만 dev backend로 향한다. 자세한 frontend Workers 배포 절차는 운영 배포 페이지의 "Cloudflare Workers (frontend dev)" 항목 참조 — 본 페이지는 backend Lightsail 운영에 집중한다.
Lightsail 인스턴스 메타데이터¶
| 항목 | 값 |
|---|---|
| AWS account | 023888247019 (alias ob, see ~/.aws/config) |
| Region | ap-northeast-2 (Seoul) |
| Lightsail instance | semugpt-backend (Ubuntu 24.04 / 4 GB / 80 GB SSD + 4 GB swap / x86_64) |
| Bundle | medium_3_0 |
| Static IP | 3.39.17.132 |
| SSH alias | ssh semugpt-aws (key: ~/.ssh/aws-semugpt) |
| 허용 SSH IP | 49.175.194.2/32 (사무실 only — 다른 IP는 임시 추가 필요) |
| Cloudflare Tunnel ID | 078d8083-eba8-4df2-8026-ebafcb53666b |
| 비용 | $24/mo (medium_3_0; static IP 무료(attach 중), 스냅샷 0, bandwidth quota 4 TB — aws lightsail get-bundles로 검증) |
자동 복구 매트릭스¶
| 레이어 | 시스템 | 자동 재시작 |
|---|---|---|
| Cloudflare Tunnel | Lightsail systemd: cloudflared |
✅ KeepAlive (systemd default) |
| Spring Boot Backend | Lightsail systemd: semugpt-backend |
✅ Restart=on-failure, RestartSec=30s |
| Docker infra | Docker engine | ✅ restart=unless-stopped (mysql / es / redis 전부) |
| Lightsail VM 자체 | AWS | ✅ AWS infra HA |
Lightsail box 재부팅 시: docker → backend → cloudflared 순으로 자동 복구. 사용자 개입 불필요. 단, 디스크 풀은 자동 복구되지 않는다 — 아래 "디스크 정리" 참조.
3. 진단¶
양쪽(local + dev URL)을 한 줄로 확인.
curl -s -o /dev/null -w "BACKEND %{http_code} (%{time_total}s)\n" --max-time 10 https://semu-gpt-dev.bootalk.co.kr/actuator/health
curl -s -o /dev/null -w "FRONTEND %{http_code} (%{time_total}s)\n" --max-time 10 https://semu-chat-dev.bootalk.co.kr/
둘 다 200이면 정상.
503 / 502 결정 트리¶
flowchart TD
A["actuator/health 503"] --> B{"ssh semugpt-aws
df -h /"}
B -->|"Use% 100%"| C["디스크 풀
→ 디스크 정리 runbook 실행"]
B -->|"여유 있음"| D{"systemctl status
semugpt-backend"}
D -->|"failed"| E["sudo systemctl restart semugpt-backend
+ journalctl로 원인 확인"]
D -->|"active"| F{"docker compose ps"}
F -->|"mysql/es/redis 중
unhealthy"| G["docker compose restart
해당 서비스"]
F -->|"모두 healthy"| H["cloudflared 측
502 가능성
→ sudo systemctl restart cloudflared"]
세부 진단 명령:
ssh semugpt-aws 'sudo systemctl status semugpt-backend cloudflared; sudo docker compose -f /opt/semugpt-2026/docker-compose.dev.yml ps'
4. 복구 절차¶
백엔드만 재시작 (가장 흔한 경우)¶
ssh semugpt-aws 'sudo systemctl restart semugpt-backend'
sleep 5
ssh semugpt-aws 'curl -s http://localhost:8080/actuator/health'
인프라 컨테이너 재시작¶
Cloudflare Tunnel 재시작 (502가 cloudflared 측일 때)¶
Lightsail VM 재부팅 (마지막 수단)¶
디스크 정리 (Use% ≥90%)¶
가장 흔한 culprit은 /var/log/syslog.1(p6spy SQL 로그 spam)과 ~/.gradle(빌드 캐시). 2026-05-04에 syslog.1이 35GB까지 차서 503 발생 → systemd drop-in quiet-logs.conf로 영구 fix됨. 그 외 원인으로 풀 시:
ssh semugpt-aws '
sudo truncate -s 0 /var/log/syslog.1
sudo find /home/ubuntu/.gradle/caches -mindepth 1 -delete 2>/dev/null
sudo docker system prune -af --filter "until=72h"
df -h /
sudo systemctl restart semugpt-backend
'
⚠️ ~/.gradle/wrapper는 보존할 것 — 안 그러면 재시작 시 Gradle wrapper 재다운로드(~5-10분 추가).
5. 코드 배포 (develop 브랜치)¶
develop에 push 된 코드를 dev에 반영:
ssh semugpt-aws 'cd /opt/semugpt-2026 && git fetch && git checkout develop && git pull && sudo systemctl restart semugpt-backend'
sleep 90 # 컴파일 + 시작 60-90초
curl -s https://semu-gpt-dev.bootalk.co.kr/actuator/health
⚠️ CI/CD 자동화는 별도 이슈에서 처리 예정. 현재는 수동.
Lightsail-only 로컬 수정 사항 (git에 커밋 안 됨)¶
/opt/semugpt-2026/docker-compose.dev.yml에 두 곳 수정이 있다(4 GB 박스에 맞춤):
- ES_JAVA_OPTS=-Xms1g -Xmx1g # 원본 2g/2g → 1g/1g (4GB 박스에서 2g면 OOM)
- reindex.remote.whitelist=172.17.0.1:9202 # 원본 34.50.1.57:9200 (폐기된 GCP VM)
git pull 시 conflict 발생 가능 — Lightsail 측 수정을 우선해 머지.
6. 적극 백엔드 개발 (Lightsail 정지 후 로컬 실행)¶
코드를 빠르게 반복 테스트하려면 Lightsail backend를 잠시 정지하고 로컬 backend로 dev URL을 받지 않게 한다.
# Lightsail backend 정지
ssh semugpt-aws 'sudo systemctl stop semugpt-backend'
# 로컬 backend 실행
cd apps/backend
./gradlew bootRun --args='--spring.profiles.active=local'
# 작업 끝나면 복귀
ssh semugpt-aws 'sudo systemctl start semugpt-backend'
⚠️ Lightsail backend가 정지되어 있는 동안 외부에서 semu-gpt-dev.bootalk.co.kr 접속 시 502가 보인다. 클라이언트 데모 시간을 피해 작업.
7. 다른 IP에서 SSH 접속¶
기본 firewall은 사무실(49.175.194.2/32)만 허용한다. 집·외부 카페에서 접속하려면 임시 추가:
MY_IP=$(curl -s ifconfig.me)
# 사무실 룰 + 본인 임시 IP 동시 허용
aws --profile ob lightsail put-instance-public-ports --instance-name semugpt-backend \
--port-infos "fromPort=22,toPort=22,protocol=tcp,cidrs=49.175.194.2/32,${MY_IP}/32"
# 작업 끝나면 사무실만 남기기
aws --profile ob lightsail put-instance-public-ports --instance-name semugpt-backend \
--port-infos "fromPort=22,toPort=22,protocol=tcp,cidrs=49.175.194.2/32"
8. 모니터링¶
Lightsail box에 디스크/헬스 모니터링과 Slack 알림이 2026-05-04부터 설치되어 있다.
| 위치 | 역할 |
|---|---|
/etc/systemd/system/semugpt-backend.service.d/quiet-logs.conf |
LOGGING_LEVEL_P6SPY=WARN + LOGGING_LEVEL_ORG_HIBERNATE_SQL=WARN (p6spy 로그 spam 영구 fix). 로컬 Mac에는 적용 안 됨 — 로컬은 SQL DEBUG 그대로. |
/usr/local/bin/disk-alarm.sh + /etc/cron.hourly/disk-alarm |
매 시간 root fs 사용률 체크. ≥80%면 Slack 알림 (state transition만). |
/usr/local/bin/health-monitor.sh + /etc/cron.d/semugpt-health |
5분마다 localhost:8080/actuator/health 체크. UP↔DOWN 전환 시에만 Slack 알림. |
/etc/semugpt/slack-webhook.url |
Slack incoming webhook URL (mode 0600, root-only, git 미커밋) |
/etc/semugpt/slack-mention.txt |
알림 prefix mention (현재 <!here> @주우철) |
상태 확인:
ssh semugpt-aws 'sudo journalctl -t disk-alarm -t health-monitor --since "24 hours ago" --no-pager | tail -20'
ssh semugpt-aws 'cat /var/log/DISK_ALARM 2>/dev/null; cat /var/lib/semugpt/last-health-state'
상세 정책 및 알림 운영은 모니터링 페이지 참조.
9. ES password 주의 (! 문자)¶
Dev ES password uiti0701!의 ! 때문에 shell parsing이 자주 깨진다. echo -n과 Python b"..." 모두 escape 시퀀스로 변환된다. 유일한 안전 방법은 printf:
# base64 토큰 생성
printf 'elastic:uiti0701!' | base64
# 결과: ZWxhc3RpYzp1aXRpMDcwMSE=
# curl 사용
curl -H 'Authorization: Basic ZWxhc3RpYzp1aXRpMDcwMSE=' 'http://localhost:9200/...'
Python ES client / CLI --es-url에서는 !를 %21로 URL-encode:
알려진 한계 / 개선 예정¶
- CI/CD 부재:
developpush가 dev URL에 자동 반영되지 않음 — 수동 ssh + git pull 필요 - 백업 부재: Lightsail 스냅샷 미설정. RDS 차원의 PITR도 dev에는 없음 (dev DB는 휘발성 docker volume)
- dev DB는 docker volume — Lightsail box rebuild 시 데이터 손실. 재인덱싱 6-24시간 비용
- dev frontend는 Cloudflare Workers, backend는 Lightsail — 인프라 이원화로 장애 조사 시 양쪽 모두 확인 필요
- 자동 로그인 계정이 정정일 — 본인 환경에서 권한/소유권 테스트할 때 혼동 주의