تحلیل حادثه: کاوشگری که دروغ میگفت
یک قطعی چهلودو دقیقهای که علتش نه پایگاهداده بود و نه شبکه، بلکه یک readinessProbe بود که همیشه ۲۰۰ برمیگرداند.
در ساعت ۱۴:۰۳ روز سهشنبه، نرخ خطای سرویس پرداخت یکی از مشتریان از صفر به هفتاد درصد رسید. کوبرنتیز هیچ Pod ناسالمی گزارش نمیکرد. تمام Replicaها Ready بودند.
خط زمانی
- ۱۴:۰۳ — انتشار تدریجی نسخهی ۲.۱۴.۰ به گام پنجاه درصد رسید.
- ۱۴:۰۵ — هشدار نرخ خطا فعال شد. نفر آمادهباش پذیرفت.
- ۱۴:۱۱ — بازگشت به نسخهی قبل آغاز شد. خطاها ادامه یافتند.
- ۱۴:۲۹ — مشخص شد Connection Pool پایگاهداده در تمام Podها اشباع شده است.
- ۱۴:۴۵ — سرویس با
maxPoolSizeافزایشیافته بازگردانده شد. خطاها متوقف شدند.
چه چیزی شکست
کاوشگر آمادگی سرویس به مسیر /healthz وصل بود که فقط یک 200 ثابت برمیگرداند:
app.get('/healthz', (_req, res) => res.status(200).send('ok'));این کاوشگر هرگز نمیتوانست شکست بخورد. وقتی Pod تازه بالا میآمد و هنوز نتوانسته بود اتصال پایگاهداده بگیرد، کوبرنتیز آن را آماده اعلام میکرد و ترافیک به سمتش میفرستاد. Podی که نمیتوانست کار کند، ترافیک میگرفت و آن را دور میریخت.
بدتر از آن، هنگام بازگشت، همان اتفاق برای Podهای نسخهی قدیمی هم افتاد: آنها هم بلافاصله آماده اعلام شدند و پیش از گرم شدن Pool، سیل ترافیک را دریافت کردند.
اصلاح
کاوشگر آمادگی حالا وابستگیهای حیاتی را با یک مهلت کوتاه بررسی میکند — اما کاوشگر زندهبودن این کار را نمیکند:
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 2
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 20
failureThreshold: 6این تمایز مهم است.
readyzمیپرسد «آیا الان میتوانم پاسخ بدهم؟» وhealthzمیپرسد «آیا اصلاً زندهام؟». اگرhealthzهم پایگاهداده را بررسی میکرد، کندی پایگاهداده باعث ریاستارت همزمان تمام Podها میشد و یک قطعی چهلودو دقیقهای به یک قطعی چند ساعته تبدیل میشد.
اقدامات
سه اقدام اصلاحی، هر کدام با مالک و تاریخ. مهمترینشان این نبود که این سرویس را درست کنیم — این بود که یک بررسی conftest اضافه کنیم که هر readinessProbe مشترک با livenessProbe را در خط لوله رد کند. حادثهای که فقط برای یک سرویس رفع شود، برای بقیه هنوز در راه است.