شبكات Kubernetes والتخزين

مشروع: كشف التطبيق وإدامة بياناته

18 دقيقة الدرس 10 من 31

مشروع: كشف التطبيق وإدامة بياناته

يربط هذا الدرس الختامي كل ما تعلمته في التيوتوريال معاً: Ingress مع إنهاء TLS، وعزل الشبكة عبر NetworkPolicy، وتخزين دائم باستخدام PersistentVolumeClaim — وكل ذلك على مجموعة تطبيقات واقعية. المثال هو Guestbook API (واجهة خلفية بـ Node.js لا تحتفظ بحالة، مقرونة بنسخة Redis تحتفظ بالبيانات)، غير أن كل تقنية تُطبَّق مباشرةً على خدمات Java في الإنتاج، أو خطوط بيانات Python، أو أي حمل عمل يحتاج إلى حالة.

ما ستبنيه: نشران (api + redis)، وخدمتان، وIngress واحد بشهادة TLS من Let's Encrypt، وNetworkPolicy تقصر الاتصال على المسار api → redis فقط، وPersistentVolumeClaim يبقى سليماً بعد إعادة تشغيل البود وفشل العقدة.

الخطوة 1 — Namespace وعزل الموارد

أعطِ دائماً لكل تطبيق حقيقي Namespace خاصاً به. يمنحك ذلك حدود RBAC واضحة، ويجعل مخرجات kubectl مقروءة، ويسمح لمحددات NetworkPolicy بالتأثير فقط على البودات التي تعنيك.

kubectl create namespace guestbook kubectl config set-context --current --namespace=guestbook

الخطوة 2 — التخزين الدائم لـ Redis

Redis هو مخزن في الذاكرة، لكن خاصية الاستمرارية (RDB/AOF) تكتب على القرص. بدون PVC، يُفقد كل البيانات عند إعادة تشغيل البود. يطلب الـ PVC أدناه حجم 10Gi من StorageClass الافتراضي في الكلستر — على AWS هو gp3، وعلى GKE هو standard-rwo. وضع الوصول ReadWriteOnce صحيح: Redis عملية كتابة واحدة ولا يجوز مشاركة الحجم مع نسخة أخرى.

# redis-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: redis-data namespace: guestbook spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi # اترك storageClassName فارغاً لاستخدام الافتراضي --- # redis-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: redis namespace: guestbook spec: replicas: 1 selector: matchLabels: app: redis template: metadata: labels: app: redis tier: cache spec: containers: - name: redis image: redis:7.2-alpine args: ["--appendonly", "yes"] ports: - containerPort: 6379 volumeMounts: - name: data mountPath: /data resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi" volumes: - name: data persistentVolumeClaim: claimName: redis-data --- apiVersion: v1 kind: Service metadata: name: redis namespace: guestbook spec: selector: app: redis ports: - port: 6379 targetPort: 6379 clusterIP: None # headless — فقط بود الـ api يجب أن يتحدث مع redis مباشرة
لماذا Headless لـ Redis؟ الخدمة الـ Headless (clusterIP: None) تجعل DNS يُعيد IP البود مباشرةً بدلاً من VIP. يتيح ذلك لعملاء Redis استخدام منطق consistent-hashing أو تجميع الاتصالات دون نقرة إضافية عبر kube-proxy.

الخطوة 3 — نشر API بلا حالة وخدمته

الـ API قابل للتوسع أفقياً: ثلاث نسخ، بدون حالة محلية. يقرأ متغير البيئة REDIS_HOST لتحديد موقع Redis. نوع الخدمة هو ClusterIP — يجب أن لا تكون LoadBalancer لأن Ingress controller سيوجه حركة المرور إليها داخلياً.

# api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: guestbook-api namespace: guestbook spec: replicas: 3 selector: matchLabels: app: guestbook-api template: metadata: labels: app: guestbook-api tier: api spec: containers: - name: api image: gcr.io/myorg/guestbook-api:v1.2.0 ports: - containerPort: 8080 env: - name: REDIS_HOST value: redis.guestbook.svc.cluster.local - name: REDIS_PORT value: "6379" readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "256Mi" --- apiVersion: v1 kind: Service metadata: name: guestbook-api namespace: guestbook spec: selector: app: guestbook-api ports: - port: 80 targetPort: 8080

الخطوة 4 — Ingress مع TLS

هنا يدخل الحركة الخارجية إلى الكلستر. يفترض المانيفست أدناه أنك تستخدم ingress-nginx controller وcert-manager مع ClusterIssuer باسم letsencrypt-prod. الحاشية cert-manager.io/cluster-issuer هي كل ما تحتاج إضافته — cert-manager يراقبها، ويستصدر شهادة TLS عبر ACME HTTP-01، ويخزنها في Secret باسم guestbook-tls. يقرأ Ingress controller هذا السيكريت ثم يُنهي TLS قبل إعادة توجيه HTTP العادي إلى الخدمة الخلفية.

# ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: guestbook-ingress namespace: guestbook annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "4m" spec: ingressClassName: nginx tls: - hosts: - guestbook.example.com secretName: guestbook-tls rules: - host: guestbook.example.com http: paths: - path: / pathType: Prefix backend: service: name: guestbook-api port: number: 80
يجب أن يحل DNS قبل أن يتمكن cert-manager من إصدار الشهادة. أنشئ سجل DNS من نوع A يشير guestbook.example.com إلى الـ IP الخارجي لـ Ingress controller قبل تطبيق هذا المانيفست. سيفشل cert-manager في التحقق عبر ACME HTTP-01 — ويتراجع بشكل متصاعد — إذا لم يُحل النطاق. استخدم kubectl describe certificate guestbook-tls -n guestbook لمتابعة حالة الإصدار.

