イベントはログではない
一言でいうと
イベントは、Operatorがユーザーに語りかける唯一の場所ですが、保存期間が短く、同じ理由のものは1つにまとめられる要約の仕組みです。この性質に合わせて使ってはじめて、価値が生まれます。
なぜこの場所が必要なのか
Operatorを作っていると、こんな状況に出会います。コントローラーは、「イメージタグが存在しないため、調整を中止した」ことを知っています。ところが、その事実があるのは、コントローラーのPodのログだけです。カスタムリソースを作った開発者には、そのログを見る権限も、そんなものがあるという知識もありません。その人の画面には、何も起きないリソースが1つあるだけです。
statusを書けばよいのではないかと思いますが、statusは今の状態です。「今Readyではない」とは書けても、「10分前に1回失敗して、復旧した」は入りません。過程を残すには、時間軸を持つ別の場所が必要で、それがEventです。
どう動くのか
イベントはAPIオブジェクトで、ほかのオブジェクトと同じように、ネームスペースに保存されます。ところが、APIが2つあります。
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の1語(FailedScheduling、Pulled、BackOff)であり、ここに長い文を入れると、同じ出来事が毎回違う理由で記録され、集計が崩れます。詳しい内容は、messageの側です。
同じ理由の繰り返しは、新しいイベントではありません。イベントレコーダーは、同じ対象・同じ理由・同じメッセージに再び出会うと、オブジェクトをもう1つ作らずに、countを上げてlastTimestampを更新します。一覧の画面で(x12 over 3m)のように見えるのが、その結果です。調整ループが毎秒何回も回っても、etcdがイベントであふれない理由が、この集計です。
typeは、APIが検査しません。Criticalと書いても、オブジェクトは作られます。ところが、イベントを読むツールは、NormalとWarningの2種類しかないと想定しています。kubectl events --types=は、それ以外の値をまったく受け付けません。強制されていないのに、慣例を守る必要がある、典型的な場所です。
そして、イベントは消えます。kube-apiserverの--event-ttlのデフォルト値は1時間です。1時間後に調査を始めると、その出来事のイベントはありません。
現場での姿
1つ目は、ユーザーの画面に理由が出ないOperatorです。コントローラーのログには明確なエラーがあるのに、CRのdescribeには何もありません。ユーザーは問い合わせを入れ、運用者はログを取って貼り付けてあげます。イベント3行で済むはずのことを、人間のやり取りの往復で処理しているのです。調整の開始・成功・失敗だけでも残せば、問い合わせが大きく減ります。
2つ目は、イベントを監査記録として使った事故です。「誰がいつ何を変更したかは、イベントを見ればよい」と信じて設計したのに、いざ事故の調査に入ると、1時間が過ぎて何も残っていないというケースです。長く残す必要があるものは、statusの条件や、外部のストレージに送った記録でなければなりません。イベントは、今何が起きているのかを見る窓です。
3つ目は、スケジューラーの文がそのまま診断書であることです。Podが起動しないときに、人が実際に読むのは、0/3 nodes are available: 3 Insufficient cpuのような1行です。よいイベントメッセージの手本でもあります。数字が入っていて、何が足りないのか名前が入っていて、次に何をすべきか見当がつきます。Operatorのイベントも、この水準を目指す価値があります。
このラボ環境の限界
kwokクラスターのPodは偽物なので、kubeletが残すイベント(Pulling・Started・BackOff)は見られません。その代わり、スケジューラーは本物が動いているため、FailedSchedulingは実際に記録されます。そして、コントローラーのないカスタムリソースに対するイベントは、このラボで人が直接作って入れます。Operatorがするはずの仕事を、手で一度やってみるわけです。
次のラボですること
カスタムリソースを対象に、2つのAPIでそれぞれイベントを作成してみて、対象を抜かしたイベントがどう拒否されるか、慣例外のtypeがツールからどう消えるかを確認します。集計フィールドを直接上げて、一覧の画面の表記がどう変わるかを見て、スケジューラーが残す本物のイベントまで集め、最後に、イベントだけを読んで事故を再構成する表を作ります。