AWS Lambda في العمل في جافا 11. الذهاب بدون خادم إلى الإنتاج

هذه المقالة هي دليل حول كيفية البدء بسرعة وبدون ألم باستخدام AWS Lambda مع مثال بسيط. مناسب لكل من المطور الذي لم يعمل مع Lambda على الإطلاق ، والذي كان يعرف Cloud لتقييم رؤية أخرى لتطوير تطبيقات Serverless.

صورة


المقدمة


تحية للجميع.
اسمي ألكسندر جروزديف ، أنا رئيس فريق جافا في DINS. لأكثر من عامين ، كنت أعمل بشكل وثيق مع البنية التحتية AWS ولدي خبرة في كتابة الطلبات لـ AWS وفي نشر هذه التطبيقات. في عملي ، اضطررت إلى استخدام ElasticBeanstalk و ECS و Fargate و EKS ، وبالطبع AWS Lambda.

كل من هذه الخدمات جيدة بطريقتها الخاصة ، وفي إطار هذه المقالة لن أحث على استخدام AWS Lambda فقط كبديل لجميع الخيارات الأخرى. أريد فقط أن أبين مدى سهولة بدء استخدام لامدا ، وفي أي الحالات ستحصل على فوائد استخدامه.

يريد الجميع ، من المطورين إلى المديرين ، عملية واضحة لتقديم التغييرات للعملاء. كلما كان هذا المسار أكثر شفافية وقل الجهد المطلوب ، كلما كان أكثر ربحية. في أغلب الأحيان ، لا يرغب المطورون والمختبرون في معرفة المكان الذي سيتم نشر التطبيق فيه ، وأي قطعة من الأجهزة تختارها ، وفي المنطقة التي تريد وضع النسخ المتماثلة فيها ، وما إلى ذلك. في الرسم البياني التالي ، يمكنك رؤية فئة الخدمات "aaS" (كخدمة) ، حيث تمثل AWS Lambda فئة FaaS.

صورة

FaaS في باختصار
FaaS , . , , CRUD C, R, U, D Create Read. , — .


فكرة


لإثبات ما هو تطوير حل بدون خادم ، قررت أن آخذ مثالاً عاديًا إلى حد ما. أعتقد أنك شاهدت "اتصل بنا" في العديد من المواقع ، حيث يمكنك ترك بريدك أو هاتفك وطرح سؤال ليتم الرد عليه لاحقًا. الشاشة أدناه ، النموذج ليس فائقًا ، لكن موضوع هذه المقالة ليس التصميم المادي.


من خلال ملء هذا النموذج ، سينتظر العميل المحتمل مكالمة أو رسالة منك.
من الناحية التقنية ، تحتاج إلى:
  • حفظ البيانات من النموذج إلى قاعدة البيانات ؛
  • إرسال خطاب إلى وكيل يجيب على السؤال ؛
  • ربما اكتب خدمة أخرى حيث يقوم الوكيل بتمييز العمل المنجز على أساس نفس قاعدة البيانات (غير مدرجة في نطاق المقالة).


تنفيذ الفكرة متاح على جيثب .

هندسة معمارية


أول ما يتبادر إلى الذهن أثناء التنفيذ هو ما نحتاج إليه:
  • آلة تحت الظهر ، أمامية ، صور ، إلخ ،
  • سيارة بقاعدة
  • الجهاز مع خدمة البريد الإلكتروني.




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


يوضح الرسم البياني فصلًا واضحًا للغاية بين وظائف كل مكون.
  • AWS Lambda هو المكون الرئيسي الذي يحتوي على الكود / المنطق ،
  • S3 مسؤولة عن تخزين الموارد الثابتة ونصوص JS ،
  • CloudFront - لآليات التخزين المؤقت والدعم متعدد المناطق ،
  • SES - خدمة البريد الإلكتروني ،
  • DynamoDB - أساس تخزين البيانات من النموذج (من من ، أي سؤال ، إلى أين ترسل الإجابة) ،
  • Gateway API - HTTP API لـ lambda ،
  • المسار 53 - مطلوب إذا كنت تريد إضافة اسم نطاق جميل.


لن يتم استخدام كل هذه المكونات في دليلنا التالي ، حتى لا يتم تمديد المقالة.
Route53 و CloudFront هي مكونات بسيطة إلى حد ما يمكنك القراءة عنها بشكل منفصل.
المفسدون الصغار الذين سيعطوننا مثل هذا الحل:
  • نحن نبتعد عن دعم أجهزة EC2 ، لا تصحيح عبر ssh ،
  • التكوين السهل: تكوين الاختناق / التخزين المؤقت / الوصول بنقرة واحدة ،
  • يدعم سياسات الوصول: تقييد الحقوق ومنح الوصول ،
  • تسجيل / مراقبة خارج الصندوق ،
  • ادفع فقط مقابل الموارد / الطلبات المستخدمة.


