شبكة الخدمات: Istio وLinkerd

تشغيل الشبكة وأبرز المزالق

18 دقيقة الدرس 9 من 27

تشغيل الشبكة وأبرز المزالق

تشغيل شبكة الخدمات في بيئة الإنتاج على نطاق واسع يختلف اختلافاً جوهرياً عن مجرد تثبيتها. العمل الهندسي الحقيقي يكمن في إدارة الترقيات، والتحكم في تكاليف الأداء، والحكمة في معرفة متى تُضيف الشبكة تعقيداً أكبر مما تُزيله. يتناول هذا الدرس هذه الأبعاد التشغيلية الثلاثة بالعمق الذي يتوقعه مهندس SRE أو مهندس منصة أول.

استراتيجيات الترقية

تتبع مستويات التحكم في شبكات الخدمات — Istio و Linkerd — دورات إصدار متسارعة (كل ستة إلى ثمانية أسابيع تقريباً لـ Istio). التأخر بإصدارين أو أكثر عن آخر إصدار يُشكّل مخاطر أمنية ودعم فني جدية. تتشارك استراتيجيات الترقية في الإنتاج نمطاً مشتركاً: الفصل بين ترقية مستوى البيانات ومستوى التحكم، والتحقق في بيئة الكناري أولاً، والحفاظ دائماً على مسار للرجوع إلى الإصدار السابق.

Istio: الترقية بنهج الكناري المعتمد على المراجعات

منذ الإصدار Istio 1.10، يُوصى باستخدام علامات المراجعة (revision tags). تُثبَّت مستوى تحكم جديد بجانب القديم، ثم يُرحَّل نسبة صغيرة من الـ namespaces إلى المراجعة الجديدة، وبعد التحقق، يُرحَّل الباقي.

# الخطوة 1 — تثبيت المراجعة الجديدة (الحالية = 1.21، الجديدة = 1.22) istioctl install --set revision=1-22 --set profile=default -y # الخطوة 2 — إنشاء علامة canary تشير إلى مستوى التحكم الجديد istioctl tag set canary --revision=1-22 --overwrite # الخطوة 3 — وضع علامة على namespace واحد لاستخدام المراجعة الجديدة kubectl label namespace payments istio.io/rev=canary --overwrite # الخطوة 4 — إعادة تشغيل دورية لحقن بروكسيات جديدة في هذا الـ namespace kubectl rollout restart deployment -n payments # الخطوة 5 — التحقق: يجب أن تكون إصدارات البروكسي في payments هي 1.22.x istioctl proxy-status -n payments # الخطوة 6 — تحريك العلامة الافتراضية إلى المراجعة الجديدة (تنقل جميع الـ namespaces غير المُعلَّمة) istioctl tag set default --revision=1-22 --overwrite kubectl rollout restart deployment --all-namespaces # الخطوة 7 — بعد التأكد، إزالة المراجعة القديمة istioctl uninstall --revision=1-21 -y kubectl delete validatingwebhookconfigurations istio-validator-1-21-istiod
قاعدة الإنتاج: لا تُنفّذ kubectl rollout restart على جميع الـ namespaces دفعة واحدة. قسّمها على مراحل namespace تلو الآخر لتجنب ازدحام عمليات bootstrap للبروكسيات التي تُثقل istiod. في بيئات Google-scale، تمتد الترقية التدريجية من ساعتين إلى أربع ساعات عبر مئات الـ namespaces.

Linkerd: الترقية عبر واجهة CLI

مسار ترقية Linkerd أبسط. مستوى تحكمه عديم الحالة وبروكسياته تُحقن تلقائياً. الترقية القياسية لإصدار فرعي تستغرق أقل من عشر دقائق:

# ترقية الـ CLI محلياً أولاً curl --proto '=https' --tlsv1.2 -sSfL https://run.linkerd.io/install | sh linkerd version # تحقق أن CLI هو 2.15.x # ترقية مستوى التحكم في مكانه (Kubernetes rolling update) linkerd upgrade | kubectl apply -f - # انتظار اكتمال الترقية kubectl rollout status deploy -n linkerd # تدوير مستوى البيانات (إعادة تشغيل الـ workloads للحصول على إصدار بروكسي جديد) kubectl rollout restart deploy -n prod # التحقق من صحة كل شيء linkerd check linkerd viz stat deploy -n prod
يستخدم Linkerd trust anchors (شهادات CA الجذرية) بصلاحية افتراضية مدتها 10 سنوات، وissuer certificates بصلاحية افتراضية 24 ساعة. تدوير الـ issuer تلقائي، لكن تدوير trust anchor يتطلب تدخلاً يدوياً كل بضع سنوات. أضف هذا الموعد في التقويم قبل انتهاء الصلاحية — انتهاء صلاحية trust anchor يُعطّل mTLS بصمت عبر الشبكة بأكملها.