الخطوة 5 — NetworkPolicy: العزل بمبدأ الثقة الصفرية

بشكل افتراضي، يسمح Kubernetes بكل حركة المرور بين البودات داخل الكلستر. في الإنتاج، يعني ذلك أن بود API مخترقاً يمكنه الاتصال مباشرةً بأي قاعدة بيانات أو مخزن أسرار أو نقطة تحكم داخلية. تفرض NetworkPolicies وضعية أمان الشبكة بمبدأ أقل الصلاحيات. السياستان أدناه تُطبقان قائمة سماح صارمة:

  • Redis يقبل TCP/6379 فقط من البودات المُعلَّمة بـ tier: api في نفس الـ namespace.
  • الـ API يقبل المنفذ 8080 من namespace الـ Ingress controller فقط، ولا شيء آخر.
# network-policy.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: redis-allow-api-only namespace: guestbook spec: podSelector: matchLabels: app: redis policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: tier: api ports: - protocol: TCP port: 6379 egress: [] # Redis لا تحتاج أبداً لبدء اتصالات --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: api-allow-ingress-controller namespace: guestbook spec: podSelector: matchLabels: app: guestbook-api policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: ingress-nginx ports: - protocol: TCP port: 8080
End-to-end request flow: Internet → Ingress → API → Redis (with PVC) Internet HTTPS :443 TLS Ingress nginx controller TLS termination cert-manager TLS NetworkPolicy ✓ HTTP :8080 guestbook-api Deployment (3 replicas) tier: api Service: ClusterIP NetworkPolicy ✓ readinessProbe /healthz TCP :6379 Redis Deployment (1 replica) Service: Headless NetworkPolicy ✓ appendonly yes PVC: redis-data 10Gi RWO gp3 Namespace: guestbook
مسار الطلب من البداية إلى النهاية: تُنهى HTTPS عند Ingress controller، ثم تُوجَّه إلى بودات الـ API (محمية بـ NetworkPolicy)، التي تكتب البيانات الدائمة في Redis المدعوم بـ PVC.

الخطوة 6 — التطبيق والتحقق

طبِّق كل شيء بترتيب التبعيات، ثم تأكد من صحة كل طبقة قبل الانتقال إلى التالية.

# تطبيق جميع المانيفستات kubectl apply -f redis-pvc.yaml kubectl apply -f redis-deployment.yaml kubectl apply -f api-deployment.yaml kubectl apply -f ingress.yaml kubectl apply -f network-policy.yaml # التحقق من أن PVC في حالة Bound (الـ StorageClass وفّر الحجم) kubectl get pvc -n guestbook # التحقق من أن البودات في حالة Running وجاهزة kubectl get pods -n guestbook -w # التحقق من أن Ingress يمتلك عنواناً (الـ IP الخارجي لـ LB) kubectl get ingress -n guestbook # مراقبة إصدار شهادة TLS من cert-manager (~60 ثانية على كلستر سليم) kubectl describe certificate guestbook-tls -n guestbook # اختبار سريع للنقطة النهائية المباشرة curl -v https://guestbook.example.com/healthz # اختبار تطبيق NetworkPolicy: يجب أن ينتهي بمهلة (لا مسار من default إلى redis) kubectl run test-pod --rm -it --image=busybox --namespace=default \ -- sh -c "nc -zv redis.guestbook.svc.cluster.local 6379" # يجب أن ينجح (بود من طبقة api → redis) kubectl exec -n guestbook deploy/guestbook-api \ -- sh -c "nc -zv redis.guestbook.svc.cluster.local 6379"

أنماط الفشل في الإنتاج والتحصين

لكل خطوة في هذا المشروع نمط فشل مقابل يظهر في الكلسترات الحقيقية:

  • PVC عالق في Pending — الـ StorageClass غير موجود أو CSI driver غير مُثبَّت. شغِّل kubectl describe pvc redis-data -n guestbook؛ ابحث في قسم Events عن خطأ الـ provisioner.
  • الشهادة عالقة في Pending — لم ينتشر DNS بعد، أو تحدي ACME HTTP-01 محجوب بسبب NetworkPolicy. يُنشئ cert-manager Ingress مؤقتاً وبود تحدٍّ HTTP في namespace الخاص به. تأكد من أن NetworkPolicy في namespace الـ API لا تمنع المنفذ 80 الوارد من cert-manager.
  • 502 Bad Gateway من Ingress — فحص الجاهزية (readiness probe) يفشل، لذا لا أعضاء في Endpoints. صِف البود وافحص سجلات الفحص: kubectl describe pod -n guestbook -l app=guestbook-api.
  • فقدان البيانات بعد إعادة تشغيل بود Redis — نسيت تركيب PVC، أو أُحذف PVC. دائماً عيِّن reclaimPolicy: Retain على StorageClasses في الإنتاج للحماية من الحذف العرضي للـ PVC.
أدِر مجموعة المانيفستات هذه بـ GitOps. أودِع جميع ملفات YAML في مستودع وأدِرها بـ Argo CD أو Flux. بذلك، يصبح kubectl delete namespace guestbook (عرضياً أو خبيثاً) حدثاً ذاتي الإصلاح — يُعيد مراقب GitOps إنشاء الـ namespace ويُعيد مزامنة كل مورد في غضون ثوانٍ.