망가진 MLOps 플랫폼을 되살렸다 — 이제 셀프서비스·정책 보호·운영 신뢰성 확보

발행: (2026년 5월 23일 AM 09:42 GMT+9)
10 분 소요
원문: Dev.to

출처: Dev.to

GitHub Copilot 챌린지에 제출 — 마감일 2026년 6월 7일. GitHub Copilot을 능동적인 아키텍처 및 디버깅 파트너로 활용해 제작했습니다.
4월 4일, 나는 이 플랫폼을 포기했습니다. Backstage가 충돌했고, ArgoCD는 망가졌으며, KServe는 모델 하나도 제공하지 못했습니다. 나는 떠났고, 플랫폼은 죽은 채로 남겨두었습니다.
48일 뒤, 나는 돌아와서 이를 GitOps, 정책 적용, 그리고 결정론적 복구가 가능한 셀프 서비스 AI 추론 플랫폼으로 재구축했습니다.
21개의 체크, 실패 0회. 어떤 머신에서도 재현 가능합니다.

Repo: github.com/sodiq-code/neuroscale-platform

이는 주장에 불과합니다. 아래 영상은 실제 k3d 클러스터에 대해 모든 체크가 실시간으로 실행되는 모습을 보여줍니다.
▶ 전체 스모크 테스트 데모 보기 — 21 체크, 0 실패

━━━ Milestone A — GitOps Spine (ArgoCD) ━━━
[✓ PASS] 모든 ArgoCD pod이 Running 상태
[✓ PASS] ArgoCD Applications: 정상 7개, 진행 중 0개, 총 7개
[✓ PASS] ArgoCD 동기화 가시성: Unknown 상태 없음 (7/7 Synced)
[✓ PASS] Drift self‑heal: nginx‑test가 재생성되어 약 20초 만에 Ready 상태

━━━ Milestone B — AI Serving Baseline (KServe) ━━━
[✓ PASS] KServe controller‑manager: 복제본 1개 사용 가능
[✓ PASS] InferenceServices: 2/2 Ready=True
[✓ PASS] 추론 요청: demo‑iris‑2가 예측을 반환함
↳ Response: {“predictions”:[1,1]}

━━━ Milestone C — Golden Path (Backstage) ━━━
[✓ PASS] Backstage 배포: 복제본 1개 사용 가능
[✓ PASS] demo‑iris‑2 InferenceService 존재 (scaffolder 출력)
[✓ PASS] demo‑iris‑2 ArgoCD Application 존재 (ApplicationSet 출력)

━━━ Milestone D — Guardrails (Kyverno + CI) ━━━
[✓ PASS] Kyverno pod 실행 중: 3개
[✓ PASS] Kyverno ClusterPolicies 설치: 정책 5개
[✓ PASS] Admission 차단: 비준수 InferenceService가 올바르게 거부됨

━━━ Milestone F — Production Hardening ━━━
[✓ PASS] ApplicationSet neuroscale‑model‑endpoints 존재
[✓ PASS] ArgoCD에 Application 7개 존재 (ApplicationSet + static)
[✓ PASS] default 네임스페이스에 ResourceQuota 존재
[✓ PASS] default 네임스페이스에 LimitRange 존재
[✓ PASS] 비루트 admission 차단: root‑container Deployment 거부
[✓ PASS] OpenCost 배포 정상: 복제본 1개 사용 가능

PASS 21 / FAIL 0 / SKIP 1
✓ 모든 체크 통과. 플랫폼이 정상이며 데모 준비 완료.

단일 SKIP은 drift self‑heal 전제조건 체크입니다 — 이전 테스트 실행 후 정상적인 상황입니다. drift self‑heal 자체는 위 출력과 영상에서 확인할 수 있듯 통과했습니다.
어떤 머신에서도 재현 가능:

bash scripts/bootstrap.sh     # 약 5분 — Docker + k3d 필요
bash scripts/smoke-test.sh    # 21 체크, 모두 초록색

NeuroScale는 2026년 2월에 Kubernetes 기반 AI 추론 플랫폼으로 시작되었습니다. 4월 초에 포기되었고 — Backstage 충돌, ArgoCD 파손, KServe가 모델 하나도 제공하지 못함. 이 챌린지 직전 마지막 커밋은 4월 4일이었습니다. 그 뒤 48일간 침묵이 이어졌습니다.
내가 돌아왔을 때 발견한 내용:

  • Backstage: CrashLoopBackOff — 재시작 14회. Helm values 중첩 버그로 인해 프로브 타이밍이 무시되었습니다.
  • ArgoCD repo‑server: CrashLoopBackOff — 모든 애플리케이션이 Unknown 상태를 표시, 즉 ArgoCD가 상태를 평가조차 못함.
  • KServe: READY=False — 기본 설정이 Istio ingress를 가정했지만 클러스터는 Kourier를 사용하고 있었습니다. 오류: “virtual service not found”.
  • 정책 적용: 없음. 루트 컨테이너, 리소스 제한 없음, :latest 태그 — 자유롭게 배포됨.
  • Drift 탐지: 없음. 수동 kubectl 변경이 조용히 누적됨.
    배포 프로세스는 vim → kubectl apply → 기대였습니다. 개발자들은 모델 배포를 두려워했습니다. 플랫폼은 기술적으로 없던 것보다도 못했습니다.

