اللاخوادم والعمليات المدفوعة بالأحداث

الحاويات تلتقي بـ Serverless

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

الحاويات تلتقي بـ Serverless

يعمل نموذج الدوال كخدمة (FaaS) بكفاءة عالية للحوسبة عديمة الحالة والقصيرة العمر — لكنه يفشل حين تحتاج إلى طبقة نظام تشغيل مخصصة، أو حزمة نشر أكبر مما يتسع له Lambda، أو بيئة تشغيل غير مدعومة، أو أحمال عمل تحتاج مئات الميغابايت من أوزان نماذج التعلم الآلي. تحل الحاويات بلا خوادم (Serverless Containers) هذه المشكلة بالجمع بين نموذج تغليف الحاويات ونموذج التشغيل اللاخادمي: لا تُهيئ جهازاً افتراضياً أو تُدير عقدة مجموعة، بينما تُشغّل صور حاويات اعتباطية. تُعدّ AWS Fargate وGoogle Cloud Run المنصتين المهيمنتين، وفهم بنيتهما على مستوى المستوى التحكمي هو ما يُميّز المهندسين القادرين على اتخاذ قرارات السعة والتكلفة المبنية على مبادئ عن غيرهم.

Fargate: الحاويات بلا خوادم على AWS

Fargate ليس خدمة مستقلة — بل هو نوع تشغيل (launch type) لكل من Amazon ECS وAmazon EKS. حين تُشغّل مهمة على Fargate، تُهيّئ المستوى التحكمي لـECS أو EKS جهازاً افتراضياً صغيراً (microVM) عبر المحاكي الافتراضي Firecracker — التقنية ذاتها المستخدمة داخلياً في Lambda — لكل مهمة، ثم تحقن بيئة تشغيل الحاويات فيه وتربط واجهة شبكة بـVPC الخاص بك. تحصل على عزل تام للحاويات بدقة الجهاز الافتراضي، وتأخر جدولة دون ثانية على عقدة مستوى البيانات الساخنة، وكامل سطح API الخاص بـECS أو EKS دون إدارة أي عقدة.

تحديد حجم مهمة Fargate أكثر خشونة من Lambda: تُحدَّد وحدة المعالجة بزيادات 256 وحدة CPU (0.25 vCPU) حتى 16 vCPU، والذاكرة بزيادات 512 ميغابايت حتى 120 غيغابايت. تدفع لكل vCPU-ثانية وغيغابايت-ثانية أثناء تشغيل المهمة — لا تكلفة خمول، لكن لا طبقة مجانية أيضاً بعد الاثني عشر شهراً الأولى.

توفر Fargate Spot (لـECS) تخفيضاً يصل إلى 70% في التكلفة مقابل خطر المقاطعة. تُستنزف المهام بإشعار دقيقتين — نفس نموذج EC2 Spot لكن بدقة المهمة. استخدمه للمهام الدفعية، ومشغّلي CI، والعمال عديمي الحالة القابلين لإعادة المحاولة. لا تستخدم Fargate Spot أبداً للمهام ذات الحالة التي لا تتحمل الإنهاء المفاجئ.

نموذج شبكة وصلاحيات IAM في Fargate

تحصل كل مهمة Fargate على واجهة شبكة مرنة (ENI) خاصة بها مُحقونة في VPC. لكل مهمة عنوان IP خاص بها ومجموعة أمان خاصة بها وتخضع لسجلات تدفق VPC. لا يوجد مساحة شبكة مشتركة بين المهام — تحصل على عزل متعدد المستأجرين حقيقي. المقايضة هي كثافة ENI: للحسابات حدود ENI لكل منطقة، وانفجار مفاجئ بمئات مهام Fargate قد يستنفد هذا الحد بصمت.

ينقسم نموذج IAM إلى دورين: دور تنفيذ المهمة (task execution role) (تستخدمه Fargate لسحب الصورة من ECR وكتابة السجلات إلى CloudWatch — كود تطبيقك لا يستخدم هذا الدور قط) ودور المهمة (task role) (يُفترض من قِبل كود تطبيقك عبر نقطة نهاية منح بيانات الاعتماد على 169.254.170.2). الخلط بينهما هو المصدر الأكثر شيوعاً لأخطاء AccessDeniedException عند فشل الحاويات في السحب من ECR الخاص. إذا رأيت فشل سحب الصورة، افحص دور التنفيذ أولاً لا دور المهمة.