تجريبي


تدريب


لبدء تطوير حلنا بدون خادم ، يجب أن تستوفي المتطلبات التالية:


بعد تثبيت جميع الأدوات المذكورة أعلاه ، تحتاج إلى تكوين aws-cli للوصول البعيد إلى AWS. اتبع التعليمات . سيتطلب ذلك إنشاء مستخدم جديد وإلغاء تحميل مفتاح الوصول والمفتاح السري الخاص به.

تجميع المشروع


1. إنشاء مشروع من قالب


افتح الدليل للمشروع المستقبلي ومنه قم بتشغيل SAM CLI. اتبع التعليمات:


ملاحظة: اعتمادًا على إصدار SAM-CLI ، قد تختلف الأوامر قليلاً ، لكنها تظل كلها بديهية. ما عليك سوى اختيار الأكثر تشابهًا مع تلك التي تم استخدامها أعلاه. يمكنك أيضًا اختيار أداة بناء مختلفة إذا لم يكن Gradle مناسبًا لك.

مشروع "Hello، World!" جاهز. الآن يمكنك العمل على اسم المشروع والحزم والتبعيات وكود المصدر.

2. دعنا نتعامل مع الإدمان


أضف التبعيات التالية إلى build.gradle:
dependencies {
    // AWS
    implementation group: 'com.amazonaws.serverless', name: 'aws-serverless-java-container-core', version: '1.4'
    implementation group: 'com.amazonaws', name: 'aws-lambda-java-core', version: '1.2.0'
    implementation group: 'com.amazonaws', name: 'aws-java-sdk-ses', version: '1.11.670'
    implementation group: 'com.amazonaws', name: 'aws-java-sdk-dynamodb', version: '1.11.670'
    implementation group: 'com.amazonaws', name: 'aws-lambda-java-log4j2', version: '1.1.0'

    // Utils
    implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.10.0'
    implementation group: 'commons-io', name: 'commons-io', version: '2.6'

    // Test
    testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.1.0'
    testImplementation group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.4'
    testImplementation group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.4'
    testImplementation group: 'junit', name: 'junit', version: '4.12'
}

الرئيسية هي AWS SDK. سيسمح لك بالعمل مع خدمات محددة ، مثل SES و DynamoDB ، إلخ.

3. نكتب لامدا


  • نقوم بتغيير فئتي طلب القالب وردود طلب RequestHandler إلى AwsProxyRequest و ContactUsProxyResponse.
    public class App implements RequestHandler<AwsProxyRequest, ContactUsProxyResponse>
    ...
    public ContactUsProxyResponse handleRequest(AwsProxyRequest request, Context context) 

  • AwsClientFactory AWS SDK-.
    /**
     * Just an util class for an eager initialization of sdk clients.
     */
    public class AwsClientFactory {
    
        private static final Logger LOG = LogManager.getLogger(AwsClientFactory.class);
    
        private final AmazonSimpleEmailService sesClient;
        private final DynamoDB dynamoDB;
    
        /**
         * AWS regions should be env variables if you want to generalize the solution.
         */
        AwsClientFactory() {
            LOG.debug("AWS clients factory initialization.");
            sesClient = AmazonSimpleEmailServiceClient.builder().withRegion(Regions.EU_WEST_1).build();
            AmazonDynamoDB dynamoDBClient = AmazonDynamoDBClientBuilder.standard().withRegion(Regions.EU_WEST_1).build();
            dynamoDB = new DynamoDB(dynamoDBClient);
        }
    
        DynamoDB getDynamoDB() {
            return dynamoDB;
        }
    
        AmazonSimpleEmailService getSesClient() {
            return sesClient;
        }
    
    }
    

  • ObjectMapper .
    
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final AwsClientFactory AWS_CLIENT_FACTORY = new AwsClientFactory();
    

  • .
    
     private SendEmailResult sendEmail(ContactUsRequest contactUsRequest) {
            String emailTemplate = getEmailTemplate();
            String email = fillTemplate(emailTemplate, contactUsRequest);
    
            SendEmailRequest sendEmailRequest =
                    new SendEmailRequest(
                            System.getenv("SENDER_EMAIL"),
                            new Destination(List.of(System.getenv("RECIPIENT_EMAIL"))),
                            new Message()
                                    .withSubject(
                                            new Content()
                                                    .withCharset(UTF_8.name())
                                                    .withData(contactUsRequest.getSubject()))
                                    .withBody(new Body()
                                            .withHtml(new Content()
                                                    .withCharset(UTF_8.name())
                                                    .withData(email))));
            LOG.info("Email template is ready");
            return AWS_CLIENT_FACTORY.getSesClient().sendEmail(sendEmailRequest);
    }
    
    private String fillTemplate(String emailTemplate, ContactUsRequest contactUsRequest) {
            return String.format(
                    emailTemplate,
                    contactUsRequest.getUsername(),
                    contactUsRequest.getEmail(),
                    contactUsRequest.getPhone(),
                    contactUsRequest.getQuestion());
    }
    
    private String getEmailTemplate() {
            try {
                return IOUtils.toString(
                        Objects.requireNonNull(this.getClass().getClassLoader()
                                                   .getResourceAsStream("email_template.html")),
                        UTF_8);
            } catch (IOException e) {
                throw new RuntimeException("Loading an email template failed.", e);
            }
    }
    
    private void addEmailDetailsToDb(ContactUsRequest contactUsRequest, SendEmailResult sendEmailResult) {
            AWS_CLIENT_FACTORY.getDynamoDB().getTable("ContactUsTable")
                              .putItem(new Item()
                                      .withPrimaryKey("Id", sendEmailResult.getMessageId())
                                      .withString("Subject", contactUsRequest.getSubject())
                                      .withString("Username", contactUsRequest.getUsername())
                                      .withString("Phone", contactUsRequest.getPhone())
                                      .withString("Email", contactUsRequest.getEmail())
                                      .withString("Question", contactUsRequest.getQuestion()));
    }
    

  • .
    
    private ContactUsProxyResponse buildResponse(int statusCode, String body) {
            ContactUsProxyResponse awsProxyResponse =
                    new ContactUsProxyResponse();
            awsProxyResponse.setStatusCode(statusCode);
            awsProxyResponse.setBody(getBodyAsString(body));
            awsProxyResponse.addHeader(CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
            awsProxyResponse.addHeader("Access-Control-Allow-Origin", "*");
            return awsProxyResponse;
    }
    
     private String getBodyAsString(String body) {
            try {
                return OBJECT_MAPPER.writeValueAsString(new ContactUsResponseBody(body));
            } catch (JsonProcessingException e) {
                throw new RuntimeException("Writing ContactUsResponseBody as string failed.", e);
            }
    }
    

  • . «// WARMING UP».
    
    if (Optional.ofNullable(request.getMultiValueHeaders()).map(headers -> headers.containsKey("X-WARM-UP")).orElse(FALSE)) {
                LOG.info("Lambda was warmed up");
                return buildResponse(201, "Lambda was warmed up. V1");
    }
    

  • handleRequest()
    
    @Override
    public ContactUsProxyResponse handleRequest(AwsProxyRequest request, Context context) {
            LOG.info("Request was received");
            LOG.debug(getAsPrettyString(request));
    
            if (Optional.ofNullable(request.getMultiValueHeaders()).map(headers -> headers.containsKey("X-WARM-UP")).orElse(FALSE)) {
                LOG.info("Lambda was warmed up");
                return buildResponse(201, "Lambda was warmed up. V1");
            }
    
            ContactUsRequest contactUsRequest = getContactUsRequest(request);
    
            SendEmailResult sendEmailResult = sendEmail(contactUsRequest);
            LOG.info("Email was sent");
    
            addEmailDetailsToDb(contactUsRequest, sendEmailResult);
            LOG.info("DB is updated");
    
            return buildResponse(200,
                    String.format("Message %s has been sent successfully.", sendEmailResult.getMessageId()));
    }
    



المنطق الأساسي بسيط للغاية ، لذلك لا أعتقد أنه يستحق وصفه بالتفصيل. هناك عدة نقاط تستحق الاهتمام بها.

الأول هو تسجيل الدخول. تحتوي إحدى حجج طريقة السياق لامدا على الكثير من المعلومات المساعدة ، بالإضافة إلى مسجل. لذلك ، لا يمكنك إنشاء مسجل منفصل ، ولكن استخدام سياق لامدا المقدم. للقيام بذلك ، يكفي الاتصال قبل الاستخدام:
LambdaLogger logger = context.getLogger();


النقطة الثانية هي الاحماء. نظرًا لأن إنشاء بيئة قابلة للتنفيذ لـ lambda أمر كسول ، فإن بدء تشغيل JVM ، وتحميل مسار الفصل وتنفيذ التعليمات البرمجية يستغرق بعض الوقت. قد تستغرق المكالمة الأولى عدة ثوانٍ ، وهذا ليس جيدًا إذا كتبت واجهات برمجة تطبيقات متزامنة مختلفة. في مثل هذه الحالات ، يمكنك أن تخبر AWS أنك بحاجة إلى إبقاء العديد من حالات لامدا في حالة تأهب. ولكن هذا يتطلب من شخص ما استدعاء لامدا. إذا فعلنا ذلك باستخدام الإصدار الأساسي من الشفرة ، فسنرسل في الواقع رسالة ونكتب بعض البيانات غير الدقيقة إلى قاعدة البيانات.
لتجنب ذلك ، يمكننا إضافة نوع من معالجة الطلب لتمييز الطلب الحقيقي عن طلب الإحماء. على سبيل المثال ، يمكننا إضافة رأس خاص للطلب. في حالتنا ، سيتم استخدام رأس "X-WARM-UP" بأي قيمة - لكي نفهم أن هذا طلب إحماء ونحتاج فقط إلى إرجاع نوع من الاستجابة دون تنفيذ منطق الأعمال.

آخر شيء أود أن ألفت الانتباه إليه هو استخدام المتغيرات الثابتة. هذا لا يجعل حالة لامدا ذات الحالة في حالتنا ، فهو ببساطة يسمح بإعادة استخدام الأشياء الموجودة والمهيأة. إذا تغيرت الكائنات التي تريد تهيئتها بشكل ثابت أثناء تنفيذ التعليمات البرمجية ، فحاول مرة أخرى التفكير فيما إذا كان هذا سيتداخل مع عمل مكالمات لامدا اللاحقة. أيضًا ، إذا كانت هذه الكائنات تستخدم متغيرات البيئة أثناء التهيئة ، فلا يجب أن تجعلها ثابتة.

4. نكتب الاختبارات


يمكن تغطية رمز لامدا بنفس أنواع الاختبارات التي تستخدمها في معظم الحالات. ولكن للحفاظ على نمو المقالة أكثر من ذلك ، سأغطي اختبار تطبيقات Serverless في المقالة التالية حول اختبار AWS Lambda والتطوير الداخلي. على الرغم من أن اختبارات الوحدة متاحة بالفعل في المستودع .

5. نكتب نموذجًا لموارد SAM


بعد أن كتبنا واختبرنا لامدا محليًا ، نحتاج إلى كتابة نموذج SAM لنشر جميع هذه الموارد في AWS.

قليلا عن SAM
Serverless Application Model – serverless . , CloudFormation DSL, – Lambda, Gateway, DynamoDB . .
SAM, CloudFormation, Terraform – IaC-. SAM Serverless-, Terraform .
Terraform DINS.

في الواقع ، دعونا نلقي نظرة على الموارد التي نحتاج إلى الإعلان عنها في قالب SAM .

AWS :: Serverless :: الوظيفة
يتم بالفعل ملء الميزات الرئيسية لنا إذا استخدمنا SAM init لإنشاء المشروع. ولكن هنا كيف تبدو في المثال الخاص بي:
CodeUri: contact-us-function
Handler: com.gralll.sam.App::handleRequest
Runtime: java8
MemorySize: 256

هنا أعتقد أن كل شيء واضح بما فيه الكفاية.
CodeUri هو الدليل الذي يحتوي على
Handler lambda - المسار الكامل إلى طريقة
Runtime - ما تتم كتابة
MemorySize lambda عليه - يتحدث عن نفسه

يتحدث عن الذاكرة : يتوفر ما يصل إلى 3 غيغابايت من ذاكرة الوصول العشوائي في lambda ، والتي تتوافق مع مورد من 2 وحدات المعالجة المركزية. أي أنه لا يمكنك ضبط وحدة المعالجة المركزية بشكل منفصل ، فقط زيادة / تقليل حجم الذاكرة.

الكتلة التالية مطلوبة لتحديد طريقة النشر.
AutoPublishAlias: live
DeploymentPreference:
  Type: Canary10Percent10Minutes

AutoPublishAlias - يسمح لك بإضافة اسم مستعار إلى كل إصدار جديد تم نشره . هذا ضروري لتنفيذ نشر الكناري.
Canary10Percent10Minutes - نوع من النشر سيسمح لك بحمل نسختين من لامدا في وقت واحد: القديم والجديد ، ولكن إعادة توجيه 10٪ فقط من حركة المرور إلى الإصدار الجديد. إذا لم تكن هناك مشاكل خلال عشر دقائق ، فسيتم أيضًا إعادة توجيه بقية حركة المرور إلى الإصدار الجديد.
يمكنك قراءة المزيد حول استخدام الميزات المتقدمة في صفحة SAM .

فيما يلي متغيرات البيئة التي سيتم استخدامها من التعليمات البرمجية. بعد ذلك ، كتلة أخرى لامدا:
Events:
  ContactUs:
    Type: Api
    Properties:
      Path: /contact
      Method: post

في ذلك ، يجب أن نصف دوافع استدعاء لامدا. في حالتنا ، ستكون هذه طلبات API Gateway .
هذا شكل مبسط إلى حد ما لوصف واجهة برمجة التطبيقات ، ولكن يكفي أن تقوم واجهة برمجة تطبيقات Gateway التي تم إنشاؤها حديثًا بإعادة توجيه جميع طلبات POST / الاتصال إلى لامدا الخاصة بنا.

بالطبع ، نحن بحاجة إلى وصف الجوانب الأمنية. خارج الصندوق ، لن تتمكن لامدا التي تم إنشاؤها من الوصول إلى قاعدة البيانات أو خدمة البريد الإلكتروني. لذلك نحن بحاجة إلى أن يصف بوضوح ما سيتم السماح به. هناك عدة طرق لمنح الوصول داخل AWS. سنستخدم السياسات القائمة على الموارد:
Policies:
  - AWSLambdaExecute
  - Version: '2012-10-17'
    Statement:
      - Effect: Allow
        Action:
          - ses:SendEmail
          - ses:SendRawEmail
        Resource: 'arn:aws:ses:eu-west-1:548476639829:identity/aleksandrgruzdev11@gmail.com'
      - Effect: Allow
        Action:
          - dynamodb:List*
        Resource: '*'
      - Effect: Allow
        Action:
          - dynamodb:Get*
          - dynamodb:PutItem
          - dynamodb:DescribeTable
        Resource: 'arn:aws:dynamodb:*:*:table/ContactUsTable'

يرجى ملاحظة أنه بالنسبة لجدول قاعدة البيانات ، حددنا اسمًا محددًا ، لذلك ، سنحتاج إلى إنشاء جدول بنفس الاسم.
بخصوص SES: ترى عنوان بريدي الإلكتروني. في حالتك ، يجب أن يكون عنوانك المؤكد. كيف تفعل ذلك ، انظر هنا .
مباشرة بعد ذلك ، يمكنك العثور على هوية الهوية لهذا المورد بالنقر فوق العنوان الذي تم إنشاؤه واستبداله بالبريد الإلكتروني في المثال أعلاه.
لقد اكتشفوا نوعًا ما لامدا. الآن دعنا ننتقل إلى قاعدة البيانات.

AWS :: Serverless :: SimpleTable لمهامنا ، سننشئ
جدول ContactUsTable واحدًا فقط:
ContactUsTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: Id
      Type: String
    TableName: ContactUsTable
    ProvisionedThroughput:
      ReadCapacityUnits: 2
      WriteCapacityUnits: 2

من الحقول المطلوبة - معرف فقط ، وتشير أيضًا إلى ReadCapacityUnits و WriteCapacityUnits. لن نتطرق بالتفصيل إلى القيم التي نختارها ، لأن هذا أيضًا موضوع موسع إلى حد ما. يمكنك قراءتها هنا . بالنسبة لتطبيق الاختبار ، تكون القيم الصغيرة من ترتيب 1-2 كافية أيضًا. يمكن أن تؤخذ المعلمات العامة

Globals
في هذه الكتلة إذا قمت ، على سبيل المثال ، بالإعلان عن العديد من الموارد من النوع Function أو API.
Globals:
  Function:
    Timeout: 15
  Api:
    Cors:
      AllowOrigin: "'*'"
      AllowHeaders: "'Content-Type,X-WARM-UP,X-Amz-Date,Authorization,X-Api-Key'"

استخدمته لتعيين مهلة للوظيفة وبعض إعدادات Cors لاستدعاء Gateway API لاحقًا من صفحتي الثابتة مع نموذج ContactUs.

المخرجات
تسمح لك هذه الكتلة بتعريف بعض المتغيرات ديناميكيًا في سياق AWS CloudFormation العالمي.
Outputs:
  ContactUsApi:
    Description: "API Gateway endpoint URL for Prod stage for ContactUs function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/contact/"
  ContactUsFunction:
    Description: "ContactUs Lambda Function ARN"
    Value: !GetAtt ContactUsFunction.Arn
  ContactUsFunctionIamRole:
    Description: "Implicit IAM Role created for ContactUs function"
    Value: !GetAtt ContactUsFunctionRole.Arn

على سبيل المثال ، أعلنا عن متغير ContactUsApi ، والذي سيتم تعيينه على قيمة ، كعنوان عام لنقطة نهاية واجهة برمجة التطبيقات التي تم إنشاؤها.
نظرًا لأننا نستخدم $ {ServerlessRestApi} ، فسيقوم AWS بإدراج المعرف الفريد لـ Gateway API الجديدة في السلسلة. ونتيجة لذلك ، سيتمكن أي تطبيق يمكنه الوصول إلى CloudFormation من الحصول على هذا العنوان - وبالتالي لا يمكنك ترميز عنوان URL لخدماتك. حسنًا ، هناك ميزة أخرى وهي أنه من الملائم جدًا رؤية قائمة المخرجات - بعض المعلومات الوصفية حول مجموعتك.
يمكن العثور على قائمة كاملة بالوظائف والمعلمات التي يمكنك استخدامها هنا .

المعلمات
بالإضافة إلى كل ما سبق ، يمكنك إضافة كتلة معلمات. ستساعد هذه الخيارات في جعل القالب أكثر تنوعًا. في أي كتلة أخرى من القالب ، يمكنك عمل مراجع لهذه المعلمات ، وبالتالي لا ترميز بعض القيم. على سبيل المثال ، في نموذجي يمكن أن يكون البريد الإلكتروني ، SES ARN ، مقدار الذاكرة ، إلخ.

هذا هو القالب بأكمله. لا أحد يمنع إضافة المزيد من الموارد القريبة ، على سبيل المثال ، مجموعة S3 ، أو أي مورد CloudFormation آخر ، أو مورد مخصص بشكل عام .

6. نشرع في النشر


من أجل تجميع مشروعنا ، لن نستخدم Gradle ، ولكن SAM.
المجسم
F:\aws\projects\contact-us-sam-app>sam build
Building resource 'ContactUsFunction'
Running JavaGradleWorkflow:GradleBuild
Running JavaGradleWorkflow:CopyArtifacts

Build Succeeded

Built Artifacts  : .aws-sam\build
Built Template   : .aws-sam\build\template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy –guided


يؤدي تشغيل الأمر sam build من جذر المشروع إلى جمع كل الملفات الضرورية تلقائيًا في مجلد .aws-sam : الفئات والاعتماديات ونموذج SAM.
بعد ذلك ، تحتاج إلى إنشاء مجموعة S3 ، حيث تقوم SAM لاحقًا بتحميل جميع القطع الأثرية التي تم جمعها.
يمكن القيام بذلك إما من خلال وحدة تحكم AWS المستندة إلى المستعرض ، أو باستخدام الأمر
aws s3 mb s3://bucket-name

ملاحظة: يتم إنشاء جميع المجموعات في سياق عالمي ، ويتم تفحصها بين جميع الحسابات. لذلك لا يمكنك إنشاء مجموعة إذا كان شخص ما قد أنشأها بالفعل في حسابك.

عندما يكون الدلو جاهزًا ، نفّذ الأمر:
sam package --output-template-file packaged.yaml --s3-bucket <YOUR_BACKET>

نتيجة الحزمة
F:\aws\projects\contact-us-sam-app>sam package --output-template-file packaged.yaml --s3-bucket contact-us-sam-app
Uploading to ea0c122c06a50d9676fbf9000a80a3bf  9212768 / 9212768.0  (100.00%)

Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
sam deploy --template-file F:\aws\projects\contact-us-sam-app\packaged.yaml --stack-name <YOUR STACK NAME>


أشرت إلى مجموعة جهات الاتصال الخاصة بنا ، وقمت SAM بتحميل جميع الموارد إلى الموقع المحدد. بعد ذلك ، يخبرك SAM بالفعل بأمر لإنشاء مجموعة من الموارد ، وبالتالي تضمين قرارك. نقوم بتنفيذ الأمر ، بعد الانتهاء قليلاً:
sam deploy --template-file packaged.yaml --region eu-west-1 --capabilities CAPABILITY_IAM --stack-name contact-us-sam-app

كما ترون ، أضفت - قدرات CAPABILITY_IAM . سيؤدي ذلك إلى تمكين CloudFormation لإنشاء موارد IAM. خلاف ذلك ، سوف تتلقى خطأ InsufficientCapabilities عند إنشاء.
فيما يلي سجل عمل هذا الأمر (الصورة قابلة للنقر). أصبحت التفاصيل مثل حالة كل مورد وقيم المخرجات متاحة فقط في أحد أحدث إصدارات SAM CLI.


7. تحقق من حالة CloudFormation


يمكننا الانتظار حتى اكتمال النشر في وحدة التحكم حتى تظهر رسالة توضح أن المكدس قد تم نشره (سجلات أمر النشر في الفقرة السابقة):
Successfully created/updated stack - contact-us-sam-app in eu-west-1

ولكن في حالتنا ، لن ترى هذه الرسالة إلا بعد عشر دقائق بسبب وضع نشر الكناري. لذلك ، من السهل فتح وحدة تحكم المتصفح ومشاهدة المكدس هناك.

بعد مرور بعض الوقت ، ستتغير الحالة إلى CREATE_COMPLETE ، مما يعني إكمالها بنجاح.
في علامة التبويب الأحداث ، يمكنك رؤية حالة جميع الموارد. إذا كان المكدس الخاص بك يتقلب ، يمكنك العثور على رسائل خطأ تفصيلية.
على سبيل المثال ، هذا: UPDATE_FAILED - إذا قمت بتكوين Gateway API بشكل غير صحيح في القالب.


في علامة التبويب "الموارد" ، يمكنك العثور على جميع الموارد التي تم إنشاؤها. لا تفاجأ بكمياتها. على الرغم من أننا أعلنا فقط عن وظيفة وجدول قاعدة بيانات في قالب SAM ، فقد قامت CloudFormation بإنشاء العديد من الموارد الأخرى لنا. إذا نظرت إلى النوع الخاص بهم ، يمكنك فهم الخدمة التي ينتمون إليها.
بالنسبة إلى Api Gateway ، تم إنشاؤها ضمنيًا:
  • لا توجد خوادم
  • ServerlessRestApiDeployment
  • ServerlessRestApiProdStage

أيضا لامدا تم إنشاء العديد من الأشياء الإضافية.
افتح المخرجات الآن وابحث عن عنوان URL لواجهة برمجة التطبيقات الخاصة بنا. انسخه ، سيكون مفيدًا قريبًا.

8. نموذج أتصال HTML


كما تذكر ، قررت إنشاء نموذج ContactUs ، والآن نحن بحاجة إلى جعله متاحًا بطريقة أو بأخرى ليس فقط على الجهاز المحلي.

التكوين
بالنسبة للنموذج نفسه ، على سبيل المثال ، قررت أخذ أبسط نموذج HTML وإضافة مكالمات Gateway API عبر ajax.

بالإضافة إلى النموذج نفسه ، أضفت زوجين من الأزرار لتصحيح الأخطاء وتبسيط الحشو:
تعيين الافتراضي - يستبدل ، كما هو متوقع ، المعلمات الافتراضية المحددة داخل HTML.
$("#url-input").val("https://4ykskscuq0.execute-api.eu-west-1.amazonaws.com/Prod/contact");
$("#name-input").val("Mike");
$("#email-input").val("Mike@somemail.com");
$("#phone-input").val("+79999999999");
$("#description-input").val("How much does it cost?");

إذا كنت تنوي استخدام هذه الوظيفة ، فقم بتغيير إدخال url إلى المسار إلى Gateway API ، الذي نسخته من Outputs .

الاستضافة
  • إنشاء دلو S3 جديد.
  • نقوم بتحميل ملف HTML في المجموعة عن طريق تحديد خيار جعل الملف عامًا.
  • نذهب إلى الدلو حيث قمنا بتحميل الملف.
  • انتقل إلى Properties (خصائص) ، ثم شغّل استضافة موقع Static على الويب وشاهد نقطة النهاية الجديدة المتاحة للجميع.


9. فحص الصحة


طلب قياسي
الآن يمكنك اتباع الرابط إلى النموذج الخاص بك ، انقر فوق تعيين الافتراضي وإرسال.
وبهذه الطريقة ستقدم طلبًا يمر عبر Gateway API إلى AWS Lambda.

إذا قمت بتكوين كل شيء بشكل صحيح بعد فترة ، فستتلقى رسالة مثل:
ناجح: تم إرسال الرسالة 0102016f28b06243-ae897a1e-b805-406b-9987-019f21547682-000000 بنجاح.

هذا يعني أنه تم تسليم الرسالة بنجاح. تحقق من صندوق البريد الخاص بك الذي حددته في قالب SAM. إذا لم تقم بتغيير القالب ، فستكون الرسالة بهذا التنسيق:

يمكنك أيضًا فتح DynamoDB والتأكد من ظهور إدخال جديد.

ميزات البداية الباردة
أعتقد أنك لاحظت أن الرسالة حول الإرسال الناجح جاءت بعد وقت طويل إلى حد ما. ويرجع ذلك إلى حقيقة أن خدمة AWS Lambda ، بعد أن تلقت طلبًا للمعالجة ، بدأت في رفع مثيل تطبيق Java الخاص بك ، وهذا يشمل رفع الحاوية مع نظام التشغيل و JRE ، وتحميل مسار الفصل ، وتهيئة جميع المتغيرات ، وبعد ذلك فقط بداية المقبض Request ( ) . هذا يسمى بداية باردة.

حاول ملء النموذج وإرساله مرة أخرى. هذه المرة جاء الجواب على الفور تقريبا ، أليس كذلك؟ إذا انقضت أكثر من 20-30 دقيقة بين الطلبين الأول والثاني ، فقد تختلف النتيجة.
ما سبب ذلك؟ ومع حقيقة أن مخابئ AWS Lambda استخدمت بالفعل حاويات مع lambdas لإعادة استخدامها. هذا يقلل من وقت التهيئة للسياق بأكمله لبدء الطريقة.
لا يوجد ارتباط واضح لطول مدة تخزين lambdas اعتمادًا على أي شيء ، ولكن بعض الناس قرروا تجريبيًا أن هذا يعتمد بشكل مباشر على القيمة المختارة لذاكرة الوصول العشوائي. أي أن لامدا بذاكرة 128 ميجا بايت ستكون متاحة لفترة أطول من 3 جيجا بايت. ربما هناك معلمات أخرى ، على سبيل المثال ، متوسط ​​الحمل على المنطقة التي يتم فيها تنفيذ لامداس ، ولكن هذا غير دقيق.
لذلك ، جرب نفسك وخطط وقت التخزين المؤقت إذا كنت تستخدم طلبات متزامنة.

الاحماء
كخيار إضافي ، يمكنك استخدام التدفئة لامدا. في الكود ، أضفت فحص رأس X-WAMP-UP . إذا كان هناك واحد ، فإن لامدا ترجع ببساطة استجابة دون تنفيذ أي منطق تجاري ، ولكن الحاوية ستكون جاهزة للمكالمات اللاحقة.
يمكنك استدعاء لامدا بنفسك ، على سبيل المثال ، بواسطة مؤقت التاج ، باستخدام CloudWatch. سيساعدك ذلك إذا كنت لا تريد أن ينضم عملاؤك إلى Cold Start.
في نموذج HTML ، يمكنك العثور على زر وضع WarmUp ، الذي يضيف هذا الرأس الخاص إلى الطلب. يمكنك التحقق من عدم إرسال أي رسالة أو الكتابة إلى قاعدة البيانات ، ولكن الرد من لامدا يصل ، وأن المكالمة اللاحقة للطلب الحقيقي لن تستغرق الكثير من الوقت.

تلخيص


خلال المقالة ، مررنا بجميع المراحل الرئيسية من تصميم التطبيق إلى إصداره في ما يسمى بالإنتاج .
آمل أن يتمكن الأشخاص الذين سمعوا بالفعل عن Serverless و AWS Lambda ، لكن لم يكن لديهم خبرة عملية ، من استخدام هذا الدليل وأن يشعروا بأن هذا يعطي مزايا كبيرة في سرعة نشر بعض حلول البرامج وليس ذلك فقط.

الفوائد
بالنسبة لي ، لقد حددت أهم المزايا:
  • Free Tier , , .
  • 1-2 . /.
  • , , .
  • Serverless . , — . SAM AWS. AWS , , .
  • Lambda , .
  • Serverless , , . ELK/Promethes/Grafana .
  • , API. API Key, IAM , .
  • حسنًا ، ربما يكون الشيء الأساسي هو Serverless. ليست هناك حاجة للتفكير في مثيل EC2 الذي تحتاج إلى الدفع مقابله ، وكيفية تكوين الوصول إليه ، وكيفية تكوين التنبيهات في حالة تعطل التطبيق ، وتكوين التحجيم التلقائي ، وما إلى ذلك.


العيوب
للأسف ، هذا لا يعني أنه يمكنك الآن استخدام هذا الحل واستخدامه كجزء من نظام المؤسسة بدلاً من الخدمات الدقيقة المفضلة لديك في الكأس. في كثير من الأحيان ، تحتاج إلى التعمق أكثر لتحديد حل أكثر ملاءمة في حالة معينة.
ولدي أيضًا تعليقات يجب الانتباه إليها عند اختيار لامدا:
  • بداية باردة. له أهمية خاصة عند استخدام الاستعلامات المتزامنة ولغات مثل Java أو C #. يتم حل المشكلة بشكل معتدل عن طريق الاحماء ، ولكن من الأفضل التفكير في مثل هذا الحل مقدمًا ومقارنة التكلفة والفوائد المحتملة
  • . 3GB , 2 , , - Fargate, , .
  • API, , .
  • / 1-2 Serverless, .
  • إذا كان لديك بالفعل بنية أساسية لتطوير الخدمات المصغرة و CI / CD في Coober ، فسيكون من الصعب مرة أخرى القول (خاصة للمديرين) بالحاجة إلى دعم عملية CI / CD أخرى.
  • حسنا ، حيث بدون اختبار. ومن الصعب جدًا اختبار لامدا من حيث الإنتاجية ، لأنه لا يزال مختلفًا عن اختبار الأداء العادي ، ويجب أخذ العديد من العوامل في الاعتبار.


الاستخدام
بشكل عام ، يتناسب استخدام AWS Lambda والخدمات الأخرى من دون خادم بشكل جيد مع مبادئ عدم التزامن والتطوير القائم على الأحداث. ومن خلال استخدام المعالجة غير المتزامنة ، يمكن تحقيق النتائج المثلى من حيث الأداء والتكلفة. فيما يلي قائمة بالحلول التي تلعب فيها حلول Serveress دورًا مهمًا:


ملاحظة


نظرًا لأن المقالة اتضح أنها واسعة النطاق للغاية ، ولا أريد تضخيمها أكثر من ذلك ، فمن المرجح أن أجهز استمرارًا سأعرض فيه كيفية إجراء التطوير بكفاءة أكبر باستخدام جميع ميزات وحدة تحكم AWS وإطار SAM وحتى IntelljIDEA. حسنًا ، بما أنني حذفت جزء الاختبار ، فسأحاول وصف هذا الجانب من التطوير بمزيد من التفاصيل. أيضًا ، إذا كانت لديك أي رغبات ، فما الذي تريد إضافته إلى المقالة التالية ، أو الأسئلة ، فلا تتردد في الكتابة في التعليقات أو في الرسائل الخاصة.

تمت الإضافة: AWS Lambda in Action. الجزء 2

بعض الروابط الهامة والمفيدة من المقالة:

Source: https://habr.com/ru/post/undefined/


All Articles