본문으로 건너뛰기
고급약 40분

Kubernetes 위에서 검증하기 — 선언형 상태·conditions·오퍼레이터·테스트 클러스터·적합성 시험

Kubernetes 기반 플랫폼에서 QA가 읽고 판정해야 할 것은 리소스의 spec과 status, conditions와 이벤트다. 선언형 모델에서의 검증 기준 쓰기, observedGeneration으로 오래된 상태를 걸러내는 법, 오퍼레이터와 커스텀 리소스 검증, kind·가상 클러스터로 PR 환경 만들기, 적합성 시험과 버전 호환 매트릭스, pytest로 상태를 판정하는 패턴을 다룬다.

  • #Kubernetes
  • #CRD
  • #오퍼레이터
  • #conditions
  • #kind
  • #적합성시험

개념

선언형 시스템에서 "요청"은 원하는 상태를 적는 일이다

이 레슨에는 Kubernetes 고유의 말이 계속 나온다. 넷만 먼저 알아 두면 된다. 파드(Pod) 는 컨테이너 한두 개를 묶어 돌리는 가장 작은 단위, spec 은 "이렇게 되기를 원한다" 고 적는 칸, status 는 "지금 실제로 이렇다" 고 시스템이 적어 주는 칸, 컨트롤러 는 그 둘을 보고 현재를 원하는 쪽으로 끌고 가는 프로그램이다. 뒤에 나오는 CRD 는 Kubernetes 가 원래 모르는 새 종류의 자원을 직접 정의해 추가하는 장치이고, PVC 는 "이만큼의 저장 공간을 달라" 고 적어 두는 신청서다.

Kubernetes에서 사용자는 "파드를 만들어라"라고 명령하지 않는다. "이런 상태이기를 원한다"고 spec 에 적는다. 컨트롤러가 그것을 보고 현재 상태를 바꾼 뒤 status 에 결과를 적는다. 그래서 Kubernetes 위의 플랫폼 검증은 거의 모두 이 한 문장으로 요약된다.

spec에 적은 것이 status에 반영되었는가, 그리고 반영되는 과정이 허용된 방식이었는가.

레슨 1의 비동기 수렴 검증이 Kubernetes에서는 표준 필드로 제공된다. QA가 읽어야 할 필드는 정해져 있다.

필드뜻검증에서 쓰는 방법
metadata.generationspec이 바뀔 때마다 올라가는 번호내가 적용한 변경의 번호
status.observedGeneration컨트롤러가 마지막으로 처리한 spec 번호generation과 같아야 status를 믿을 수 있다
status.conditions[]type·status(True/False/Unknown)·reason·message·lastTransitionTime판정의 1차 근거
이벤트리소스에 대해 컨트롤러가 남긴 기록실패 원인·경로 증거

observedGeneration을 확인하지 않으면 과거를 검증한다

가장 흔한 거짓 통과가 여기서 나온다.

1. 추론 엔드포인트 리소스의 replicas 를 2 → 4 로 수정 (generation 7 → 8)
2. 테스트가 곧바로 status 조회
3. status.conditions: Ready=True   ← 하지만 observedGeneration 은 아직 7
4. 테스트 통과 판정

3번의 Ready=True 는 replicas 2였던 과거 상태 의 판정이다. 컨트롤러는 아직 새 spec을 보지도 않았다. 판정 기준을 이렇게 쓴다.

판정: observedGeneration == generation 이고 conditions[type=Ready].status == "True"
     그리고 Ready 조건의 lastTransitionTime 이 변경 적용 시각 이후

마지막 줄은 Ready 가 한 번도 False로 내려가지 않고 True로 남아 있는 경우를 걸러낸다. 롤링 업데이트가 아예 시작되지 않았는데 이전 Ready를 보고 통과하는 경우다. 커스텀 리소스가 observedGeneration 을 제공하지 않는다면 그 자체를 결함 후보 로 올린다. 관측 가능성이 없는 리소스는 운영자도, 자동 판정자도 믿을 수 없다.

conditions를 기준으로 쓰기

Kubernetes API 규약은 conditions를 관측 가능한 상태의 사실 로 쓰라고 권한다. 플랫폼이 제공하는 커스텀 리소스의 conditions 설계 자체가 QA의 검토 대상이다.