Fargate task launch architecture showing control plane, data plane, VPC, and IAM AWS Account / VPC ECS / EKS Control Plane Amazon ECR Container Images IAM Roles Execution + Task Role Firecracker microVM (Fargate Data Plane) Fargate Task Container A app image Sidecar otel-collector ENI (private IP / SG) VPC subnet injection CloudWatch Logs / Metrics VPC Routing ALB / Service Connect schedules image pull credentials logs traffic
بنية إطلاق Fargate: تجدول المستوى التحكمية لـECS/EKS المهام على أجهزة Firecracker الافتراضية؛ تحصل كل مهمة على ENI خاصة بها في VPC وبيانات اعتماد IAM عبر نقطة نهاية البيانات الوصفية الداخلية.
# ECS Fargate task definition — production pattern # Register with: aws ecs register-task-definition --cli-input-json file://task-def.json { "family": "api-worker", "requiresCompatibilities": ["FARGATE"], "networkMode": "awsvpc", "cpu": "512", "memory": "1024", "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole", "taskRoleArn": "arn:aws:iam::123456789012:role/api-worker-task-role", "containerDefinitions": [ { "name": "app", "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/api-worker:sha-abc1234", "portMappings": [{ "containerPort": 8080 }], "secrets": [ { "name": "DB_PASSWORD", "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/db-password" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "/ecs/api-worker", "awslogs-region": "us-east-1", "awslogs-stream-prefix": "ecs" } }, "healthCheck": { "command": ["CMD-SHELL", "curl -f http://localhost:8080/healthz || exit 1"], "interval": 15, "timeout": 5, "retries": 3, "startPeriod": 30 }, "stopTimeout": 30 } ] }

Google Cloud Run: الحاويات بلا خوادم محدودة بالطلب

يتخذ Cloud Run موقفاً فلسفياً مختلفاً: إنه مصمم من الأساس حول معالجة طلبات HTTP لا تنفيذ الحاويات بصورة عامة. يُعيَّن خدمة Cloud Run إلى عنوان URL؛ حين تصل حركة المرور، يجدول Cloud Run نسخة حاوية خلف ذلك العنوان ويوجه الطلب. حين تنخفض حركة المرور إلى الصفر، تخفض المنصة النسخ إلى الصفر — قدرة لا يوفرها Fargate افتراضياً. قدّمت Cloud Run v2 أيضاً أداة الوظائف (Jobs) إلى جانب الخدمات، مما يوفر تنفيذاً دفعياً غير HTTP بنفس نموذج البنية التحتية الصفري.

وحدة القياس هي نسخة الحاوية (container instance). تعالج كل نسخة طلبات متعددة متزامنة (تُضبط عبر --concurrency، افتراضياً 80). يُحدَّد إجمالي إنتاجية الطلبات بضرب التزامن في عدد النسخ. يحتسب Cloud Run الفواتير فقط لوحدة المعالجة والذاكرة المستهلكة فعلياً أثناء معالجة الطلبات — وقت الخمول بين الطلبات على نسخة حية لا يُحتسب (ما لم تُفعّل --cpu-always-allocated للعمل في الخلفية كمستمعي Pub/Sub).

اضبط --min-instances 1 للخدمات الإنتاجية الحساسة لزمن الاستجابة. نسخة واحدة ساخنة تُزيل بدايات الإقلاع الباردة كلياً للحركة التي تبلغ أقل من 80 RPS في المتوسط، وتكلفة تشغيل نسخة واحدة دائمة بـ1 vCPU و512 ميغابايت حوالي 12 دولاراً شهرياً — أرخص بكثير من غرامة زمن الاستجابة p99 لبدايات الإقلاع الباردة في ميزانية SLO الخاصة بك.
# Cloud Run production deployment — Terraform (google_cloud_run_v2_service) resource "google_cloud_run_v2_service" "api" { name = "api-service" location = "us-central1" ingress = "INGRESS_TRAFFIC_ALL" template { scaling { min_instance_count = 1 max_instance_count = 200 } containers { image = "us-central1-docker.pkg.dev/my-project/api/server:${var.image_tag}" resources { limits = { cpu = "2" memory = "1Gi" } cpu_idle = false startup_cpu_boost = true } ports { container_port = 8080 } env { name = "DB_PASSWORD" value_source { secret_key_ref { secret = google_secret_manager_secret.db_password.secret_id version = "latest" } } } liveness_probe { http_get { path = "/healthz" port = 8080 } period_seconds = 10 } startup_probe { http_get { path = "/readyz" port = 8080 } failure_threshold = 3 period_seconds = 5 } } vpc_access { connector = google_vpc_access_connector.main.id egress = "PRIVATE_RANGES_ONLY" } service_account = google_service_account.api_runner.email timeout = "60s" max_instance_request_concurrency = 80 } traffic { type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST" percent = 100 } }

