درباره‌ی یه سیستم live سه تا چیز infer کردم. دوتاش غلط بود.

kafka opentelemetry terraform troubleshooting devops

تو یه هفته، رو یه migration مشخص، سه بار درباره‌ی یه سیستم live ادعا کردم، به این خاطر که یه file رو خوندم. دوتا از سه تا ادعا غلط بود. اون یکی هم تصادفی غلط بود و با هزینه‌ی کم نجات پیدا کرد. مکانیزم اصلی اینه که artefact و سیستم live دو تا دید از یه چیزن، و می‌تونن به دلایلی با هم match کنن که artefact قادر نیست بگه‌شون. فقط اون دید live حقیقته.

اون consumer هفتم

یه canary migration سه تا signal سبز داشت. collector جدید اولین traffic اش رو log زده بود. span processor ها داشتن به broker جدید می‌نوشتن. اون شش تا service که از اون metric ها row دیتابیس در میارن، feed می‌شدن. fix شیپ شد. فقط بعداً یه sweep وسیع‌تر یه service هفتم پیدا کرد، یه billing consumer، که تموم اون هفته به همون صورت disconnect بود، با همون مکانیزم، و از اون scan اسم service که cleanup رو هل می‌داد جا مونده بود.

دو تا حقیقت همزمان برقرار بود. هر signal ای که تو اتاق بود genuine بود، و اون سیستمی که داشت عوض می‌شد یه حفره داشت که cleanup روش کاغذ چسبونده بود. اون فاصله، خودش این پسته.

چرا scan ازش رد شد

fix رو با یه consumer-name prefix محدود کرده بودم. شیش تا service match شدن. اون هفتمی اون prefix رو نداشت، پس sweep ندیدش. مکانیزمش رو باید با صدای بلند گفت: topic ها قراردادن, اسم‌ها یه convention ان, و یه convention می‌تونه غلط باشه. match کردن subscription ها با service-name prefix یعنی namespace رو به جای قرارداد گرفتن. namespace دروغ گفته بود.

هزینه جلوی چشم پنهون می‌شه، چون match رو case هایی که می‌بینه success می‌شه، و اون case ای که نمی‌بینه دقیقاً همونیه که تو report نمی‌یاد. یه sweep سبز با یه sweep کامل فرق داره. اون اثبات اینه که case هایی که convention اسم‌گذاریت پوشش می‌ده، پوشیده شدن. درباره‌ی case هایی که convention ازشون رد شده ساکته، و این یه مجموعه‌ی کوچیک‌تره که هیچ وظیفه‌ای نداره خالی باشه.

یه retraction که چرخش نبود

همون هفته، artefact متفاوت، همون failure mode. Kafka consumer group های collector رو از config template ها خونده بودم تا به این نتیجه برسم که stage prefix ندارن و وقتی broker-side ACL ها enforce بشن، denied می‌شن. رو cluster live enumerate کردم، تک‌تکشون prefix داشتن و covered بودن.

قاعده‌ی کلی هنوز سر جاش بود. یه billing-side consumer واقعاً prefix نداره و واقعاً وقتی rule ها سخت‌تر بشن می‌شکست. خطا speculative نبود؛ از یه ادعای مشخص درباره‌ی همین N تا group می‌اومد که داده‌ی live پشتش نبود. template ها شبیه حقیقت به نظر می‌رسن چون artefact ای هستن که platform بهت می‌ده، ولی rendering یکی دیگه از intention ان، نه یه قرارداد با broker. دلیلی که retraction خودش رو گرفت این بود که تصویر بزرگ‌تر همون migration یه جای دیگه شکسته بود، و خوندن live cluster از قبل reflex شده بود.

شکل جالب این اشتباه اینه که ساختنش ارزون بود و شیپ کردنش راحت: یه پاراگراف confident تو runbook، یه تیک رو review، یه تغییر convention اسم‌گذاری که به یه fleet اعمال می‌شه. هزینه‌ی غلط بودن صفر بود تا لحظه‌ای که یه query مصرف‌کننده‌ی دیگه به اون group بدون prefix می‌خورد و اون rule ای که واقعاً enforce نشده بود، silent اش می‌بست. هزینه‌ی درست بودن یه پاراگرافی بود که هیچکس دوبار نمی‌خوندش.

سومی ارزون

