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

البنية التحتية كأكواد والنشر في البيئات بلا خوادم

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

البنية التحتية كأكواد والنشر في البيئات بلا خوادم

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

مشهد البنية التحتية كأكواد للبيئات بلا خوادم

ثلاثة أدوات تهيمن على هذا المجال، كل منها بفلسفة مختلفة:

  • AWS SAM (نموذج التطبيق بلا خوادم) — امتداد مفتوح المصدر لـ CloudFormation. تتوسع تحويلات AWS::Serverless::Function إلى مجموعات من Lambda وIAM وربط مصادر الأحداث. هو الأقرب إلى CloudFormation؛ الأنسب حين تمتلك فريقك بنية تحتية CF قائمة. يدعم الاستدعاء المحلي عبر sam local invoke ومحاكاة HTTP عبر sam local start-api.
  • Serverless Framework (SLS) — DSL YAML مستقل عن مزودي الخدمة. يُترجم إلى CloudFormation الأصيل على AWS لكنه يُخفي معظم التعقيد. نظام بيئي من أكثر من 1000 إضافة. كان الخيار الافتراضي تاريخيًا؛ لا يزال الخيار الأمثل للفرق متعددة اللغات التي تستهدف Azure وGCP أيضًا.
  • AWS CDK — البنية التحتية بـلغات برمجة حقيقية (TypeScript/Python/Go). تتولى مُنشآت aws-lambda وaws-lambda-nodejs حزم الكود (عبر esbuild)، وإدارة الطبقات، وربط الأحداث. الأنسب للفرق التي تريد الأمان النوعي والحلقات والمُنشآت القابلة لإعادة الاستخدام.
أيهم تختار في 2025؟ فرق AWS التي تبني خدمات جديدة تتقارب نحو CDK. القوالب الحالية لـ SAM تستحق الإبقاء عليها — SAM وCDK يتعاملان معًا. Serverless Framework لا يزال الخيار الصحيح حين تحتاج إلى قابلية نقل متعددة الموردين أو نموذج أولي سريع مع إضافات مجتمعية.

SAM في الإنتاج

قالب SAM بسيط لكنه واقعي إنتاجيًا لـ Lambda مع API يبدو هكذا:

# template.yaml AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Globals: Function: Runtime: nodejs20.x Architectures: [arm64] # Graviton2 — أرخص بنسبة 20%، نفس الأداء MemorySize: 512 Timeout: 29 # ثانية واحدة أقل من حد API GW Environment: Variables: LOG_LEVEL: !Ref LogLevel Layers: - !Ref CommonUtilsLayer Tracing: Active # X-Ray Parameters: LogLevel: Type: String Default: info AllowedValues: [debug, info, warn, error] Resources: OrderApi: Type: AWS::Serverless::Api Properties: StageName: !Ref AWS::StackName TracingEnabled: true AccessLogDestination: DestinationArn: !GetAtt ApiAccessLogGroup.Arn OrderHandler: Type: AWS::Serverless::Function Properties: CodeUri: src/order/ Handler: index.handler AutoPublishAlias: live # SAM ينشئ إصدارًا جديدًا ويحدّث الاسم المستعار عند كل نشر DeploymentPreference: Type: Canary10Percent5Minutes # كناري 10%، ترقية بعد 5 دقائق إذا كانت التنبيهات نظيفة Alarms: - !Ref OrderErrorAlarm Hooks: PreTraffic: !Ref PreTrafficHook PostTraffic: !Ref PostTrafficHook Events: CreateOrder: Type: Api Properties: Path: /orders Method: POST RestApiId: !Ref OrderApi OrderErrorAlarm: Type: AWS::CloudWatch::Alarm Properties: AlarmName: OrderHandler-Errors MetricName: Errors Namespace: AWS/Lambda Dimensions: - Name: FunctionName Value: !Ref OrderHandler - Name: Resource Value: !Sub "${OrderHandler}:live" # يتتبع الاسم المستعار، لا $LATEST Statistic: Sum Period: 60 EvaluationPeriods: 1 Threshold: 1 ComparisonOperator: GreaterThanOrEqualToThreshold CommonUtilsLayer: Type: AWS::Serverless::LayerVersion Properties: ContentUri: layers/common/ CompatibleRuntimes: [nodejs20.x] RetentionPolicy: Retain # لا تحذف الإصدارات القديمة من الطبقات تلقائيًا Metadata: BuildMethod: nodejs20.x ApiAccessLogGroup: Type: AWS::Logs::LogGroup Properties: RetentionInDays: 30

السطر AutoPublishAlias: live هو المُبتدأ الإنتاجي الرئيسي — ينشر SAM إصدارًا ثابتًا جديدًا عند كل sam deploy ويحوّل الاسم المستعار. مقترنًا بـDeploymentPreference، يدير CodeDeploy التحويل التدريجي للكناري دون أي أدوات إضافية.

الإصدارات والأسماء المستعارة: النموذج الذهني للإنتاج

إصدارات Lambda هي لقطات ثابتة: كود + تهيئة + متغيرات بيئة. بمجرد نشرها، لا يمكن تغييرها. الأسماء المستعارة مؤشرات قابلة للتعديل. هذا الفصل يمنحك ثلاث قدرات إنتاجية:

  1. إصدارات الكناري — يمكن للاسم المستعار توزيع الحركة بالأوزان. وجّه 10٪ إلى الإصدار 42 و90٪ إلى الإصدار 41. تراقب تنبيهات CloudWatch المقاييس المرتبطة بنطاق الاسم المستعار. إذا ارتفعت الأخطاء، يعيد CodeDeploy وزن الاسم المستعار للكناري إلى صفر. لا إعادة نشر لـ Lambda، ولا موجة بدء بارد ناتجة عن نشر جديد.
  2. التراجع في ثوانٍ — الأمر aws lambda update-alias --function-name OrderHandler --name live --function-version 41 ذري ويستغرق ميلي ثانية. قارن ذلك بانتشار حاوية يجب عليها استنزاف الـ Pods.
  3. تثبيت البيئة للمستهلكين التابعين — تشير ربط مصادر الأحداث وتكاملات API GW إلى ARN الاسم المستعار، لا إلى $LATEST. لا تنكسر أبدًا عبر عمليات النشر.
Lambda Versioning and Alias Traffic Splitting API Gateway POST /orders Alias: live 90% → v41 10% → v42 (canary) v41 (stable) Immutable snapshot v42 (canary) New code 90% 10% CloudWatch Alarm: Errors on alias scope Auto-Rollback alias weight → 0% Lambda Alias Traffic Splitting — Canary Release
يستهدف API Gateway ARN الاسم المستعار؛ يوزع CodeDeploy الحركة المرجّحة على إصدار الكناري ويتراجع تلقائيًا عند إطلاق تنبيه CloudWatch.

نمط CDK: الكناري مع TypeScript

المكافئ في CDK أكثر إيجازًا وأمانًا من الناحية النوعية. تُتيح وحدة aws-codedeploy نفس بدائيات CodeDeploy التي يولّدها DeploymentPreference في SAM تحت الغطاء:

// lib/order-stack.ts import * as lambda from "aws-cdk-lib/aws-lambda"; import * as lambdaNodeJs from "aws-cdk-lib/aws-lambda-nodejs"; import * as codedeploy from "aws-cdk-lib/aws-codedeploy"; import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch"; import { Duration } from "aws-cdk-lib"; const fn = new lambdaNodeJs.NodejsFunction(this, "OrderHandler", { entry: "src/order/index.ts", runtime: lambda.Runtime.NODEJS_20_X, architecture: lambda.Architecture.ARM_64, memorySize: 512, timeout: Duration.seconds(29), bundling: { minify: true, sourceMap: true }, }); // نشر إصدار ثابت عند كل نشر const version = fn.currentVersion; // الاسم المستعار "live" يشير إلى أحدث إصدار const alias = new lambda.Alias(this, "LiveAlias", { aliasName: "live", version, }); // تنبيه الأخطاء مرتبط بنطاق الاسم المستعار const errorAlarm = new cloudwatch.Alarm(this, "OrderErrors", { metric: alias.metricErrors({ period: Duration.minutes(1) }), threshold: 1, evaluationPeriods: 1, }); // كناري CodeDeploy: 10% لمدة 5 دقائق، التراجع عند التنبيه new codedeploy.LambdaDeploymentGroup(this, "OrderDeploymentGroup", { alias, deploymentConfig: codedeploy.LambdaDeploymentConfig.CANARY_10PERCENT_5MINUTES, alarms: [errorAlarm], autoRollback: { deploymentInAlarm: true, failedDeployment: true }, });
currentVersion خداع شائع في CDK. خاصية fn.currentVersion تنشئ مورد Version جديدًا عند كل synth يرصد تغييرًا في الكود أو التهيئة — وهذا بالضبط ما تريده. لكن إذا كتبت fn.addVersion("v1") بشكل ثابت، ينشر مرة واحدة فقط. استخدم currentVersion حصرًا ودع CDK يكتشف الفارق.

Serverless Framework: كناري متعدد المراحل

للفرق التي تستخدم Serverless Framework، تربط إضافة serverless-plugin-canary-deployments CodeDeploy بنفس الطريقة:

