KUBERNETES

مانیفست‌های Kubernetes قدم‌به‌قدم: Deployment، Service و Ingress

یک اپلیکیشن را با سه مانیفست روی Kubernetes اجرا کنید: replicas، probes، requests و limits، انواع Service و Ingress — و بفهمید هر فیلد چرا آنجاست.

کوبرنتیز را معمولاً با حجم زیادی YAML می‌شناسند، ولی وقتی منطق پشت آن روشن شود، بیشتر مانیفست‌ها تکرار همان سه الگو هستند: Deployment (برنامه را اجرا کن)، Service (آدرس ثابتی برایش بساز) و Ingress (از بیرون به آن راه بده). در این راهنما یک اپلیکیشن وب را با این سه می‌چینیم و برای هر فیلد توضیح می‌دهیم چرا آنجاست.

K8SKubernetes YAML Generatorاگر می‌خواهید قبل از خواندن، خروجی را ببینید: Deployment، Service و Ingress را با فرم بسازید.

Deployment: برنامه را اجرا و زنده نگه دار

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web                      # باید با labels داخل template یکی باشد
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: registry.example.com/web:1.8.2   # تگ ثابت، نه latest
          ports:
            - containerPort: 8080
          env:
            - name: LOG_LEVEL
              value: info
          resources:
            requests: { cpu: 100m, memory: 128Mi }
            limits:   { cpu: 500m, memory: 256Mi }
          readinessProbe:
            httpGet: { path: /healthz, port: 8080 }
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /healthz, port: 8080 }
            initialDelaySeconds: 15
            periodSeconds: 10

برچسب‌ها و selector؛ چسب کل سیستم

Deployment، Pod و Service هیچ پیوند مستقیمی ندارند؛ همه با برچسب (label) به هم وصل می‌شوند. spec.selector.matchLabels می‌گوید «من مسئول Podهایی هستم که این برچسب را دارند» و باید دقیقاً با template.metadata.labels یکی باشد؛ در غیر این صورت API مانیفست را رد می‌کند. همان برچسب را Service هم در selector خود به کار می‌برد.

requests و limits

requests مقداری است که scheduler برای جاگذاری Pod روی یک نود در نظر می‌گیرد؛ limits سقف مصرف است. اگر مصرف حافظه از limit بیشتر شود کانتینر OOMKilled می‌شود؛ مصرف CPU بالاتر از limit فقط کند (throttle) می‌شود. اگر requests را ننویسید، Pod می‌تواند روی نودی بنشیند که جا ندارد و کل نود را ناپایدار کند.

readiness و liveness؛ دو سؤال متفاوت

  • readinessProbe: «آیا همین الان می‌توانم ترافیک بگیرم؟» اگر شکست بخورد، Pod از Service کنار گذاشته می‌شود ولی ری‌استارت نمی‌شود.
  • livenessProbe: «آیا گیر کرده‌ام؟» اگر شکست بخورد، کانتینر ری‌استارت می‌شود.
warn liveness را به وابستگی‌های بیرونی (مثل دیتابیس) گره نزنید؛ اگر دیتابیس لحظه‌ای قطع شود، همه‌ی Podها هم‌زمان ری‌استارت می‌شوند و مشکل را بزرگ‌تر می‌کنند.

به‌روزرسانی تدریجی

با maxSurge: 1 و maxUnavailable: 0 قبل از حذف هر Pod قدیمی یک Pod جدید ساخته و آماده می‌شود؛ یعنی استقرار بدون قطعی (به شرط وجود readinessProbe درست).

Service: آدرس ثابت برای Podهای متغیر

Podها می‌میرند و IP عوض می‌شود. Service یک IP و نام DNS ثابت می‌دهد و ترافیک را میان Podهای آماده تقسیم می‌کند.

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: ClusterIP
  selector:
    app: web              # Podهایی که این برچسب را دارند
  ports:
    - name: http
      port: 80            # پورتی که Service نشان می‌دهد
      targetPort: 8080    # پورت کانتینر

درون کلاستر، سایر برنامه‌ها با http://web (یا web.<namespace>.svc.cluster.local) به آن می‌رسند. نوع ClusterIP پیش‌فرض و برای پشت Ingress ایده‌آل است.

Ingress: ورود از بیرون

Ingress قواعد مسیریابی HTTP را می‌گوید؛ ولی خودش کاری نمی‌کند مگر یک Ingress Controller (مثل ingress-nginx) در کلاستر نصب باشد.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
spec:
  ingressClassName: nginx
  tls:
    - hosts: [example.com]
      secretName: example-com-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80