با inlining کردن یه Kafka SASL username تو یه values file مخالفت کردم چون SSM secret ها رو encrypted نگه می‌داره، یه objection که منطقی به نظر می‌رسید، و با یه چک یه‌خطی retract شد: codebase دقیقاً همچین یه literal یی تو یه values file دیگه داشت. convention وجود داشت؛ یه policy از رو یه default infer شده بود. این یکی ارزون گرفته شد و تقریباً ارزون‌تر از دست رفت، چون سومین بار تو سه روز بود که pattern-matching حرکت بعدی بود، و سومین miss یه هفته همونیه که operator خسته دیگه به خودش flag نمی‌زنه، چون مغز داره از قبل triage شون می‌کنه.

شکل تک‌تک این‌ها یکی‌ست: یه ادعای confident درباره‌ی سیستم live، sourced از artefact، که artefact واقعاً نمی‌تونه جوابش بده. همون شکل یه هفته سه تا لباس مختلف می‌پوشه.

چرا «از رو file استدلال کردم» حس یه جمله‌ی تمام‌شده رو نمی‌ده

artefact و سیستم live دو تا دید از یه چیزن، و دو تا دید می‌تونن به دلایل خیلی متفاوتی با هم match کنن. می‌تونن match کنن چون از همدیگه derived ان، که case کانونیکاله و تنها case ای که توش «file رو خوندم» یه جواب کامله. می‌تونن match کنن چون یکی‌شون edit شده و اون یکی نه، که همون شکل سه‌راه merge ـه و یه terraform plan داره flatten اش می‌کنه به یه diff تنها که شبیه drift معمولی به نظر می‌رسه. یا می‌تونن match کنن چون یکی‌شون به شکلی edit شده که اون یکی نمی‌تونسته ببینه‌ش، که همون cousin byte-identical این پسته: یه config که به یه source وفادار بوده که از اون موقع توسط یه binary دیگه خونده شده.

خوندن artefact بهت اون چیزی که باید می‌بود رو می‌ده. فقط سیستم live بهت اون چیزی که هست رو می‌ده. هر دوشون حس ground truth رو می‌دن. یکی‌شون خیلی کمتر از اونی که هر کدوم حس می‌کنن درسته.

اون check ها واقعاً شکل‌شون چیه

قاعده‌ی بخش قبل باید به چیزی ختم بشه که بتونی تایپش کنی. سه تا read کوچیک، یکی به ازای هر لایه از سیستم Kafka، هیچ‌کدومشون به template، prefix، یا config file وابسته نیستن. هر کدوم پایین با اون چیزی paired شدن که می‌خرنت، که مهم‌تر از flag set ـه.

consumer group ها رو cluster-wide enumerate کن. اون شکلی که consumer هفتم رو پیدا می‌کنه:

kafka-consumer-groups.sh \
  --bootstrap-server "$BROKER" \
  --describe --all-groups

flag ی --all-groups همون قسمتیه که consumer از دست رفته رو می‌گیره، چون با name prefix match نمی‌کنه؛ هر group ای که cluster می‌شناسه رو list می‌کنه، از جمله اون‌هایی که اسم‌هاشون به convention نمی‌خوره. این رو با --all-topics pair کن اگه broker بین stage ها share شده، تا read هر partition از هر topic رو در بر بگیره، نه فقط اون‌هایی که consumer config ات تصادفاً declare می‌کنه.

این همون read ایه که جای service-name prefix sweep رو می‌گیره. هر دو pass مثل inventory به نظر می‌رسن. فقط یکی‌شون cluster رو ground truth می‌گیره.

topic list رو به عنوان قرارداد بکش. وقتی artefact گفت consumer ها misalign ان، اولین چیزی که باید چک بشه اینه که چه topic هایی واقعاً رو cluster وجود دارن و partition count شون چقدره:

kafka-topics.sh \
  --bootstrap-server "$BROKER" \
  --list --exclude-internal

و بعدش --describe برای topic یا topic های مورد نظر، وقتی count مهمه:

kafka-topics.sh \
  --bootstrap-server "$BROKER" \
  --describe --topic "spans.${STAGE}"

این read ها قرارداد رو از broker می‌کشن، جایی که زندگی می‌کنه. config template ها بهت می‌گن چه چیزی باید وجود داشته باشه، که می‌برتت به اون cousin byte-identical این پست؛ دستور topic-list بهت می‌گه چی وجود داره. این دوتا اغلب diverge می‌کنن، و وقتی می‌کنن، broker درسته.

