CRD 와 오퍼레이터 · 로그에는 있는데 사용자는 모른다 · 이론
이벤트는 로그가 아니다
한 줄 요약
이벤트는 오퍼레이터가 사용자에게 말을 거는 유일한 자리이지만, 보존이 짧고 같은 이유는 하나로 합쳐지는
요약 장치다. 이 성질에 맞춰 써야 값어치가 생긴다.
왜 이 자리가 필요했나
오퍼레이터를 만들다 보면 이런 상황을 만난다. 컨트롤러는 "이미지 태그가 존재하지 않아 조정을 중단했다"
는 것을 알고 있다. 그런데 그 사실이 있는 곳은 컨트롤러 파드의 로그뿐이다. 커스텀 리소스를 만든 개발자는
그 로그를 볼 권한도, 그런 것이 있다는 지식도 없다. 그 사람 화면에는 아무 일도 일어나지 않는 리소스
하나만 있다.
status 를 쓰면 되지 않나 싶지만 status 는 지금 상태다. "지금 Ready 가 아니다" 는 적을 수 있어도
"10분 전에 한 번 실패했다가 복구됐다" 는 담기지 않는다. 과정을 남기려면 시간축을 가진 다른 자리가
필요하고, 그것이 Event 다.
어떻게 동작하나
이벤트는 API 오브젝트이고, 다른 오브젝트처럼 네임스페이스에 저장된다. 그런데 API 가 둘이다.
| | v1 (core) | events.k8s.io/v1 |
| --- | --- | --- |
| 대상 | involvedObject | regarding |
| 본문 | message | note |
| 보고자 | source.component | reportingController·reportingInstance |
| 시각 | firstTimestamp·lastTimestamp | eventTime |
| 반복 | count | series.count |
이름만 다르고 저장소는 같다. 새 API 로 만든 이벤트를 옛 API 로 조회하면 그대로 나오는데, 옛
스키마에 대응이 없는 칸은 비어 보인다. 새 API 가 생긴 이유는 대규모 클러스터에서 이벤트 쓰기가
API 서버에 주는 부담을 줄이려고 스키마를 정리한 것이고, 옛 API 는 호환을 위해 남아 있다.
reason 은 이 오브젝트에서 가장 중요한 필드다. 사람이 읽는 문장이 아니라 기계가 세는 열쇠이기
때문이다. 그래서 관례가 짧은 CamelCase 한 낱말(FailedScheduling, Pulled, BackOff)이고, 여기에
긴 문장을 넣으면 같은 사건이 매번 다른 이유로 기록돼 집계가 무너진다. 자세한 내용은 message 쪽이다.
같은 이유의 반복은 새 이벤트가 아니다. 이벤트 기록기는 같은 대상·같은 이유·같은 메시지를 다시
만나면 오브젝트를 하나 더 만들지 않고 count 를 올리고 lastTimestamp 를 갱신한다. 목록 화면에서(x12 over 3m) 처럼 보이는 것이 그 결과다. 조정 루프가 초당 몇 번씩 돌아도 etcd 가 이벤트로 넘치지
않는 이유가 이 집계다.
type 은 API 가 검사하지 않는다. Critical 이라고 적어도 오브젝트는 만들어진다. 그런데 이벤트를 읽는
도구들은 Normal 과 Warning 두 가지만 있다고 전제한다 — kubectl events --types= 는 그 밖의 값을
아예 받지 않는다. 강제되지 않는데도 관례를 지켜야 하는 전형적인 자리다.
그리고 이벤트는 사라진다. kube-apiserver 의 --event-ttl 기본값은 1시간이다. 한 시간 뒤에 조사를
시작하면 그 사건의 이벤트는 없다.
현장에서 만나는 모습
첫째, 사용자 화면에 이유가 없는 오퍼레이터. 컨트롤러 로그에는 명확한 오류가 있는데 CR 의
describe 에는 아무것도 없다. 사용자는 문의를 넣고, 운영자는 로그를 떠서 붙여 준다. 이벤트 세 줄이면
끝날 일을 사람 왕복으로 처리하는 것이다. 조정의 시작·성공·실패만 남겨도 문의가 크게 준다.
둘째, 이벤트를 감사 기록으로 쓴 사고. "누가 언제 무엇을 바꿨는지는 이벤트를 보면 된다" 고 믿고
설계했다가, 정작 사고 조사에 들어가니 한 시간이 지나 아무것도 없는 경우다. 길게 남겨야 하는 것은
status 의 조건이나 바깥 저장소로 보낸 기록이어야 한다. 이벤트는 지금 무슨 일이 벌어지는지를 보는
창이다.
셋째, 스케줄러의 문장이 곧 진단서. 파드가 뜨지 않을 때 사람이 실제로 읽는 것은0/3 nodes are available: 3 Insufficient cpu 같은 한 줄이다. 좋은 이벤트 메시지의 본보기이기도 하다 —
숫자가 들어 있고, 무엇이 모자란지 이름이 들어 있고, 다음에 무엇을 할지 짐작하게 한다. 오퍼레이터의
이벤트도 이 수준을 목표로 삼을 값어치가 있다.
이 실습 환경의 한계
kwok 클러스터의 파드는 가짜라서 kubelet 이 남기는 이벤트(Pulling·Started·BackOff)는 볼 수 없다.
대신 스케줄러는 진짜로 돌기 때문에 FailedScheduling 은 실제로 찍힌다. 그리고 컨트롤러가 없는
커스텀 리소스에 대한 이벤트는 이 실습에서 사람이 직접 만들어 넣는다 — 오퍼레이터가 할 일을 손으로
한 번 해 보는 셈이다.
다음 실습에서 할 것
커스텀 리소스를 대상으로 두 API 로 각각 이벤트를 만들어 보고, 대상을 빠뜨린 이벤트가 어떻게 거절되는지,
관례 밖의 type 이 도구에서 어떻게 사라지는지 확인한다. 집계 필드를 직접 올려 목록 화면의 표기가 어떻게
바뀌는지 보고, 스케줄러가 남기는 진짜 이벤트까지 모아 마지막에는 이벤트만 읽어 사고를 재구성하는 표를 만든다.