콘텐츠로 이동

개발 환경

로컬 개발 환경(개발자 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 컨테이너

통합 실행 (권장)

# 백엔드(8080) + 프론트엔드(3010)를 동시에 띄운다.
./scripts/dev-local.sh

스크립트는 docker-compose.dev.yml의 MySQL / ES / Redis가 떠 있는 것을 전제한다. 처음 1회 컨테이너 부팅:

docker compose -f docker-compose.dev.yml up -d

개별 실행

# 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.krAWS 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'

인프라 컨테이너 재시작

ssh semugpt-aws 'cd /opt/semugpt-2026 && sudo docker compose -f docker-compose.dev.yml restart'

Cloudflare Tunnel 재시작 (502가 cloudflared 측일 때)

ssh semugpt-aws 'sudo systemctl restart cloudflared'

Lightsail VM 재부팅 (마지막 수단)

aws --profile ob lightsail reboot-instance --instance-name semugpt-backend

디스크 정리 (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:

--es-url 'http://elastic:uiti0701%21@localhost:9200'

알려진 한계 / 개선 예정

  • CI/CD 부재: develop push가 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 — 인프라 이원화로 장애 조사 시 양쪽 모두 확인 필요
  • 자동 로그인 계정이 정정일 — 본인 환경에서 권한/소유권 테스트할 때 혼동 주의

관련 문서