مقارنة الإقلاع البارد: Fargate مقابل Cloud Run

لكلتا المنصتين تأخر في الإقلاع البارد، لكن المصادر تختلف. في Fargate، التكلفة المسيطرة هي سحب الصورة: صورة ECR بحجم 500 ميغابايت على ENI باردة قد تستغرق 15-45 ثانية، متجاوزةً تماماً وقت إقلاع Firecracker (~125 مللي ثانية). الحل هو تحسين الصورة بعدوانية — بنيات متعددة المراحل، صور أساسية بلا توزيع (distroless)، وتحسين سحب صورة Fargate عبر التحميل الكسول (تدعم Amazon ECR مؤشر Seekable OCI / SOCI، مما يبدأ تشغيل الحاوية قبل اكتمال سحب كل الطبقات — يقلل الإقلاع البارد الفعلي بنسبة 50-80% للصور الكبيرة). في Cloud Run، يهيمن وقت تهيئة التطبيق على الإقلاع البارد لا سحب الصورة، لأن Cloud Run يخزّن طبقات الصورة مؤقتاً عبر النسخ. ثنائي Go بصورة 50 ميغابايت يبدأ في أقل من 200 مللي ثانية؛ تطبيق Node.js يستورد 300 ميغابايت من node_modules قد يستغرق 2-4 ثوانٍ.

لا تستخدم وسوم :latest في نشرات الإنتاج على أي من المنصتين. يخزّن Fargate الملخص المحلول عند تسجيل تعريف المهمة، لذا إعادة الوسم لا تُحدّث المهام الجارية — لكنها تخلق تبايناً بين ما يقوله تعريف المهمة وما يعمل فعلاً. يُثبّت Cloud Run أيضاً على ملخص عند النشر. دائماً ابنِ وسوم صور غير قابلة للتغيير (SHA git أو إصدار دلالي) وحدّث تعريف المهمة أو مراجعة Cloud Run صراحةً. هذا أيضاً يجعل التراجع حتمياً.

الاختيار بين Fargate وCloud Run

هذا ليس سؤال تقنية في المقام الأول — بل سؤال ملف حمل العمل. اختر Fargate حين: أنت عميق في منظومة AWS وتحتاج تكامل VPC الأصلي أو صلاحيات IAM الدقيقة، أو تُشغّل أحمال عمل أطول من مهلة طلب Cloud Run لمدة 60 دقيقة؛ تحتاج توافق EKS لتشغيل الأدوات الأصلية لـKubernetes (Helm، Argo CD، KEDA)؛ أو مهامك الدفعية تتطلب تعريفات مهام متعددة الحاويات مع أنماط sidecar. اختر Cloud Run حين: أنت على GCP أو تبني خدمات جديدة تُهم فيها اقتصاديات القياس إلى الصفر؛ تريد أبسط مسار ممكن لنشر خدمة HTTP (أمر gcloud run deploy وحيد)؛ أو تُشغّل مستهلكين مدفوعين بالأحداث على Pub/Sub حيث يزيل تكامل Eventarc الأصلي لـCloud Run الحاجة إلى عملية عامل سحب كلياً.

المسار الناضج تشغيلياً على أي من المنصتين هو: وسوم صور غير قابلة للتغيير، فحوصات صحة بفترات سماح startPeriod أو مجسات بدء تشغيل واقعية، معالجة إيقاف رشيقة (التقاط SIGTERM واستنزاف الطلبات الجارية ضمن مهلة الإنهاء على المنصة)، الأسرار من مدير الأسرار لا كمتغيرات بيئة مدمجة في الصور، والتسجيل المهيكل بصيغة JSON الذي يمكن لتجميع سجلات المنصة تحليله وتوجيهه إلى خلفية الرصد الخاصة بك.