يبدو نشر الدوال بلا خوادم أمرًا بسيطًا بالنظرة الأولى — مجرد ضغط الملفات في 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 أيضًا.
أيهم تختار في 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 هي لقطات ثابتة: كود + تهيئة + متغيرات بيئة. بمجرد نشرها، لا يمكن تغييرها. الأسماء المستعارة مؤشرات قابلة للتعديل. هذا الفصل يمنحك ثلاث قدرات إنتاجية:
إصدارات الكناري — يمكن للاسم المستعار توزيع الحركة بالأوزان. وجّه 10٪ إلى الإصدار 42 و90٪ إلى الإصدار 41. تراقب تنبيهات CloudWatch المقاييس المرتبطة بنطاق الاسم المستعار. إذا ارتفعت الأخطاء، يعيد CodeDeploy وزن الاسم المستعار للكناري إلى صفر. لا إعادة نشر لـ Lambda، ولا موجة بدء بارد ناتجة عن نشر جديد.
التراجع في ثوانٍ — الأمر aws lambda update-alias --function-name OrderHandler --name live --function-version 41 ذري ويستغرق ميلي ثانية. قارن ذلك بانتشار حاوية يجب عليها استنزاف الـ Pods.
تثبيت البيئة للمستهلكين التابعين — تشير ربط مصادر الأحداث وتكاملات API GW إلى ARN الاسم المستعار، لا إلى $LATEST. لا تنكسر أبدًا عبر عمليات النشر.
يستهدف 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 بنفس الطريقة:
يدعم كل من 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 فارغًا ويخرج بنظافة.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية