تطور البنية التحتية: من الخوادم إلى Serverless
تطور البنية التحتية: من الخوادم إلى Serverless
كل قرار نشر تتخذه بوصفك مهندس DevOps يتشكّل وفق نموذج البنية التحتية الذي اختاره فريقك. على مدى 30 عاماً مضت، انتقلت الصناعة عبر أربع حقب متميزة: الخوادم المادية، والآلات الافتراضية، والحاويات، وأخيراً Serverless — كل حقبة حلّت مشكلات حقيقية نشأت في الحقبة السابقة. يتتبع هذا الدرس تلك الرحلة بصدق من يدير كل طبقة منها في بيئات الإنتاج الفعلية.
الحقبة الأولى — الخوادم المادية (Bare Metal)
الخادم المادي هو جهاز حقيقي يحمل نظام تشغيل واحداً مثبّتاً مباشرةً على العتاد. لا توجد طبقة افتراضية. أنت تمتلك كل دورة معالج وكل بايت في الذاكرة وكل خصوصية في ذلك الجهاز بعينه.
بالنسبة للأعباء الصحيحة — حوسبة الأداء العالي (HPC)، ومحركات التداول المالي منخفضة الكمون، وقواعد البيانات التي تدفع حدود العتاد — لا يزال Bare Metal هو الخيار الصحيح في 2025. لكن لاستضافة التطبيقات العامة يعاني من ثلاثة عيوب قاتلة:
- بطء التوفير: تركيب الرفوف والكابلات وضبط BIOS وتثبيت نظام التشغيل — من أيام إلى أسابيع.
- التأثير العكسي للجار المزعج: إذا أكل برنامج جامح كل الذاكرة، تنهار جميع التطبيقات على الجهاز.
- استخدام ضعيف للموارد: متوسط استخدام المعالج على الخوادم المادية في الصناعة أقل من 20%.
الحقبة الثانية — الآلات الافتراضية (VMs)
VMware ESXi (2001) وXen (2003) وKVM (2007) — مشرفات Virtual (Hypervisors) تسمح لجهاز مادي واحد بتشغيل أنظمة تشغيل معزولة كاملة في آنٍ واحد. AWS EC2 (2006) حوّل هذا إلى خدمة مرافق: ادفع بالساعة، وفّر في دقائق. كان ذلك ثورياً.
تحزم الآلة الافتراضية نواة نظام تشغيل كاملة وبرامج تشغيل النواة ومكتبات النظام وتطبيقك في صورة واحدة (AMI على AWS، VMDK لـ VMware). هذا الاكتمال هو أيضاً تكلفتها: صورة Ubuntu العادية نحو 1 غيغابايت. وقت الإقلاع يُقاس بالدقائق. على نطاق Netflix، الانتظار ثلاث دقائق حتى تخدم VM جديدة الطلبات أثناء ارتفاع حاد في الحمل أمر غير مقبول.
الحقبة الثالثة — الحاويات (Containers)
جعل Docker (2013) مساحات أسماء Linux kernel والـ cgroups (كلتاهما موجودتان منذ 2008) في متناول كل مطوّر. الفكرة الجوهرية: التطبيقات لا تحتاج نواة منفصلة، بل تحتاج فقط عزلاً. الحاويات تشترك في نواة المضيف لكن ترى نظام ملفاتها الخاص وفضاء PID الخاص وواجهة الشبكة الخاصة وحدود الموارد الخاصة بها.
النتيجة العملية: صورة الحاوية أصغر بـ 10–100 مرة من صورة الآلة الافتراضية، وتبدأ في أقل من ثانية، ويمكنك تحميل أعباء عمل أكثر بـ 10 أضعاف على نفس العتاد. Dockerfile يجعل بيئة التشغيل قابلة للإعادة — لا مزيد من "يعمل على جهازي."
Dockerfile — بناء متعدد المراحل للإنتاج (Node.js API)
# المرحلة 1: بناء التبعيات FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev # المرحلة 2: بناء التطبيق FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm run build # المرحلة 3: صورة تشغيل صغيرة FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production # مستخدم غير root — ضروري لأمان الحاوية RUN addgroup -S appgroup && adduser -S appuser -G appgroup COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules USER appuser EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=10s CMD wget -qO- http://localhost:3000/health || exit 1 CMD ["node", "dist/server.js"]الحقبة الرابعة — تنسيق الحاويات (Container Orchestration)
تشغيل حاوية واحدة على جهاز واحد أمر بسيط. لكن تشغيل 10,000 حاوية على 500 عقدة — مع إصلاح ذاتي عند فشل العقد، وتوجيه الحركة إلى نسخ سليمة، وتحديثات متدحرجة بدون توقف، وحقن الأسرار — يستلزم منسّقاً (Orchestrator). Kubernetes (K8s)، الذي أصدرته Google عام 2014 مستنداً إلى نظامها الداخلي Borg، أصبح المعيار الصناعي.
نموذج كائنات Kubernetes يُترجَم مباشرةً إلى متطلبات الإنتاج:
- Pod — أصغر وحدة قابلة للنشر؛ 1 أو أكثر من الحاويات تشترك في فضاء الشبكة وأجهزة التخزين.
- Deployment — يعلن الحالة المرغوبة (مثل "5 نسخ من هذه الصورة")؛ يُوفّق Kubernetes الواقع ليطابقها.
- Service — عنوان IP افتراضي ثابت واسم DNS أمام مجموعة ديناميكية من Pods (توزيع الحمل واكتشاف الخدمة).
- HorizontalPodAutoscaler (HPA) — يضبط عدد النسخ استناداً إلى المعالج/الذاكرة/المقاييس المخصصة.
- Namespace — قسم منطقي من الكلستر؛ البيئات متعددة الفرق تستخدم namespace منفصلة لكل فريق/بيئة.
Kubernetes Deployment + HPA (نمط إنتاج)
apiVersion: apps/v1 kind: Deployment metadata: name: api-service namespace: production spec: replicas: 3 selector: matchLabels: app: api-service strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 # تحديث بدون توقف: لا تقتل القديم قبل جاهزية الجديد maxSurge: 1 template: metadata: labels: app: api-service spec: containers: - name: api image: registry.example.com/api-service:v2.4.1 # دائماً استخدم وسماً محدداً، ليس :latest ports: - containerPort: 3000 resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" readinessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 3000 initialDelaySeconds: 15 periodSeconds: 20 --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-service-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-service minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60:latest. إذا نشرت image: my-app:latest واستُبدلت عقدة، سيسحب Kubernetes أحدث صورة على تلك العقدة بينما تعمل العقد الأخرى بالإصدار القديم. ستحصل على كلستر ذو إصدارات مختلطة بدون أي طريقة للتراجع. دائماً ضع وسماً ثابتاً كـ SHA commit من git (image: my-app:a3f1d9c) أو إصدار دلالي (image: my-app:v2.4.1). يجب أن تبني خط CI/CD الصورة وترفعها بالوسم، ثم تحدّث الـ manifest.الحقبة الخامسة — Serverless ودوال كخدمة (FaaS)
قدّم AWS Lambda (2014) تجريداً جديداً: ارفع دالة، حدّد مُشغّلاً، ادفع لكل 100 ملي-ثانية تنفيذ. لا خوادم لتوفيرها، لا حاويات لإدارتها، لا طاقة خاملة لدفع تكاليفها. المنصة تتولى التوسع من الصفر إلى آلاف التنفيذات المتزامنة تلقائياً.
يتألق Serverless في أعباء العمل المُدفوعة بالأحداث، أو المتقطعة، أو غير المنتظمة: معالجة الصور المُشغَّلة برفع ملفات على S3، ونقاط نهاية API خلف API Gateway، والمهام المجدولة، ومعالجة الدفق. أما الأعباء طويلة التشغيل أو ذات الحالة أو الحساسة للكمون حيث يكون وقت البدء البارد (50–500 ملي-ثانية لدالة Node.js، 1–10 ثوانٍ لدالة JVM) غير مقبول، فـ Serverless الخيار الخاطئ.
AWS Lambda + API Gateway (Terraform IaC)
resource "aws_lambda_function" "image_resizer" { function_name = "image-resizer" filename = "dist/image-resizer.zip" source_code_hash = filebase64sha256("dist/image-resizer.zip") handler = "index.handler" runtime = "nodejs20.x" timeout = 30 memory_size = 512 environment { variables = { BUCKET_OUT = aws_s3_bucket.processed.bucket } } # الوضع داخل VPC يضيف ~600ms إلى البدء البارد — استخدم فقط عند الحاجة لموارد VPC # vpc_config { ... } } resource "aws_lambda_permission" "s3_invoke" { statement_id = "AllowS3Invoke" action = "lambda:InvokeFunction" function_name = aws_lambda_function.image_resizer.function_name principal = "s3.amazonaws.com" source_arn = aws_s3_bucket.uploads.arn } resource "aws_s3_bucket_notification" "trigger" { bucket = aws_s3_bucket.uploads.id lambda_function { lambda_function_arn = aws_lambda_function.image_resizer.arn events = ["s3:ObjectCreated:*"] filter_suffix = ".jpg" } depends_on = [aws_lambda_permission.s3_invoke] }اختيار التجريد المناسب
الفرق الحقيقية لا تختار نموذجاً واحداً وتطبّقه في كل مكان. Netflix تُشغّل معظم خدماتها على حاويات (Kubernetes عبر Titus)، وبعض أعباء الدفعات على آلات افتراضية، وخطوط الأنابيب المُدفوعة بالأحداث على Lambda. شجرة القرار تعتمد على:
- تحتاج أقصى أداء للعتاد؟ Bare metal أو Dedicated Hosts (EC2 dedicated).
- تحتاج عزلاً على مستوى VM أو نظام تشغيل قديم؟ آلات افتراضية (EC2، GCE، Azure VM).
- تُشغّل microservices أو APIs بحركة مرور متوقعة؟ حاويات على Kubernetes (EKS، GKE، AKS).
- أعباء عمل مدفوعة بالأحداث أو متقطعة أو غير منتظمة؟ Serverless (Lambda، Cloud Functions، Azure Functions).