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

terraform helm eks state troubleshooting devops

یه terraform plan برای یه observability stack مدیریت‌شده صفر برگشت. on-call دو هفته قبل تو یه incident دستی fix اش کرده بود. fix زنده بود. fix تو file بود. فقط state عقب بود. یه apply هر خط incident-mitigating رو تو یه shot revert می‌کرد. اون «کاری نیست» پلن، تو این مورد از «no drift» قابل تشخیص نبود. این داستان یه پلن سبز نیست که یه مشکل واقعی رو گرفته باشه؛ داستان یه پلن سبزه که یکی رو قایم کرده.

یه plan یه diff ـه. سیستم سه تا دید داره

یه plan معمولی یه diff بین دو چیزه: state و file. «state» آخرین apply موفقه؛ «file» اون چیزیه که الان می‌نوشتی اگه از الان شروع می‌کردی. plan هر وقت state و file با هم match کنن، unchanged render می‌کنه، که همون سبزیه که هر operator یاد می‌گیره بخوادش.

سیستم سه تا source of truth داره، و فقط دوتاشون تو diff ان. live اون چیزیه که داره اجرا می‌شه، cluster یا workload یا storage bucket. state اون چیزیه که Terraform یا Helm آخرین بار ثبت کردن. file اون چیزیه که repo الان می‌گه. هر کدوم جداگانه author می‌شه. هر کدوم می‌تونه drift کنه. plan اون یکی اولی رو ignore می‌کنه.

سه تا source می‌تونن به دلایل خیلی متفاوتی با هم match کنن. می‌تونن match کنن چون هر سه از همون file derived ان (کیس کانونیکال، که یه plan سبز درست گزارش می‌ده). می‌تونن match کنن چون state عقبـه و file و live جلو افتادن (کیسی که خطرناک‌ترین drift رو قایم می‌کنه، چون state-عقب از دید terraform plan با no-drift یکی به نظر می‌رسه). می‌تونن match کنن چون file عقبـه و live و state با هم match ان (کیس «من هیچ‌وقت تغییر رو ننوشتم»، که plan می‌خونه‌ش به عنوان drift تو direction اشتباه). می‌تونن match کنن چون live عقبـه و state و file match ان (کیس «platform revert اش کرد چون crash کرد»، که plan دوباره drift به سمت file می‌خونه‌ش).

دو دید که با هم match کنن در حالی که با اون یکی divergence داشته باشن، همون کیسیه که یه plan سبز silent پوشش می‌ده. plan یه diff روی comparison بین دو چیزه، و اون سومی رأی نمی‌گیره.

اولین exhibit: observability stack مدیریت‌شده

وسط یه incident بود. on-call یه helm upgrade دستی روی release live زده بود؛ values تو cluster landing اومده بودن ولی file تو repo update نشده بود. incident بسته شد، تیم آخر هفته catch up کرد و file رو update کرد تا match بشه، و رو surface همه چی reconcile شد. بعد فاز planning تغییر بعدی اومد، و terraform plan clean برگشت.

کیس بدترین شکل برای silent agreement بود: live به file match می‌کرد (هردو fix رو reflect می‌کردن)، state قبل از hand-upgrade نوشته شده بود و هیچ‌وقت update نشده بود، و plan state-vs-file رو به عنوان no diff render کرد چون state و file تو همه چیز غیر از post-incident fix با هم موافق بودن؛ تو file جدید fix تنها diff بود، و state هنوز pre-incident شکل رو نگه داشته بود. هیچ‌کدومش کار plan نبود. کار plan اینه که diff بین state و file رو حساب کنه، و تنها diff اون‌جا fix جدیدی بود که file توش گنجونده بود و state نداشت. پس plan به درست گزارش داد: این file می‌خواد این fix رو apply کنه.

این همون لحظه‌ایه که سبز خطرناک می‌شه. «کاری نیست» خوندن درست می‌بود اگه state حقیقت بود و file بهش match می‌کرد. «کاری نیست» خوندن درست می‌بود اگه live، state، و file همه pre-incident شکل رو نگه می‌داشتن. «کاری نیست» خوندن غلط بود تو اون لحظه‌ای که live post-incident شکل رو نگه داشت و فقط state divergence داشت، چون یه apply هر خط incident fix رو تو یه shot revert می‌کرد.

