Event System: audit trail для кластера¶
Written by:
Igor Gorovyy
DevOps Engineer Lead & Senior Solutions Architect
Коли щось відбувається в кластері, потрібно знати що, коли і чому. Events — це журнал подій, який записує все: створення подів, планування, відмови нод.
Структура події¶
type Event struct {
Type string `json:"type"` // Normal, Warning
Reason string `json:"reason"` // Created, Scheduled, NodeNotReady
Message string `json:"message"` // людське пояснення
Object string `json:"object"` // pod/web-0, node/node-1
Timestamp time.Time `json:"timestamp"`
}
Два типи: Normal (все добре) і Warning (проблема). Object вказує, до якого ресурсу відноситься подія.
Хто записує події¶
Різні компоненти записують свої події:
// API Server - створення поду
api.store.RecordEvent(Event{
Type: "Normal", Reason: "Created",
Message: "Pod web-0 created",
Object: "pod/web-0",
})
// Scheduler - планування
s.store.RecordEvent(Event{
Type: "Normal", Reason: "Scheduled",
Message: "Pod web-0 scheduled to node-1",
Object: "pod/web-0",
})
// NodeController - відмова ноди
nc.store.RecordEvent(Event{
Type: "Warning", Reason: "NodeNotReady",
Message: "Node node-1 heartbeat timeout",
Object: "node/node-1",
})
// ReplicationController - масштабування
rc.store.RecordEvent(Event{
Type: "Normal", Reason: "Created",
Message: "Created pod web-2 for deployment web",
Object: "deployment/web",
})
Зберігання¶
func (s *Store) RecordEvent(evt Event) error {
key := fmt.Sprintf("%d-%s",
evt.Timestamp.UnixNano(), evt.Object)
return s.put(bucketEvents, []byte(key), evt)
}
Ключ — timestamp у наносекундах плюс об'єкт. BoltDB зберігає ключі відсортованими, тому останні події мають найбільші ключі.
Читання (від найновіших)¶
func (s *Store) ListEvents(limit int) ([]Event, error) {
var events []Event
s.db.View(func(tx *bolt.Tx) error {
c := tx.Bucket(bucketEvents).Cursor()
count := 0
for k, v := c.Last(); k != nil && count < limit;
k, v = c.Prev() {
var evt Event
json.Unmarshal(v, &evt)
events = append(events, evt)
count++
}
return nil
})
return events, nil
}
Cursor.Last() + Prev() — ітерація від кінця. Отримуємо найновіші події першими.
Перегляд через sheepctl¶
$ sheepctl events
TYPE REASON OBJECT MESSAGE
Normal Created pod/web-0 Pod web-0 created
Normal Scheduled pod/web-0 Pod web-0 scheduled to node-1
Normal Created pod/web-1 Created pod web-1 for deployment web
Warning NodeNotReady node/node-2 Node node-2 heartbeat timeout
Це як kubectl get events, тільки простіше.
💡 Цікаві факти¶
- Events у Kubernetes — це повноцінний об'єкт API з власним TTL: за замовчуванням вони живуть лише близько години, а потім etcd їх видаляє. Тому
kubectl get eventsпісля інциденту часто вже порожній — events не призначені бути довготривалим audit trail. - Щоб однакові події не засипали etcd, Kubernetes їх агрегує: повторювані events склеюються в один із лічильником
countі полемlastTimestamp. Наша перевіркаif condition != NodeNotReadyз минулих частин — це примітивна, ручна версія тієї самої ідеї дедуплікації. - Реальний справжній audit trail у Kubernetes — це окремий механізм, Audit Logging на рівні API Server, а не Events. Це поширене непорозуміння: Events — для людей і дебагу "що зараз коїться", audit log — для безпеки і відповідності.
- Трюк із сортуванням за ключем — не наш винахід. BoltDB (як і LevelDB, RocksDB, etcd) тримає ключі у відсортованому B+tree, тому префікс-timestamp дає безкоштовний хронологічний порядок. На цьому ж принципі будуються time-series бази.
- У Kubernetes кожен Event має поле
involvedObject— посилання на pod, node чи deployment. Саме томуkubectl describe pod web-0автоматично показує timeline подій внизу: UX побудований навколо об'єкта, а не процесу, що залогував. - Kubernetes мігрує Events з
core/v1наevents.k8s.io/v1. Нова API-група додає структуровані поля й кращу агрегацію — навіть "прості" observability-об'єкти еволюціонують під production-навантаженням. - kube-apiserver запускає фоновий garbage collector з
--event-ttl(дефолт 1 година). У нашому Shepherd store аналога немає — events накопичуються, доки сам не додас ротацію. - Kubernetes Events — це не Event Sourcing у сенсі DDD. Це сповіщення про зміни стану, а не source of truth. Spec Deployment у etcd — це правда; Event лише каже "щось із ним сталося".
Що я зрозумів, поки розбирався з темою¶
Найнесподіваніше для мене — що events це не логи. Я звик кидати все у stdout, але лог прив'язаний до процесу, який його написав: впав компонент — зник контекст. Event же прив'язаний до об'єкта (pod/web-0), а не до процесу. Тому можна спитати "що сталося саме з цим подом" і зібрати картину від різних компонентів — API, Scheduler, Agent — в одному місці. Це інший спосіб мислення: не "хто що залогував", а "що сталося з ресурсом".
І ще: timestamp у наносекундах як частина ключа спочатку виглядав як хак. Але саме він дав і унікальність, і сортування одним рядком. Іноді найпростіше рішення і є правильним.
На що звернути увагу¶
- Ключ — це
timestamp + object. Якщо два events на той самий об'єкт впадуть в одну наносекунду (малоймовірно, але можливо), один затре інший. Реальні системи додають у ключ ще й випадковий суфікс або UID. - Немає TTL: у нас events ростуть у BoltDB вічно. На довгому проді файл роздується, а
Last()+Prev()доведеться гортати все більше сміття. - Запис event — best-effort і не в одній транзакції зі зміною стану. Можна оновити статус ноди, але впасти до
RecordEvent— і подія загубиться. Event не є гарантією, що зміна сталася.
Що можна покращити¶
- Додати TTL/ротацію: видаляти events старші за N годин або тримати фіксовану кількість останніх (ring buffer).
- Зробити агрегацію повторюваних events із лічильником
countіlastTimestampзамість запису кожного дубля окремо. - Додати фільтрацію на рівні API (
?object=pod/web-0,?type=Warning) замістьgrepна клієнті. - Стрімити events через
watch(SSE або long-poll), щобsheepctl events -wпоказував їх у реальному часі, а не лише знімок.
Спробуй сам¶
sheepctl events
# Фільтруй по типу:
sheepctl events | grep Warning
# Через API:
curl -s localhost:9876/api/v1/events | jq '.[0:5]'
Серія Orchestrator завершена. Починаємо Image Registry — свій Docker Registry з нуля.
Ресурси¶
- Event API reference — Kubernetes Event v1
- events.k8s.io API — структуровані Event-об'єкти з агрегацією
- Audit logging — справжній security audit trail, окремо від Events
- kubectl: перегляд events — як оператори зазвичай їх читають
- Event sourcing — класична стаття Мартіна Фаулера (схожа ідея, інша мета)
- BoltDB cursor — як
Last()+Prev()обходить ключі у зворотному порядку
Вихідний код циклу: github.com/igorgorovoy/sheep-shepherd-meadow
Попередня: Pod Lifecycle | Наступна: OCI Distribution Spec
