← شروع آموزش

WIUNIX ACADEMY

PVC در Pending یا mount ناموفق؛ تشخیص مرحله‌های CSI در Kubernetes

صفحهٔ 2 از 3؛ قبل از ادامه، مراحل قبلی و چیزهایی رو که لازم داری مرور کن.

آموزش متناسب با محیط تو

کجا اجرا می‌کنی؟

سیستم‌عامل و نسخهٔ سرورت رو انتخاب کن تا مراحل، دستورها و راه‌حل‌های همون محیط رو ببینی.

Ubuntu 26.10بررسی‌شده با مستندات رسمی ·

آموزش متناسب با مقادیر تو

تنظیمات این آموزش

مقادیر نمونه رو با مشخصات سرورت عوض کن تا متن و دستورها با هم تغییر کنن. رمز، کلید خصوصی و توکن اینجا وارد نکن. مقدارها فقط توی همین زبانه می‌مونن و به سرور یا لینک اشتراک‌گذاری فرستاده نمی‌شن. قبل از اجرا، DNS، گواهی‌ها و تنظیمات دسترسی شبکه رو روی سرورت بررسی کن.

اگر آی‌پی، دامنه یا پورت‌های نمونه با سرورت فرق دارن، بخش تنظیمات پایین رو باز کن. مقدارها رو عوض کن و «اعمال در آموزش» رو بزن؛ بعد دستورهای تازه رو کپی کن.

تنظیم مقادیر برای محیط من9 مقدار قابل‌تغییر
05

قدم بعدی

پاد مصرف‌کننده رو به claim و نود واقعی وصل کنیم

UID پاد و مرجع کنترل‌کننده رو ثبت کن تا پاد جایگزین رو با نمونهٔ زمان خطا اشتباه نگیری. نام volumeها و claimها، شرط زمان‌بندی و رخدادهای همین شیء رو بخون. claim فرم باید واقعاً یکی از claimهای همین پاد باشه؛ شباهت نام در یک namespace کافی نیست.

ممکنه پاد قبل از انتخاب هر نود در Pending بمونه. حالت ContainerCreating همراه نام نود یعنی به مرحلهٔ بعدی رسیده‌ایم. رخدادها مدت محدود نگه داشته می‌شن و تکرارها ممکنه تجمیع بشن؛ فهرست خالی ثابت نمی‌کنه خطایی رخ نداده. زمان و UID دقیق رو نگه دار و اگر لازم شد از شواهد مرکزی یا کنترل‌کننده کمک بگیر.

bash

همان شل Bash مجاز روی میزبان مدیریت

k get pod 'example-app' -n 'apps' -o jsonpath='{.metadata.uid}{"\n"}{.metadata.ownerReferences}{"\n"}{.status.phase}{"\t"}{.spec.nodeName}{"\n"}{range .spec.volumes[*]}{.name}{"\t"}{.persistentVolumeClaim.claimName}{"\n"}{end}{range .status.conditions[*]}{.type}{"="}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'
k events -n 'apps' --for 'pod/example-app'
از کجا بفهمم درست انجام شده؟

UID پاد، ارجاع واقعی PVC، انتخاب نود و مرحلهٔ فعلیِ شکست معلومه.

06

قدم بعدی

قبل از خطادانستن Pending، خود درخواست رو بخون

UID، وضعیت و spec کامل claim رو همراه selected-node ببین. اندازه، روش دسترسی، حالت volume، selector، منبع اختیاری داده، کلاس و volumeName صریح در spec دیده می‌شن. این تنظیم عملیاتی رو خصوصی نگه دار؛ برای فهمش نیازی به دریافت Secret نداریم.

ندادن storageClassName با رشتهٔ خالیِ صریح فرق داره. حالت اول می‌تونه از کلاس پیش‌فرض و رفتار defaulting همان کلاستر استفاده کنه؛ رشتهٔ خالی می‌گه کلاس نمی‌خوام و از فراهم‌کنندهٔ پویا درخواست ساخت نمی‌کنه. selector یا volumeName مشخص هم انتظار اتصال رو عوض می‌کنه. فقط برای حذف برچسب Pending، این فیلدها رو patch نکن.