# serverless.yml service: order-service frameworkVersion: "4" provider: name: aws runtime: nodejs20.x region: us-east-1 architecture: arm64 plugins: - serverless-esbuild - serverless-plugin-canary-deployments custom: esbuild: bundle: true minify: true canarySettings: type: Canary10Percent5Minutes alarms: - name: OrderHandlerErrors namespace: AWS/Lambda metric: Errors threshold: 1 statistic: Sum period: 60 evaluationPeriods: 1 functions: createOrder: handler: src/order/index.handler memorySize: 512 timeout: 29 deploymentSettings: type: ${self:custom.canarySettings.type} alias: live preTrafficHook: preTrafficHook alarms: ${self:custom.canarySettings.alarms} events: - httpApi: path: /orders method: POST

ربط المرحلة قبل وبعد حركة المرور

يدعم كل من SAM وCDK خطافات دورة الحياة — دوال Lambda يستدعيها CodeDeploy قبل وبعد تحويل الحركة. هنا تشغّل اختبارات الدخان على الإصدار الجديد بشكل معزول:

  • PreTrafficHook — استدعِ الإصدار الجديد مباشرةً (لا عبر الاسم المستعار) وتحقق من أن حمولة اختبار معروفة تُعيد HTTP 200 بالمخطط المتوقع. استدعِ codedeploy:PutLifecycleEventHookExecutionStatus للإبلاغ عن النجاح أو الفشل. الفشل هنا يوقف الكناري قبل أن تصل طلبية إنتاجية واحدة إلى الكود الجديد.
  • PostTrafficHook — بعد الترقية الكاملة، تحقق من التأثيرات الجانبية على المنظومة: أشكال سجلات DynamoDB، أعداد رسائل SNS، عمق طابور SQS. استخدمه لاكتشاف أخطاء "التلف الصامت" التي لا تظهر كأخطاء Lambda.
لا تستدعِ الاسم المستعار في الخطاف. إذا استدعى PreTrafficHook الاسم المستعار live فقد يصل إلى الإصدار القديم (الموزون بـ90٪). استدعِ دائمًا ARN الإصدار المحدد — وهو متاح في متغير البيئة FUNCTION_VERSION_ARN الذي يُضيفه CodeDeploy إلى الخطاف.

الاحتفاظ بالإصدارات وتنظيفها

تحتفظ Lambda بكل إصدار منشور حتى تحذفه. أنبوب CI/CD نشط ينشر 20-30 إصدارًا يوميًا لكل دالة. على النطاق الواسع، تتراكم الإصدارات المتبقية إلى مئات لكل دالة، وتستنزف حصة تخزين الكود الإقليمية البالغة 75 جيجابايت، وتبطئ ترقيم صفحات ListVersionsByFunction. أفضل ممارسة: عيّن RemovalPolicy في CDK أو RetentionPolicy: Delete في SAM للإصدارات القديمة، أو شغّل دالة Lambda أسبوعية للتنظيف عبر AWS SDK. احتفظ دائمًا بأحدث إصدارين على الأقل للتراجع.

التكامل مع أنبوب النشر

يبدو أنبوب CI/CD الاحترافي لبيئات بلا خوادم هكذا في GitHub Actions:

# .github/workflows/deploy.yml name: Deploy Order Service on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write # OIDC لمصادقة AWS — لا مفاتيح طويلة الأمد contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Configure AWS credentials (OIDC) uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GithubActionsDeployRole aws-region: us-east-1 - run: npm ci - name: Run unit tests run: npm test - name: SAM build run: sam build --use-container # قابل للاستنساخ، لا يحتاج بيئة تشغيل محلية - name: SAM deploy (canary) run: | sam deploy \ --stack-name order-service-prod \ --s3-bucket my-sam-artifacts-prod \ --capabilities CAPABILITY_IAM \ --no-fail-on-empty-changeset \ --parameter-overrides LogLevel=info \ --confirm-changeset false - name: Monitor CodeDeploy rollout run: | DEPLOY_ID=$(aws deploy list-deployments \ --application-name order-service-prod-OrderHandler \ --query "deployments[0]" --output text) aws deploy wait deployment-successful --deployment-id "$DEPLOY_ID"

الخطوة الأخيرة تعطّل الأنبوب حتى يُرقّي CodeDeploy الإصدار بالكامل أو يتراجع عنه. إذا حدث تراجع، يفشل الأنبوب، ويُوسَم الـ commit، ويتلقى المهندس المناوب تنبيه PagerDuty — حلقة SRE القياسية التي تعرفها من انتشارات Kubernetes.

أوامر نشر SAM مقابل CDK. يُعبّئ sam deploy الملفات ويرفعها ثم يُفوّض إلى CloudFormation. يُولّد cdk deploy قالب CloudFormation ويفعل الشيء ذاته. كلاهما idempotent ويكتشف الفوارق — نشر بدون تغييرات يُنتج changeset فارغًا ويخرج بنظافة.