ESC را فشار دهید تا بسته شود

رفع خطای CrashLoopBackOff در Kubernetes؛ عیب‌یابی Exit Code، Probe و OOMKilled

فهرست

وقتی وضعیت یک Pod در Kubernetes به CrashLoopBackOff می‌رسد، معمولاً کانتینر بعد از اجرا متوقف می‌شود و kubelet پیش از تلاش مجدد، فاصله زمانی بین Restartها را افزایش می‌دهد. این عبارت علت خطا نیست. برای تشخیص درست باید ابتدا kubectl describe pod، سپس kubectl logs --previous و مقدارهای lastState، exitCode و رویدادهای Pod را بخوانید. در ادامه با روش مرحله‌به‌مرحله و قابل بازگشت، خطا را از OOMKilled، تنظیم اشتباه Probe و مشکل اجرای برنامه جدا می‌کنیم.

CrashLoopBackOff دقیقاً به چه معناست؟

در این وضعیت یک یا چند کانتینرِ Pod مکرراً اجرا و متوقف می‌شوند. Kubernetes برای جلوگیری از تلاش پی‌درپی و بی‌فاصله، Restart بعدی را با تأخیر انجام می‌دهد. در مقابل، ImagePullBackOff به مشکل دریافت Image اشاره دارد و Pending معمولاً به مسائل زمان‌بندی یا تأمین منابع مربوط است. این وضعیت‌ها را نباید با یکدیگر یکی دانست.

همچنین CrashLoopBackOff با برچسب unhealthy در Docker یکسان نیست: ممکن است فرایند Docker همچنان در حال اجرا باشد، اما Healthcheck آن شکست بخورد. برای شناخت تفاوت، آموزش عیب‌یابی Docker Container unhealthy و HEALTHCHECK را ببینید.

سناریو و پیش‌نیازها

فرض کنیم برنامه‌ای به نام api در Namespace به نام production اجرا شده است. به kubectl با سطح دسترسی فقط‌خواندنی برای مشاهده Podها، Logها و Eventها نیاز دارید. در ابتدای کار هیچ Deployment یا ConfigMap را تغییر ندهید. نام Pod واقعی را از خروجی خوشه بردارید؛ نام نمونه زیر را بدون بررسی در محیط خود اجرا نکنید.

kubectl config current-context
kubectl get namespaces
kubectl get pods -n production -o wide
kubectl get deployment -n production

هشدار: اگر به کلاستر Production وصل هستید، Context و Namespace را دوباره بررسی کنید. دستورهای تشخیصی زیر خواندنی هستند؛ Restart، تغییر Probe و Rollback می‌توانند روی دسترس‌پذیری سرویس اثر بگذارند.

گام اول: رویدادهای Pod و دلیل Restart را بخوانید

در Shell مقدار POD را با نام دقیق Pod مسئله‌دار جایگزین کنید. خروجی describe معمولاً وضعیت Waiting، دلیل Termination، تعداد Restart و پیام‌های رویداد را نشان می‌دهد.

NS=production
POD=api-xxxxxxxxxx-yyyyy

kubectl get pod "$POD" -n "$NS" -o wide
kubectl describe pod "$POD" -n "$NS"
kubectl get events -n "$NS" 
  --field-selector involvedObject.name="$POD" 
  --sort-by=.metadata.creationTimestamp

در انتهای خروجی describe به عبارت‌هایی مانند Back-off restarting failed container، OOMKilled و Liveness probe failed توجه کنید. پیام Back-off فقط رفتار تلاش مجدد را توصیف می‌کند؛ علت اصلی ممکن است چند سطر قبل‌تر یا داخل لاگ برنامه باشد.

گام دوم: لاگ اجرای قبلی کانتینر را بررسی کنید

در CrashLoop سریع، خروجی kubectl logs بدون گزینه --previous ممکن است لاگ اجرای فعلی و تازه‌شروع‌شده را نشان دهد و خطای اجرای قبلی را از دست بدهید. ابتدا نام کانتینرها را پیدا کنید، بعد لاگ همان کانتینر را بخوانید.

kubectl get pod "$POD" -n "$NS" 
  -o jsonpath='{.spec.containers[*].name}'

kubectl logs "$POD" -n "$NS" 
  --all-containers=true --tail=150 --timestamps

kubectl logs "$POD" -n "$NS" 
  -c api --previous --tail=150 --timestamps

در خط آخر، api نام container است و لزوماً با نام Deployment برابر نیست. اگر کانتینر قبلاً Restart نشده یا لاگ قبلی موجود نباشد، دستور --previous می‌تواند خطا بدهد؛ این به‌تنهایی نشانه خرابی Kubernetes نیست. خطاهای اتصال دیتابیس، نبود متغیر محیطی، Migration ناموفق، Permission denied یا فایل اجرایی نادرست را از متن واقعی لاگ بررسی کنید.

