اون EKS 401 که هیچ ربطی به credential نداشت

kubernetes eks aws devops platform-engineering troubleshooting

اولین prod rollout از pipeline دیپلوی جدید ما، توی همه step ها به جز آخری سبز بود. identity check، chart pull، update-kubeconfig؛ همه‌ش موفق بود. بعدش helm upgrade با یه خط fail شد:

Error: Kubernetes cluster unreachable: the server has asked for the client to provide credentials

اولین چیزی که همه بهش می‌رسن اینه که «AWS access key رو rotate کن.» این instinct اشتباهه. error از AWS نمیاد. token صادر شد، sign شد، و به kube-apiserver ارائه شد؛ apiserver اون رو رد کرد. کلاستر هیچ RBAC ای برای principal ای که داشت درخواست می‌داد نداشت.

این شکل واقعی failure هست، end-to-end، روی یه pipeline سالم که هیچی rotate نشده، هیچی expire نشده، و توی لایه credential همه‌چیز درست بوده.

چرا instinct اشتباهه

کلمه «credentials» توی این error گمراه‌کننده‌ست. این message از client-go میاد، client library خود Kubernetes، نه از AWS STS. failure های AWS credential خیلی زودتر ظاهر می‌شن با یه message متفاوت، معمولاً چیزی شبیه exec: ... could not get token، و توی step aws eks get-token fail می‌شن، نه توی helm upgrade.

توی trace ما، همه‌چیز قبل از step helm کار کرد:

  • IRSA role مربوط به runner، از طریق AssumeRoleWithWebIdentity، cross-account deploy role رو assume کرد.
  • deploy role chart رو از operations ECR pull کرد.
  • update-kubeconfig یه kubeconfig معتبر نوشت که به prod کلاستر اشاره می‌کرد.
  • aws eks get-token یه token برگردوند.

token قابل صدور بود. token قابل ارائه بود. بعدش کلاستر token رو رد کرد. این یه 401 هست، و یه 401 یه مشکل authorization هست، نه یه مشکل authentication به معنای AWS ای.

مدل ذهنی سه‌لایه

«Credentials» توی یه pipeline دیپلوی EKS می‌تونه توی سه لایه متفاوت fail بشه، و فقط یکیش این wording رو تولید می‌کنه. یه راه سریع برای جدا کردنشون:

لایهکارش چیهmessage failureاولین check
AWS STSsession رو برای runner یا deploy role صادر می‌کنهcould not get token، ExpiredToken، AccessDeniedaws sts get-caller-identity --profile <profile>
access control ‌EKSIAM principal رو به یه identity ‌Kubernetes مپ می‌کنهserver has asked for the client to provide credentials (با token معتبر)aws eks list-access-entries --cluster-name <c>
RBAC ‌Kubernetesبه identity مپ‌شده اجازه می‌ده کار خاصی انجام بدهForbidden، cannot ...kubectl auth can-i --list

یه 401 از apiserver توی لایه وسط می‌شینه. یه 403 پایین می‌شینه. connection refused یا timeout یه مشکل network یا kubeconfig هست و اصلاً توی این جدول نیست. وقتی بفهمی روی کدوم لایه هستی، بقیه diagnostic مکانیکیه.

دو تأیید توی سی ثانیه

قبل از اینکه دنبال هر fix بری، سمت AWS رو به‌عنوان سالم verify کن تا آخرش key ای rotate نکنی که هیچ‌وقت مشکل نبود:

# سمت A: AWS identity ای که deploy role تو assume می‌کنه
aws sts get-caller-identity --profile <spoke-account>

# سمت B: خود کلاستر می‌تونه برای اون identity یه token صادر کنه
aws eks get-token --cluster-name <cluster> --region <region> --profile <spoke-account>

اگه هر دو موفق بودن، تو با یه مشکل credential سر و کار نداری. با یه مشکل access entry سر و کار داری. token توی سمت B دقیقاً همون token ای هست که kube-apiserver می‌بینه؛ اگه سمت B یه token برگردونه، kube-apiserver هم یکی می‌بینه. token مشکل نیست.

بعدش چک کن که کلاستر واقعاً کدوم principal ها رو می‌شناسه:

aws eks list-access-entries \
  --cluster-name <cluster> \
  --region <region> \
  --profile <admin-profile>

اگه principalArn مربوط به deploy role توی این لیست نیست، کلاستر هیچ رکوردی براش نداره. هر request ای که این role بزنه، توی لایه access control رد می‌شه، صرف‌نظر از اینکه token چقدر معتبره.