# 학습 작업 커스텀 리소스의 status 예시
status:
  observedGeneration: 3
  conditions:
    - type: Admitted          # 쿼터 큐에 입장했는가 (레슨 5 Kueue)
      status: "True"
      reason: QuotaReserved
      lastTransitionTime: "2026-09-16T01:02:03Z"
    - type: Running
      status: "True"
      reason: AllWorkersReady
    - type: CheckpointHealthy
      status: "False"
      reason: CheckpointWriteTimeout
      message: "step 12000 checkpoint write exceeded 300s"

이 status에서 QA가 뽑는 기준은 이렇다.

기준판정
조건 타입 목록이 문서와 일치문서에 없는 조건 타입이 나오거나, 문서의 조건이 안 나오면 실패
False 조건에는 reason과 message 필수빈 reason은 운영자가 원인을 알 수 없다
reason 값은 열거형자유 문자열이 섞이면 알림 규칙·대시보드가 깨진다
조건 간 모순 없음Running=True 인데 Admitted=False 는 불가능
전이 시각 단조성lastTransitionTime 이 뒤로 가지 않음

"조건 간 모순 없음"은 결정 테이블(모듈 3 레슨 3)로 전수 검증할 수 있다. 조건 네 개면 조합 16개 중 허용 조합을 표로 정의하고, 장애 주입 중 관측된 조합이 모두 허용 목록 안에 있는지 본다.

오퍼레이터 검증 — 조정 루프의 성질

GPU 플랫폼은 대부분 오퍼레이터 로 구성된다. NVIDIA GPU Operator, 학습 작업 오퍼레이터, 추론 서빙 오퍼레이터, 스토리지 오퍼레이터. 오퍼레이터는 커스텀 리소스를 감시하며 현재 상태를 원하는 상태로 끌고 가는 조정 루프(reconcile loop) 다. 조정 루프가 가져야 할 성질이 곧 검증 기준이다.

성질검증 방법
수렴spec을 적용하면 정해진 시간 안에 Ready
멱등성같은 spec을 여러 번 적용해도 하위 리소스가 늘지 않는다
자기 치유하위 리소스(Deployment·Service·ConfigMap)를 수동으로 지우거나 바꾸면 되살린다
소유권하위 리소스에 ownerReference가 있고, 상위 삭제 시 함께 정리
삭제 완료finalizer가 정리를 마치고 제거되어 리소스가 Terminating에 멈추지 않는다
재시작 내성오퍼레이터 파드를 조정 도중 죽여도 재시작 후 이어서 수렴한다
드리프트 복구누군가 하위 리소스를 직접 수정하면 원하는 상태로 되돌리거나, 의도적으로 두는 필드는 문서화

finalizer에 멈춘 리소스 는 운영에서 매우 흔하다. 삭제를 요청했는데 영원히 Terminating으로 남는다. 원인은 대개 오퍼레이터가 외부 리소스(스토리지·DNS·과금) 정리에 실패하고 재시도를 멈춘 것이다. 삭제 테스트의 판정 기준에 "리소스 객체가 API에서 사라짐" 까지 넣어야 이 결함이 잡힌다.

재시작 내성은 레슨 7의 장애 주입과 이어진다. 조정 도중(하위 리소스 일부만 만든 시점)에 오퍼레이터 파드를 죽이는 시험은 부분 생성 누수와 중복 생성을 가장 잘 드러낸다.

테스트 클러스터 — PR마다 클러스터를 만들 수 있는가

모듈 38 레슨 4의 PR 층을 Kubernetes 플랫폼에서 돌리려면 PR마다 격리된 환경이 필요하다. 선택지는 비용과 충실도의 교환이다.

방식충실도비용·속도적합한 용도
kind (Docker 컨테이너를 노드로 쓰는 로컬 클러스터)제어 평면 동작은 실제와 같음. GPU·실제 네트워크·스토리지 없음1~2분, 무료오퍼레이터·CRD·조정 루프·API 검증 (PR 층)
가상 클러스터 (호스트 클러스터 안의 격리된 제어 평면)제어 평면 격리, 호스트의 노드·GPU를 빌려 씀수십 초, 호스트 자원 공유실제 노드가 필요하지만 클러스터 전체는 과한 검증
네임스페이스 격리 (공유 스테이징)실제와 같음. 클러스터 범위 리소스 충돌 가능즉시, 저렴CRD 설치가 필요 없는 워크로드 검증
전용 스테이징 클러스터가장 높음비쌈, 공유회귀·릴리스 전 층

GPU가 필요한 기준을 kind에서 흉내 낼 때는 노드에 확장 리소스 를 광고해 스케줄링과 쿼터까지만 검증한다. 모듈 38 레슨 4에서 말한 "흉내 검증과 실물 검증을 기준 문서에 적는다"가 여기서 구체화된다.