گام سوم: Exit Code و OOMKilled را از وضعیت واقعی استخراج کنید

kubectl get pod "$POD" -n "$NS" 
  -o jsonpath='{range .status.containerStatuses[*]}{.name}{" | restart="}{.restartCount}{" | waiting="}{.state.waiting.reason}{" | last reason="}{.lastState.terminated.reason}{" | exit="}{.lastState.terminated.exitCode}{"n"}{end}'

kubectl get pod "$POD" -n "$NS" 
  -o jsonpath='{range .spec.containers[*]}{.name}{" requests="}{.resources.requests}{" limits="}{.resources.limits}{"n"}{end}'
نشانهتفسیر اولیهبررسی بعدی
OOMKilled یا Exit Code 137ممکن است پردازش به علت مصرف حافظه یا SIGKILL متوقف شده باشد؛ کد 137 به‌تنهایی اثبات OOM نیست.Reason، Memory Limit، Eventها و نمودار مصرف حافظه
Exit Code 1خطای عمومی اجرای برنامهلاگ برنامه، Environment و Dependencyها
Exit Code 126/127مشکل دسترسی/اجرا یا نبود CommandEntrypoint، Image، Permission و مسیر باینری
Liveness probe failedProbe سلامت زنده‌بودن برنامه شکست خورده و ممکن است سبب Restart شود.Port، Path، Timeout، زمان راه‌اندازی و Startup Probe

اگر Metrics Server در کلاستر نصب است، مصرف فعلی را هم بررسی کنید؛ نبود Metrics Server یعنی دستور زیر ممکن است خطا دهد و دلیلی برای رد مشکل حافظه نیست.

kubectl top pod "$POD" -n "$NS" --containers

اگر OOMKilled تأیید شد، قبل از افزایش Limit، نشتی حافظه، اندازه Heap و بار واقعی برنامه را بررسی کنید. بالا بردن بی‌حساب Limit ممکن است فقط فشار حافظه را به Node منتقل کند.

گام چهارم: Liveness، Readiness و Startup Probe را تفکیک کنید

readinessProbe برای تعیین آمادگی دریافت ترافیک است و شکست آن به‌تنهایی معمولاً کانتینر را Restart نمی‌کند. livenessProbe در صورت شکست‌های متوالی می‌تواند باعث Restart شود. startupProbe به برنامه‌های کندراه‌انداز فرصت می‌دهد تا پیش از فعال‌شدن Liveness و Readiness، شروع اولیه را کامل کنند.

kubectl get pod "$POD" -n "$NS" -o yaml
kubectl describe deployment api -n "$NS"

در Manifest بررسی کنید Port و Path واقعاً توسط برنامه پاسخ داده می‌شوند. نمونه زیر فقط برای برنامه‌ای معتبر است که Endpoint /healthz را روی پورت 8080 پیاده‌سازی کرده باشد؛ آن را بدون تست کپی نکنید.

startupProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 5
  failureThreshold: 24
livenessProbe:
  httpGet:
    path: /healthz
    port: 8080
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3

در این مثال Startup Probe تا حدود ۱۲۰ ثانیه فرصت بررسی فراهم می‌کند، اما مقدار مناسب باید از زمان بوت واقعی برنامه به دست بیاید. اگر Probe به Host/Port یا Dependency اشتباه وصل شده، اول همان علت را اصلاح کنید؛ غیرفعال‌کردن دائمی Liveness راهکار اصولی نیست. برای جداسازی مشکلات شبکه Pod از خطای برنامه، مبانی Container Networking در Kubernetes هم مفید است.

گام پنجم: ConfigMap، Secret و دستور شروع برنامه را بررسی کنید

اگر CrashLoop بعد از Deploy جدید آغاز شده، تغییر Image Tag، متغیرهای محیطی، Secretهای ارجاع‌شده، Command/Args، Working Directory و دسترسی Volume را با نسخه سالم قبلی مقایسه کنید. برای مشاهده مرجع‌های Environment و Image بدون انتشار مقدار Secretها:

kubectl get deployment api -n "$NS" 
  -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{" image="}{.image}{"n"}{end}'

kubectl describe pod "$POD" -n "$NS"
kubectl rollout history deployment/api -n "$NS"

وجود Secret در لیست Environment به معنی قابل‌خواندن‌بودن مقدار آن یا معتبر بودن Credentialهایش نیست. مقدار Secret، توکن سرویس و فایل پیکربندی حساس را در تیکت عمومی یا لاگ قابل‌دسترسی منتشر نکنید. اگر مشکل از Database یا API خارجی است، شبکه، DNS، TLS و Authentication را جداگانه تست کنید.