مقدار pathType: Prefix یعنی همه‌ی مسیرهایی که با / شروع می‌شوند. secretName به یک Secret از نوع kubernetes.io/tls اشاره می‌کند که گواهی و کلید را دارد (معمولاً با cert-manager ساخته می‌شود).

اعمال و بررسی

kubectl apply -f web.yaml
kubectl rollout status deployment/web
kubectl get pods -l app=web -o wide
kubectl describe pod <pod-name>       # بخش Events دلیل اکثر مشکل‌ها را می‌گوید
kubectl logs deploy/web --tail=100
kubectl get svc,ingress

قبل از apply بهتر است ساختار YAML را بررسی کنید؛ یک تورفتگی اشتباه بیشتر از هر چیز دیگری وقت می‌گیرد. برای پیش‌نمایش تغییرات روی کلاستر زنده از kubectl diff -f web.yaml هم می‌توانید استفاده کنید.

YAMLYAML Validatorنحو YAML را بررسی کنید و محل دقیق خطای تورفتگی را پیدا کنید.

پیام‌های خطای رایج و معنی آن‌ها

وضعیتمعنیاول چه ببینید
ImagePullBackOffدانلود ایمیج شکست خوردهنام و تگ ایمیج؛ imagePullSecrets برای رجیستری خصوصی؛ دسترسی نود به رجیستری (روی سرور ایران، آینه‌ی رجیستری)
CrashLoopBackOffبرنامه بالا می‌آید و می‌میردkubectl logs --previous
Pendingجایی برای اجرا پیدا نشدهEvents: منابع نود، PVC، taint
OOMKilledحافظه از limit بیشتر شدlimits.memory و نشتی حافظه
0 endpointsService به Podی وصل نیستselector و برچسب Pod؛ readiness

قدم‌های بعدی

  • HPA (HorizontalPodAutoscaler): تعداد replicas را بر اساس CPU/حافظه خودکار تغییر می‌دهد؛ حتماً requests داشته باشید که معیار محاسبه است.
  • ConfigMap و Secret: تنظیمات را از ایمیج جدا کنید و با envFrom یا volume بخوانید.
  • PodDisruptionBudget: هنگام نگهداری نودها جلوی قطع هم‌زمان همه‌ی نمونه‌ها را بگیرد.
  • Namespace و RBAC: محیط‌ها و دسترسی‌ها را جدا کنید.

همه‌ی این منابع (Deployment تا HPA و PVC) در سازنده‌ی مانیفست آماده‌اند. و اگر هنوز کلاستر ندارید، راهنمای نصب kubeadm، kubelet و kubectl روی سرور ایران را از مخزن داخلی ببینید؛ نسخه‌ها و دستورها هم روی صفحه‌ی Kubernetes در dacker.ir آمده است.

جمع‌بندی و مطالعه‌ی بعدی

مانیفست‌های پایه (Deployment، Service و Ingress) پایه‌ی هر استقرار هستند. برای مرحله‌ی بعد، تنظیم requests، limits و HPA پایداری و مقیاس‌پذیری را تأمین می‌کند، مدیریت ConfigMap و Secret تنظیمات و رمزها را از ایمیج جدا می‌کند و راهنمای عیب‌یابی با kubectl وقتی پادها بالا نمی‌آیند مسیر بررسی را نشان می‌دهد.

پرسش‌های متداول

تفاوت Deployment و Pod چیست؟
Pod کوچک‌ترین واحد اجراست و اگر بمیرد کسی آن را برنمی‌گرداند. Deployment تعداد مورد نظر Pod را نگه می‌دارد، آن‌ها را جایگزین می‌کند و به‌روزرسانی تدریجی انجام می‌دهد. برای برنامه‌های بدون حالت تقریباً همیشه Deployment می‌نویسید.
ClusterIP، NodePort و LoadBalancer چه فرقی دارند؟
ClusterIP فقط داخل کلاستر دیده می‌شود (پیش‌فرض و بهترین انتخاب پشت Ingress)، NodePort روی یک پورت از همه‌ی نودها باز می‌شود، و LoadBalancer یک لودبالانسر ابری می‌سازد (در محیط بدون ابر معمولاً نیاز به افزونه‌هایی مثل MetalLB دارد).
چرا Pod من در حالت Pending می‌ماند؟
معمولاً scheduler نودی با منابع کافی (طبق requests) پیدا نکرده، یا PVC بسته نشده، یا taint و nodeSelector مانع شده‌اند. kubectl describe pod در بخش Events دلیل را می‌نویسد.
Secret امن است؟
مقدار Secret فقط Base64 است و هر کس به آن دسترسی RBAC داشته باشد می‌خواندش. برای امنیت واقعی از encryption at rest، محدودکردن RBAC و ابزارهایی مثل External Secrets یا Vault استفاده کنید.