AWS Lambda وإطار عمل Serverless هما أسرع طريقة للبدء في عالم Serverless!
سيعلّمك هذا الدليل كيفية تطوير واختبار ونشر دوال AWS Lambda باستخدام إطار عمل Serverless، وأيضًا التعرّف على إدارة هوية الوصول (IAM) وCloudFormation وS3 Bucket وAPI Gateway وCloudWatch وخدمات رائعة أخرى.
لهذا العرض التوضيحي، سنستخدم Node.js لبساطتها، وأوقات بدء تشغيل باردة (Cold Starts) منخفضة، ونظام بيئي ضخم (npm) من الأدوات المتاحة لنا. سنتعلّم كيفية إنشاء واجهات برمجة تطبيقات، وتفعيل CORS، وتشغيل Serverless دون اتصال، ومعالجة الأخطاء، واستخدام الوسطاء، والتكامل مع TypeScript، وإعداد النشر المستمر والمراقبة في بيئة الإنتاج. يمكنك الحصول على الكود الكامل على GitHub.
ما هو Serverless؟
الحوسبة اللاخادمية (Serverless) هي طريقة لتوفير خدمات الواجهة الخلفية على أساس الاستخدام الفعلي. يتيح مزوّد Serverless للمستخدمين كتابة الكود ونشره دون عناء القلق بشأن البنية التحتية الأساسية. كمطورين، نهتم فقط بالكود الذي نكتبه، بينما تُوسَّع الخوادم وتُدار من قبل مزوّدي الخدمة السحابية.
الإعداد المعتاد لدوال Lambda في AWS يدوي. ما يتيحه لنا إطار عمل Serverless هو كتابة كل الخطوات اليدوية كبنية تحتية كشيفرة (IaaC)، بالإضافة إلى إدارة الموارد ونشر دوالنا بسهولة.
بعض خصائص دوال Lambda:
- تُدار الخوادم وتُوفَّر من قبل مزوّد الخدمة السحابية لديك (ومن هنا جاء الاسم، Serverless)
- لا حاجة للدفع مسبقًا. تدفع مقابل كل استدعاء. بالإضافة إلى وجود مستويات مجانية.
- صغيرة وبسيطة. رائعة لتفريغ بعض أجزاء العمل من خادمك الرئيسي (المدفوعات، إرسال البريد الإلكتروني، ضغط الصور، استخلاص بيانات الويب، إلخ.)
- تتوسع تلقائيًا وبلا حدود.
- يمكن دمجها بسهولة مع خدمات أخرى مثل S3 وDynamoDB وCognito وSQS، إلخ.
- تُستدعى بالأحداث (HTTP، Sockets، S3، إلخ.)
- تحتاج دوال Lambda وقتًا للتشغيل (الإحماء) عند أول استخدام - يُسمى هذا بدء التشغيل البارد (Cold Start).
جمال إطار عمل Serverless هو أنه غير حصري لـ AWS. يدعم عدة مزوّدي خدمة سحابية بما فيهم Azure وGCP وAWS، إلخ، بالإضافة إلى عدة لغات مثل Python وNode.js وC# وJava وغيرها.
المتطلبات الأساسية
تأكد من امتلاك حساب AWS، وAWS-CLI، وNode.js مثبَّتة على جهازك. يمكنك التحقق من الأخيرة بتشغيل aws --version وnode -v في الطرفية لديك.
إعداد دور IAM
للبدء، نحتاج لتوليد أذونات لمستخدم Lambda لدينا.
الخطوة 1: سجّل الدخول إلى حساب AWS لديك وابحث عن IAM.
الخطوة 2: انقر على Users (ضمن Access Management) في الشريط الجانبي على اليسار، ثم انقر على زر Add users على اليمين.
الخطوة 3: في الشاشة التالية، أدخل اسم المستخدم (أي اسم نريده) وانقر على خانة الاختيار Access key — Programmatic access. ثم التالي.
الخطوة 4: في شاشة الأذونات، انقر على Attach existing policies directly، ثم انقر على سياسة AdministratorAccess وانقر على التالي. لأغراض هذا العرض التوضيحي، سنستخدم سياسة Admin.
الخطوة 5: نصل إلى صفحة Tags التي يمكننا تخطيها. ثم نصل إلى الصفحة الأخيرة وننقر على زر Create user.
الخطوة 6: في الشاشة الأخيرة، نحصل على مفتاح الوصول والمفتاح السري اللذين سنستخدمهما للمصادقة مع إطار عمل Serverless. هذان مهمان للغاية.
ستُظهر AWS بيانات الاعتماد هذه مرة واحدة فقط ولن تظهرها مرة أخرى أبدًا. إذا فقدناها، سنحتاج لتوليد بيانات جديدة. لا تشاركها مع أي شخص.
انتهينا من موقع AWS في الوقت الحالي. الآن بعد أن أصبح لدينا مستخدم AWS Admin، افتح طرفية وثبّت إطار عمل Serverless على جهازك:
$ npm i -g serverlessبعد ذلك، سجّل الدخول إلى Serverless عبر الطرفية باستخدام بيانات الاعتماد التي وَلَّدناها للتو. (sls اختصار لـ serverless)
$ sls config credentials --provider aws --key our_key --secret our_secretأصبحت هذه ثابتة الآن على جهازنا. كل عملية نشر لـ AWS سنقوم بها ستذهب مباشرة إلى حساب AWS لدينا. إذا أردت تغيير بيانات الاعتماد هذه، انتقل إلى:
- ~/.aws/credentials على Linux أو macOS
- C:\Users\USERNAME\.aws\credentials على Windows
إذا كان لديك عدة حسابات AWS، يمكنك بسهولة التبديل من نشر Lambda من حساب إلى آخر فقط عن طريق تبديل بيانات الاعتماد هذه.
إنشاء أول دالة Serverless لنا
للبدء، يمكننا تشغيل serverless --help لرؤية قائمة الأوامر المتاحة لنا.
للحفاظ على البساطة، سنستخدم أمر sls (اختصار لـ serverless) من الآن فصاعدًا.
نختار بعدها أحد الخيارات ونضغط على Enter لتوليد مشروع. طريقة أخرى للقيام بذلك هي إنشاء قالب AWS Node.js وتحديد المسار (المجلد) الذي سيعيش فيه تطبيقنا اختياريًا.
$ sls create --template aws-nodejs --path my-first-serverless-appإذا دخلنا إلى المجلد الذي وَلَّدناه للتو، سنجد ملفين مثيرين للاهتمام: handler.js وserverless.yml.
'use strict';
module.exports.hello = async (event) => {
return {
statusCode: 200,
body: JSON.stringify(
{
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
},
null,
2
),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};الأجزاء الرئيسية هنا هي عبارة module.exports (التي تستخدم صياغة CommonJS) واسم دالة Lambda hello التي تعيد استجابة معينة. هذه دالة معالِج (handler) تستدعيها AWS، لذا يجب تصديرها. يجب أن يكون كل الكود المتعلق بالدالة بداخلها.
الملف الآخر لدينا هو ملف serverless.yml. هنا نكتب البنية التحتية لتطبيقنا.
لنفصّلها:
- Services — اسم خدمتنا التي ستُولَّد على AWS.
- Provider — حدد مزوّد الخدمة السحابية (AWS، Azure، إلخ.) بالإضافة إلى بيئة وقت التشغيل.
- Functions — يمكن أن يكون لدينا دالة واحدة أو أكثر تستدعيها AWS. لكل دالة اسم ومعالِج. handler.hello يمثّل اسم الملف واسم الدالة المُصدَّرة.
- Events — مُحفّزات Lambda التي ستشغّل الدالة. يمكن تفعيل Lambda عبر HTTP، أو حدث Socket، أو رفع S3، إلخ.
- Resources — حدد الموارد الإضافية التي تستخدمها دالتك، مثل DynamoDB وS3 Bucket وCognito، إلخ.
- Environment — أعدّ متغيرات البيئة (المفاتيح السرية) المستخدَمة من قبل الخدمة بأكملها أو لكل دالة.
- Plugins — إضافات خاصة لبيئتك، مثل Serverless-Offline للاستدعاء المحلي أو إضافة Serverless TypeScript.
اقرأ المزيد عن ملف serverless.yml.
نشر واختبار دالتنا
بما أنني أعيش في أوروبا، سأغيّر المنطقة إلى الأقرب إليّ.
# you can overwrite defaults here
stage: dev
region: eu-central-1ضع في اعتبارك أنه بمجرد توليد دالة Lambda في منطقة معينة، ستعيش في تلك المنطقة. يمكنك تبديل المناطق في شريط التنقل الرئيسي على AWS Console.
الآن لننشر (deploy).
$ sls deploy
بما أن هذا أول نشر لنا، سيستغرق أطول وقت. الآن لننتقل إلى AWS لنرى ما لدينا.
عند النشر، يولّد ملف serverless.yml مكدّس CloudFormation ويربط جميع الموارد التي تستخدمها دالتنا.
إذا بحثنا عن CloudFormation في AWS Console وانتقلنا إليه، سنرى دالتنا (طالما أننا في المنطقة الصحيحة).
إذا نقرنا على دالتنا في الجدول، ستفتح نافذة جديدة. ثم انقر على تبويب Resources لعرض جميع الموارد المرتبطة بدالتنا - أدوار IAM، وحاوية S3 التي نُشرت فيها Lambda لدينا، والرابط إلى الدالة نفسها.
النقر على الدالة (المستطيل الأزرق) سينقلنا إلى شاشة AWS Lambda.
بالتمرير للأسفل، يمكننا رؤية كود دالتنا. يمكننا اختبارها بالنقر على زر Test البرتقالي.
سيُظهر هذا نافذة منبثقة لكتابة تفاصيل حول اختبارنا. ندخل Event name ونترك كل شيء آخر على الإعدادات الافتراضية.
الآن انقر Save في النافذة المنبثقة ثم على زر Test في شريط التنقل.
اختر الحدث الذي أنشأناه للتو وانقر على زر Test البرتقالي مرة أخرى. نرى أن كل شيء سار على ما يرام والكود المُعاد هو ما كتبناه في الدالة.
لنغيّر كود دالتنا قليلًا وننشرها مرة أخرى.
'use strict';
module.exports.hello = async (event) => {
return 'Hello World!'
};معامل event هو كائن يعيد معلومات عن الطلب، مثل طريقة الطلب، والمحتوى، ومعاملات الاستعلام، إلخ.
هذه المرة لم نغيّر بنيتنا التحتية، لذا يمكننا استخدام اختصار لنشر كود الدالة فقط:
sls deploy -f hello // -f is shorthand for function
بعدها يمكننا التوجه إلى AWS Console واختبار كودنا مرة أخرى.
الآن قد تتساءل هل يمكننا استخدام وحدات NPM في دوال Lambda لدينا؟ نعم، يمكننا! لكن، هناك أمور يجب مراعاتها:
- لا يمكن لـ Lambda تشغيل بناء NPM نيابة عنا، لذا يجب علينا رفع (نشر) مجلد node_modules مع الدالة.
- دوال Lambda مقيَّدة بـ 50 ميغابايت. إذا تجاوز مجلدنا هذا الحد، يمكننا عزل المشروع بأكمله في حاوية (Dockerize) ونشره على AWS ECS.
بدلًا من ذلك، يمكننا رفع كودنا إلى حاوية S3 وربط Lambda بـ S3 - وهو ما يقوم به إطار عمل Serverless بالفعل نيابة عنا خلف الكواليس.
من هنا يمكننا إضافة محفّزات لدالة Lambda لدينا، وإعداد المهلة الزمنية (timeout)، وخيارات إعداد أخرى، لكننا لن نفعل ذلك بهذه الطريقة. نستخدم إطار عمل Serverless لإعداد البنية التحتية - حتى لا نحتاج للنقر يدويًا في كل مكان.
إنشاء واجهات برمجة تطبيقات باستخدام إطار عمل Serverless
لاستدعاء دالة Lambda لدينا من العميل لدينا (تطبيق ويب، تطبيق جوال، أو Postman)، نحتاج لإعداد نقطة دخول - API Gateway.
للقيام بذلك، نعدّل ملف serverless.yml ونضيف HTTP API في قسم الأحداث.
functions:
hello:
handler: handler.hello
events:
- httpApi:
path: /
method: get$ sls deploy
هذه المرة نحصل أيضًا على نقطة نهاية API كاستجابة. إذا زرنا هذا الرابط في المتصفح، يجب أن يعيد الاستجابة الصحيحة.
طريقة أخرى لاستدعاء هذه الواجهة البرمجية هي مباشرة من الطرفية:
$ sls invoke -f hello -l // where -f stands for function and -l for logs
إذا انتقلنا إلى دالة Lambda لدينا في AWS Console، يمكننا أن نرى أن الدالة الآن لديها رابط إلى API Gateway.
إضافة طلب POST
لنوسّع دالتنا بمسار آخر. يمكننا استرجاع طريقة HTTP من كائن الحدث ثم كتابة التنفيذ. هنا أسجّل أيضًا كائن الحدث في وحدة التحكم.
module.exports.hello = async (event) => {
console.log('event :>> ', event);
if (event.requestContext.http.method === 'POST') {
return {
statusCode: 201,
body: JSON.stringify({
message: 'Resource created!',
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({
message: 'Resource retrieved!',
}),
};
};الآن لنعدّل ملف YAML:
functions:
hello:
handler: handler.hello
events:
- httpApi:
path: /
method: get
- httpApi:
path: /create
method: postما فعلناه هنا هو إنشاء مسارين في نفس الملف. يمكننا أيضًا إنشاء دوال متعددة (ملفات معالِج) وفصل كل مسار إلى دالته الخاصة. اقرأ المزيد عن أنماط بنية Serverless.
سنسمح أيضًا بـ CORS في قسم المزوّد بحيث يمكن للجميع الوصول إلى مساراتنا:
provider:
name: aws
runtime: nodejs12.x
httpApi:
cors: trueلنشغّل أمر النشر مرة أخرى:
$ sls deployالآن لدينا نقطتا نهاية يمكننا التفاعل معهما.
لنختبر هذين المسارين في Postman:
خطوة مهمة واحدة لإرسال طلب POST هي ضبط ترويسة Content-Type على application/json.
تطبيق الويب:
const URL = `https://nkipcuru5g.execute-api.eu-central-1.amazonaws.com`;
fetch(URL, options)
.then((response) => {
return response.json();
})
.then((jsonObject) => {
console.log(jsonObject) // {message: 'Resource retrieved!'}
})
.catch((error) => {
console.error(error);
});const options = {
method: 'POST',
data: {},
headers: {
'Content-Type': 'application/json'
}
};
const URL = `https://nkipcuru5g.execute-api.eu-central-1.amazonaws.com/create`;
fetch(URL, options)
.then((response) => {
return response.json();
})
.then((jsonObject) => {
console.log(jsonObject) // {message: 'Resource created!'}
})
.catch((error) => {
console.error(error);
}); المراقبة باستخدام CloudWatch وServerless Console
لفهم ما يجري في دوالنا بشكل أفضل، يمكننا الانتقال إلى خدمة CloudWatch. أسهل طريقة لإيجاد سجلات دالتنا هي الانتقال إلى شاشة الدالة والنقر على تبويب Monitor.
يفتح هذا قائمة من الخيارات، لكن ما يهمنا هو خيار View logs in CloudWatch.
ثم نصل إلى الصفحة حيث يمكننا رؤية السجلات بالترتيب والنقر على كل منها لمعاينة ما حدث في تلك اللحظة.
هنا يمكننا رؤية قائمة الأحداث التي وقعت وحتى كائن event لدينا الذي سجّلناه سابقًا في وحدة التحكم.
طريقة أخرى لمراقبة السجلات هي استخدام Serverless Console المقدَّم من إطار عمل Serverless. لتفعيله، ببساطة شغّل:
$ sls --consoleأولًا، سيطلب منك تفعيل الوصول إلى AWS وإنشاء دور IAM.
بعدها نحتاج للتسجيل أو تسجيل الدخول إلى Serverless Dashboard المفتوح في المتصفح.
بعد بضع دقائق من الإعداد، ننتقل إلى Serverless Console ونجد خدمتنا. نحتاج أيضًا لتفعيل Logs وTraces ووضع Dev.
ثم نجري بضعة طلبات (GET أو POST) وستظهر المقاييس تقريبًا في الوقت الفعلي.
تشغيل دوال Serverless دون اتصال
تطوير الميزات ثم النشر فقط لتتمكن من اختبارها في بيئة الإنتاج يستهلك وقتًا هائلًا. أنشأ فريق إطار عمل Serverless ميزة Serverless Offline التي تتيح لنا اختبار كودنا محليًا.
للبدء نحتاج لتهيئة مشروع NPM:
$ npm init -y
سيُنشئ هذا ملف package.json في مجلد المشروع.
الآن لنثبّت serverless-offline كاعتمادية تطوير:
$ npm i --save-dev serverless-offlineالخطوة التالية هي إضافة إضافة serverless-offline إلى ملف serverless.yml لدينا:
plugins:
- serverless-offlineمع وجود هذا، يمكننا استخدام أمر المساعدة للتحقق من أن كل شيء مُعدّ بشكل صحيح:
$ serverless-offline --help
الآن نشغّل Serverless محليًا:
$ sls offline start
الآن لنختبره باستخدام Postman:
ويمكننا رؤية طلباتنا مسجَّلة في وحدة التحكم.
إذا احتجنا لإجراء تغيير في الكود، نوقف الخادم ببساطة (CTRL/CMD + C) ونشغّله مرة أخرى. لعشّاق NPM، يمكننا ربط هذا الأمر بسكريبت في ملف package.json:
{
"name": "my-first-serverless-app",
"version": "1.0.0",
"description": "",
"main": "handler.js",
"scripts": {
"dev": "serverless offline start"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"serverless-offline": "^12.0.3"
}
}الآن يمكننا تشغيل:
$ npm run dev
# or if you're using yarn
$ yarn dev استخدام متغيرات البيئة
أحيانًا في تطبيقاتنا، نريد إضافة كلمات مفتاحية خاصة، مثل مفاتيح واجهة برمجة تطبيقات خارجية، وأسرار JWT، وسلاسل اتصال قاعدة البيانات، إلخ. يمكننا تحديد متغيرات البيئة في قسم المزوّد من ملف YAML لدينا:
provider:
name: aws
runtime: nodejs12.x
httpApi:
cors: true
environment:
DUMMY_API_KEY: 'Dummy value'بعدها يمكننا الوصول إلى هذا المتغيّر باستخدام كائن process.env في handler.js:
module.exports.hello = async (event) => {
console.log('Dummy env var :>> ', process.env.DUMMY_API_KEY);
...
}إذا أجرينا طلبًا آخر إلى الخادم، يجب أن نرى هذا السجل مطبوعًا في الطرفية.
كان متغيّر البيئة المضبوط سابقًا عامًا للخدمة بأكملها، لكن يمكننا أيضًا ضبط متغيرات لكل دالة ستتجاوز المتغيرات العامة (إذا كان لها نفس الاسم):
functions:
hello:
handler: handler.hello
environment:
DUMMY_API_KEY: 'New Dummy value' # will override the previous
events:
- httpApi:
path: /
method: get
- httpApi:
path: /create
method: postبمجرد ضبط المتغيرات، يمكننا إيجادها في شاشة AWS Lambda، تحت Configuration > Environment variables.
اقرأ المزيد عن متغيرات البيئة في إطار عمل Serverless.
معالجة الأخطاء
في مرحلة ما، قد نريد التحقق من صحة إدخال المستخدم. هنا نعدّ شرطًا للتحقق من صحة محتوى الطلب، وإذا كان غير صالح، نعيد خطأ.
module.exports.hello = async (event) => {
if (event.requestContext.http.method === 'POST') {
if (!event.body) {
console.log('gonna throw error')
throw new Error('Body field is required')
}
return {
statusCode: 201,
body: JSON.stringify({
message: "Resource created!",
}),
};
}
...
}بعدها نشغّل هذا باستخدام Postman ونلقي نظرة على الطرفية.
لم يسر ذلك كما هو متوقع. لدينا مشكلتان:
- صنف Error المدمج في JavaScript لا يمتلك رموز حالة HTTP
- هذا الخطأ غير مُعالَج (وتطبيقنا معطَّل بشكل محتمل)
لحل المشكلة الأولى، سنثبّت ونُعدّ حزمة NPM http-errors:
$ npm i http-errorsconst createError = require('http-errors'); // importing package
module.exports.hello = async (event) => {
if (event.requestContext.http.method === 'POST') {
if (!event.body) {
throw createError(400, `Field "body" is required!`); // using it
}الآن نحصل على خطأ أكثر قابلية للقراءة.
لكن، لا يزال رمز الحالة 200. لإصلاح هذا، نحتاج لتقديم منطق معالجة الأخطاء.
الوسطاء (Middlewares)
بالانتقال إلى المشكلة الثانية - سنعزز دالة Lambda لدينا بالوسطاء. لهذا الغرض، لدينا حزمة NPM تُسمى Middy.
$ npm i @middy/core @middy/http-error-handler @middy/error-loggerثبّتنا للتو ثلاث حزم:
- غلاف دالة Middy لـ Lambda
- معالِج الأخطاء
- مسجّل الأخطاء (بحيث لا نحتاج لتسجيل الأخطاء يدويًا)
أولًا، نغلّف الدالة بأكملها بـ middy. بعدها نستفيد من دالة use()، ونرفقها في نهاية دالة middy ونمرر كل وسيط نريد استخدامه.
const middy = require('@middy/core');
const httpErrorHandler = require('@middy/http-error-handler');
const errorLogger = require('@middy/error-logger');
module.exports.hello = middy(async (event) => {
...
})
.use(httpErrorHandler())
.use(errorLogger())لنرسل طلبًا غير صالح.
يُرمى الخطأ كما هو متوقع، لكن التطبيق لا يزال يعمل. إذا نظرنا في Postman، يمكننا أن نرى أن رمز حالة HTTP هو الآن 400 (Bad Request).
وبالطبع، إذا أرسلنا المحتوى في الطلب (حتى لو كان فارغًا)، ستكون الاستجابة ناجحة.
وسيط Middy أكثر من مجرد معالِج أخطاء. يمكننا استخدامه لتوحيد الطلبات، وإخفاء الترويسات غير المرغوبة، وتخزين الاستجابة مؤقتًا، وإعداد CORS، وSSM، إلخ. اقرأ المزيد عن Middy.
مهم: أحدث نسخة من Middy (4.0.9) غير متوافقة مع Node.js 12. إذا كنت تستخدم Node.js 14 أو أقل، استخدم نسخ الحزم هذه:
"@middy/core": "^2.5.7",
"@middy/error-logger": "^2.5.7",
"@middy/http-error-handler": "^2.5.7"لاستخدام أحدث نسخة من Middy، انتقل إلى قسم المزوّد من ملف YAML وغيّر نسخة Node.js runtime إلى 16:
provider:
name: aws
runtime: nodejs16.xبعدها انشر وكل شيء يجب أن يعمل كما هو متوقع:
$ sls deploy
TypeScript
لنضف أيضًا أمان الأنواع (type safety) إلى دوالنا. سنبدأ بتثبيت بعض الحزم:
$ npm i aws-lambda
$ npm i --save-dev @types/aws-lambda @types/node @types/http-errors serverless-plugin-typescript typescriptلسنا بحاجة لإعادة تثبيت Middy لأن هذه الحزمة تدعم TypeScript جاهزة. نحتاج أيضًا لتهيئة TypeScript للحصول على ملف tsconfig.json:
tsc --initوأضف الإضافة التي ثبّتناها للتو (serverless-plugin-typescript) إلى قسم plugins في ملف YAML لدينا:
plugins:
- serverless-offline
- serverless-plugin-typescriptجمال هذه الإضافة هو أنها ستقوم ببناء TypeScript نيابة عنا. عند التشغيل، ستنشئ مجلد .build محدَّدًا في ملف tsconfig.json. الآن يمكننا إعادة تسمية ملف handler.js إلى handler.ts والبدء بإعادة الهيكلة.
tsconfig.json
{
"compilerOptions": {
"preserveConstEnums": true,
"strictNullChecks": true,
"sourceMap": true,
"allowJs": true,
"target": "es2017",
"outDir": ".build",
"moduleResolution": "node",
"lib": ["es2017"],
"rootDir": "./",
"strict": true,
"module": "commonjs",
"esModuleInterop": true
},
"include": ["**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}handler.ts
import { APIGatewayEvent } from 'aws-lambda';
import middy from '@middy/core';
import httpErrorHandler from '@middy/http-error-handler';
import errorLogger from '@middy/error-logger';
import createError from 'http-errors';
export const hello = middy(async (event: APIGatewayEvent) => {
if (event.requestContext.routeKey?.includes('POST')) {
if (!event.body) {
throw createError(400, 'Field "body" is required!');
}
return {
statusCode: 201,
body: JSON.stringify({
message: 'Resource created!',
}),
};
}
return {
statusCode: 200,
body: JSON.stringify({
message: 'Hello AWS Lambda TypeScript on Serverless Framework!',
}),
};
})
.use(httpErrorHandler())
.use(errorLogger());serverless.yml
service: my-first-serverless-app
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs16.x
httpApi:
cors: true
environment:
DUMMY_API_KEY: 'Dummy value'
stage: dev
region: eu-central-1
functions:
hello:
handler: handler.hello
environment:
DUMMY_API_KEY: 'New Dummy value'
events:
- httpApi:
path: /
method: get
- httpApi:
path: /create
method: post
plugins:
- serverless-offline
- serverless-plugin-typescriptلنشغّل npm run dev:
رفع التغييرات إلى Git وإنشاء خط أنابيب النشر
بدلًا من النشر يدويًا في كل مرة نجري فيها تغييرًا، نريد نشر تغييراتنا إلى AWS في كل مرة نرفع فيها كودنا إلى Git.
أولًا، سجّل الدخول إلى حساب GitHub لديك وأنشئ مستودعًا جديدًا. في الشاشة التالية، أعطِ المستودع اسمًا وانقر على زر Create repository.
هيّئ المستودع في مجلد مشروعك بتشغيل git init، والذي سيُبرز بعدها جميع الملفات غير المدرجة في .gitignore.
git init
git add .
git commit -m "initial commit"
git branch -M main
git remote add origin https://github.com/USERNAME/Your-Repository.git
git push -u origin mainالآن يجب أن يُنشَر كودنا إلى Git.
إنشاء خط أنابيب النشر
هل تتذكر مفتاح الوصول والمفتاح السري اللذين وَلَّدناهما في صفحة IAM؟ سنستخدم هذين المفتاحين ونخزّنهما في مستودع GitHub لدينا للسماح بعمليات النشر إلى AWS.
نبدأ بإضافة أسرار (secrets) إلى مستودع GitHub لدينا. في صفحة المستودع، انقر على Settings في التنقل العلوي، ثم على Secrets، Actions، ثم على زر New repository secret.
ثم أضف كل واحد على حدة.
عد إلى محرر الكود، في مجلدك الجذر، أنشئ مجلد .github، ثم مجلد workflows بداخله، ثم ملف main.yml (.github/workflows/main.yml).
يمكننا إيجاد مثال على ملف main.yml في صفحة Serverless GitHub Actions. يجب أن يبدو هكذا:
name: Deploy Lambda to AWS
on:
push:
branches:
- main
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci # install dependencies from package.json file
- name: serverless deploy
uses: serverless/[email protected]
with:
args: deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}الآن نُدرج هذه التغييرات ونرفعها إلى Git:
git add .
git commit -m "Added Github deployment actions"
git pushعند الرفع، يجب أن نرى مهمة جديدة تعمل تحت تبويب Actions في مستودع GitHub.
يمكننا النقر عليها لتوسيعها.
بمجرد اكتمال النشر، وسّع قسم serverless deploy وخذ نقطتي نهاية API:
لنختبر إحداهما في Postman للتحقق من أنها تعمل.
إزالة دالة Lambda
لإزالة هذه الدالة من AWS، ببساطة شغّل:
$ sls remove
تأكد من تشغيل هذا الأمر لكل مرحلة (dev، production، إلخ.). يمكنك بناء دالة جديدة فقط بإعادة نشر الدالة الموجودة.
ملاحظات حول Lambda وإطار عمل Serverless
- إذا أدخلنا أمرًا مهجورًا (deprecated)، سيعرض إطار عمل Serverless تحذيرات في الطرفية عند النشر.
- إذا أدخلنا أمرًا غير صالح، سيرمي الإطار خطأً ولن ينشر.
- فصل المسارات لكل دالة يتيح لنا نشر الدالة فقط (جزء من الكود) التي تغيّرت.
- التصحيح صعب لأنك لا تستطيع الاتصال عبر SSH بالجهاز لرؤية ما حدث خطأً. السجلات هي أفضل خيار لك لإيجاد المشكلات.
- لا يُنصَح باستخدام دوال Lambda مع الأنظمة التي تكون فيها الديمومة (persistence) مهمة. بسبب التوسع التلقائي، لا يمكننا أبدًا التأكد من النسخة التي نستخدمها، ومع طلبات القراءة والكتابة المتكررة، هناك احتمال أن تصبح البيانات غير متسقة (حالات تسابق - Race Conditions).
المزيد من ميزات Serverless
الأوامر
# Authenticate with AWS
sls config credentials --provider aws --key access_key --secret secret
# Create Project
sls
sls create -t aws-nodejs -p my-app
# Deploy Project
sls deploy # deploy whole project
sls deploy -f hello # deploy just a specified function
# Local Testing (with Serverless-Offline installed and plugin set)
sls offline start # starts the app on localhost:3000
# Remove Project
sls remove # Remove the project from AWSمجموعة الأدوات: يمتلك Visual Studio Code إضافة Serverless-IDE تساعد في كتابة ملفات serverless.yml.
المراقبة: رأينا بالفعل كيفية مراقبة المشكلات باستخدام CloudWatch وServerless Console. خدمة رائعة أخرى هي Dashbird، التي تمنحك نظرة عامة مفصلة جدًا على دوال Lambda لديك وترسل تنبيهات بريد إلكتروني متكررة عند حدوث خطأ ما.
من السهل جدًا أيضًا إعدادها مع حساب AWS لديك. التوثيق الرسمي.
لمعرفة المزيد
كانت هذه مجرد نظرة صغيرة على عالم Serverless. أشجعك بالتأكيد على استخدام AWS Lambda بنفسك وتجربة الأشياء المتاحة.
- توثيق AWS Lambda
- توثيق إطار عمل Serverless
- Serverless Console
- منتدى إطار عمل Serverless
- دورة مكثفة عن إطار عمل Serverless مع AWS Lambda
- قائمة تشغيل Youtube لإطار عمل Serverless
- إطار عمل Serverless على AWS Youtube
- تطبيق Express.js مع Serverless
- Node.js Serverless TypeScript
يمكنك إيجاد الكود بأكمله في مستودع AWS-Lambda-Starter الخاص بي. شكر خاص لشريكي في الجريمة Dzenan Dzafic.