CRD 와 오퍼레이터 · 로그에는 있는데 사용자는 모른다 · 실습
로그에는 있는데 사용자는 모른다 — 오퍼레이터가 말하게 하기
목표
두 이벤트 API 로 이벤트를 직접 만들어 필수 필드와 집계를 확인하고, 관례를 어겼을 때 도구에서 어떻게 사라지는지 본 뒤, 이벤트만 읽어 사고를 재구성하는 표를 만든다.
왜 중요한가
오퍼레이터가 무슨 일을 했는지 사용자가 알 수 있는 자리는 세 곳뿐이다 — 컨트롤러 로그, 리소스의 status, 그리고 이벤트. 로그는 클러스터 운영자만 보고, status 는 지금 상태만 말할 뿐 과정을 담지 않는다. 사용자가 실제로 읽는 자리는 kubectl describe 아래쪽의 Events 뿐이다. 그래서 오퍼레이터를 만드는 일의 절반은 무엇을 이벤트로 남길지 정하는 일이다. 다만 이벤트는 로그가 아니다. 기본 보존이 한 시간이고, 같은 이유의 반복은 새 오브젝트가 아니라 count 증가로 합쳐지며, API 가 둘인데 저장소는 하나다. 이 성질을 모르고 이벤트에 감사 추적을 기대면 정작 사고를 조사할 때 아무것도 남아 있지 않다.
단계
1. /root/op-events/pipeline-crd.yaml 에 CRD pipelines.ev.labhub.io 를 쓰세요 — 그룹 ev.labhub.io, 종류 Pipeline, 복수형 pipelines, 버전 v1 하나이고 스키마에는 spec.stage(string) 만 둡니다. 네임스페이스 op-events 를 만들고 /root/op-events/pipeline-build1.yaml 로 Pipeline build-1(stage build)을 적용한 뒤 그 metadata.uid 를 /root/op-events/pipeline-uid.txt 에 한 줄로 저장하세요.
2. /root/op-events/event-started.yaml 에 v1 Event build-1-started 를 쓰세요 — involvedObject 는 Pipeline build-1(apiVersion·kind·name·namespace·실제 uid), reason 은 ReconcileStarted, type 은 Normal, message 는 한 문장, firstTimestamp 와 lastTimestamp 는 지금 시각, count 는 1, source.component 는 pipeline-operator 입니다. 적용한 뒤 kubectl -n op-events events 의 출력을 /root/op-events/events-list.txt 에 저장하세요.
3. /root/op-events/event-bad.yaml 에 Event build-1-orphan 을 쓰되 involvedObject 를 아예 넣지 않습니다(reason NoTarget, type Normal, message 한 문장). 적용을 시도해 결과를 /root/op-events/invalid-event.txt 에 모으세요 — 첫 줄은 apply-rc=<종료 코드> 이고 그 아래에 서버가 낸 문장을 그대로 붙입니다.
4. /root/op-events/event-weird.yaml 에 Event build-1-weird 를 쓰세요 — 대상은 Pipeline build-1, reason 은 StageUnknown, 그리고 type 을 관례 밖의 값 Critical 로 적습니다(시각 필드는 2단계와 같은 형식으로 채웁니다). 적용한 뒤 kubectl -n op-events events --types=Critical 과 kubectl -n op-events events --types=Warning 을 차례로 실행해 결과를 /root/op-events/type-report.txt 에 모으세요 — 두 명령의 출력과 각각의 종료 코드(critical-rc=, warning-rc=)가 들어가야 합니다.
5. 2단계의 이벤트가 네 번 더 일어났다고 가정하고 집계 필드를 갱신하세요 — kubectl -n op-events patch event build-1-started --type=merge 로 count 를 5 로, lastTimestamp 를 지금 시각으로 바꿉니다. 그다음 kubectl -n op-events events 의 출력을 /root/op-events/count-report.txt 에 저장하세요.
6. /root/op-events/event-done.yaml 에 events.k8s.io/v1 Event build-1-done 을 쓰세요 — 대상은 regarding(Pipeline build-1, 실제 uid 포함), reason 은 ReconcileSucceeded, note 는 한 문장, type 은 Normal, eventTime 은 지금 시각(소수점 이하 6자리), reportingController 는 pipeline-operator, reportingInstance 는 pipeline-operator-0, action 은 Reconcile 입니다. 적용한 뒤 두 API 로 각각 목록을 뽑아 /root/op-events/both-apis.txt 에 저장하세요 — core: 로 시작하는 줄들과 new: 로 시작하는 줄들이 각각 kubectl get events 와 kubectl get events.events.k8s.io 의 이름 목록이어야 합니다.
7. /root/op-events/hungry-pod.yaml 에 파드 hungry 를 쓰세요 — 컨테이너 하나(app, 이미지 busybox:1.36)에 requests.cpu 를 "64" 로 요구합니다. 적용하고 스케줄러가 이벤트를 남길 때까지 기다린 뒤 kubectl -n op-events get events --field-selector reason=FailedScheduling -o wide 의 출력을 /root/op-events/scheduler-event.txt 에 저장하세요.
8. /root/op-events/timeline.sh 를 만드세요 — op-events 의 모든 이벤트를 <type> <reason> <대상종류>/<대상이름> 한 줄씩으로 찍되 정렬하고 중복을 없앱니다. 나이·시각·count 처럼 볼 때마다 달라지는 값은 넣지 않습니다. 출력을 /root/op-events/timeline.txt 에 저장하고, 그 표만 보고 무슨 일이 있었는지 /root/op-events/incident.txt 에 사람이 읽을 문장으로 적으세요 — 스케줄러가 남긴 이유와 오퍼레이터가 남긴 이유가 모두 언급돼야 하고, 관례 밖의 type 이 섞여 있다는 것도 지적해야 합니다.
참고
v1Event 는involvedObject·reason·type·message·source를 씁니다.events.k8s.io/v1은 같은 자리를regarding·note·reportingController로 부릅니다.type의 관례는Normal과Warning두 가지뿐입니다 — API 는 강제하지 않습니다.kubectl events와kubectl describe가 같은 이벤트를 다른 모양으로 보여 줍니다.- 흔한 실수: reason 에 긴 문장을 넣어 집계가 되지 않게 만든다.
- 흔한 실수: 이벤트를 감사 기록으로 쓴다 — 기본 보존은 한 시간입니다.
- 참고: https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/event-v1/
- 참고: https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/
단계 8개
- 이벤트를 붙일 대상을 만든다
- 오퍼레이터가 첫 마디를 남긴다
- 대상 없는 이벤트는 만들 수 없다
- 관례 밖의 type 은 도구에서 사라진다
- 같은 이유의 반복은 새 이벤트가 아니다
- API 는 둘인데 저장소는 하나다
- 진짜 컨트롤러가 남기는 이벤트를 받아 본다
- 이벤트만 읽어 사고를 재구성한다