EKS access entry ها واقعاً چی هستن

access entry های EKS جایگزین API-side برای ConfigMap ‌aws-auth هستن. موقعی معرفی شدن که AWS الگوی aws-auth رو deprecate کرد؛ الگویی که IAM role ها رو به user ‌Kubernetes مپ می‌کرد با دستی edit کردن یه فایل YAML توی kube-system. این migration سال‌هاست داره ادامه پیدا می‌کنه؛ خیلی از کلاسترها هنوز با authenticationMode: CONFIG_MAP کار می‌کنن و به aws-auth تکیه می‌کنن، بعضی‌ها با API_AND_CONFIG_MAP دوره گذار، و کلاسترهای جدیدتر فقط API.

توی هر mode ای که API رو شامل بشه، زنجیره اینه:

  1. IAM principal (role تو) یه EKS token presign شده ارائه می‌ده.
  2. apiserver token رو در برابر STS validate می‌کنه.
  3. apiserver principal رو توی جدول access entry lookup می‌کنه.
  4. اگه principal پیدا بشه، access policy های متصل‌شده به گروه‌ها یا user های Kubernetes مپ می‌شن.
  5. اون گروه‌ها و user ها بعدش تحت RBAC معمول Kubernetes قرار می‌گیرن.

یه 401 توی step 3 یعنی lookup خالی برگشته. principal سمت AWS معتبره و token هم سمت STS معتبره؛ کلاستر فقط هیچ row ای براش نداره. token ارائه می‌شه، lookup fail می‌شه، apiserver درخواست credential می‌کنه (چون هیچی برای مپ کردن نداره)، و client-go این رو به‌عنوان «server has asked for the client to provide credentials» به user گزارش می‌ده.

دقیقاً همین wording وقتی ظاهر می‌شه که یه role انسانی بخواد روی یه کلاستر fresh ‌recreate شده کار کنه. identity creator کلاستر implicit admin می‌گیره و کار می‌کنه؛ بقیه به یه entry نیاز دارن.

gap از کجا اومد توی مورد ما

ماژول EKS ما یه map به اسم access_entries می‌گیره. هر entry این شکلیه: { principal_arn = "..." }، و ماژول aws_eks_access_entry رو به‌علاوه یه association پیش‌فرض AmazonEKSClusterAdminPolicy توی cluster scope می‌سازه. اضافه کردن یه key به این map تنها تغییر code-side ای هست که لازمه تا به یه deploy role دسترسی کامل به یه کلاستر بدی.

entry مربوط به CI deploy role موقع rollout ‌EKS ‌prod به map اضافه شد. entry خواهر dashboard-admin توی همون block از قبل روی کلاستر بود، که ثابت می‌کرد block حداقل یه بار apply شده بود. entry مربوط به deploy بعد از اون آخرین apply اضافه شد. «edit بدون apply» یکی از راحت‌ترین اشتباه‌هاییه که می‌شه با Terragrunt کرد، چون plan output برای یه entry جدید تنها کوچیک و تمیزه و هزینه skip کردن apply «فقط همین یه بار» نامرئی می‌مونه تا اولین deploy بهش نیاز پیدا کنه.

sandbox و pre هر دو کار کردن چون entry ‌شون خیلی قبل‌تر از این gap apply شده بود. prod اولین deploy از طریق pipeline جدید بود، پس gap فقط اونجا برای اولین بار ظاهر شد. envهای قبلی درگیر نبودن.

fix

terragrunt apply --all --target <env>/eks

apply فقط موقعی strict additive هست که هیچ principal دیگه‌ای که declare شده از قبل click-ops وجود نداشته باشه. توی مورد ما entry ‌های خواهر dashboard-admin و viewer-role از قبل روی کلاستر live بودن، پس apply با ResourceInUseException fail می‌شد اگه cold اجراش می‌کردیم. اولین کار verify کردن state زنده کلاستره:

aws eks list-access-entries \
  --cluster-name <cluster> \
  --region <region> \
  --profile <admin-profile>

اگه entry هایی که ماژول می‌خواد manage کنه از قبل روی کلاستر هستن، قبل از apply اون‌ها رو import کن:

terragrunt import 'aws_eks_access_entry.this["dashboard-admin"]' \
  '<cluster>:<dashboard-admin-role-arn>'

وقتی import تموم شد، یه apply روی همون ماژول می‌شه یه create تمیز برای entry جدید و یه no-op برای entry های موجود. proof ‌end-to-end اجرای بعدی deploy هست. اگه apply موفق بود و deploy هنوز fail شد، entry در واقع به یه policy association قابل‌استفاده مپ نشده، و list-associated-access-policies توقف بعدیه.

