0. Contents
1. Overview
번호를 누르면 해당 절로 이동하고, 리소스 칸을 누르면 설명이 열린다
설계 요약
- Serving.01 의 외부 개발자용 API 계획을 접었다
- 대신 사용자의 GPU 클러스터에 Helm 으로 설치하는 애드온을 만든다
- 애드온은 관측, 측정, 서빙의 3층이고 층마다 필요한 권한이 다르다
- 우리 클러스터
kukie-gpu는 상품이 아니라 먼저 설치해 검증하는 곳이다
| 번호 | 흐름 | 상태 |
|---|---|---|
| 1 | 관리자가 helm install 로 필요한 층을 켠다 | 구현 |
| 2 | Kukie 가 읽기 권한으로 지표와 상태를 가져온다 | 1층 구현 |
| 3 | 앱의 요청이 kukie-counter 를 거쳐 vLLM 으로 간다 | 구현 전 |
| 4 | 측정이 만든 기준선으로 429 를 판단한다 | 구현 전 |
2. Background — 외부 API 계획의 중단
계획: 외부 개발자용 LLM API
- 오픈소스 LLM 을 우리 GPU 에 올린다
- 외부 개발자가 API 키로 쓰게 한다
- 1단계로 L40S 한 장의 동시 처리량까지 쟀다
품질 격차
Artificial Analysis Intelligence Index 로 비교했다(2026-11-03 조회).
| 모델 | 지수 | 비고 |
|---|---|---|
| Claude Sonnet 5.5 (max) | 54 | 상용 |
| Claude Sonnet 5.5 (medium) | 30 | 상용, 노력 수준을 낮춘 설정 |
| GLM-5.3-Flash | 28 | 300B 대 파라미터, 큰 GPU 두 장 필요 |
| Qwen3.8-27B | 20 | L40S 한 장에 들어가는 쪽 |
| gpt-oss-20b | 15 (이전 지수) | Serving.01 에서 동시 32 |
- 한 장에 들어가며 빠른 모델일수록 지수가 낮다
- 무료로 열어도 개발자가 상용 모델 대신 쓸 이유가 약하다
비용
- 상시 GPU 1대(g6e.xlarge)는 월 약 240만 원이다
- 사용자가 없어도 전부 우리 몫이다
3. Direction — 사용자 클러스터 애드온
제품을 바꾼 근거는 품질 지수와 상시 비용이다. 지수는 Artificial Analysis Intelligence Index(2026-11-03 조회)
바뀐 전제
- Kukie 는 사용자 클러스터를 대신 관리하는 서비스이고, 외부 API 는 별도 사업에 가까웠다
- Kukie 는 이미 사용자 클러스터의 GPU 지표를 읽는 화면을 갖고 있다
- 회사는 성능보다 데이터 통제 때문에 자기 GPU 에 모델을 올리기도 한다. 그 경우 품질 격차가 곧바로 탈락 사유가 되지 않는다
우리 클러스터 kukie-gpu 의 역할
- 애드온을 먼저 설치해 깨지는 곳을 찾는다
- 올려 본 모델과 이미지, 옵션 조합을 카탈로그로 쌓는다
유지한 기술 목표
- 라우팅, 어드미션 컨트롤, 오토스케일링을 쿠버네티스 위에서 직접 구현한다
- 빠진 것은 외부 고객 대상의 과금, 공개 도메인, 약관이다
4. Layers — 권한 기준의 3층 구조
1층과 3층의 권한은 kukie-gpu 클러스터의 ClusterRole 에서 읽은 실제 값이다
층을 권한으로 나눈 이유
- 회사 클러스터에 쓰기 권한을 요구하면 설치 승인부터 어렵다
- 읽기만 허용하는 곳도 1층은 쓸 수 있다
- 층마다 따로 켜므로 필요해질 때 권한을 늘린다
2층을 사람이 누르는 이유
- 측정은 GPU 를 가득 채운다
- 남의 운영 GPU 에서 설치 직후 자동으로 돌면 사고다
- 시간 상한도 함께 둘 계획이다
노드 자동 꺼짐이 애드온에 없는 이유
- 우리 클러스터는 켠 지 2시간이 지난 GPU 노드를 자동으로 내린다
- 사용자 클러스터에서 같은 동작은 남의 작업을 죽일 수 있어 넣지 않았다
5. Helm Chart — 설치 구조와 책임 경계
Helm 을 고른 이유: 환경마다 다른 값
- GPU 노드 레이블, namespace, 장치 플러그인 유무가 클러스터마다 다르다
- 오퍼레이터가 자기 자신을 설치하는 방식은 설치 전 단계의 문제를 해결하지 못한다
차트 하나에 층별 스위치를 두어, 설치 명령은 같고 켜는 범위만 달라진다
CRD 를 crds/ 폴더에 두지 않은 이유
- Helm 은
crds/의 CRD 를 업그레이드하지 않고, 값으로 끌 수도 없다 - 그래서 템플릿에 두고
oven.enabled로 켠다 - 템플릿의 CRD 는 Go 코드에서 생성한 파일의 복사본이다. CI 가 원본과 다르면 실패시킨다
preflight: 환경 문제를 체크리스트로
설치 직후 hook Job 이 한 번 돌아 결과를 ConfigMap 에 남긴다. 검사가 실패해도 설치를 막지 않는다.
| 검사 | 통과 기준 | 실패할 때 할 일 |
|---|---|---|
gpu | 노드에 nvidia.com/gpu 가 있다 | 노드 켜기, 장치 플러그인 |
driver | DCGM 지표에 드라이버 버전이 있다 | DCGM Pod 와 이미지 확인 |
huggingface | API 가 10초 안에 200 을 준다 | 프록시 또는 다른 모델 출처 |
storage | 클래스와 CSI 드라이버가 있고 테스트 PVC 가 묶인다 | 스토리지 클래스 지정 |
permission | 지정한 ServiceAccount 로 리소스를 만들 수 있다 | RBAC 범위 확인 |
책임 경계
| 구분 | 책임 |
|---|---|
| 카탈로그 모델 | 우리. kukie-gpu 에서 돌려 본 조합 |
| 직접 지정한 모델 | 사용자. 안 뜬 이유는 우리가 status 로 보여 준다 |
| 드라이버, 스토리지, 네트워크 | 사용자. preflight 가 빠진 것을 알려 준다 |
스토리지 드라이버를 대신 설치하지 않는 이유는 클라우드마다 다르고 IAM 권한이 필요해 차트의 범위를 넘기 때문이다.
6. Measurement Loop — 측정, 기준선, 429
그래프 모양은 동작을 설명하는 예시이고 측정값이 아니다
기준선을 설치마다 재는 이유
- Serving.01 에서 같은 L40S 라도 모델에 따라 최대 동시 요청이 4 에서 32 까지 갈렸다
- 하드웨어와 모델이 달라지면 우리 값을 그대로 쓸 수 없다
- 측정 전에는 보수적인 기본값을 쓴다
판단에 쓰는 값: 대기 요청 수와 KV 캐시
- 둘 다 vLLM
/metrics로 읽는다 - GPU 메모리 사용량은 vLLM 이 미리 잡아 늘 높으므로 한계를 구분하지 못한다
여유 폭(hysteresis)
- 닫는 값과 여는 값을 다르게 둔다
- 같은 값을 쓰면 경계에서 429 가 켜졌다 꺼지기를 반복한다
한계: 합성 부하
- 입력 1,000 · 출력 200토큰 기준의 값이라 실제 트래픽과 다르다
- 운영 지표로 기준선을 갱신하는 것은 그 뒤의 일이다
7. Build or Adopt — 직접 구현과 기존 도구
기준은 하나다. 흔한 것은 가져다 쓰고, 측정과 어드미션처럼 우리 것만 직접 만든다
KubeAI: 모델 수명 주기 안에 넣을 기능
KubeAI 는 Apache 2.0 으로 유지되는 프로젝트다. 비교는 문서와 기능 기준이고, 직접 설치해 돌려 보지는 않았다.
| 기능 | KubeAI | kukie-oven |
|---|---|---|
| CRD 로 모델 선언, 파일 받기, vLLM 실행 | 있음 | 있음 |
| OpenAI 호환 프록시 | 있음 | 없음(kukie-counter 몫) |
| 요청에 따른 자동 확장, 0대까지 내리기 | 있음 | 없음 |
| vLLM 외 엔진 | 있음 | 없음 |
| GPU 상태 기준 429 | 없음 | 없음(구현 전) |
- 모델을 띄우는 일만 보면 KubeAI 로도 충분하다
- 직접 구현한 이유는 우리가 넣으려는 기능이 모델의 수명 주기 안쪽에 있기 때문이다
- 측정은 모델이 뜬 다음 단계이고, 그 결과가 같은 모델의 429 기준이 되며, 실패 원인은 그 모델의 상태로 보여야 한다
| 넣으려는 기능 | ServedModel 에서의 자리 | KubeAI 위에 얹을 때 |
|---|---|---|
| 측정 실행과 결과 | phase Measuring, status.baseline | 남의 CRD 라 별도 객체에 상태가 나뉜다 |
| 모델별 어드미션 기준 | spec.admission | 별도 객체를 모델과 짝지어 관리한다 |
| 실패 원인과 힌트 | status.reason, status.message | 별도 컨트롤러가 해석해 다른 곳에 쓴다 |
| Kukie 화면 연동 | ServedModel 하나를 읽는다 | 두 객체를 합쳐 읽는다 |
- 표의 필드는 CRD 에 있고, 측정과 어드미션의 동작은 구현 전이다. 실패 원인은 지금 쿠버네티스 문구를 그대로 보여 준다
- 대가: 자동 확장과 0대까지 내리기가 없고 유지보수를 우리가 맡는다
- 다시 볼 조건: 이 두 기능이 측정과 어드미션보다 먼저 필요해질 때 KubeAI 전환을 검토한다
LiteLLM: 필요한 기능과의 대조
LiteLLM 은 여러 제공사의 API 를 하나로 묶는 도구다.
| 필요한 것 | LiteLLM | 직접 구현 |
|---|---|---|
| GPU 상태 기준 429 | 없음 | 어차피 직접 |
| 모델별 라우팅, OpenAI SDK 호환 | 있음 | 쉬움. vLLM 이 이미 같은 형식이다 |
| 키 발급과 폐기 | 있음, Postgres 필요 | 보통. Secret 에 해시만 저장 |
| 키별 토큰 집계 | 있음 | 까다로움. 스트리밍 마지막 조각을 읽어야 한다 |
| 외부 제공사 연결 | 있음 | 필요 없음 |
- 결정: LiteLLM 을 넣지 않고 Go 게이트웨이를 직접 만든다
- 다시 볼 조건: 1토큰까지 정확한 정산이나 외부 제공사 연결이 필요해질 때
- 이 결정의 전제는 측정과 429 의 연결이다. 그 연결을 만들지 않는다면 KubeAI 와 LiteLLM 을 쓰는 편이 낫다
이름: 오븐과 카운터
kukie-oven:ServedModel을 받아 모델을 띄운다kukie-counter: 요청을 받아 키를 확인하고, 줄이 길면 429 로 돌려보낸다
8. Repositories — 인프라와 애드온의 분리
독자가 다르다. 왼쪽은 우리가 main 을 적용하고, 오른쪽은 사용자가 버전을 골라 설치한다
분리한 이유
- 우리 클러스터의 Terraform 과 사용자 클러스터에 설치되는 코드는 독자가 다르다
- 애드온은 버전을 붙여 릴리스하고, 인프라는 main 을 그대로 적용한다
- 애드온 코드에 우리 AWS 계정이나 클러스터를 전제로 넣지 않는 규칙을 레포 단위로 지킬 수 있다
릴리스
- 태그
v<차트 버전>을 달면 이미지와 차트를 GHCR 에 올린다 - 차트의 values 에 방금 만든 이미지의 digest 를 채운다
- 공개 전까지 패키지는 비공개다
9. Corrections — 설계 중 고친 판단
KubeAI 유지보수 상태
- 처음에는 릴리스가 멈췄다고 판단했다
- GitHub API 로 다시 확인하니 2026-10-30 에도 릴리스가 있었다
오퍼레이터의 근거
- 처음에는 “KubeAI 에 429 와 사이드카 필드가 없다” 를 근거로 들었다
- 앞단 게이트웨이로 해결되는 문제라 근거가 약했다
- 측정, 기준선, 실패 원인이 모델 수명 주기 안에 있다는 점으로 근거를 고쳤다
LiteLLM
- 처음에는 프록시를 가져다 쓰자고 판단했다
- 필요한 기능을 표로 대조하니 핵심인 429 가 없고 Postgres 가 따라왔다
어드미션의 위치
- 설계 문서는 vLLM Pod 옆의 사이드카였고 키와 라우팅을 범위 밖으로 두었다
- API 키와 OpenAI SDK 제공이 필요해지면서 게이트웨이 하나로 합치는 안이 나왔다
- 아직 정하지 않았다. 복제본이 여러 개면 파드별 판단이 어려워지는 대가가 있어 부하 측정 뒤에 정한다
10. Current State — 구현 현황과 남은 일
| 항목 | 상태 |
|---|---|
| 1층 차트와 preflight | 구현. GHCR 에 비공개 릴리스(0.3.0). kukie-gpu 의 1층은 아직 손으로 적용한 상태다 |
kukie-oven | 구현. L40S 에서 gpt-oss-20b 를 Ready 까지 올리고 실제 요청을 확인. 사람 개입 없이 14분 11초 |
kukie-gpu 스토리지 | 적용. 모델 볼륨을 GPU 노드에 붙여 확인 |
kukie-gpu 자동 꺼짐 | 적용. 시작 시각을 앞당겨 실제 노드가 내려가는 것을 확인 |
| 허깅페이스 후보 수집 | 구현. 인기 50개 중 3개가 남았다 |
| 2층 측정 Job | 구현 전 |
kukie-counter, 429 | 구현 전. 형태 미결 |
| Kukie 화면 연동 | 지표 표시만. 모델 상태 표시는 구현 전 |
남은 일
- 어드미션 위치를 정한다
- 측정 Job 과 기준선을 만든다
kukie-counter를 만들어 루프를 잇는다
관련 문서
- Kukie Serving.01 Baseline on One L40S — 기준선의 원형이 된 수동 측정
- TTFT — 첫 토큰까지의 시간
- ITL — 토큰 사이 간격
- KV 캐시 — 요청마다 차지하는 메모리
- CRD —
ServedModel의 형식 - Sidecar — 어드미션 위치 후보