개발자가 Backstage 폼을 작성하면, 플랫폼이 나머지를 모두 처리합니다.
Backstage 폼 → PR 생성 → CI 검증 → Merge → ArgoCD 동기화
  → ApplicationSet 탐색 → KServe 엔드포인트 활성화 → 예측 작동

kubectl 필요 없음. YAML 편집 필요 없음. 부수적인 지식도 필요 없음. template.yaml이 준수된 InferenceService 매니페스트를 생성하고 PR을 열며, neuroscale-model-endpoints ApplicationSet이 이를 자동으로 발견합니다.

다섯 개 필드. kubectl 없음. YAML 없음. DNS 패턴은 클라이언트 측에서 강제, 비용 센터는 필수. Next를 클릭하면 Backstage가 나머지를 수행합니다.

두 단계, 총 9초. PR이 열리면 ApplicationSet이 다음 ArgoCD 동기화 시 이를 잡아냅니다.
Git이 진실의 원천. Drift는 자동 교정됩니다.

$ kubectl delete deploy nginx-test -n default
# 20초 후...
$ kubectl get deploy nginx-test -n default
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
nginx-test   1/1     1            1           8s   # ArgoCD에 의해 자동 재생성됨

selfHeal: trueprune: true. 수동 클러스터 변경은 지속될 수 없습니다.

PR 시점(CI): kubeconform이 스키마를 검증합니다. kyverno-cli가 5개 정책을 렌더링된 매니페스트에 대해 이중 exit‑code + stdout 체크로 시뮬레이션해 false‑green을 방지합니다. 전체 파이프라인은 .github/workflows/guardrails-checks.yaml에 있습니다.
Admission 시점(클러스터): Kyverno가 비준수 리소스를 클러스터에 도달하기 전에 차단합니다.

강제 적용되는 다섯 가지 정책:

정책차단 대상
require-standard-labels-inferenceserviceowner 및 cost‑center 라벨 누락
require-standard-labels-deploymentDeployment에 소유 라벨 누락
require-resource-requests-limitsCPU/메모리 요청·제한 없음
disallow-latest-image-tag:latest 이미지 태그 사용
disallow-root-containersrunAsNonRoot: true 없이 실행되는 컨테이너

모든 워크로드는 Kyverno가 강제하는 owner와 cost‑center 라벨을 가지고 있어야 하며, 라벨 없이 배포는 불가능합니다. OpenCost는 Prometheus를 통해 이를 읽어 팀별 비용 분류를 제공합니다. CI 파이프라인은 PR에 CPU/메모리 변화량을 코멘트하고, 임계치를 초과하는 워크로드를 표시합니다.

각 실패 모드에 대한 문서화된 런북을 제공하며, 3명령어, 2분 회복 절차를 포함합니다. 전체 런북은 docs/runbook.md에 있습니다.

Copilot이 이 플랫폼을 직접 작성한 것은 아닙니다. 세 번의 정확한 순간에 시니어 인프라 어드바이저 역할을 수행해, 내가 며칠씩 머물러 있을 수 있었던 상황을 탈출시켰습니다.

문제 1: KServe가 READY=False에 머물고 있었습니다. 오류: “virtual service not found” — Istio 개념이 Kourier를 사용하는 클러스터에 적용된 경우였습니다.

Copilot은 실제 레포 파일을 검색해 비‑Istio 설정이 이미 올바른 것을 확인하고, 근본 원인은 오래된 캐시된 설정임을 찾아냈습니다. 또한 중요한 트레이드오프를 제시했는데, Istio는 약 1GB 메모리를 추가로 사용하지만 Kourier는 200MB 미만입니다. 8GB 개발 노드에서는 Istio가 재현성을 파괴했을 것입니다.

해결: serving-stack 오버레이를 재적용하고, ConfigMap에서 disableIstioVirtualHost=true를 확인한 뒤, 컨트롤 플레인 pod를 재시작했습니다. 결과: 추론 정상 작동, 800MB 메모리 절감.

문제 2: kyverno-cli가 CI에서 초록색으로 표시됐지만, 의도적으로 비준수 매니페스트를 테스트했을 때도 통과했습니다. 가드레일이 아무것도 검사하지 않았습니다.

Copilot이 밝혀낸 두 가지 undocumented kyverno-cli 동작:

  1. 단일 --resource 플래그에 여러 경로를 지정하면 첫 번째 경로 이후는 모두 무시됩니다.
  2. 위반 내용이 stdout에 출력돼도 exit code는 0입니다.

수정된 .github/workflows/guardrails-checks.yaml:

# Per-resource fan
0 조회
Back to Blog

관련 글

더 보기 »

내 스킬

프로젝트를 위한 AI 지시문을 만들고, 설치하고, 관리하세요 — 코딩이 필요 없습니다. CREATE 이름을 정하고, 카테고리를 선택하고, 원하는 것을 설명하세요 — 마법사가 자동으로 구성합니다.