ביליתם שבוע בהגדרת Serverless Framework, ובפריסה הראשונה פונקציות Lambda לא מתחילות – שגיאה 502, CloudWatch ריק. נשמע מוכר? פרסנו יותר מ-50 פרויקטים serverless על AWS, GCP ו-Azure, ובכל פעם נתקלנו באותן מלכודות: תפקידי IAM מוגדרים שגוי, משתני סביבה שנשכחו, ו-builds גדולים מדי שגורמים ל-cold start גבוה. לפי הנתונים שלנו, מחסנית serverless מכווננת כראוי מפחיתה עלויות תשתית פי 3–5, אבל רק אם ההגדרה ללא רבב. לדוגמה, לאחר העברת פלטפורמת מסחר אלקטרוני, עלויות התשתית החודשיות ירדו מ-800$ ל-200$, חיסכון של 75%. במאמר זה, אנו מפרקים כל שלב מההתקנה ועד CI/CD, עם הגדרות אמיתיות ופרקטיקות מוכחות בקרב. תלמדו כיצד להימנע מטעויות אופייניות, לקצץ 40% מזמני cold start, ולארגן אחסון סודות מאובטח. אנו נשענים על ניסיון מ-50+ פרויקטים ושומרים על התצורות שלנו מעודכנות עם הגרסאות העדכניות ביותר של תוספים וריצות. הצוות שלנו מחזיק בהסמכות AWS Certified Solutions Architect ויש לו למעלה מ-5 שנות ניסיון מעשי ב-serverless. אנו מבטיחים הפחתת cold start של לפחות 40% עם התצורה האופטימלית שלנו.
הגדרת Serverless Framework
הגדרה שלב אחר שלב
- התקינו את Serverless Framework גלובלית:
npm install -g serverless serverless --version # 3.x или 4.x serverless create --template aws-nodejs-typescript --path my-service cd my-service npm install - צרו שירות חדש:
src/functions/ - היכנסו לשירות והתקינו תלויות:
src/libs/ - הגדירו את
serverless.ymlעם provider, תוספים ופונקציות. - פרסו בפעם הראשונה:
@aws-sdk/*
מבנה הפרויקט כולל תיקיית service: my-web-service frameworkVersion: '3' plugins: - serverless-esbuild - serverless-offline - serverless-dotenv-plugin provider: name: aws runtime: nodejs20.x region: eu-west-1 stage: ${opt:stage, 'dev'} memorySize: 512 timeout: 10 logRetentionInDays: 14 environment: NODE_ENV: ${self:provider.stage} DB_PASSWORD: ${ssm:/my-service/${self:provider.stage}/db-password~true} API_KEY: ${ssm:/my-service/api-key~true} iam: role: statements: - Effect: Allow Action: [s3:GetObject, s3:PutObject] Resource: 'arn:aws:s3:::${self:custom.bucketName}/*' - Effect: Allow Action: [dynamodb:Query, dynamodb:PutItem, dynamodb:UpdateItem] Resource: !GetAtt UsersTable.Arn httpApi: cors: allowedOrigins: ['https://my-site.com', 'http://localhost:3000'] allowedHeaders: ['Content-Type', 'Authorization'] allowedMethods: [GET, POST, PUT, DELETE] custom: bucketName: my-service-${self:provider.stage}-assets esbuild: bundle: true minify: ${strToBool(${ssm:/my-service/minify, 'false'})} sourcemap: true target: node20 platform: node concurrency: 10 external: - '@aws-sdk/*' - 'pg-native' serverless-offline: httpPort: 3001 lambdaPort: 3002 functions: - ${file(src/functions/api/index.ts)} - ${file(src/functions/worker/index.ts)} resources: Resources: UsersTable: Type: AWS::DynamoDB::Table Properties: TableName: ${self:service}-${self:provider.stage}-users BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: pk AttributeType: S - AttributeName: sk AttributeType: S KeySchema: - AttributeName: pk KeyType: HASH - AttributeName: sk KeyType: RANGE TimeToLiveSpecification: AttributeName: ttl Enabled: true עבור handlers ותיקיית // src/functions/api/index.ts import type { AWS } from '@serverless/typescript'; const apiFunction: AWS['functions'] = { api: { handler: 'src/functions/api/handler.main', events: [{ httpApi: { method: 'ANY', path: '/api/{proxy+}', authorizer: { name: 'jwtAuthorizer', type: 'jwt', identitySource: '$request.header.Authorization', issuerUrl: 'https://cognito-idp.eu-west-1.amazonaws.com/${env:COGNITO_POOL_ID}', audience: ['${env:COGNITO_CLIENT_ID}'], }, }, }], environment: {}, }, }; export default apiFunction; // src/libs/lambda.ts import middy from '@middy/core'; import middyJsonBodyParser from '@middy/http-json-body-parser'; import httpErrorHandler from '@middy/http-error-handler'; import cors from '@middy/http-cors'; import type { APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2 } from 'aws-lambda'; type Handler = (event: APIGatewayProxyEventV2) => Promise<APIGatewayProxyStructuredResultV2>; export const middyfy = (handler: Handler) => middy(handler) .use(middyJsonBodyParser()) .use(httpErrorHandler()) .use(cors({ origin: process.env.ALLOWED_ORIGIN ?? '*' })); עבור מודולי עזר. הקובץ ~true הוא לב התצורה.
מה זה Cold Start ואיך לנצח אותו?
Cold start הוא הזמן מהבקשה הראשונה ועד ביצוע ה-handler, הנגרם על ידי אתחול זמן הריצה וטעינת הקוד. הבדיקות שלנו מראות שעם bundle לא מותאם, cold start יכול להגיע ל-500 אלפיות השנייה. השתמשו ב-esbuild עם tree shaking – זה מקטין את גודל ה-bundle ב-60% ומוריד את ה-cold start מ-450ms ל-120ms (שיפור של 73%). исключите תלויות מובנות כמו serverless offline start, שכבר קיימות בסביבת Lambda. עבור פונקציות רגישות לשהייה, הפעילו Provisioned Concurrency (עד פי 3 בעלות, אבל cold start = 0). בניגוד ל-webpack, esbuild מהיר פי 10–100 ומציע bundling מותאם ל-serverless.
תצורת serverless invoke local --function נכונה
npm install -g serverless
serverless --version # 3.x или 4.x
serverless create --template aws-nodejs-typescript --path my-service
cd my-service
npm install
תצורת פונקציות ו-Middleware
service: my-web-service
frameworkVersion: '3'
plugins:
- serverless-esbuild
- serverless-offline
- serverless-dotenv-plugin
provider:
name: aws
runtime: nodejs20.x
region: eu-west-1
stage: ${opt:stage, 'dev'}
memorySize: 512
timeout: 10
logRetentionInDays: 14
environment:
NODE_ENV: ${self:provider.stage}
DB_PASSWORD: ${ssm:/my-service/${self:provider.stage}/db-password~true}
API_KEY: ${ssm:/my-service/api-key~true}
iam:
role:
statements:
- Effect: Allow
Action: [s3:GetObject, s3:PutObject]
Resource: 'arn:aws:s3:::${self:custom.bucketName}/*'
- Effect: Allow
Action: [dynamodb:Query, dynamodb:PutItem, dynamodb:UpdateItem]
Resource: !GetAtt UsersTable.Arn
httpApi:
cors:
allowedOrigins: ['https://my-site.com', 'http://localhost:3000']
allowedHeaders: ['Content-Type', 'Authorization']
allowedMethods: [GET, POST, PUT, DELETE]
custom:
bucketName: my-service-${self:provider.stage}-assets
esbuild:
bundle: true
minify: ${strToBool(${ssm:/my-service/minify, 'false'})}
sourcemap: true
target: node20
platform: node
concurrency: 10
external:
- '@aws-sdk/*'
- 'pg-native'
serverless-offline:
httpPort: 3001
lambdaPort: 3002
functions:
- ${file(src/functions/api/index.ts)}
- ${file(src/functions/worker/index.ts)}
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:service}-${self:provider.stage}-users
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
TimeToLiveSpecification:
AttributeName: ttl
Enabled: true
איך לנהל סביבות ביעילות?
השתמשו ב-stages שונים וב-SSM Parameter Store לאחסון סודות מאובטח. פרמטרים מוצפנים משתמשים בסיומת sharp. לפיתוח מקומי, הריצו mkdir -p layer/nodejs cd layer/nodejs npm install sharp ; כדי לבדוק פונקציה ספציפית, השתמשו ב-layers: sharp: path: layer compatibleRuntimes: [nodejs20.x] . לעולם אל תאחסנו סודות ב-Git – זו אחת הטעויות הנפוצות ביותר שמובילות לדליפות מידע.
למה esbuild הוא הבחירה הטובה ביותר ל-Bundling?
לפי מדריך המפתחים של AWS Serverless, esbuild עם tree shaking מקטין את גודל ה-bundle בעד 40% ומוריד את ה-cold start. בניגוד ל-webpack, הוא מהיר פי 10–100 ואינו דורש תצורה מורכבת. עבור ספריות מקוריות כמו npx serverless deploy --stage prod, צרו Lambda Layer – זה שומר אותן נפרדות ומעדכן אותן באופן עצמאי.
// src/functions/api/index.ts
import type { AWS } from '@serverless/typescript';
const apiFunction: AWS['functions'] = {
api: {
handler: 'src/functions/api/handler.main',
events: [
{
httpApi: {
method: 'ANY',
path: '/api/{proxy+}',
authorizer: {
name: 'jwtAuthorizer',
type: 'jwt',
identitySource: '$request.header.Authorization',
issuerUrl: 'https://cognito-idp.eu-west-1.amazonaws.com/${env:COGNITO_POOL_ID}',
audience: ['${env:COGNITO_CLIENT_ID}'],
},
},
},
],
environment: {},
},
};
export default apiFunction;
// src/libs/lambda.ts
import middy from '@middy/core';
import middyJsonBodyParser from '@middy/http-json-body-parser';
import httpErrorHandler from '@middy/http-error-handler';
import cors from '@middy/http-cors';
import type { APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2 } from 'aws-lambda';
type Handler = (event: APIGatewayProxyEventV2) => Promise<APIGatewayProxyStructuredResultV2>;
export const middyfy = (handler: Handler) =>
middy(handler)
.use(middyJsonBodyParser())
.use(httpErrorHandler())
.use(cors({ origin: process.env.ALLOWED_ORIGIN ?? '*' }));
צרפו את ה-layer:
mkdir -p layer/nodejs cd layer/nodejs npm install sharp השפעה בעולם האמיתי: הפחתת Cold Start ועלויות
בפרויקט אחד לפלטפורמת מסחר אלקטרוני, הפחתנו את ה-cold start מ-450 ms ל-120 ms (שיפור של 73%) באמצעות esbuild עם tree shaking והעברת תלויות גדולות (כמו serverless.yml) ל-Lambda Layer. עלות התשתית החודשית ירדה ב-75% בהשוואה להגדרת VPS הקודמת שלהם, חיסכון של 600$ בחודש, תוך קנה מידה חלק במהלך מבצעי מכירות.
השוואה: Serverless לעומת VPS
| קריטריון | Serverless (Lambda) | VPS (Nginx + Node) |
|---|---|---|
| קנה מידה | אוטומטי | ידני (auto-scaling) |
| עלות במצב סרק | ~0 | משלמים על משאבים |
| Cold start | 100‑500 ms | 0 ms |
| זמן ביצוע מקסימלי | 15 דקות | ללא הגבלה |
| תחזוקת תשתית | הספק | אתם עושים זאת |
תחת עומס בלתי צפוי, serverless חוסך עד פי 5 בהשוואה ל-VPS. קנה המידה האוטומטי של serverless עדיף לאין שיעור על קנה מידה ידני במהלך עליות תנועה. עבור תעבורה מתמשכת מעל 1000 בקשות/שנייה, VPS עשוי להיות זול יותר.
תוספים פופולריים ל-Serverless Framework
| תוסף | מטרה |
|---|---|
| serverless-esbuild | Bundling מהיר עם tree shaking |
| serverless-offline | אמולציה מקומית של Lambda ו-API Gateway |
| serverless-dotenv-plugin | טעינת קבצי .env |
| serverless-ssm-fetch | שליפה אוטומטית של פרמטרים מ-SSM |
CI/CD עבור Serverless Framework
הגדירו GitHub Actions או GitLab CI לפריסות אוטומטיות ל-stages שונים. ב-workflow, כללו שלבים: checkout, התקנת תלויות, ופריסה דרך npx serverless deploy --stage prod. אחסנו סודות ב-GitHub Secrets או ב-GitLab CI Variables. צינור טיפוסי dev → staging → prod אורך 2–3 דקות.
מה כלול בהגדרת Serverless Framework שלכם?
-
תצורת
serverless.ymlעם IAM, VPC וסביבות - אופטימיזציית build (esbuild, tree shaking, Lambda Layers)
- צינור CI/CD (GitHub Actions / GitLab CI) עבור dev/staging/prod
- ניהול סודות דרך SSM או Secrets Manager
- דיאגרמת ארכיטקטורה מקיפה ותיעוד
- גישה למאגר המלא עם התצורה
- הדרכת צוות
- תמיכה לאחר פריסה (24/7 במהלך המעבר)
- הפחתת cold start מובטחת של לפחות 40%
לוחות זמנים
הגדרה בסיסית עם פונקציה אחת ופריסה – יום אחד. תשתית מלאה עם פונקציות מרובות, DynamoDB, SSM ו-CI/CD – 3–4 ימים. העברה מ-Express – 1–2 שבועות. התמחור נקבע באופן אישי לפי מורכבות והיקף.
צרו קשר לבדיקת ארכיטקטורת serverless. קבלו ייעוץ על הגדרת Serverless Framework – נעזור לכם להימנע מטעויות נפוצות ולהאיץ את הפיתוח.







