Clustara(Kubernetes 운영 허브)의 기동·종료·관측·백업·장애 대응 절차를 한 문서에 정리했습니다. 클러스터 등록·K8s 운영 기능은 K8s 운영 허브 가이드 를 참고하세요.
| 항목 | 값 |
|---|---|
| Go 버전 | 1.24 이상 (go.mod 기준) |
| OS | Linux / Windows / macOS |
| DB | SQLite (기본) 또는 PostgreSQL |
| 포트 | 기본 :9090 (LISTEN_ADDR 로 변경 가능) |
| 데이터 디렉토리 | ./data (SQLite + fallback ndjson) |
필수 환경변수는 단 두 가지입니다.
GATEWAY_SECRET=<openssl rand -hex 32> # provider key 암호화 키 — 운영 필수
ADMIN_TOKEN=<openssl rand -hex 32> # 어드민 API/UI 접근 토큰GATEWAY_SECRET 은 한 번 정해지면 절대 바꾸면 안 됩니다(저장된 provider key 를 복호화 못 함). 운영 전에 반드시 안전한 값으로 고정하세요.
관리자 네트워크를 제한할 때는 아래 값을 부트스트랩 기본값으로 둘 수 있습니다. 기동 후에는 런타임 설정 → 관리자 IP 허용 정책에서 재기동 없이 검증·변경할 수 있습니다.
ADMIN_IP_ALLOWLIST_ENABLED=false
ADMIN_IP_ALLOWED_CIDRS="10.20.0.0/16,203.0.113.10"
ADMIN_TRUSTED_PROXY_CIDRS="10.0.0.0/8"
ADMIN_IP_EMERGENCY_TOKEN=<openssl rand -hex 32>프록시 CIDR은 Clustara에 직접 연결되는 로드밸런서/Ingress 주소만 좁게 등록하세요. 허용 정책 활성화는 현재 접속 IP 포함 여부를 서버가 검증합니다. 비상 토큰을 사용한 요청은 admin_ip_break_glass 감사 이벤트로 남습니다.
# Windows / PowerShell
$env:UPSTREAM_API_KEY = "sk-..."
$env:GATEWAY_SECRET = "dev-only-secret"
$env:ADMIN_TOKEN = "dev-admin"
go run ./cmd/clustara# Linux / macOS
UPSTREAM_API_KEY=sk-... \
GATEWAY_SECRET=dev-only-secret \
ADMIN_TOKEN=dev-admin \
go run ./cmd/clustara기동 로그에 Clustara listening addr=:9090 database=sqlite 가 보이면 정상입니다.
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -trimpath -ldflags "-s -w" -o clustara ./cmd/clustara
./gatewaydocker build -t clustara:dev .
docker run -d --name clustara --restart=always \
-p 9090:9090 \
-v $PWD/data:/data \
-e UPSTREAM_BASE_URL=https://api.openai.com \
-e UPSTREAM_API_KEY=sk-... \
-e ADMIN_TOKEN=$(openssl rand -hex 32) \
-e GATEWAY_SECRET=$(openssl rand -hex 32) \
clustara:devdocker-compose.yml 이 함께 제공됩니다. .env 또는 셸 환경변수에 비밀값을 두고 한 줄로 띄웁니다.
export GATEWAY_VERSION=v0.1.0
export UPSTREAM_API_KEY=sk-...
export ADMIN_TOKEN=$(openssl rand -hex 32)
export GATEWAY_SECRET=$(openssl rand -hex 32)
docker compose up -d
docker compose logs -f gateway운영 망에 인터넷이 없을 때는 외부에서 릴리즈 패키지를 만들어 옮깁니다.
# 인터넷이 되는 환경에서 산출
./scripts/release.sh -v v0.1.0 -p linux/amd64
# release/clustara-v0.1.0.tar.gz, .sha256, README-offline-*.md 생성폐쇄망 서버에서:
sha256sum -c clustara-v0.1.0.tar.gz.sha256
gunzip -c clustara-v0.1.0.tar.gz | docker load
docker run -d ... clustara:v0.1.0 # 2.3 절과 동일curl -fsS http://localhost:9090/health # {"status":"ok"}
curl -fsS http://localhost:9090/ready # {"status":"ready"}
curl -fsS http://localhost:9090/metrics # Prometheus exposition부팅이 정상이면 /ready 가 200 을 반환합니다. DB 가 잠시 끊겨도 /ready 는 503, /health 는 그대로 200 입니다.
기동 직후 어드민 UI 도 확인하세요.
http://<host>:9090/admin
ADMIN_TOKEN 을 설정한 경우 UI 상단의 "관리자 토큰" 입력란에 그 값을 넣어야 데이터를 받아옵니다.
콘솔에서 Ctrl+C (SIGINT) 또는 kill -TERM <pid> (SIGTERM). Clustara는 다음을 보장합니다.
- HTTP 서버에 graceful shutdown (15초 타임아웃)
- 비동기 감사 로그 큐를 끝까지 flush — drop 카운트는
/metrics의proxy_log_events_dropped_total으로 확인 가능 - 보존 워커 / 알림 워커 stop
이상이 정상 종료입니다. kill -KILL 은 마지막 수단입니다(미flush 큐가 fallback ndjson 으로 빠집니다).
docker stop clustara # SIGTERM 후 10초 grace
docker logs clustara --tail=20
docker rm clustara # 컨테이너 제거 (이미지/데이터는 유지)docker compose stop gateway # graceful
docker compose down # 컨테이너만 제거 (볼륨 보존)종료 후 다음을 확인:
ls -lah data/ # gateway.db 가 정상 사이즈
test -s data/fallback.ndjson || true # 비정상 종료 시 fallback 에 잔여 로그 가능만약 fallback.ndjson 에 데이터가 남았다면, 다음 기동 후 설정 탭의 "Fallback 로그 재처리" 또는 POST /admin/fallback 으로 DB 에 재반영하세요. 성공한 라인은 파일에서 제거되고, 파싱 실패나 DB 재삽입 실패 라인은 그대로 남습니다.
| 메트릭 | 의미 |
|---|---|
proxy_requests_total |
누적 프록시 요청 수 |
proxy_stream_requests_total |
SSE 스트리밍 요청 수 |
proxy_upstream_errors_total |
upstream 오류 (502/504 등) |
proxy_quota_blocked_total |
쿼터로 차단된 요청 (429) |
proxy_kill_switch_blocked_total |
Kill switch 로 차단된 요청 (503) |
proxy_alerts_fired_total |
알림 규칙 발화 횟수 |
proxy_alerts_delivered_total |
webhook 전송 성공 |
proxy_llm_evaluations_total |
프로세스가 관측한 LLM evaluation 누적 수 |
proxy_llm_evaluation_failures_total |
프로세스가 관측한 실패 LLM evaluation 누적 수 |
proxy_log_queue_depth |
비동기 로그 큐 잔량 (gauge) |
proxy_log_events_dropped_total |
큐 가득 차서 drop 된 감사 로그 |
proxy_log_events_written_total |
DB 에 쓰인 감사 로그 |
proxy_request_duration_ms |
전체 요청 지연 히스토그램 |
proxy_first_chunk_duration_ms |
upstream 첫 응답 청크 지연 히스토그램 |
권장 알람: proxy_log_events_dropped_total > 0 (5분 윈도우), proxy_upstream_errors_total 의 분당 증가, proxy_log_queue_depth > 80% of LOG_QUEUE_SIZE, proxy_first_chunk_duration_ms P95 급증.
/admin/alerts 에서 Clustara 자체 알림 규칙을 설정하면 외부 모니터링 없이도 Slack/Teams/사내 웹훅으로 즉시 통보합니다. 자체 알림 지표에는 requests/errors/krw/tokens, 지연 기반 latency_p95_ms/first_chunk_p95_ms, LLM 평가 기반 llm_eval_failures/llm_eval_failure_rate, MCP/도구 기반 tool_errors/tool_error_rate/tool_loop/mcp_new_tools, 이상 탐지 anomaly_zmax, 예산 소진 예측 budget_burn_ratio(등록된 예산 중 최대 월말 예상/월 예산 비율) 가 포함됩니다. 자세한 사용법은 관리자 가이드 참조.
/admin/anomalies 는 모델별 요청당 비용·전체 지연·첫 청크 지연을 최근 윈도우(기본 1시간) 와 장기 기준선(기본 7일) 으로 비교해 z-score 가 임계(기본 3) 를 넘는 항목을 반환합니다. 대시보드의 "이상 징후" 카드에도 표시되며, anomaly_zmax 알림 지표로 임계 초과 시 통보할 수 있습니다.
curl "http://localhost:9090/admin/anomalies?baseline=7d&recent=1h&z=3"기준선 표본이 일정해도(분산 0) 평균의 5% 를 최소 노이즈로 두어 진짜 급증을 놓치지 않습니다. 최소 표본(기준선 20건, 최근 5건) 미만 모델은 노이즈 방지를 위해 제외됩니다.
어드민의 LLM 관측 탭과 API는 Datadog LLM Observability의 운영 기능을 Clustara 내부 데이터로 제공합니다.
curl "http://localhost:9090/admin/llm/traces?limit=100"
curl "http://localhost:9090/admin/llm/sessions?limit=100"
curl "http://localhost:9090/admin/llm/prompts?limit=100"
curl "http://localhost:9090/admin/llm/patterns?limit=50"
curl "http://localhost:9090/admin/llm/insights?window=24h&limit=50"
curl "http://localhost:9090/admin/llm/timeseries?window=24h&bucket=hour"
curl "http://localhost:9090/admin/llm/feedback?limit=100"
curl "http://localhost:9090/admin/llm/evaluations?limit=100"운영 권장:
- agent/chat 단위로
X-LLM-Session-ID를 넣어 세션별 비용·오류·평가 실패를 묶습니다. - 프롬프트 템플릿은
X-LLM-Prompt-Name,X-LLM-Prompt-Version또는 body의metadata._dd.ml_obs.prompt_tracking로 버전 추적합니다. - gateway-managed evaluation 실패가 많은 session/prompt/pattern을 우선 조사합니다.
- 사람이 직접 본 품질 판단은
POST /admin/llm/feedback로 남겨 운영 피드백과 자동 평가를 분리해 봅니다. - 사내 평가기나 CI가 별도 품질 점수를 계산한다면
POST /admin/llm/evaluations로 제출해 같은 trace detail에서 보이게 합니다.
- 표준 출력 (slog JSON 또는 텍스트). systemd / docker logs / 컨테이너 stdout 로 수집하세요.
- 비상시
data/fallback.ndjson— DB 쓰기가 실패하거나 비정상 종료 시 마지막 보루.
Fallback 상태 확인과 재처리:
curl http://localhost:9090/admin/fallback
curl -X POST http://localhost:9090/admin/fallback재처리 결과의 imported 는 DB 에 새로 들어간 로그, duplicates 는 이미 DB 에 있어 제거한 로그, failed / remaining 은 파일에 남겨둔 라인입니다.
자동/수동 백업은 scripts/backup.sh (또는 .ps1) 으로 수행합니다. sqlite3 가 있으면 .backup 명령으로 락 충돌 없이 일관 사본을 만듭니다.
./scripts/backup.sh -d data -o backups -k 14 # 14일 보관
# 산출: backups/gateway-20260602-1430.tar.gz크론 예시 (매일 04:00):
0 4 * * * /opt/clustara/scripts/backup.sh -d /opt/clustara/data -o /opt/clustara/backups -k 30 >> /var/log/proxy-backup.log 2>&1-
Clustara 중지
docker compose stop gateway
-
손상된
data/gateway.db를 다른 곳으로 옮기고 백업을 풉니다.mv data/gateway.db data/gateway.db.broken tar -xzf backups/gateway-YYYYMMDD-HHMM.tar.gz -C /tmp cp /tmp/data/gateway.db data/gateway.db
-
기동
docker compose up -d gateway curl -fsS http://localhost:9090/ready
GATEWAY_SECRET 이 백업 시점과 동일해야 provider key 가 복호화됩니다. 다르면 /admin/providers 의 키들이 "복호화 실패" 가 되니, 그 경우 어드민에서 키만 재입력하세요(다른 데이터는 그대로 살아 있습니다).
POSTGRES_DSN=postgres://user:pass@host:5432/db?sslmode=disable 또는 DATABASE_URL 을 설정하면 자동으로 PostgreSQL 을 사용합니다. SQLite 와 동일한 스키마가 자동 생성됩니다. 백업은 운영 중인 Postgres 의 표준 백업(pg_basebackup/pg_dump) 으로 수행하세요.
서비스 데이터의 CSI VolumeSnapshot이 readyToUse인지 확인한 뒤 서비스 상세의 복구 미리보기에서 새 PVC 이름과 원본 이상 용량을 입력합니다. Clustara는 동일 클러스터·Namespace, 서비스 유형, 기존 PVC 이름 충돌을 검사하고 dataSource.kind: VolumeSnapshot PVC를 Manifest Change Studio 승인 초안으로 생성합니다. PVC가 Bound로 수집되면 복구 원장은 성공으로 전환됩니다.
이 흐름은 기존 PVC와 워크로드를 자동 변경하지 않습니다. 클론 PVC의 데이터 검증 후 StatefulSet/Deployment 볼륨 전환을 별도 변경 요청으로 수행하고, 전환 전 원본 PVC와 스냅샷 보존 정책을 확인하세요. 타 Namespace 또는 타 클러스터 복구는 VolumeSnapshotContent 이동과 스토리지 드라이버 정책 검증이 필요하므로 현재 자동 흐름에서 차단됩니다.
Redis 백업은 별도 Bound 백업 PVC와 Kubernetes Secret의 password key를 사용합니다. 생성된 Job은 REDISCLI_AUTH 환경변수와 redis-cli --rdb로 RDB를 저장하고 비어 있지 않은 파일을 확인한 경우에만 성공합니다. Secret 원문은 Clustara DB, API, Job YAML에 포함하지 않습니다.
RDB 복구 전 서비스 상세에서 중지 요청을 생성해 Action Center 승인·실행을 완료하고 최신 인벤토리를 수집하세요. Restore Preview는 워크로드의 spec.replicas=0, 준비 Replica 0, 실행 중 Pod 없음, 백업 PVC와 대상 데이터 PVC의 Bound 상태 및 서비스 연관성을 모두 검사합니다. 복구 Job 완료 후 시작 요청을 승인하고 Redis 연결, key 수, 복제 상태, 최근 백업 원장을 검증하세요.
단독 JupyterLab의 파일 아카이브 백업은 중지 요청 승인·실행과 최신 인벤토리 수집 후 사용합니다. 작업공간 PVC는 read-only, 별도 Bound 백업 PVC는 read-write로 마운트해 .tar.gz를 생성하며 Secret 원문을 사용하지 않습니다. 실행 중 Pod가 있거나 원본·백업 PVC가 동일하면 요청이 차단됩니다.
복구 Job은 아카이브의 절대/상위 경로 이탈과 symlink, hardlink, 특수 파일을 거부하고 대상 PVC의 .clustara-restore/<작업ID>에만 해제합니다. 완료 후 staging 파일을 검증하고 필요한 파일만 작업공간으로 승격한 다음 시작 요청을 승인하세요. JupyterHub는 아래 사용자별 매핑 절차를 추가로 적용합니다.
JupyterHub 상세의 사용자 작업공간 표는 hub.jupyter.org/username과 배포/인스턴스 라벨, Notebook Pod의 PVC volume mount를 함께 사용합니다. active 사용자는 서버를 먼저 중지하고 최신 인벤토리를 수집해야 하며, conflict는 라벨 사용자와 Pod mount 소유권이 불일치하므로 수정 전 백업하지 마세요.
사용자별 백업 요청에는 사용자명, 원본 PVC, 별도 백업 PVC를 모두 입력합니다. 복구 시 백업 작업 원장에 기록된 사용자와 대상 사용자·PVC 매핑을 다시 비교하므로 다른 사용자 PVC로의 복구는 차단됩니다. staging 복구 완료 후 해당 사용자 권한으로 파일을 검증하고 필요한 파일만 승격한 다음 Notebook 서버를 시작하세요.
서비스 상세의 JupyterHub API 설정에 Hub URL과 최소 scope의 서비스 토큰을 등록하고 연결 테스트를 실행합니다. 토큰은 서비스 범위 Credential Vault에 암호화되며 화면, API 응답, 감사 로그에는 반환되지 않습니다. 토큰 교체 시 새 값만 입력하고 저장한 뒤 다시 연결 테스트를 수행하세요.
Named Server 표에서 실행 상태, 마지막 활동, 유휴 시간을 확인할 수 있습니다. 시작·중지 버튼은 실제 API를 즉시 호출하지 않고 Action Center 요청을 생성합니다. 자동 유휴 정책을 사용하면 Service reconcile이 기준을 넘긴 서버별 종료 승인 요청을 한 번만 생성합니다. 승인 실행 직전에 서버 활동을 다시 조회하며 최근 활동, pending, stopped 또는 삭제 상태이면 idle guard가 실행을 실패 처리합니다. 유휴 종료가 실패한 경우 사용자를 강제 종료하지 말고 현재 활동과 JupyterHub 이벤트를 확인한 뒤 새 요청을 생성하세요.
ServiceInstance 자동 동기화는 기본 300초 주기로 실행되며, 다중 Clustara Pod에서는 인스턴스별 DB lease가 중복 실행을 막습니다. 아래 환경변수는 초기값이고 운영 중에는 설정 → 런타임 설정 → k8s.services에서 재시작 없이 변경할 수 있습니다.
| 환경변수 | 기본값 | 설명 |
|---|---|---|
K8S_SERVICE_RECONCILE_ENABLED |
true | 자동 동기화 활성화 |
K8S_SERVICE_RECONCILE_INTERVAL_SECONDS |
300 | 인스턴스별 재평가 주기 |
K8S_SERVICE_RECONCILE_BATCH_SIZE |
100 | tick당 최대 처리 수 |
K8S_SERVICE_RECONCILE_TIMEOUT_SECONDS |
30 | 인스턴스 1건 제한 시간 |
K8S_SERVICE_INVENTORY_STALE_SECONDS |
900 | 인벤토리 신뢰 가능 최대 나이 |
K8S_SERVICE_JUPYTERHUB_IDLE_THRESHOLD_MINUTES |
60 | JupyterHub 유휴 종료 후보 기본 기준(분) |
K8S_SERVICE_JUPYTERHUB_HTTP_TIMEOUT_SECONDS |
10 | JupyterHub REST API 제한 시간(초) |
K8S_SERVICE_JUPYTERHUB_USER_LIMIT |
500 | 한 번에 조회할 최대 JupyterHub 사용자 수 |
K8S_SERVICE_HEALTH_RETENTION_DAYS |
90 | Health snapshot 보존 일수 |
오래된 행이 무한히 쌓이지 않도록 백그라운드 워커가 매 RETENTION_INTERVAL (기본 1시간) 마다 다음을 삭제합니다.
| 환경변수 | 기본값 | 대상 |
|---|---|---|
RETENTION_REQUEST_DAYS |
90 | request_logs + prompt/response/token/language/llm_evaluations/llm_feedback 자식 테이블 |
RETENTION_PROMPT_DAYS |
30 | prompt_logs |
RETENTION_RESPONSE_DAYS |
30 | response_logs |
RETENTION_INTERVAL |
1h | cleanup 워커 주기 |
값을 0 으로 두면 해당 항목은 정리하지 않습니다. 변경 후에는 Clustara 재기동이 필요합니다. 어드민 UI 설정 탭에서 "지금 정리 실행" 으로 수동 트리거할 수도 있습니다.
오작동한 사내 도구가 비용을 폭주시키는 경우 1초 안에 차단할 수 있습니다.
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"disabled":true,"reason":"릴리즈 롤백 중"}' \
http://localhost:9090/admin/kill-switch또는 어드민 UI → "안전" 탭 → "
복귀:
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"disabled":false}' \
http://localhost:9090/admin/kill-switch쿼터를 0 으로 두면 그 시점부터 모든 요청이 429 가 됩니다.
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"scope":"api_key","scope_value":"key_xxxx","period":"daily","krw_limit":1}' \
http://localhost:9090/admin/quotas또는 키 자체를 비활성화: 어드민 UI 사용자 탭 → 키 클릭 → 비활성화.
/metrics의proxy_upstream_errors_total분당 증가 확인/admin/providers에서 timeout_ms 조정- 어드민 "안전" 탭에서 모델별 알림 규칙 활성화 (
metric=errors, scope=model) - 일시적으로 다른 provider 로 라우팅: provider 의
model_patterns를 조정해 트래픽을 분기
SQLite 의 경우 디스크가 가득 차면 모든 쓰기가 실패하고 fallback.ndjson 으로 빠집니다.
df -h로 디스크 확인RETENTION_*을 줄여 임시로 강제 정리: 어드민 → 설정 → "지금 정리 실행"- 디스크가 늘었으면 어드민 설정 탭의 "Fallback 로그 재처리" 또는
POST /admin/fallback으로 누락 로그를 DB 에 반영합니다.
- 어드민 사용자 탭에서 해당 키 즉시 "비활성화"
- 어드민 설정 → 변경 이력 → 의심 시점부터 정렬 → CSV 다운로드
- 프롬프트 탭에서 해당 키 ID 로 검색 + #의심 태그 부여
GATEWAY_SECRET까지 유출되었다면 — provider key 모두 재발급 + DB 의provider_configs갱신
-
GATEWAY_SECRET을 무작위 32바이트로 고정 (개발용 기본값 사용 금지) -
ADMIN_TOKEN설정 + 운영자만 알도록 관리 - 회계/감사 부서에는
ADMIN_READONLY_TOKEN별도 발급 - HTTPS 종단은 앞단의 Nginx / Traefik / Cloud LB 에 위임 (Clustara 자체는 HTTP)
-
LOG_RAW_PROMPTS=true,LOG_RAW_BODIES=true를 켤 경우 별도 DB 암호화 / 디스크 암호화 필수 - PII 마스킹 규칙 (한국 주민번호, 카드, 휴대전화, 이메일, AWS/GitHub 토큰 등) 은 기본 활성화 — 비활성화 옵션은 없습니다
- 백업 디렉토리도 동일 수준으로 보호 (DB 사본이므로)
- Clustara의
/admin*은 사내망에서만 접근 가능하도록 ACL/방화벽으로 분리 - Webhook URL 은 외부에 노출되지 않는 사내 Slack/Teams 채널로
Q. 재기동 시 통계는 유지되나요? A. 네. SQLite/Postgres 에 모두 영구 저장되며 컨테이너만 갈아끼워도 같은 데이터 디렉토리만 마운트하면 그대로 이어집니다.
Q. Clustara가 다운되면 호출이 어떻게 되나요? A. Clustara가 다운된 동안 클라이언트는 연결 실패를 받게 됩니다. HA 가 필요하면 여러 인스턴스를 띄우고 앞단에 LB 를 두세요. 그때는 SQLite 대신 PostgreSQL 을 권장합니다.
Q. 로그가 너무 많이 쌓여요.
A. RETENTION_REQUEST_DAYS 등을 줄이거나, 어드민 "설정 → 데이터 보존 정책 → 지금 정리 실행" 을 누르세요. 백업이 있다면 더 공격적으로 줄일 수 있습니다.
Q. provider key 를 잃어버렸어요. A. 평문 키는 저장하지 않습니다. AES-GCM 으로 암호화된 형태만 보관하며 어드민에서도 노출되지 않습니다. 분실 시 vendor 측에서 새 키를 발급받아 어드민 "프로바이더" 폼에 재입력하세요.