LabHub
배우기 러닝패스 코스

K8s 망가뜨리기 — 범인은 내 YAML · 정상인데 왜 배송되지 않을까 · 이론

정상인데 왜 배송되지 않을까

LabHub 에서 이어서 보기

한 줄 요약

Service가 존재한다는 사실, Pod가 실행 중이라는 사실, 사용자의 요청이 성공한다는 사실은 서로 다른 증거입니다.

왜 이게 필요했나

택배 조회 서비스의 화면이 멈췄습니다. 담당자는 kubectl get pods에서 Running을 보고 “쿠버네티스는 정상”이라고 답합니다. 그런데 고객의 요청은 여전히 실패합니다. 누구의 관측이 틀린 것일까요? 둘 다 사실일 수 있습니다. Running은 Pod 생명주기의 상태이고, 사용자가 요청한 주소에서 올바른 응답을 받았다는 의미가 아닙니다. 서로 다른 질문의 답을 한 단어로 묶으면 장애 분석이 엉뚱한 곳으로 갑니다.

이 코스에서는 여러분이 직접 범인이 됩니다. 실습용 VM 안에서 작은 HTTP 서비스를 만든 뒤 Service 선택자, readiness 경로, 컨테이너 명령을 하나씩 바꿉니다. 증상을 예상하고 실제 응답을 관측한 다음 원인을 좁혀 복구합니다. 실험 대상은 labhub-mystery 네임스페이스의 parcel뿐입니다. 호스트 클러스터의 노드·CoreDNS·CNI·etcd를 멈추는 과제가 아니며 다른 사람의 서버를 건드릴 이유도 없습니다.

YAML의 들여쓰기와 Deployment·Service의 기본 역할, kubectl get·describe·patch 사용법을 알고 시작하세요. 처음 명령행을 배우는 코스가 아니라, 명령이 성공한 다음에도 업무가 실패할 수 있다는 것을 배우는 중급 실습입니다. 모든 장애는 학습자가 의도적으로 만들며, 복구보다 먼저 장애 당시 증거를 남깁니다.

어떻게 동작하나

Deployment는 원하는 Pod 템플릿과 replica 수를 선언합니다. 컨트롤러가 ReplicaSet과 Pod를 만들어 그 선언을 실현합니다. Service는 다른 역할입니다. app=parcel 같은 선택자로 대상 Pod를 고르고, 일정한 ClusterIP와 포트를 제공합니다. EndpointSlice에는 그 Service가 선택한 주소와 준비 상태가 나타납니다. Service 객체를 만들었다고 자동으로 올바른 Pod가 선택되는 것은 아닙니다.

Service selector ── 비교 ── Pod metadata.labels        │ 일치한 대상        ▼EndpointSlice의 주소와 ready 조건        │ 대상 포트로 전달        ▼컨테이너의 HTTP 응답

실습의 정상값은 Service와 Pod 모두 app=parcel, 서비스 포트와 대상 포트 모두 8080입니다. 앱은 / 요청에 labhub-mystery-v1이라는 한 줄을 반환합니다. 포트가 열리는지만 보는 대신 응답 본문도 확인하는 이유는 다른 프로세스나 잘못된 서비스의 성공 응답을 구분하기 위해서입니다. 200이라는 숫자 하나도 어떤 요청과 어떤 본문을 확인했는지 없으면 충분한 증거가 아닙니다.

Service의 selector를 app=missing으로 바꾸면 무엇이 먼저 바뀔까요? Deployment의 Pod 템플릿은 바꾸지 않았으므로 Pod를 재시작할 이유가 없습니다. 앱의 Pod IP로 직접 요청하면 여전히 응답합니다. 반면 선택자에 맞는 Pod가 없어 Service의 EndpointSlice에서 대상이 사라지고, Service IP 요청은 실패합니다. 이때 컨테이너 이미지를 다시 빌드하거나 Pod를 삭제하는 것은 원인과 맞지 않는 행동입니다.