bash

همان شل Bash مجاز روی میزبان مدیریت

k get pvc 'data-example' -n 'apps' -o jsonpath='{.metadata.uid}{"\n"}{.status.phase}{"\n"}{.spec}{"\n"}{.metadata.annotations.volume\.kubernetes\.io/selected-node}{"\n"}'
k events -n 'apps' --for 'persistentvolumeclaim/data-example'
از کجا بفهمم درست انجام شده؟

درخواست واقعی و نود انتخاب‌شده ثبت شده و تفاوت کلاس غایب با کلاس صریحاً خالی رو از spec می‌بینی.

07

قدم بعدی

انتظار برای مصرف‌کننده رو از شکست ساخت حجم جدا کنیم

بعد از خواندن claim، نام کلاس واقعی رو در فرم بذار و binding mode و provisioner رو بخون. حالت Immediate می‌تونه قبل از تعیین محل پاد حجم بسازه یا متصل کنه. WaitForFirstConsumer عمداً منتظر پادی می‌مونه که امکان زمان‌بندی داشته باشه تا حجم متناسب با همان محل انتخاب یا ساخته بشه؛ پس Pending بدون مصرف‌کننده ممکنه طبیعی باشه.

اگر provisioner برابر kubernetes.io/no-provisioner است، دنبال PV ایستا یا محلیِ جدا آماده‌شده باش، نه تخصیص پویای CSI. برای کلاس CSI، پشتیبانی همین درایور از mode، topology و روش دسترسی/volume رو تأیید کن. سهمیهٔ namespace، ظرفیت درخواستی و سهمیه یا ظرفیت backend سه بررسی جدا هستن. reclaim policy کلاس پیامد حذف رو مشخص می‌کنه، نه دکمهٔ تعمیر رو.

bash

همان شل Bash مجاز روی میزبان مدیریت

k get storageclass 'example-storage' -o jsonpath='{.metadata.name}{"\n"}{.provisioner}{"\n"}{.volumeBindingMode}{"\n"}{.reclaimPolicy}{"\n"}{.allowedTopologies}{"\n"}'
k get resourcequota -n 'apps'
از کجا بفهمم درست انجام شده؟

روش کلاس و محدودهٔ سهمیه روشن می‌کنه انتظار طبیعی است یا مرحلهٔ ساخت حجم شکست خورده.

08

قدم بعدی

در WaitForFirstConsumer اول جای پاد رو بررسی کن

رخداد زمان‌بندی رو کنار node selector، affinity، taint/toleration و درخواست منابع پاد بخون. وقتی نود واجدشرایط نداریم، claim هم نمی‌تونه حجم منطقه‌ای مناسب بگیره. selected-node فقط نشانهٔ هماهنگی مرحله‌هاست؛ ساخت یا mount موفق رو ثابت نمی‌کنه.

با spec.nodeName پاد رو مجبور به نود نکن؛ دورزدن scheduler در این روش می‌تونه claim رو در Pending نگه داره. برچسب topology یا annotation انتخاب نود رو هم کورکورانه پاک نکن. محل مجاز واقعی رو با مسئول پلتفرم و ذخیره‌ساز روشن کن و اگر مشکل از طراحی workload یا کلاس بود، منبع همان بخش رو از مسیر معمولِ بررسی‌شده اصلاح کن.

bash

همان شل Bash مجاز روی میزبان مدیریت

k get pod 'example-app' -n 'apps' -o jsonpath='{.spec.nodeName}{"\n"}{.spec.nodeSelector}{"\n"}{.spec.affinity}{"\n"}{.spec.tolerations}{"\n"}{range .spec.containers[*]}{.name}{"\t"}{.resources}{"\n"}{end}'
k events -n 'apps' --for 'pod/example-app'
از کجا بفهمم درست انجام شده؟

شرط جای‌گیری پاد و محل قابل‌دسترسی ذخیره‌ساز با هم سنجیده شده‌اند؛ پاد رو به نود تحمیل نکردیم.

09

قدم بعدی

بعد از Bound، رابطهٔ دقیق PV رو تأیید کن