nvidia.com/gpu 를 노드 status.capacity 에 패치로 광고 → 스케줄러는 GPU 가 있는 것처럼 파드를 배치
검증 가능:  쿼터 입장, 스케줄링 제약(노드 셀렉터·톨러레이션), 상태 전이, 삭제 후 할당 반환
검증 불가:  장치 플러그인 동작, CUDA 실행, MIG 분할, DCGM 지표 → 회귀 층의 실물 GPU 노드로

버전 호환과 적합성 시험

Kubernetes는 대략 4개월마다 새 마이너 버전이 나온다. 2026년 8월 26일 v1.37이 나왔고, 상류 프로젝트는 최신 세 개 마이너 버전 정도를 지원한다. 설치형 Private Cloud 플랫폼은 고객이 쓰는 여러 버전에서 동작해야 하므로 호환 매트릭스 가 검증 계획의 뼈대가 된다.

[호환 매트릭스 — 릴리스 2026.38 후보]
                     k8s 1.35   k8s 1.36   k8s 1.37
GPU Operator 25.x      ✓ 회귀     ✓ 회귀     ✓ 릴리스 전
GPU Operator 26.x      —         ✓ 회귀     ✓ 회귀
업그레이드 1.35→1.36    ✓ 릴리스 전 (실행 중 학습 작업 유지 기준 포함)
업그레이드 1.36→1.37    ✓ 릴리스 전
제거 예정 API 사용      0건 (정적 검사, 매 PR)

마지막 줄이 중요하다. Kubernetes는 버전마다 베타 API를 제거한다. 플랫폼의 매니페스트·헬름 차트·오퍼레이터가 제거 예정 API를 쓰는지 를 매 PR에서 정적으로 검사하면, 고객이 클러스터를 올린 날 플랫폼이 설치되지 않는 사고를 막는다.

플랫폼이 클러스터를 직접 만들어 제공한다면(관리형 Kubernetes 기능), 그 클러스터가 Kubernetes 적합성(conformance) 을 만족하는지도 검증 대상이다. CNCF의 공인 Kubernetes 프로그램은 상류 적합성 테스트 결과 제출로 인증하며, Sonobuoy나 hydrophone 같은 도구로 적합성 스위트를 돌린다. 적합성 스위트는 오래 걸리므로 릴리스 전 층에 둔다.

pytest로 Kubernetes 상태 판정하기

판정 코드는 모듈 38 레슨 4의 판정 레코드를 내도록 짠다.

def assert_reconciled(api, kind, name, ns, timeout_s):
    """observedGeneration 과 Ready 조건을 함께 확인한다."""
    obj = api.get(kind, name, ns)
    applied_gen = obj["metadata"]["generation"]
    applied_at = now_utc()
    history = []
 
    def ready():
        o = api.get(kind, name, ns)
        st = o.get("status", {})
        conds = {c["type"]: c for c in st.get("conditions", [])}
        snapshot = (st.get("observedGeneration"), conds.get("Ready", {}).get("status"))
        history.append(snapshot)
        if st.get("observedGeneration") != applied_gen:
            return False
        r = conds.get("Ready")
        return r is not None and r["status"] == "True" and parse(r["lastTransitionTime"]) >= applied_at
 
    if not poll(ready, timeout_s):
        raise ReconcileTimeout(kind, name, applied_gen, history, api.events(kind, name, ns))

실패 시 상태 이력과 이벤트 를 함께 남기는 것이 핵심이다. "Ready가 안 됐다"만으로는 결함 리포트를 쓸 수 없다. 이벤트에 "0/4 nodes are available: 4 Insufficient nvidia.com/gpu" 가 있으면 제품 결함이 아니라 환경 용량 문제로 바로 분류된다.

실무에서 왜 필요한가

Kubernetes 기반 AI 플랫폼 QA에서 "kubectl을 쓸 줄 안다"는 출발선일 뿐이다. 실제로 차이를 만드는 것은 status를 믿어도 되는 시점 을 아는 것, conditions 설계의 결함을 짚는 것, 오퍼레이터의 조정 루프 성질을 기준으로 바꾸는 것이다. observedGeneration을 보지 않는 테스트 스위트는 매일 통과하면서 매일 과거를 검증한다.

설치형 플랫폼 기업에서 호환 매트릭스와 제거 예정 API 검사는 고객 사고 예방 과 직결된다. 고객이 클러스터를 올리는 날 플랫폼이 멈추면, 그 원인이 상류 변경이라도 고객에게는 플랫폼 결함이다.