تكاليف الأداء: أرقام حقيقية

كل بروكسي في نموذج الـ sidecar يُضيف قفزتين على كل استدعاء بين الخدمات — واحدة للخروج وواحدة للدخول. فهم التكلفة الفعلية يمنع المبالغة في التهيئة والمفاجآت في الإنتاج.

زمن الاستجابة الإضافي (p50 / p99) من البنش مارك المنشورة والبيانات الواقعية:

  • Istio (Envoy sidecar): ~1–2 ملي ثانية إضافية عند p50، ~5–10 ملي ثانية عند p99 بحمل معتدل. عند تزامن عالٍ (>10 آلاف طلب في الثانية لكل pod)، يرتفع الذيل p99 بشكل ملحوظ.
  • Linkerd (Rust proxy): ~0.5–1 ملي ثانية عند p50، ~2–4 ملي ثانية عند p99. حجم الـ microproxy المكتوب بـ Rust يُترجم إلى زمن ذيل أقل.
  • Ambient mode (Istio 1.22+): أقل من ملي ثانية للطبقة L4 فقط؛ waypoint للطبقة L7 يُضيف ~1–2 ملي ثانية لكنه مشترك على مستوى الـ namespace، لا لكل pod.

استهلاك CPU والذاكرة لكل sidecar:

  • Envoy (Istio): ~50–100m CPU عند الخمول، ~50–70 MB RSS. عند 1000 pod، هذا يعني 50–100 نواة وذاكرة 50–70 GB تستهلكها البروكسيات وحدها.
  • Linkerd proxy: ~5–10m CPU عند الخمول، ~10–15 MB RSS. أخف بمرتبة كاملة.

لقياس تكاليف الأداء الأساسية في مجموعتك، نفّذ اختبار حمل على خدمة مع وبدون الـ injection بنفس ملف الحمل:

# اختبار k6 — احفظه كـ mesh-bench.js import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { scenarios: { constant_load: { executor: 'constant-arrival-rate', rate: 500, timeUnit: '1s', duration: '2m', preAllocatedVUs: 50, }, }, thresholds: { http_req_duration: ['p(99)<50'], }, }; export default function () { const res = http.get('http://checkout.prod.svc.cluster.local/health'); check(res, { 'status 200': (r) => r.status === 200 }); } # التشغيل بدون شبكة (استبعاد الـ namespace من الحقن): kubectl label namespace prod istio-injection=disabled --overwrite kubectl rollout restart deploy -n prod k6 run mesh-bench.js --out json=no-mesh.json # التشغيل مع الشبكة: kubectl label namespace prod istio-injection=enabled --overwrite kubectl rollout restart deploy -n prod k6 run mesh-bench.js --out json=with-mesh.json
فخ البنش مارك: أرقام زمن الاستجابة لا تُقدّم الصورة كاملة. الشبكة تُضيف تكلفة ثابتة، لكن الذيل هو الأهم. الفرق بين p99.9 عند 5 ملي ثانية مقابل 1 ملي ثانية هو الفرق بين SLO بنسبة 99.9% وSLO بنسبة 99.5% عند تجميع مئات قفزات الخدمات.

متى لا تستخدم شبكة الخدمات

أفرطت الصناعة في التوجه نحو "ضع كل شيء في شبكة" بين عامَي 2019 و2022. المهندسون الأول في الشركات الكبرى توصلوا منذ ذلك الحين إلى موقف أكثر دقة: الشبكة مبررة فقط عندما تتجاوز القيمة التشغيلية التكلفة التشغيلية لملف عملك المحدد.

لا تستخدم الشبكة إذا:

  • مجموعة صغيرة (<20 خدمة، <50 pod): تكلفة مستوى التحكم ومنحنى التعلم وعبء إدارة الترقيات تفوق الفائدة. mTLS وإعادة المحاولات قابلة للتحقيق بمكتبات على مستوى التطبيق أو بوابة API وحدها.
  • الخدمات الحساسة لزمن الاستجابة ذات fan-out العالي: محرك المزايدة في الوقت الفعلي أو نظام التداول المنخفض الكمون الذي يُجري أكثر من 50 استدعاءً دفعياً لكل طلب لا يتحمل حتى 1 ملي ثانية تراكمية لكل قفزة. قِس أولاً.
  • أحمال الدُفعات أو الـ Jobs قصيرة العمر: حقن sidecar في Pod يعيش 30 ثانية يُهدر وقت bootstrap وذاكرة. يجب استثناء Kubernetes Jobs و CronJobs عادةً عبر PodAnnotation: sidecar.istio.io/inject: "false".
  • الفِرق التي تفتقر لخبرة Kubernetes/Envoy: الشبكة تُضخّم الأخطاء في الإعداد. سياسة AuthorizationPolicy خاطئة النطاق يمكنها إسقاط 100% من حركة المرور إلى خدمة بصمت.
  • التطبيقات الضخمة أو ذات طبقتين: موازن حمل + إنهاء TLS + قاطع دائرة على مستوى التطبيق كافٍ تماماً.