یه diagram از مسیر

کل pipeline، با نقطه failure برجسته‌شده:

  ┌───────────────┐     ┌────────────────────┐     ┌───────────────┐
  │  runner IRSA  │ ──▶ │  cross-account     │ ──▶ │  aws eks      │
  │  role         │     │  deploy role       │     │  get-token    │
  └───────────────┘     └────────────────────┘     └───────────────┘


  ┌───────────────┐     ┌────────────────────┐     ┌───────────────┐
  │  Kubernetes   │ ◀── │  access entry      │ ◀── │  kube-        │
  │  RBAC         │     │  lookup  ❌  no row │     │  apiserver    │
  └───────────────┘     └────────────────────┘     └───────────────┘

runner IRSA role، cross-account role رو assume می‌کنه، cross-account role یه token صادر می‌کنه، apiserver token رو دریافت می‌کنه، apiserver principal رو توی جدول access entry lookup می‌کنه، و lookup هیچی برنمی‌گردونه. همه‌چیز قبل از lookup سالمه. همه‌چیز بعدش unreachable می‌شه.

الگوی کلی‌تر

«Credentials» یه کلمه‌ست که توی یه pipeline دیپلوی EKS سه لایه متفاوت رو پوشش می‌ده: credential های AWS (آیا یه principal معتبر داری؟)، access control ‌EKS (آیا کلاستر اون principal رو می‌شناسه؟)، و RBAC ‌Kubernetes (آیا اون principal اجازه داره کاری که می‌خواد انجام بده رو؟؟). فقط لایه دوم message بالا رو تولید می‌کنه. rotate کردن AWS key به لایه دوم کمک نمی‌کنه. آپدیت kubeconfig به لایه دوم کمک نمی‌کنه. تنها چیزی که به لایه دوم کمک می‌کنه یه aws_eks_access_entry برای principal ای هست که داره درخواست می‌زنه.

وقتی message می‌گه «credentials»، قبل از rotate کردن هر کدومشون، اول چک کن که منظورش کدوم credential هاست. error message بدترین بخش diagnostic هست؛ از هر سه لایه عبور می‌کنه و توی دو تا از سه تا مورد به لایه اشتباه اشاره می‌کنه. زنجیره aws sts get-caller-identityaws eks get-tokenaws eks list-access-entries توی کمتر از یه دقیقه ابهام رو حل می‌کنه و بهت می‌گه کدوم لایه رو fix کنی.

همین الگو توی failure mode های مجاور هم ظاهر می‌شه. یه role admin انسانی که روی کلاستر اصلی کار می‌کرد و بعد از یه destroy/recreate از کار افتاد، همون gap هست با یه trigger متفاوت. یه role ‌CI که روی env قبلی کار می‌کرد و روی env جدید از کار افتاد، همون gap هست با یه history ‌apply متفاوت. token سالمه. کلاستر سالمه. entry غایبه.

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

کلاستر جدید روی هر node تا ۱۱۰ تا pod می‌برد. Production روی ۵۸ گیر کرده بود.

همون instance type، نصف تراکم pod. بالا بردن max pods روی یه کلاستر EKS زنده یعنی: یه launch template که node group هیچ‌وقت نمی‌تونه بگیردش، یه تنظیم CNI که باید اول بشینه، و یه Terraform plan پر از تغییرهایی که هیچ‌کس سفارش نداده بود.

kubernetes aws eks terraform devops
$ cat AIAGENTS .md
· 8 دقیقه مطالعه

Hermes Agent روی EKS: از docker-compose تا Helm

ایجنت هوش مصنوعی خودآموز Nous Research در یه محیط پروداکشن-محور روی EKS. ECR خصوصی،، IRSA، مدیریت API Key به عنوان Secret، و یه Helm Chart سفارشی که از روی manifest خام ساختیم.

ai-agents aws eks kubernetes llm helm devops
$ cat DNS .md
· 7 دقیقه مطالعه

یه رکورد DNS رو دوبار پاک کردم. بار سوم پرسیدم چرا.

یه endpoint عمومی توی سه روز دو بار قطع شد، و هر دو بار همون fix پنج‌دقیقه‌ای جواب داد: پاک کردن یه رکورد AAAA یتیم. باگ واقعی یه رکورد ownership توی DNS بود که هیچ‌وقت نمی‌تونه خودش رو صاحب بشه، دقیقاً روی apex یه zone دلگیت‌شده.

dns aws route53 kubernetes devops