window ای که live و file در برابر یه state-عقب backend با هم match می‌کردن، همون bug ـه. آسون missed می‌شه، چون live و file واقعاً match می‌کنن، و یه plan سبز رؤیاست. postmortem می‌خوند «apply تو تغییر بعدی production fix رو revert کرد»، و درست بود، و می‌تونست گرون‌ترین framing ممکن از یه misunderstanding ای باشه که plan خودش نمی‌تونست بگیره.

دومین exhibit: cluster Kubernetes مدیریت‌شده، یه روز بعد

همون شکل، سطح متفاوت. terraform plan repo برای یه cluster Kubernetes مدیریت‌شده با یه تغییر concrete تنها برگشت: یه node-group که general_arm رو از 6 به 4 resize می‌کرد، یه drain ظرفیت واقعی که عمداً request شده بود. اون تنها diff تو output بود. اون همه‌ی حقیقت هم نبود.

سه‌تا از node-group های دیگه روی همون cluster، state-عقب بودن. یه code sync یه هفته قبل land شده بود؛ live با hand-out-of-band edits تو یه upstream change update شده بود، live از قبل به file match می‌کرد؛ فقط state هیچ‌وقت config جدید رو ندیده بود. plan state-vs-file رو no diff render کرد چون state و file match بودن، و apply بعدی live شکل رو back به هر چیزی که state فکر می‌کرد cluster یه هفته قبل شکل داشت revert می‌کرد.

اون یه diff بلند operator attention رو dominate کرد، که دقیقاً نکته‌ست. operator ها تمایل دارن اولین diff ای که می‌بینن رو بخونن، اون رو تو ذهن check کنن، و بقیه رو زیر «no changes» file کنن. silent ها هیچ‌وقت تو diff نمی‌یان چون تو diff نیستن. cluster یه apply ظرفیت واقعی می‌زد و silent سه group دیگه رو به یه شکلی revert می‌کرد که هیچ‌کس نگاه نمی‌کرد. postmortem می‌خوند «apply changes ناخواسته رو با خودش drag کرد»، و درست بود، و cause رو به اون diff بلند نسبت می‌داد به جای اون سه‌تای silent.

هردو exhibit همون مکانیزم رو walk می‌کنن. چیزی که عوض می‌شه اینه کدوم دو دید match می‌کردن. exhibit اول live-و-file-موافق، state-عقب بود. exhibit دوم live-و-state-موافق، file-عقب بود. مکانیزم همونه: silent agreement دو-موافق-یکی-مخالف، plan سبز، apply اون چیزی رو revert می‌کنه که سیستم live داشت و state نمی‌دونست.

چرا یه plan نمی‌تونه ببیندش

terraform plan با state و file مشورت می‌کنه. تو apply time با live مشورت نمی‌کنه. helm template تو plan time با live مشورت نمی‌کنه. هیچ‌کدوم از دو ابزار برای verify کردن سیستم live طراحی نشدن؛ هردوشون برای verify کردن این طراحی شدن که با given آخرین state شناخته‌شده چی عوض می‌شد. اون three-way merge ای که platform نمی‌زنه همونیه که خودت باید بزنی، قبل از plan، نه به عنوان بخشی ازش.

شکل limitation بین ابزارها consistent ـه. هر چیزی که بیرون از model پلتفرم یه جایی ثبت شده باشه، همون چیزیه که plan نمی‌تونه ببیندش. برای Terraform، اون live API ـه. برای Helm، اون release live ایه که helm template query نمی‌کنه. برای kubectl diff در برابر server-side apply، اون admission controller ـه که هر چی modify کرده local dry-run هیچ‌وقت نمی‌بیندش. ابزار plan و platform دارن به دو source مختلف توجه می‌کنن؛ اون source سوم هر کدومیه که user یادش می‌ره mention کنه.