실습 과제

과제 1 — 거짓 통과 찾기 (15분)

아래 테스트 로그에서 판정이 틀린 곳을 찾고 올바른 판정 기준을 쓴다.

10:00:00  patch InferenceService/llm-a spec.replicas 2→4 (generation 11→12)
10:00:01  get status: observedGeneration=11, Ready=True (lastTransitionTime 09:41:10)
10:00:01  PASS: InferenceService Ready
10:00:40  event: Warning FailedScheduling 0/6 nodes available: insufficient nvidia.com/gpu

과제 2 — conditions 검토 (20분)

본문의 학습 작업 status를 바탕으로 Admitted·Running·CheckpointHealthy·Succeeded·Failed 다섯 조건의 허용 조합표 를 만든다. 불가능한 조합 다섯 개 이상을 명시한다.

과제 3 — 오퍼레이터 검증 계획 (25분)

"추론 엔드포인트 오퍼레이터"(Deployment·Service·HPA·모델 캐시 PVC를 만든다)에 대해 수렴·멱등성·자기 치유·소유권·삭제 완료·재시작 내성 여섯 성질의 시험 절차와 판정 기준을 쓴다.

과제 4 (선택) — kind로 GPU 흉내 내기 (30분)

kind 클러스터를 만들고 노드 하나에 확장 리소스 nvidia.com/gpu: 2 를 광고한 뒤, GPU 1개를 요청하는 파드 3개를 만들어 스케줄링 결과를 관찰한다. 무엇을 검증할 수 있었고 무엇을 검증할 수 없었는지 적는다.

자가 체크리스트

  • spec·status·generation·observedGeneration·conditions·이벤트의 역할을 설명할 수 있다
  • observedGeneration을 확인하지 않을 때 생기는 거짓 통과를 예로 들 수 있다
  • conditions 설계를 조건 타입·reason 열거형·모순 조합·전이 시각으로 검토할 수 있다
  • 오퍼레이터의 조정 루프 성질 일곱 가지를 검증 기준으로 바꿀 수 있다
  • finalizer에 멈춘 리소스를 잡으려면 삭제 판정 기준에 무엇을 넣어야 하는지 안다
  • kind·가상 클러스터·네임스페이스 격리·전용 스테이징의 충실도와 용도를 구분할 수 있다
  • 확장 리소스로 GPU를 흉내 낼 때 검증 가능한 것과 불가능한 것을 말할 수 있다
  • 호환 매트릭스와 제거 예정 API 검사의 필요성을 설명할 수 있다
  • 실패 시 상태 이력과 이벤트를 남기는 판정 코드를 설계할 수 있다

흔한 실수

Ready=True만 본다. observedGeneration과 전이 시각을 함께 보지 않으면 과거를 검증한다.

삭제 테스트를 "삭제 요청 성공"으로 끝낸다. finalizer에 멈춘 리소스는 영원히 남는다.

kind에서 통과했으니 GPU 기능이 검증됐다고 쓴다. 흉내로 검증한 범위를 기록한다.

최신 Kubernetes에서만 검증한다. 설치형 플랫폼의 고객은 여러 버전에 흩어져 있다.

실패 시 "timeout"만 남긴다. 이벤트가 없으면 환경 문제와 제품 결함을 가를 수 없다.

참고 자료

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

컨테이너 — 컨테이너
이 커리큘럼에서 두 가지로 쓰인다. ① 프로그램과 그것을 돌리는 데 필요한 것들을 한 상자에 담아, 어느 컴퓨터에서나 똑같이 돌아가게 만든 것 ② 화면에서 여러 요소를 하나로 묶는 바깥 틀. 서버·배포 이야기면 ①, 화면·접근성 이야기면 ②다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
GPU — 그래픽처리장치
비슷한 계산을 한꺼번에 많이 하는 데 특화된 부품. 그림과 기계학습에 쓰인다.
DNS — 도메인 이름 체계
사람이 읽는 주소를 컴퓨터가 쓰는 번호로 바꿔 주는 전화번호부.
PR — 풀 리퀘스트
"내가 고친 코드를 본체에 합쳐 달라"고 올리는 요청. 합치기 전에 사람들이 보고 의견을 단다.
AI — 인공지능
사람이 규칙을 일일이 적지 않아도 데이터에서 규칙을 찾아 판단하는 소프트웨어.

용어집 전체 보기