終了は判決ではない
プロセスの不在に対してページを鳴らす監視は、すでに答えの見えている問いに答えています。ジョブが消えたかどうかは検出が安上がりで、しかも肝心なことであることはほとんどありません。それが失敗したのか、それともデプロイや再起動やオペレータによって意図的に止められたのかこそが、ページの分かれ目であり、むき出しの終了はその物語のごくわずかしか運びません。
そのページが来たのは夜明けを少し過ぎたころで、そして誤っていました。二十分ごとに走り、わずかな作業をこなして終了する、あるクラスに属する周期的な同期ジョブが、失敗フックを発火させてアラームを上げていたのです。通知は、プロセスが kill された、シグナル十五、ただの終了だ、と告げていました。実際には何も壊れてはいませんでした。夜のあいだにマシンは無人アップグレードを走らせており、そのアップグレードの過程のどこかで、それが降りてきたときに走っていたすべてのユニットへ停止シグナルが送られたのです。同期はその三十秒前に始まっていて、走行の途中でそれを受けました。二十分後の次のティックはクリーンに走り、その後のどれも同じでした。ジョブは一秒たりとも不健全ではなかったのです。しかも通知は空ではありませんでした。物語のすべて、シグナル番号、プロセスがエラーを返したのではなく kill されたという事実を運んでいました。監視はそのすべてを持っていながら、それでも意図的なシャットダウンを失敗と呼びました。この種のユニットでは、監視が当てはめた規則が、終了シグナルを障害として読んだからです。
これは、監視にできる最も安上がりなこと、つまり何かが消えたと気づくことの内側に潜む故障モードです。不在は検出しやすい。それはまた、ほとんどいつもページを鳴らすには誤った対象でもあります。なぜなら不在は原因ではなく、その影だからで、原因は、監視の立つ場所からは同じに見える二つの山に分かれます。一方の山では作業が壊れています。バグ、悪い入力、消えてしまった依存。もう一方では作業が外から終わらされていて、たいていはそれを終わらせてよい何かによってです。フリートを順に回すデプロイ、再起動、手で停止を走らせるオペレータ、古びたプロセスだと見なして取ったものを回収するアイドルスイープ、私の同期を捕まえたあのアップグレード。プロセスがまだそこにあるかだけを問う監視は、その問いには両者を見分けるすべがないので、両方の山に対して同時にページを鳴らします。代わりにユニット自身の失敗判定につながれた監視は、その判定と同じだけしか良くなく、夜明けのページは、誤って較正された判定がどう見えるかそのものです。いずれにせよ、ページが左右されるべきは不在という事実ではなく、その出所です。何がプロセスを終わらせたのか、そしてそれは終わらせてよいものだったのか。
ページャがうるさくなったときの反射は、時間に手を伸ばすことです。一つのサンプルでページを鳴らすな。それが居座るかどうか、待って見よ。この本能は健全で、道具にも組み込まれています。for 句を持つ Prometheus のアラート規則は、発火条件を pending の状態に留め、その条件が全期間にわたって連続して真であり続けて初めて、それを本物のアラートへ昇格させます。for のない規則は、それを引き当てた最初の評価で鳴り出します。そして時間は、ある形の非失敗にとっては正しい軸です。外部 API への周期的なポーリングは一度タイムアウトします。ネットワークはネットワークだからで、そしてまさに次の試行で自ら回復します。その一度のちらつきで人を呼び出すのは、読まずにページャを払いのけることを人に教え込むやり方なので、代わりに連続を待ちます。それが本物だと判ずる前に、一時間の到達不能を。それは時間が、まさに得意な仕事、一過性を篩にかけて傾向へと落とし込む仕事をしているのです。
けれども時間は、夜明けのページに対してはせいぜい部分的な直しにすぎず、なぜかを正確に言っておく価値があります。それが答えのすべてであるべきに見えるからです。for 句は不在を、それがどれだけ続くかで並べ替えます。そして持続時間は正当性の代理でしかなく、決してその読み取りではありません。代理の一部は成り立ちます。長時間走るサービスを順に回すデプロイは数秒消え、再起動は一、二分で、数分の for はそのどちらもやり過ごします。これは本当に役立ち、日常的な停止のかなりの割合を覆います。しかしそれを買えるのは、あらゆる本物のページを同じ窓のぶんだけ遅らせることによってだけで、正当な停止が短くない瞬間に目が見えなくなります。保守のために落とされ、落としたままにされたユニットは、何時間も続くクリーンで意図的な不在です。走行の途中で止められた周期的なジョブは次のティックまで消えたままで、私の二十分の同期にとってそれは、十分の for がまっすぐ突き抜けてそれでもページを鳴らす、長くクリーンな沈黙です。長く待つことは一過性を持続から分けます。それは失敗したものを意図されたものから分けることはできません。正当な停止はクラッシュと寸分たがわず持続しうるし、しばしばそれ以上だからです。
ページャが実際に必要とする区別は、より長いことではなく、より深いことです。プロセスがなぜ消えたのかを知らねばなりません。驚くべきは、オペレーティングシステムがその答えの一部をすでに携えていて、そのうえで、ある種のユニットについてはそれを捨ててしまうことです。systemd は、ふつうの長時間走るサービスを監督するとき、SIGTERM による終了を既定で成功として扱います。その終了ステータスの規則は、通常の終了コードであるゼロをクリーンと数え、oneshot でないあらゆるサービスについては、シグナル SIGHUP、SIGINT、SIGTERM、SIGPIPE もクリーンと数えます。その既定は、プロセスには偽造できない出所を、監督者が記録しているということです。プログラムが自分について何を言ったかではなく、どのシグナルが着いたか、そして悪い戻り値ではなくシグナルこそがそれを終わらせたのだ、ということを。あなたが停止を頼んだサービスへ送られる停止シグナルは障害ではなく、契約もまさにそう言っています。しかしその既定は例外つきで書かれていて、oneshot 型に対してです。そして、始まって、区切られた一片の作業をこなし、終了するジョブは、まさに oneshot です。それらのユニットについては停止シグナルがクリーンな集合から外へ戻され、デーモンにとっては行儀のよいシャットダウンを意味するまさにその SIGTERM が、周期的なジョブにとっては失敗を意味し、失敗フックが火を噴きます。直しは、その区別を手で取り戻すことです。SuccessExitStatus=SIGTERM を通じて、このユニットでは終了シグナルがクリーンな終わりとして数えられると宣言するのです。そうすれば外部からの停止はアラームを鳴らさなくなり、一方で本物の障害、ジョブが返したゼロ以外のコード、より手荒な SIGKILL によるメモリ不足の kill は、なお鳴り続けます。これはアラームを黙らせはしません。アラームに、それ単独では引けなかった一つの区別、失敗した対終わらされた、の代役となる規則を手渡すのです。そしてその規則が代役としてどれほど良いかが、ここから先が拠って立つ問いであることが分かります。
これを、似ている問題の隣にしまい込むのは簡単なので、線を引いておく価値があります。近くにある議論、すなわち 長時間走るエージェントを見張るのに存在チェックは誤ったやり方だ、なぜならそれは懸命に考えているエージェントと永遠に詰まったエージェントを見分けられないから、という主張があり、そこでの直しは、プロセスが存在するかを問うのをやめ、作業が残す持続的な痕跡から進捗を読み取ることです。それは本物の直しで、作業がまだ走っていて、読むべき痕跡をまだ残しているあいだは効きます。ページは逆の状況です。当のものは消えています。それを書くはずのプロセスがそこにいないので、まだ書かれつつある痕跡はなく、何も進んでいないので、参照すべき進捗もありません。残るのは去り方だけです。終了コード、シグナル、誰がそれを送ったのかについて監督者が記録した何もかも。不在にとって、出所はあなたが読むと選べる複数のシグナルのうちの一つではありません。それは存在する唯一の証拠であり、そのうち信を置くに値する部分は、まさに去ったプロセスが著さなかった部分です。こうして二つの判断は離れ、離れたままになります。ヘルスチェックは、生きているものが動いているかを問い、それを知るために痕跡を読みます。ページャは、終わりが失敗だったのか選択だったのかを問い、終わり方そのものの形のほか読むものを持ちません。
systemd から引いて見れば原理は素朴で、そして少し居心地が悪い。終了コードは、監視が一度も見ていなかった物語の一バイトの要約であり、しかも悪いことに、その主体が自分について書いた要約です。これは分散したものを運用することから来る、古くて苦労して得た戒めです。あるコンポーネントに健全かどうかを尋ねて答えをそのまま信じてはいけません。壊れていてそれを知らないコンポーネントこそ、その言葉が最も値打ちのないものだからです。出所は、プロセス以外の何かがそれを記録した度合いにちょうど応じて信頼できます。SuccessExitStatus がその仕事をなしうるのは、監督者が物語のアウトオブバンドの半分、どのシグナルが着いたか、そしてプログラムではなくカーネルがそれを届けたということ、を保っていたからにほかなりません。プログラムが選ぶ半分、去りぎわに手渡す戻り値は、あるものの自分についての申告を信じないあらゆる理由を受け継ぎます。ですから、こなせる場面で割に合う一手は、失敗しつつあるプロセス以外の何かが記録した事実に対してページを鳴らすこと、そしてそのような事実の付いていないむき出しの不在を、判決ではなく問いとして保つことです。監督者がメモリ不足の kill を記録したユニットは、あなたが後ろ盾になれるやり方で失敗しています。ただ消えただけで、なぜについて何も記録されていないユニットは、問いを立てたのであり、問いはまだページではありません。これはアラートの世界の最も古い規律を、不在へと向け直したものです。人を起こすのは、その人が手を打たねばならないことのためであって、閾値を越えるあらゆる事象のためではない、と。
けれどもそれを、ジョブがそもそも一度も走らないときに黙っていてよい許可へと固めてはいけません。それはすべての中で最も危険な不在です。分類すべきシグナルを何も生まないからです。むき出しの不在に決してページを鳴らさないことの補完は、成功の不在に対してページを鳴らすことです。古びてしまった最終正常完了のタイムスタンプは、肯定的で、帰属可能な事実です。作業はあなたが名指せる時刻から成功していない、というもので、それは、静かにスケジュールから外れたジョブ、あるいは失敗してゼロで終了したジョブを、当のものがついぞ出さないかもしれない苦情を出すのを待たずに、ふたたび声高にします。
その再分類こそ、正直さが宿らねばならない場所です。出所をシグナルの形で符号化することは、それを観測することと同じではないからです。クリーンな集合を広げて SIGTERM を含めれば、あなたは、壊れた何ものもそのまさに扉から出ていくことは決してない、と賭けているのです。たいていは何も出ません。本物の障害は、ゼロ以外のコードを返すか、マシンのメモリを食い尽くして、あなたの規則が覆わない、より手荒な kill をくらう傾向があります。しかし賭けには縁があります。たまたま SIGTERM を経て終了する障害、シグナルを飲み込んでクリーンに戻るハンドラ、実は誤った理由で出された停止、ただ静かなだけのプロセスを回収するアイドルスイープ。これらはいまや、規則がそのシグナルに与えた無実を受け継ぎます。ハングはそもそもシグナルを残さないので、この規則はそれを決して見ません。開始タイムアウトの設定されていない oneshot は、一度も失敗しないまま永遠に詰まって座っていられます。それは隣にあるヘルスチェックの問題であって、ページャのものではありません。そして、あなたがページの山に残したメモリ不足の kill は、それ自体が判断の分かれる話です。隣人の食欲のために kill されたプロセスは、壊れたというより、容量をめぐる論争に負けたのですから。これらのどれも、この取引を見送る理由ではありません。あなたは、大きく確実な偽のページの源、ユニットの一生にわたるあらゆるデプロイとあらゆる再起動を、小さくまれな偽の沈黙の源と引き換えました。そして次のティックで自ら回復するジョブにとって、それはほとんどいつも正しい交換です。それが何でないかといえば、読み取りではありません。賭けよりうまくやれるとき、そのやり方は、出所を推し量るのをやめ、それを源で記録し始めることです。デプロイに、スイープに、保守の窓に、このユニットを、この時刻に、この理由で停止した、と書き留めさせるのです。そうすれば不在はそもそも問いではなくなります。
ページャは、スタック全体の中で、その全目的が人の注意を費やすことにある唯一の計器であり、そして、手に入る最も安上がりなシグナル、すなわちむき出しの不在で発火するように作られていることが最も多い計器です。何かが意図的に停止されるたびに鳴るアラームは、安全装置ではありません。それは、アラームは何も意味しないということを、辛抱強く効果的に運用者に教え込む機械であり、その教えが最もさえるのは、日常的なアップグレードの翌朝、夜明けどきです。代わりにそれが答えるべきは、ページャが人を起こすその瞬間に人が実際に抱く問い、すなわち、消えたのか、という自分で見て取れることではなく、失敗したのか、それとも何かがそれを終わらせたのか、であり、そして、二つが本当に見分けられない唯一の場所では、その答えは読み取りではなく賭けなのだと認めることです。終了は判決ではありません。それは一つの証拠であり、規律のすべては、それが何の証拠なのかを知るまでページを鳴らすのを拒むことにあります。