این شاخه فقط وقتی معنی داره که claim نام حجم متصل‌شده رو داده باشه. همان نام کشف‌شده رو در فرم بذار. namespace و نام و UIDِ claimRef رو با claim قبلی تطبیق بده و کلاس، ظرفیت، روش دسترسی، حالت volume و node affinity رو مقایسه کن. یک نام دوباره‌استفاده‌شده یا PV قدیمیِ Released خودبه‌خود همان رابطهٔ داده نیست.

Bound فقط رابطهٔ کنترل‌پلین رو تأیید می‌کنه، نه attach و mount یا خواندن دادهٔ برنامه. ReadWriteOnce معمولاً نوشتن رو به یک نود محدود می‌کنه، نه یک پاد؛ چند پاد روی همان نود هنوز ممکنه از حجم استفاده کنن. ReadWriteOncePod قابلیت دیگری در CSI با sidecar سازگار است. از mode درخواستی یا برچسب سبز Bound نمی‌شه سازگاری دادهٔ برنامه رو نتیجه گرفت.

bash

همان شل Bash مجاز روی میزبان مدیریت

k get pv 'example-volume' -o jsonpath='{.metadata.name}{"\n"}{.status.phase}{"\n"}{.spec.claimRef.namespace}{"/"}{.spec.claimRef.name}{"\t"}{.spec.claimRef.uid}{"\n"}{.spec.storageClassName}{"\n"}{.spec.capacity}{"\n"}{.spec.accessModes}{"\n"}{.spec.volumeMode}{"\n"}{.spec.persistentVolumeReclaimPolicy}{"\n"}{.spec.csi.driver}{"\n"}{.spec.nodeAffinity}{"\n"}'
از کجا بفهمم درست انجام شده؟

PV به UID دقیق همان claim مربوطه و معنا و محل قابل‌استفادهٔ حجم روشنه.

10

قدم بعدی

درایور واقعی و ثبت‌شدنش روی نود رو ببین

این شاخهٔ کلاستری رو فقط با مجوز واگذارشده و نود واقعاً انتخاب‌شده اجرا کن. نام درایور از PV خوانده می‌شه، نه از حدس. نیاز attach و روش lifecycle/mount در CSIDriver رو با نام درایورهای ثبت‌شده و کلیدهای topology نود تطبیق بده؛ بعد label و taint همان نود رو ببین.

بعضی درایورها به مرحلهٔ attach کنترل‌کننده نیاز ندارن. اگر attachRequired برابر false باشه، نبود VolumeAttachment می‌تونه طبیعی باشه. اگر خود CSIDriver موجود نباشه، پیش‌فرض Kubernetes نیاز به attach است؛ نبود شیء نه سلامت درایور رو ثابت می‌کنه و نه می‌تونیم از روی نبودش نتیجه بگیریم attach لازم نیست. مستندات همین درایور نصب‌شده رو دنبال کن. نبود ثبت نود هم مشکل افزونه یا ثبت درایور روی نود است و باید به مسئول ذخیره‌ساز برسه.

bash

همان شل Bash مجاز روی میزبان مدیریت

driver=$(k get pv 'example-volume' -o jsonpath='{.spec.csi.driver}') || exit 1
test -n "$driver" || { printf 'STOP: no CSI driver in this PV; use the actual storage owner path.\n' >&2; exit 1; }
k get csidriver "$driver" -o jsonpath='{.metadata.name}{"\n"}{.spec.attachRequired}{"\n"}{.spec.volumeLifecycleModes}{"\n"}{.spec.fsGroupPolicy}{"\n"}'
k get csinode 'worker-a' -o jsonpath='{range .spec.drivers[*]}{.name}{"\t"}{.topologyKeys}{"\n"}{end}'
k get node 'worker-a' -o jsonpath='{.metadata.labels}{"\n"}{.spec.taints}{"\n"}'
از کجا بفهمم درست انجام شده؟

درایور CSI واقعی، رفتار attach و ثبت نود/محل مشخصه یا نبود دید و مجوز آن صریح ثبت شده.

همهٔ آموزش‌های آکادمی ↗