یه plan که clean برمی‌گرده یه گزارش نیست که live به file match می‌کنه. یه گزارشه که state به file match می‌کنه، که information ایه که operator باید به یه سؤال دیگه تبدیل کنه، اون سؤال این‌که live چی داره می‌کنه که هیچ‌کدومشون نمی‌دونن.

راه واقعی برای خوندنش

سه read ارزون در برابر سه source، به ترتیب: state از backend، file از disk، live از API. بذارشون کنار هم. کیسی که دو تاشون موافق ان و یکی شون divergence داره، همون کیسیه که یه plan سبز silent پوشش می‌ده. کیسی که هر سه موافق ان، همون کیسیه که یه plan سبز درست گزارش می‌ده. تشخیص دادنشون هزینه‌اش یه state pull و یه read ـه، no apply، no review latency.

برای Terraform:

# Read 1: state
terraform state pull
# Read 2: file
terraform plan -no-color
# Read 3: live
# هر چیزی که platform expose می‌کنه (مثلاً aws eks describe-cluster ...)

نکته command ها نیستن. نکته اینه که سه read با هم answer می‌دن «کدوم دید عقبـه»، که همون سؤالیه که یه plan سبز تنها نمی‌تونه جواب بده.

برای Helm:

# Read 1: state
helm get values <release> -n <ns>
# Read 2: file
helm template <release> <chart> -f values.yaml
# Read 3: live (بعد از dry-run)
helm diff upgrade <release> <chart> -f values.yaml

هر جفت read رو با چشم می‌شه cross-check کرد. هر جفتی که در برابر یه read سوم divergence داره که با هیچ‌کدوم match نمی‌کنه، همون کیسه که pause کردن روش می‌ارزه. read ها بار اول setup شون بیشتر از run شون طول می‌کشه؛ بعد از بار اول یه script ان، و script اون check ایه که قبل از هر plan اجرا می‌شه، و check همون چیزیه که diff سبز رو از حدس به تصمیم تبدیل می‌کنه.

باارزش‌ترین لحظه برای اجرای این همون لحظه‌ایه که داری یه plan رو «کاری نیست» صدا می‌کنی بعد از این‌که اخیراً hand-fix هایی روی همون سیستم بوده. این همون لحظه‌ی تنهاست که یه diff سبز احتمالاً بیشتر غلبه تا درست، و همون لحظه‌ی تنهاست که خوندن دید سوم ارزونه. هزینه ثابته؛ زمان‌بندی همه چیزه.

قانون و دوتا

قبل از plan هر سه source رو بخون؛ plan یه diff روی gap ـه، statement حقیقت نیست.

contribution exhibit اول اینه که سبزترین plan به عنوان «کاری نیست» خونده شد، و هزینه‌ی غلط بودن revert کردن fix ای بود که cluster رو survivable کرده بود. contribution exhibit دوم اینه که بلندترین diff تو plan اون‌های silent رو drown کرد، و operator ها تمایل دارن اولین diff ای که می‌بینن رو بخونن. اون‌ها همون شکل رو دارن چون مکانیزم همونه؛ چیزی که فرق داره اینه کدوم دید عقب بود.

دفعه‌ی بعد که یه plan بعد از یه hand-fix یا out-of-band edit روی همون سیستم unremarkable برگشت، سؤال این نیست «این درسته؟». سؤال اینه کدوم از سه دید اون یکیه که عقبـه. جواب هر چی source سوم بگه، و خوندنش کمتر از نوشتن این پست طول می‌کشه.

$ cat KAFKA .md
· 8 دقیقه مطالعه

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

تو یه هفته سه بار درباره‌ی یه migration زنده یه ادعا کردم، از روی یه config file، یه name prefix، یا یه template، و دوبار اون ادعا غلط بود. artefact و سیستم live دو تا دید از یه چیزن، و می‌تونن به دلایلی با هم match کنن که artefact نمی‌تونه بگه‌شون. فقط یکی‌شون حقیقته.

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

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

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

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

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

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

cdktf terraform iac typescript devops troubleshooting