WIUNIX ACADEMY
PVC در Pending یا mount ناموفق؛ تشخیص مرحلههای CSI در Kubernetes
صفحهٔ 2 از 3؛ قبل از ادامه، مراحل قبلی و چیزهایی رو که لازم داری مرور کن.
آموزش متناسب با محیط تو
کجا اجرا میکنی؟
سیستمعامل و نسخهٔ سرورت رو انتخاب کن تا مراحل، دستورها و راهحلهای همون محیط رو ببینی.
آموزش متناسب با مقادیر تو
تنظیمات این آموزش
مقادیر نمونه رو با مشخصات سرورت عوض کن تا متن و دستورها با هم تغییر کنن. رمز، کلید خصوصی و توکن اینجا وارد نکن. مقدارها فقط توی همین زبانه میمونن و به سرور یا لینک اشتراکگذاری فرستاده نمیشن. قبل از اجرا، DNS، گواهیها و تنظیمات دسترسی شبکه رو روی سرورت بررسی کن.
اگر آیپی، دامنه یا پورتهای نمونه با سرورت فرق دارن، بخش تنظیمات پایین رو باز کن. مقدارها رو عوض کن و «اعمال در آموزش» رو بزن؛ بعد دستورهای تازه رو کپی کن.
تنظیم مقادیر برای محیط من9 مقدار قابلتغییر
قدم بعدی
پاد مصرفکننده رو به claim و نود واقعی وصل کنیم
UID پاد و مرجع کنترلکننده رو ثبت کن تا پاد جایگزین رو با نمونهٔ زمان خطا اشتباه نگیری. نام volumeها و claimها، شرط زمانبندی و رخدادهای همین شیء رو بخون. claim فرم باید واقعاً یکی از claimهای همین پاد باشه؛ شباهت نام در یک namespace کافی نیست.
ممکنه پاد قبل از انتخاب هر نود در Pending بمونه. حالت ContainerCreating همراه نام نود یعنی به مرحلهٔ بعدی رسیدهایم. رخدادها مدت محدود نگه داشته میشن و تکرارها ممکنه تجمیع بشن؛ فهرست خالی ثابت نمیکنه خطایی رخ نداده. زمان و UID دقیق رو نگه دار و اگر لازم شد از شواهد مرکزی یا کنترلکننده کمک بگیر.
همان شل 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، انتخاب نود و مرحلهٔ فعلیِ شکست معلومه.
قدم بعدی
قبل از خطادانستن Pending، خود درخواست رو بخون
UID، وضعیت و spec کامل claim رو همراه selected-node ببین. اندازه، روش دسترسی، حالت volume، selector، منبع اختیاری داده، کلاس و volumeName صریح در spec دیده میشن. این تنظیم عملیاتی رو خصوصی نگه دار؛ برای فهمش نیازی به دریافت Secret نداریم.
ندادن storageClassName با رشتهٔ خالیِ صریح فرق داره. حالت اول میتونه از کلاس پیشفرض و رفتار defaulting همان کلاستر استفاده کنه؛ رشتهٔ خالی میگه کلاس نمیخوام و از فراهمکنندهٔ پویا درخواست ساخت نمیکنه. selector یا volumeName مشخص هم انتظار اتصال رو عوض میکنه. فقط برای حذف برچسب Pending، این فیلدها رو patch نکن.
همان شل 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 میبینی.
قدم بعدی
انتظار برای مصرفکننده رو از شکست ساخت حجم جدا کنیم
بعد از خواندن claim، نام کلاس واقعی رو در فرم بذار و binding mode و provisioner رو بخون. حالت Immediate میتونه قبل از تعیین محل پاد حجم بسازه یا متصل کنه. WaitForFirstConsumer عمداً منتظر پادی میمونه که امکان زمانبندی داشته باشه تا حجم متناسب با همان محل انتخاب یا ساخته بشه؛ پس Pending بدون مصرفکننده ممکنه طبیعی باشه.
اگر provisioner برابر kubernetes.io/no-provisioner است، دنبال PV ایستا یا محلیِ جدا آمادهشده باش، نه تخصیص پویای CSI. برای کلاس CSI، پشتیبانی همین درایور از mode، topology و روش دسترسی/volume رو تأیید کن. سهمیهٔ namespace، ظرفیت درخواستی و سهمیه یا ظرفیت backend سه بررسی جدا هستن. reclaim policy کلاس پیامد حذف رو مشخص میکنه، نه دکمهٔ تعمیر رو.
همان شل Bash مجاز روی میزبان مدیریت
k get storageclass 'example-storage' -o jsonpath='{.metadata.name}{"\n"}{.provisioner}{"\n"}{.volumeBindingMode}{"\n"}{.reclaimPolicy}{"\n"}{.allowedTopologies}{"\n"}'
k get resourcequota -n 'apps'روش کلاس و محدودهٔ سهمیه روشن میکنه انتظار طبیعی است یا مرحلهٔ ساخت حجم شکست خورده.
قدم بعدی
در WaitForFirstConsumer اول جای پاد رو بررسی کن
رخداد زمانبندی رو کنار node selector، affinity، taint/toleration و درخواست منابع پاد بخون. وقتی نود واجدشرایط نداریم، claim هم نمیتونه حجم منطقهای مناسب بگیره. selected-node فقط نشانهٔ هماهنگی مرحلههاست؛ ساخت یا mount موفق رو ثابت نمیکنه.
با spec.nodeName پاد رو مجبور به نود نکن؛ دورزدن scheduler در این روش میتونه claim رو در Pending نگه داره. برچسب topology یا annotation انتخاب نود رو هم کورکورانه پاک نکن. محل مجاز واقعی رو با مسئول پلتفرم و ذخیرهساز روشن کن و اگر مشکل از طراحی workload یا کلاس بود، منبع همان بخش رو از مسیر معمولِ بررسیشده اصلاح کن.
همان شل 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'شرط جایگیری پاد و محل قابلدسترسی ذخیرهساز با هم سنجیده شدهاند؛ پاد رو به نود تحمیل نکردیم.
قدم بعدی
بعد از Bound، رابطهٔ دقیق PV رو تأیید کن
این شاخه فقط وقتی معنی داره که claim نام حجم متصلشده رو داده باشه. همان نام کشفشده رو در فرم بذار. namespace و نام و UIDِ claimRef رو با claim قبلی تطبیق بده و کلاس، ظرفیت، روش دسترسی، حالت volume و node affinity رو مقایسه کن. یک نام دوبارهاستفادهشده یا PV قدیمیِ Released خودبهخود همان رابطهٔ داده نیست.
Bound فقط رابطهٔ کنترلپلین رو تأیید میکنه، نه attach و mount یا خواندن دادهٔ برنامه. ReadWriteOnce معمولاً نوشتن رو به یک نود محدود میکنه، نه یک پاد؛ چند پاد روی همان نود هنوز ممکنه از حجم استفاده کنن. ReadWriteOncePod قابلیت دیگری در CSI با sidecar سازگار است. از mode درخواستی یا برچسب سبز Bound نمیشه سازگاری دادهٔ برنامه رو نتیجه گرفت.
همان شل 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 مربوطه و معنا و محل قابلاستفادهٔ حجم روشنه.
قدم بعدی
درایور واقعی و ثبتشدنش روی نود رو ببین
این شاخهٔ کلاستری رو فقط با مجوز واگذارشده و نود واقعاً انتخابشده اجرا کن. نام درایور از PV خوانده میشه، نه از حدس. نیاز attach و روش lifecycle/mount در CSIDriver رو با نام درایورهای ثبتشده و کلیدهای topology نود تطبیق بده؛ بعد label و taint همان نود رو ببین.
بعضی درایورها به مرحلهٔ attach کنترلکننده نیاز ندارن. اگر attachRequired برابر false باشه، نبود VolumeAttachment میتونه طبیعی باشه. اگر خود CSIDriver موجود نباشه، پیشفرض Kubernetes نیاز به attach است؛ نبود شیء نه سلامت درایور رو ثابت میکنه و نه میتونیم از روی نبودش نتیجه بگیریم attach لازم نیست. مستندات همین درایور نصبشده رو دنبال کن. نبود ثبت نود هم مشکل افزونه یا ثبت درایور روی نود است و باید به مسئول ذخیرهساز برسه.
همان شل 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 و ثبت نود/محل مشخصه یا نبود دید و مجوز آن صریح ثبت شده.