ACL ها رو از cluster live بخون. وقتی miss دوم گفت «consumer group ها deny می‌شن وقتی ACL ها سفت بشن»، اون read ای که با یه دستور جواب می‌داد این بود:

kafka-acls.sh \
  --bootstrap-server "$BROKER" \
  --list --topic "spans.${STAGE}"

این همون read ایه که inference بر پایه‌ی template داشت جاش رو می‌گرفت، و برای جواب دادن به سؤال نه به stage prefix احتیاج داره نه به config block. هر چی cluster می‌گه این principal مجاز به انجامش هست، همون کاریه که rule واقعاً می‌کنه. اگه environment ات از ACL به سبک prefix استفاده می‌کنه نه از resource name literal، با --resource-pattern-type prefixed pair اش کن، چون --list به طور default روی literal pattern هست و grant های prefix ای رو بی‌صدا پنهون می‌کنه.

سه تا read. هیچ‌کدومشون permission بیشتری از اون چیزی که یه operator با read-only cluster access از قبل داره نمی‌خوان. هیچ‌کدومشون بیشتر از چند ثانیه طول نمی‌کشن. هیچ‌کدومشون سخت نیستن برای به خاطر سپردن. سختی‌اش اینه که یادت بمونه قبل از file سراغ این‌ها بری.

قانون و check

از artefact infer کن، تو سیستم live verify کن. اون یکی رو به عنوان hypothesis باهاش رفتار کن، نه evidence.

اون consumer هفتم همون چیزیه که قانون بهت می‌خره، و check اونیه که بالا گرفتش: consumer group ها رو cluster-wide enumerate کن، all topics، all groups، بدون permission ask، پنج ثانیه wall-clock. اون fragment تقریباً مثل همون prefix sweep می‌خونه که داره عوضش می‌کنه؛ فقط حرف convention رو درباره‌ی این‌که کدوم subscription وجود داره باور نمی‌کنه. prefix یه حدس درباره‌ی یه قرارداد بود؛ topic خودش قرارداد بود.

check اونیه که به هفته‌ی بعد می‌رسه. مثال اونیه که memorize اش می‌کنه. هر دوشون رو با هم اسم بردن همون قسمتیه که اون سه تا «وایسا، این یکی از اون‌ها نیست» بعدی رو می‌خره، وقتی artefact و live برای اولین بار match نکردن، قبل از این‌که مغز وقت داشته باشه به reflex برگرده.

$ cat OPENTELEMETRY .md
· 7 دقیقه مطالعه

کانفیگ byte به byte عین production بود. همین چیز اشتباهی رو ثابت کرد.

یه config برای collector دوباره ساختیم، byte به byte با نسخه‌ی روی production مقایسه‌ش کردیم، مو نمی‌زد، و با یه عالمه چک سبز پشتش deploy کردیم. سر startup افتاد تو crash-loop. byte-identical یه خاصیت واقعیه؛ فقط جواب سؤالیه که هیچکس نپرسیده بود.

opentelemetry kafka terraform devops troubleshooting
$ cat TERRAFORM .md
· 8 دقیقه مطالعه

پلن سبز بود چون دو تا از سه دید با هم موافق بودن. اون یکی حقیقت بود.

یه terraform plan برای یه observability stack مدیریت‌شده بعد از یه incident fix دستی clean برگشت. fix زنده بود، fix تو file بود، و فقط state عقب بود. یه plan یه diff بین دو تا دیده، ولی سیستم سه‌تا داره. قبل از plan زدن هر سه‌تا رو خوندن همون چیزیه که diff سبز رو از حدس تبدیل به تصمیم می‌کنه.

terraform helm eks state troubleshooting devops
$ cat CDKTF .md
· 6 دقیقه مطالعه

باگی که تقصیرشو انداختم گردن provider

هفته‌ها یه terraform plan یه drift نشونم می‌داد که نمی‌تونستم درستش کنم، روی یه S3 bucket که از قبل درست config شده بود. گذاشتمش تو دسته‌ی «یه گیر عجیب از AWS provider». اما provider بی‌گناه بود. کد خودم داشت بهم دروغ می‌گفت، خیلی قبل‌تر از اینکه Terraform اصلاً ببینتش.

cdktf terraform iac typescript devops troubleshooting