لا تضع كامل المجموعة في الشبكة بشكل موحد:

حتى عندما تكون الشبكة مبررة، الحقن الانتقائي هو أفضل الممارسات. استثنِ: بنية التحتية للبيانات (Prometheus و Grafana ووكلاء التسجيل)، والـ StatefulSets ذات مسارات I/O الحساسة للأداء، وأي namespace لا تمتلك فيه الفريق الخبرة اللازمة لتصحيح أخطاء فشل mTLS handshake.

Mesh Operations: Upgrade Flow and Decision Points Revision-Based Canary Upgrade Flow Revision 1-21 (Old) istiod-1-21 (control plane) tag: default namespace: checkout namespace: orders namespace: users 1. install Revision 1-22 (New) istiod-1-22 (control plane) tag: canary → default (step 6) namespace: payments (step 3) checkout → migrated (step 6) orders / users → migrated validate & monitor Both revisions co-exist during migration — rollback = relabel namespace back to 1-21 tag, restart pods
تدفق الترقية التدريجية بالكناري في Istio: مستويا التحكم القديم والجديد يعملان معاً؛ تُرحَّل الـ namespaces واحداً تلو الآخر.

أبرز المزالق في الإنتاج

إلى جانب الترقيات والأداء، هذه هي أنماط الفشل التشغيلي التي تُسبب أكثر الحوادث في بيئات الشبكة الإنتاجية:

  • انتهاء صلاحية الشهادات بشكل متتالٍ: يُدير istiod شهادات الـ workload كل 24 ساعة افتراضياً. إذا كان istiod غير قابل للوصول (مثقَل، أو قُتل بسبب OOM)، ستبدأ البروكسيات في رفض mTLS handshakes بعد انتهاء TTL شهاداتها. عيّن PILOT_CERT_PROVIDER وتأكد من أن istiod يمتلك PodDisruptionBudget وهامش HPA كافيا.
  • مهلة انتهاء الـ Webhook الاستقبالي: إذا كان istiod بطيئاً أو غير متاح وتم تعيين failurePolicy: Fail، فإن جدولة جميع الـ pods عبر جميع الـ namespaces المُحقَنة تتوقف. يتحول الكثير من الفِرق إلى failurePolicy: Ignore لصالح التوافر على حساب فقدان الحقن عند الفشل.
  • ترتيب EnvoyFilter وتفاوت الإصدارات: تُطبَّق موارد EnvoyFilter بترتيب طابع وقت الإنشاء وهي مرتبطة بإصدار Envoy API. بعد الترقية، قد تتوقف EnvoyFilter قديمة تستهدف مساراً مهجوراً عن التطبيق بصمت. تحقق دائماً بعد الترقية بـ istioctl analyze.
  • Sidecars الزومبي بعد إلغاء الـ namespace: إزالة علامة الحقن من namespace لا تُخرج الـ sidecars الموجودة. الـ Pods تحتفظ ببروكسياتها حتى تُعاد تشغيلها، مما يخلق سيناريو انفصام حيث بعض الـ pods تشارك في mTLS وأخرى لا تشارك، مُسببةً 503s متقطعة.
أخطر فخ في Istio: تعيين PeerAuthentication على مستوى المجموعة بالكامل إلى وضع STRICT قبل حقن جميع الـ namespaces والـ workloads بالكامل. أي pod غير مُحقَن يفقد جميع الاتصالات الواردة فوراً. المسار الآمن هو PERMISSIVE أولاً، ثم الانتقال التدريجي namespace بـ namespace إلى STRICT بعد التأكد من تغطية الحقن.

قائمة التحقق التشغيلية للشبكات الإنتاجية

  1. ثبّت إصدار الشبكة في GitOps (HelmRelease أو ArgoCD Application) وأتمتة طلبات الترقية عبر Renovate أو Dependabot.
  2. راقب صحة مستوى التحكم كـ SLO من الدرجة الأولى: CPU وذاكرة istiod، وزمن push لـ xDS، ومعدل نجاح تدوير الشهادات.
  3. احتفظ بـ runbook مختبَر للرجوع للإصدار السابق لكل زوج من الإصدارات — ليس مجرد توثيق، بل runbook مُدرَّب عليه مع هدف make rollback-mesh في مستودع المنصة.
  4. استثنِ الـ namespaces التي لا تحتاج إلى الشبكة (batch jobs، وكلاء البنية التحتية) باستخدام علامات الـ namespace وإعدادات استثناء MeshConfig.
  5. حدد طلبات الموارد وحدودها على الـ sidecars عبر ProxyConfig لمنع تداخل الجوار الصاخب مع حاويات التطبيق.