빈 EndpointSlice를 읽는 프로그램도 조심해야 합니다. endpoints가 빈 배열일 수도 있고 null로 표현될 수도 있습니다. 둘 다 선택된 주소가 없다는 상태로 처리해야지 파서 예외로 관측 자체를 포기하면 안 됩니다. 반대로 주소 하나가 있다고 바로 정상이라고 생각해서도 안 됩니다. 다음 모듈에서 보듯 ready가 false인 주소가 기록될 수 있습니다. 주소의 존재와 라우팅 가능한 대상의 수를 나눠 보세요.

실험 중에는 변화가 즉시 모든 곳에 동시에 나타나지 않습니다. Service를 수정한 직후 컨트롤러가 EndpointSlice를 갱신하는 데 짧은 시간이 필요합니다. 명령의 종료 코드 0은 API가 수정을 받아들였다는 뜻이지 전체 데이터 경로의 반영이 끝났다는 뜻이 아닙니다. 관측 도구는 일정 시간 동안 상태를 다시 읽지만, 오류를 고치거나 상태를 위조하지 않습니다. 시간이 지나도 조건에 맞지 않으면 수정한 값과 관측한 값을 다시 비교해야 합니다.

현장에서 만나는 모습

롤아웃 이후 요청이 실패한다면 먼저 범위를 좁힙니다. 모든 요청인지 특정 경로인지, 모든 Pod인지 일부인지, Service IP로도 실패하는지, Pod IP는 응답하는지 확인합니다. DNS·Ingress·TLS·애플리케이션 의존성을 한 번에 추측하면 가설이 너무 많아집니다. 이 실습은 DNS 이름이나 외부 Ingress를 거치지 않는 ClusterIP 요청과 Pod 직접 요청을 비교합니다. 따라서 여기서 통과했다고 인터넷의 DNS와 TLS까지 검증한 것은 아닙니다.

예를 들어 Pod 직접 응답은 성공하고 Service만 실패하면, 서버 프로세스가 완전히 죽었다는 가설은 약해집니다. 다음에는 선택자, EndpointSlice, targetPort를 봅니다. 선택자가 같고 Ready 주소도 있는데 Service만 실패한다면 대상 포트의 숫자가 실제 수신 포트와 같은지 물을 수 있습니다. 반면 Pod 직접 요청도 실패하면 프로세스의 수신 주소·포트·로그·종료 상태를 먼저 확인할 근거가 생깁니다.

가설을 지지하는 증거와 가설을 확정하는 증거는 다릅니다. Pod IP 요청이 성공했다고 모든 네트워크 계층이 정상인 것은 아닙니다. 관측한 요청의 경로만 성공한 것입니다. 운영에서는 여러 replica, NetworkPolicy, 서비스 메시, 외부 로드밸런서가 추가될 수 있습니다. 이 코스는 한 변수를 바꿔 증상과 원인을 연결하는 기초 실험이며 모든 장애를 세 유형으로 분류하는 만능 진단법이 아닙니다.

직무와의 연결도 범위를 구분합니다. 2026-09-10 확인한 [Canonical SRE 공고](https://canonical.com/careers/4468036)는 Linux·Python·네트워크와 Kubernetes 운영 역량을 다룹니다. 이 코스의 관측·원인 분리·복구 연습은 그 요구를 학습 과제로 번역한 것이며 기업의 채용 시험이나 제휴 과정은 아닙니다. 명령을 많이 외우는 것보다 어떤 계층을 검사했는지 설명하고, 다음 사람이 같은 관측을 재현할 수 있게 만드는 능력에 초점을 둡니다.

세부 진단 순서는 [Kubernetes 공식 Service 디버깅 문서](https://kubernetes.io/docs/tasks/debug/debug-application/debug-service/)와 비교해 보세요. 이 실습의 포트·본문·네임스페이스는 교육용으로 정한 값입니다.

다음 확인에서 할 것

퀴즈에서 Running·EndpointSlice·실제 HTTP의 의미를 구분합니다. 마지막 모듈 실습의 1~3단계에서는 정상 기준, 선택자 오류, 선택자 복구를 각각 관측 JSON으로 저장합니다. 장애를 고친 뒤에는 당시의 빈 대상 목록을 다시 볼 수 없으므로 복구 전에 기록하세요. 그 기록은 학습자가 작성하는 실험 노트이며 위조 불가능한 감사 로그는 아닙니다.