گام ششم: مشکل منابع Node و انتخاب زیرساخت را از خرابی برنامه جدا کنید

اگر چند Pod روی یک Node دچار فشار منابع شده‌اند، وضعیت Node و مصرف CPU/RAM را بررسی کنید. در کلاسترهای مستقر روی ماشین مجازی، اندازه RAM، CPU و دسترسی کنسول در تصمیم ظرفیت‌سنجی اهمیت دارد؛ هنگام انتخاب یا ارتقای Node می‌توانید پلن‌های سرور مجازی وان‌سرور را از منظر منابع و نوع دسترسی با نیاز واقعی کلاستر مقایسه کنید. خرید یا جابه‌جایی VPS، خطای Image، Probe یا باگ برنامه را خودکار رفع نمی‌کند.

kubectl get nodes -o wide
kubectl describe node NODE_NAME
kubectl top nodes

نام واقعی Node را از kubectl get nodes جایگزین کنید. در describe node بخش‌های Conditions، Allocated resources و Eventها را بررسی کنید. در صورت MemoryPressure یا DiskPressure باید علت فشار روی Node نیز جداگانه رسیدگی شود.

رفع خطا را چگونه تأیید کنیم؟

بعد از اصلاح Manifest یا Configuration، فقط دیدن Running کافی نیست؛ وضعیت Ready، شمار Restart و سلامت سرویس را در چند دقیقه بررسی کنید. اگر برنامه توسط Deployment اداره می‌شود:

kubectl rollout status deployment/api -n "$NS" --timeout=120s
kubectl get pods -n "$NS" -o wide
kubectl get pods -n "$NS" -w
kubectl logs deployment/api -n "$NS" --tail=100

در خروجی انتظار دارید Pod جدید Ready شود، Eventهای تکراری Back-off متوقف شوند و شمار Restart در بازه پایش دوباره افزایش نیابد. مشاهده خروجی -w را با Ctrl+C متوقف کنید. برای آموزش مقدماتی شیوه Deployment و ساختار کلاستر نیز راهنمای نصب و پیکربندی Kubernetes روی لینوکس را بخوانید.

Rollback ایمن در صورت بدتر شدن وضعیت

پیش از تغییر، Manifest و شماره Revision پایدار را ثبت کنید. اگر CrashLoop دقیقاً بعد از Rollout یک Deployment شروع شده و نسخه قبلی سالم است، Undo می‌تواند تغییر Deployment را برگرداند؛ اما این کار جای بررسی داده‌های پایدار، Schema دیتابیس و سازگاری API را نمی‌گیرد.

kubectl get deployment api -n "$NS" -o yaml > api-before.yaml
kubectl rollout history deployment/api -n "$NS"

# فقط پس از تایید نسخه سالم قبلی:
kubectl rollout undo deployment/api -n "$NS"
kubectl rollout status deployment/api -n "$NS" --timeout=120s

هشدار: Rollback یک Deployment الزاماً Migration دیتابیس، Secretهای تغییریافته یا منابع خارجی را برنمی‌گرداند. برای سیستم حساس، Undo را پس از تأیید مسیر بازیابی و هماهنگی با تیم برنامه اجرا کنید.

خطاهای رایج در عیب‌یابی CrashLoopBackOff

  • اجرای پشت‌سرهم kubectl delete pod بدون خواندن --previous که فقط شواهد را از بین می‌برد.
  • تعبیر Exit Code 137 به‌عنوان اثبات قطعی OOM، بدون بررسی lastState.reason.
  • بالا بردن Memory Limit یا خاموش‌کردن Liveness بدون اصلاح مشکل اصلی برنامه.
  • اشتباه گرفتن نام کانتینر با نام Pod یا Deployment در دستور kubectl logs -c.
  • نادیده گرفتن تغییر ConfigMap/Secret، خطای EntryPoint یا زمان راه‌اندازی برنامه.

سؤالات متداول

آیا CrashLoopBackOff به معنی خرابی خود Kubernetes است؟

خیر. معمولاً بیانگر شکست تکراری اجرای کانتینر و تأخیر در Restart است. لاگ، Eventها، وضعیت Last Terminated و Probe مشخص می‌کنند مشکل از برنامه، تنظیمات یا منابع است.

چرا Pod در حالت Running است اما درخواست‌ها به آن نمی‌رسند؟

Running به معنی Ready بودن نیست. وضعیت Readiness، Endpointهای Service و شبکه کلاستر را جداگانه بررسی کنید؛ ممکن است کانتینر اجرا شده باشد اما هنوز امکان سرویس‌دهی نداشته باشد.

Rate this post
اشتراک گذاری نوشته در:

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *