DevOps Academy

Kubernetes

אורקסטרציה של containers בproduction: Pods, Deployments, Services, Ingress ו-ops יומיומי

תיאוריה

⏱ ~70 דקות

Kubernetes בשוק העבודה הישראלי — למה זה הכי נדרש

Kubernetes מופיע ב-12.9% ממשרות DevOps בישראל — המספר הגבוה ביותר מבין כל הכלים הנסרקים. כמעט כל חברה שמריצה microservices או שירותים בענן מצפה לידע K8s. אבל הקורסים הסטנדרטיים מלמדים 'הגדר Pod'. הפערים האמיתיים:

- **Readiness vs Liveness probes** — בלעדיהם, traffic נשלח לcontainer שעדיין לא מוכן
- **Resource requests + limits** — בלעדיהם, pods מורידים nodes
- **Rolling updates + rollback** — כיצד לעדכן בלי downtime ולחזור אחורה בשניות
- **kubectl debug** — כיצד לחקור pod קרוס בproduction
- **Namespaces + RBAC** — הפרדת סביבות ב-cluster אחד
- **ConfigMap + Secret** — inject configuration בלי לstick env vars לimage

נתון חשוב: בישראל, EKS (AWS) הוא ה-distribution הנפוץ ביותר, אחריו GKE. self-managed clusters נדירים מחוץ לבנקים ורפואה.

ארכיטקטורת Kubernetes — Control Plane, Nodes, etcd

Kubernetes cluster מורכב משני רכיבים עיקריים:

**Control Plane** — 'המוח' של ה-cluster:
- kube-apiserver — API endpoint לכל פקודות kubectl
- etcd — distributed key-value store שמאחסן את כל state ה-cluster
- kube-scheduler — מחליט על איזה node לרוץ כל Pod
- kube-controller-manager — מנהל controllers (ReplicaSet, Deployment, Node)

**Worker Nodes** — 'הגוף' שמריץ containers:
- kubelet — agent על כל node, מתקשר עם Control Plane
- kube-proxy — ניתוב network traffic ל-Services
- container runtime — containerd (ברוב distributions מ-K8s 1.24+)

**הקשר לDocker:** Kubernetes לא מריץ Docker containers ישירות — הוא משתמש ב-containerd (ה-runtime) שמריץ OCI-compatible images. כל image שבנינו עם Docker תקף ל-Kubernetes.

**etcd — לב ה-cluster:**

# בדיקת health של etcd (רק ב-control plane)
kubectl get componentstatuses

# מספר nodes בcluster
kubectl get nodes -o wide

# מידע מפורט על node ספציפי
kubectl describe node <node-name>

Pods, ReplicaSets, ו-Deployments — hierarchy של אובייקטים

**Pod** — היחידה הבסיסית. container אחד או יותר שחולקים network namespace ו-storage:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
  labels:
    app: my-app
spec:
  containers:
  - name: app
    image: nginx:1.25
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "100m"
      limits:
        memory: "128Mi"
        cpu: "500m"

לא יוצרים Pods ידנית בproduction — **Deployment** מנהל Pods דרך ReplicaSet:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: ghcr.io/myorg/my-app:sha-abc1234
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"

**פקודות יומיומיות:**

kubectl apply -f deployment.yaml
kubectl get deployments -n production
kubectl get pods -n production -l app=my-app
kubectl describe pod <pod-name> -n production

Liveness ו-Readiness Probes — production gap קריטי

פער production

**production gap: health probes for zero-downtime deployments**

בלי probes, Kubernetes שולח traffic לContainer שעדיין בstartup, גורם ל-502 errors.

**Readiness Probe** — האם ה-container מוכן לקבל traffic?
**Liveness Probe** — האם ה-container עדיין פועל (לא תקוע בdead-lock)?

spec:
  containers:
  - name: app
    image: my-app:latest
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 3000
      initialDelaySeconds: 10   # המתן לפני בדיקה ראשונה
      periodSeconds: 5          # בדוק כל 5 שניות
      failureThreshold: 3       # 3 כשלונות → Pod לא ready
      successThreshold: 1       # כשלון אחד מספיק להוציא מtraffic
    livenessProbe:
      httpGet:
        path: /health/live
        port: 3000
      initialDelaySeconds: 30   # אחרי שreadiness הצליח
      periodSeconds: 10
      failureThreshold: 3       # 3 כשלונות → restart container
      timeoutSeconds: 5
    startupProbe:               # עבור apps עם startup ארוך (Java, Scala)
      httpGet:
        path: /health/live
        port: 3000
      failureThreshold: 30      # 30 * 10s = 5 דקות startup timeout
      periodSeconds: 10

**מה קורה בRolling Update עם readiness:**
1. Kubernetes מפעיל Pod חדש
2. ממתין עד שreadiness Probe מצליח
3. רק אז מסיר Pod ישן מה-load balancer
4. מחכה שreplica ישן יסיים requests קיימות (terminationGracePeriodSeconds)
5. עובר ל-Pod הבא

בלי readiness Probe: שלב 3 קורה מיד אחרי container start — לפני שהapp מוכן.

Services ו-Ingress — ניתוב traffic

**Service** — stable DNS name ו-IP עבור קבוצת Pods:

apiVersion: v1
kind: Service
metadata:
  name: my-app-svc
  namespace: production
spec:
  selector:
    app: my-app    # כל Pods עם label זה
  ports:
  - port: 80       # port שנחשף ע"י ה-Service
    targetPort: 3000  # port שהcontainer מאזין עליו
  type: ClusterIP  # רק בתוך ה-cluster (ברירת מחדל)

**Service types:**
| Type | שימוש |
|------|-------|
| ClusterIP | בתוך cluster בלבד (ברירת מחדל) |
| NodePort | חושף port על כל node (dev/testing) |
| LoadBalancer | יוצר AWS/GCP LB (יקר, לאפליקציות ישירות) |
| ExternalName | alias ל-external DNS |

**Ingress** — reverse proxy ב-cluster, L7 routing:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - api.mycompany.com
    secretName: api-tls-cert
  rules:
  - host: api.mycompany.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-svc
            port:
              number: 80

**nginx-ingress controller** הוא ה-reverse proxy שמפעיל את ה-Ingress rules — בדיוק כמו nginx שלמדנו, אבל מנוהל ע"י Kubernetes.

ConfigMaps ו-Secrets — inject configuration

**ConfigMap** — configuration non-sensitive:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  NODE_ENV: production
  LOG_LEVEL: info
  DB_HOST: postgres.production.svc.cluster.local
  DB_PORT: "5432"

**Secret** — data sensitive (base64 encoded, לא encrypted בברירת מחדל!):

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: production
type: Opaque
data:
  DB_PASSWORD: c3VwM3JzM2NyM3Qh   # base64 encoded
  API_KEY: bXlhcGlrZXkxMjM=
# יצירת secret מvalue ישיר (מקודד אוטומטית)
kubectl create secret generic app-secrets \
  --from-literal=DB_PASSWORD=sup3rs3cr3t! \
  --from-literal=API_KEY=myapikey123 \
  -n production

**Inject ל-container:**

spec:
  containers:
  - name: app
    envFrom:
    - configMapRef:
        name: app-config
    - secretRef:
        name: app-secrets
    # או env var ספציפי:
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: app-secrets
          key: DB_PASSWORD

**חשוב:** K8s Secrets הם base64, לא encrypted בetcd כברירת מחדל. בproduction: הפעל Encryption at Rest, השתמש ב-External Secrets Operator עם AWS Secrets Manager/Vault, או Sealed Secrets.

Rolling Updates ו-Rollback — zero-downtime deployments

פער production

**production gap: rolling update strategy and kubectl rollout**

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0    # אף Pod לא מוריד — חובה לzero-downtime
      maxSurge: 1          # Pod נוסף אחד בזמן update

**עדכון image ב-CI/CD:**

# ב-GitHub Actions deploy job:
kubectl set image deployment/my-app \
  app=ghcr.io/myorg/my-app:sha-${{ github.sha }} \
  -n production

# המתן שה-rollout יסתיים (timeout 5 דקות)
kubectl rollout status deployment/my-app -n production --timeout=5m

**Rollback — חזרה לversions קודמות:**

# בדוק history
kubectl rollout history deployment/my-app -n production

# חזרה לversion קודם
kubectl rollout undo deployment/my-app -n production

# חזרה לversion ספציפי
kubectl rollout undo deployment/my-app --to-revision=3 -n production

# בדוק שה-rollback הצליח
kubectl rollout status deployment/my-app -n production

**Deployment pause/resume לcanary manual:**

# עצור rollout אחרי Pod הראשון (canary manual)
kubectl rollout pause deployment/my-app -n production

# בדוק metrics על ה-canary Pod
# אם הכל תקין:
kubectl rollout resume deployment/my-app -n production

kubectl debugging — חקירת בעיות בproduction

פער production

**production gap: kubectl debug and log inspection**

# בדיקת מצב כללי
kubectl get pods -n production -o wide
kubectl get events -n production --sort-by='.lastTimestamp'

# Pod לא עולה — describe מציג events ו-conditions
kubectl describe pod <pod-name> -n production
# חפש ב-Events: OOMKilled, ImagePullBackOff, CrashLoopBackOff

# Logs מcontainer רץ
kubectl logs <pod-name> -n production
kubectl logs <pod-name> -n production --previous  # מcontainer קרוס
kubectl logs <pod-name> -n production -f          # streaming
kubectl logs -l app=my-app -n production --tail=100  # מכל pods עם label

# Shell בתוך container (debug)
kubectl exec -it <pod-name> -n production -- /bin/sh

# kubectl debug — ephemeral container (K8s 1.23+)
# שימושי כשה-main container לא כולל shell
kubectl debug -it <pod-name> -n production \
  --image=busybox --target=app

# Port forward לגישה ל-service מהמחשב המקומי
kubectl port-forward svc/my-app-svc 8080:80 -n production
# עכשיו: curl http://localhost:8080/health

# בדיקת resource usage
kubectl top pods -n production
kubectl top nodes

**פירוש error states נפוצים:**
| State | משמעות | פתרון |
|-------|---------|--------|
| CrashLoopBackOff | container קורס שוב ושוב | kubectl logs --previous |
| OOMKilled | חרג מlimit memory | הגדל memory limit |
| ImagePullBackOff | לא הצליח להוריד image | בדוק imagePullSecrets, registry creds |
| Pending (no nodes match) | אין node מתאים | kubectl describe pod — בדוק node selectors/taints |

Namespaces ו-Resource Quotas — הפרדת סביבות

Namespaces מאפשרים הפרדה לוגית ב-cluster אחד:

kubectl create namespace staging
kubectl create namespace production
kubectl create namespace monitoring

# פקודות עם namespace
kubectl get pods -n staging
kubectl get all -n production

# הגדר namespace default ל-context
kubectl config set-context --current --namespace=production

**ResourceQuota — הגבלת resources לnamespace:**

apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "20"
    services: "10"

**LimitRange — הגבלות per-container:**

apiVersion: v1
kind: LimitRange
metadata:
  name: container-limits
  namespace: production
spec:
  limits:
  - type: Container
    default:          # default limit אם לא הוגדר
      cpu: "500m"
      memory: "256Mi"
    defaultRequest:   # default request
      cpu: "100m"
      memory: "64Mi"
    max:              # מקסימום מותר
      cpu: "2"
      memory: "2Gi"

HorizontalPodAutoscaler ו-resource requests — scaling אוטומטי

פער production

**production gap: resource requests/limits and HPA configuration**

HPA מוסיף ומסיר Pods לפי load:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70  # scale up כש-CPU > 70%
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

**חשוב:** HPA דורש שcpu/memory **requests** יהיו מוגדרים בDeployment — בלעדיהם HPA לא יכול לחשב % utilization.

**Requests vs Limits:**
- requests — מה ה-scheduler מבטיח לPod (כמה CPU/memory node חייב לשמור)
- limits — מקסימום שcontainer יכול לצרוך
- container שעובר memory limit → **OOMKilled**
- container שעובר CPU limit → **throttled** (לא killed)

# בדוק HPA status
kubectl get hpa -n production
kubectl describe hpa my-app-hpa -n production

# בדוק events scaling
kubectl get events -n production | grep HorizontalPodAutoscaler

תרגול מעשי

1.

2.

3.


שאלות חיבור

חבר את מה שלמדת בנושא זה לנושאים קודמים. אין תשובה אחת נכונה — חשיבה ביקורתית היא המטרה.

שאלת חיבור 1

Kubernetes Ingress controller הוא nginx שרץ בתוך cluster — ישירות מחבר ל-nginx + Web Servers (topic #5). הסבר: (1) מה ההבדל בין nginx שהגדרנו ב-topic 5 (reverse proxy על VPS) לבין nginx-ingress controller ב-Kubernetes, (2) כיצד rate limiting שלמדנו (limit_req_zone) מוגדר ב-Ingress annotations, ו-(3) כיצד TLS termination ב-Kubernetes Ingress מקביל ל-SSL termination שלמדנו ב-nginx.

מחבר ל:
nginx + Web Servers

שאלת חיבור 2

GitHub Actions CI/CD (topic #8) מחולל Docker image ודופס ל-ghcr.io — ואז Kubernetes צריך לדפלוי אותו. הסבר את ה-end-to-end flow: (1) כיצד ה-deploy job ב-GitHub Actions מעדכן את ה-Deployment ב-Kubernetes, (2) כיצד imagePullSecrets מאפשר ל-Kubernetes לשלוף image מ-ghcr.io, ו-(3) מה יקרה אם `kubectl rollout status` timeout ב-5 דקות ומה ה-pipeline צריך לעשות.

מחבר ל:
CI/CD — GitHub Actions

מוכן לבחינה?

בצע הערכה תיאורטית, תרגול CLI ושאלות חיבור כדי לסיים את הנושא.