AWS Lambda in Aktion in Java 11. Serverlos in die Produktion gehen

Dieser Artikel enthält anhand eines einfachen Beispiels eine Anleitung, wie Sie AWS Lambda schnell und problemlos verwenden können. Geeignet sowohl für Entwickler, die überhaupt nicht mit Lambda zusammengearbeitet haben, als auch für Entwickler, die Cloud kennen, um eine andere Vision für die Entwicklung serverloser Anwendungen zu evaluieren.

Bild


Einführung


Hallo alle zusammen.
Mein Name ist Alexander Gruzdev, ich bin Java Team Lead bei DINS. Seit mehr als zwei Jahren arbeite ich eng mit der AWS-Infrastruktur zusammen und habe Erfahrung sowohl beim Schreiben von Anwendungen für AWS als auch beim Bereitstellen dieser Anwendungen. Bei meiner Arbeit musste ich ElasticBeanstalk, ECS, Fargate, EKS und natürlich AWS Lambda verwenden.

Jeder dieser Dienste ist auf seine Weise gut, und im Rahmen dieses Artikels werde ich nicht dringend darauf drängen, nur AWS Lambda als Ersatz für alle anderen Optionen zu verwenden. Ich möchte nur zeigen, wie einfach es ist, Lambda zu verwenden, und in welchen Fällen Sie die Vorteile der Verwendung von Lambda erhalten.

Jeder, vom Entwickler bis zum Manager, möchte einen klaren Prozess für die Bereitstellung von Änderungen für Kunden. Je transparenter dieser Weg und je weniger Aufwand erforderlich ist, desto rentabler. In den meisten Fällen möchten Entwickler und Tester nicht wissen, wo die Anwendung bereitgestellt wird, welche Hardware ausgewählt werden soll, in welcher Region Sie Replikate ablegen möchten usw. In der folgenden Abbildung sehen Sie die Serviceklasse „aaS“ (als Service), wobei AWS Lambda die FaaS-Kategorie darstellt.

Bild

FaaS auf den Punkt gebracht
FaaS , . , , CRUD C, R, U, D Create Read. , — .


Idee


Um zu demonstrieren, was die Entwicklung einer serverlosen Lösung ist, habe ich mich für ein eher banales Beispiel entschieden. Ich denke, Sie haben auf vielen Websites ein Kontaktformular für Kontakte gesehen, auf dem Sie Ihre E-Mails oder Ihr Telefon hinterlassen und eine Frage stellen können, die später beantwortet werden soll. Der Bildschirm unten, das Formular ist nicht super, aber das Thema dieses Artikels ist nicht Materialdesign.


Wenn Sie dieses Formular ausfüllen, wartet ein potenzieller Kunde auf einen Anruf oder einen Brief von Ihnen.
Von der technischen Seite benötigen Sie:
  • Daten aus dem Formular in der Datenbank speichern;
  • Senden Sie einen Brief an einen Agenten, der die Frage beantwortet.
  • Schreiben Sie möglicherweise einen anderen Dienst, bei dem der Agent die auf der Grundlage derselben Datenbank geleistete Arbeit markiert (nicht im Umfang des Artikels enthalten).


Die Umsetzung der Idee ist auf GitHub verfügbar .

Die Architektur


Das erste, was uns bei der Implementierung einfällt, ist das, was wir brauchen:
  • Maschine unter hinten, vorne, Bilder usw.,
  • Auto mit Basis
  • Maschine mit E-Mail-Service.




Die Rückseite und die Basis müssen auf eine gute Weise repliziert werden, aber nehmen wir an, wir tun dies für unseren Mini-Store und die Belastung dieses Formulars ist minimal. Daher können alle diese Maschinen zu einer zusammengefasst und in einem einzigen Satz bereitgestellt werden.
Da wir jedoch Serverless in Betracht ziehen, bauen wir die Architektur vollständig auf serverlosen Komponenten auf.
Egal wie vielversprechend Serverless auch klingen mag, wir alle verstehen, dass die Server immer noch hier sind, sie sind einfach aus Ihrem Sichtfeld verschwunden. Und jetzt, wo die Cloud funktioniert, können Sie die Aufgaben erledigen, die Sie selbst erledigen können. Versuchen wir jedoch, nur solche serverlosen Komponenten zu verwenden:


Das Diagramm zeigt eine sehr klare Trennung der Funktionalität jeder Komponente.
  • AWS Lambda ist die Hauptkomponente, die Code / Logik enthält.
  • S3 ist für das Speichern statischer Ressourcen und JS-Skripte verantwortlich.
  • CloudFront - für Caching-Mechanismen und multiregionale Unterstützung,
  • SES - E-Mail-Service,
  • DynamoDB - die Basis zum Speichern von Daten aus dem Formular (von wem, welcher Frage, wohin die Antwort gesendet werden soll),
  • Gateway API - HTTP API für unser Lambda,
  • Route53 - wird benötigt, wenn Sie einen schönen Domainnamen hinzufügen möchten.


Nicht alle dieser Komponenten werden in unserer nächsten Anleitung verwendet, um den Artikel nicht zu dehnen.
Route53 und CloudFront sind ziemlich einfache Komponenten, über die Sie separat lesen können.
Ein kleiner Spoiler, der uns eine solche Lösung gibt:
  • Wir entfernen uns von der Unterstützung von EC2-Maschinen, kein Debuggen über ssh,
  • Einfache Konfiguration: Konfigurieren Sie Drosselung / Caching / Zugriffe mit einem Klick.
  • Unterstützt Zugriffsrichtlinien: Rechte einschränken und Zugriff gewähren,
  • Protokollierung / Überwachung ab Werk,
  • Zahlen Sie nur für verbrauchte Ressourcen / Anfragen.


Demo


Ausbildung


Um mit der Entwicklung unserer Serverless-Lösung beginnen zu können, müssen Sie die folgenden Anforderungen erfüllen:


Nach der Installation aller oben genannten Tools müssen Sie Ihre aws-cli für den Remotezugriff auf AWS konfigurieren. Folgen Sie den Anweisungen . Dazu muss ein neuer Benutzer erstellt und sein Zugriffsschlüssel und sein geheimer Schlüssel entladen werden.

Projektmontage


1. Erstellen Sie ein Projekt aus einer Vorlage


Öffnen Sie das Verzeichnis für das zukünftige Projekt und starten Sie von dort aus die SAM CLI. Befolgen Sie die Anweisungen:


Hinweis: Abhängig von der SAM-CLI-Version können die Befehle geringfügig abweichen, sie bleiben jedoch alle intuitiv. Wählen Sie einfach diejenigen aus, die den oben verwendeten am ähnlichsten sind. Sie können auch ein anderes Build-Tool auswählen, wenn Gradle nicht für Sie geeignet ist.

Das Projekt "Hallo Welt!" bereit. Jetzt können Sie den Namen des Projekts und der Pakete, Abhängigkeiten und den Quellcode bearbeiten.

2. Beschäftigen wir uns mit Sucht


Fügen Sie build.gradle die folgenden Abhängigkeiten hinzu:
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'
}

Die wichtigsten sind das AWS SDK. Mit ihnen können Sie mit bestimmten Diensten wie SES, DynamoDB usw. arbeiten.

3. Wir schreiben ein Lambda


  • Wir ändern die Vorlagenanforderungs- und Antwortklassen für RequestHandler in AwsProxyRequest und 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()));
    }
    



Die grundlegende Logik ist recht einfach, daher denke ich nicht, dass es sich lohnt, sie im Detail zu beschreiben. Es gibt einige Punkte, die es wert sind, beachtet zu werden.

Der erste ist die Protokollierung. Eines der Argumente der Context-Lambda-Methode enthält viele Zusatzinformationen sowie einen Logger. Daher können Sie keinen separaten Logger erstellen, sondern den bereitgestellten Lambda-Kontext verwenden. Dazu reicht es aus, vor der Verwendung aufzurufen:
LambdaLogger logger = context.getLogger();


Der zweite Punkt ist das Aufwärmen. Da das Erstellen einer ausführbaren Umgebung für ein Lambda verzögert ist, dauert das Starten der JVM, das Laden des Klassenpfads und das Ausführen des Codes einige Zeit. Der erste Aufruf kann einige Sekunden dauern, was nicht gut ist, wenn Sie verschiedene synchrone APIs schreiben. In solchen Fällen können Sie AWS selbst mitteilen, dass Sie mehrere Lambda-Instanzen in Alarmbereitschaft halten müssen. Dies erfordert jedoch, dass jemand ein Lambda anruft. Wenn wir dies mit der Basisversion des Codes tun, senden wir tatsächlich einen Brief und schreiben einige ungenaue Daten in die Datenbank.
Um dies zu vermeiden, können wir eine Art Anforderungsverarbeitung hinzufügen, um die tatsächliche Anforderung von der Aufwärmanforderung zu unterscheiden. Zum Beispiel können wir der Anfrage einen speziellen Header hinzufügen. In unserem Fall wird der Header "X-WARM-UP" mit einem beliebigen Wert verwendet - um zu verstehen, dass dies eine Aufwärmanforderung ist und wir nur eine Antwort zurückgeben müssen, ohne die Geschäftslogik auszuführen.

Das Letzte, worauf ich aufmerksam machen möchte, ist die Verwendung statischer Variablen. Dies macht in unserem Fall nicht die Stateful-Lambda-Instanz, sondern ermöglicht lediglich die Wiederverwendung vorhandener und initialisierter Objekte. Wenn sich die Objekte, die Sie statisch initialisieren möchten, während der Ausführung des Codes ändern, versuchen Sie erneut zu überlegen, ob dies den Betrieb nachfolgender Aufrufe des Lambda beeinträchtigt. Wenn diese Objekte während der Initialisierung Umgebungsvariablen verwenden, sollten Sie sie nicht statisch machen.

4. Wir schreiben Tests


Lambda-Code kann durch dieselben Testarten abgedeckt werden, die Sie in den meisten Fällen verwenden. Um zu verhindern, dass der Artikel noch weiter wächst, werde ich im nächsten Artikel über das Testen von AWS Lambda und die lokale Entwicklung das Testen von Anwendungen ohne Server behandeln. Obwohl Unit-Tests bereits im Repository verfügbar sind .

5. Wir schreiben eine Vorlage für SAM-Ressourcen


Nachdem wir unser Lambda lokal geschrieben und getestet haben, müssen wir eine SAM-Vorlage schreiben, um alle diese Ressourcen in AWS bereitzustellen.

Ein bisschen über SAM
Serverless Application Model – serverless . , CloudFormation DSL, – Lambda, Gateway, DynamoDB . .
SAM, CloudFormation, Terraform – IaC-. SAM Serverless-, Terraform .
Terraform DINS.

Schauen wir uns an, welche Ressourcen wir in der SAM-Vorlage deklarieren müssen .

AWS :: Serverless :: Function
Die Hauptfunktionen sind für uns bereits ausgefüllt, wenn wir SAM init zum Erstellen des Projekts verwendet haben. Aber so sehen sie in meinem Beispiel aus:
CodeUri: contact-us-function
Handler: com.gralll.sam.App::handleRequest
Runtime: java8
MemorySize: 256

Hier denke ich, dass alles klar genug ist.
CodeUri ist das Verzeichnis mit unserem
Handler- Lambda - der vollständige Pfad zur
Runtime- Methode - auf dem das
MemorySize- Lambda geschrieben ist - spricht für sich.

Apropos Speicher : Im Lambda stehen bis zu 3 GB RAM zur Verfügung, was einer Ressource von 2 CPUs entspricht. Das heißt, Sie können die CPU nicht separat einstellen, sondern nur die Speichermenge erhöhen / verringern.

Der nächste Block wird benötigt, um die Bereitstellungsmethode auszuwählen.
AutoPublishAlias: live
DeploymentPreference:
  Type: Canary10Percent10Minutes

AutoPublishAlias - Ermöglicht das Hinzufügen eines Alias zu jeder neu bereitgestellten Version . Dies ist erforderlich, um die kanarische Bereitstellung zu implementieren.
Canary10Percent10Minutes - eine Art der Bereitstellung, mit der Sie zwei Versionen des Lambda gleichzeitig speichern können : die alte und die neue, aber nur 10% des Datenverkehrs auf die neue umleiten. Wenn in zehn Minuten keine Probleme auftreten, wird auch der Rest des Datenverkehrs auf die neue Version umgeleitet.
Weitere Informationen zur Verwendung erweiterter Funktionen finden Sie auf der SAM-Seite .

Als nächstes folgen die Umgebungsvariablen, die aus dem Code verwendet werden. Danach ein weiterer Block für Lambda:
Events:
  ContactUs:
    Type: Api
    Properties:
      Path: /contact
      Method: post

Darin müssen wir die Auslöser für das Aufrufen eines Lambda beschreiben. In unserem Fall handelt es sich um API-Gateway-Anforderungen .
Dies ist eine ziemlich vereinfachte Form der Beschreibung der API, aber es reicht aus, wenn die neu erstellte Gateway-API alle POST- / Kontaktanforderungen an unser Lambda umleitet .

Natürlich müssen wir die Sicherheitsaspekte beschreiben. Das erstellte Lambda hat standardmäßig keinen Zugriff auf die Datenbank oder den E-Mail-Dienst. Wir müssen also explizit vorschreiben, was erlaubt ist. Es gibt verschiedene Möglichkeiten , innerhalb von AWS Zugriff zu gewähren. Wir werden ressourcenbasierte Richtlinien verwenden:
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'

Bitte beachten Sie, dass wir für die Datenbanktabelle einen bestimmten Namen angegeben haben. Daher müssen wir eine Tabelle mit demselben Namen erstellen.
In Bezug auf SES: Sie sehen meine E-Mail-Adresse. In Ihrem Fall muss es Ihre eigene bestätigte Adresse sein. Wie das geht, erfahren Sie hier .
Gleich danach können Sie den Identitäts-ARN dieser Ressource finden, indem Sie auf die erstellte Adresse klicken und diese im obigen Beispiel durch die E-Mail ersetzen.
Sie haben das Lambda irgendwie herausgefunden. Fahren wir nun mit der Datenbank fort.

AWS :: Serverless :: SimpleTable
Für unsere Aufgaben erstellen wir nur eine ContactUsTable- Tabelle:
ContactUsTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: Id
      Type: String
    TableName: ContactUsTable
    ProvisionedThroughput:
      ReadCapacityUnits: 2
      WriteCapacityUnits: 2

Von den erforderlichen Feldern - nur ID und außerdem ReadCapacityUnits und WriteCapacityUnits. Wir werden nicht im Detail darauf eingehen, welche Werte zu wählen sind, da dies auch ein ziemlich umfangreiches Thema ist. Sie können es hier lesen . Für eine Testanwendung sind auch kleine Werte in der Größenordnung von 1-2 ausreichend.

Globale
Allgemeine Parameter können in diesem Block entfernt werden, wenn Sie beispielsweise mehrere Ressourcen vom Typ Funktion oder API deklarieren.
Globals:
  Function:
    Timeout: 15
  Api:
    Cors:
      AllowOrigin: "'*'"
      AllowHeaders: "'Content-Type,X-WARM-UP,X-Amz-Date,Authorization,X-Api-Key'"

Ich habe damit ein Zeitlimit für die Funktion und einige Cors-Einstellungen festgelegt, um die Gateway-API später von meiner statischen Seite mit dem ContactUs-Formular aufzurufen.

Ausgaben Mit
diesem Block können Sie einige Variablen im globalen AWS CloudFormation-Kontext dynamisch definieren.
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

Beispielsweise haben wir eine ContactUsApi-Variable deklariert, die auf einen Wert als öffentliche Adresse unseres erstellten API-Endpunkts festgelegt wird.
Da wir $ {ServerlessRestApi} verwenden , fügt AWS die eindeutige Kennung unserer neuen Gateway-API in die Zeichenfolge ein. Infolgedessen kann jede Anwendung, die Zugriff auf CloudFormation hat, diese Adresse erhalten. Dadurch können Sie die URL Ihrer Dienste nicht fest codieren. Ein weiteres Plus ist, dass es sehr praktisch ist, die Liste der Ausgaben anzuzeigen - einige Metainformationen zu Ihrem Stapel.
Eine vollständige Liste der Funktionen und welche Parameter Sie verwenden können, finden Sie hier .

Parameter
Zusätzlich zu allen oben genannten Funktionen können Sie einen Parameterblock hinzufügen. Diese Optionen tragen dazu bei, die Vorlage vielseitiger zu gestalten. In jedem anderen Block der Vorlage können Sie auf diese Parameter verweisen und dabei einige Werte nicht fest codieren. In meiner Vorlage können es beispielsweise E-Mails, SES ARN, Speicherplatz usw. sein.

Das ist die ganze Vorlage. Niemand verbietet das Hinzufügen weiterer Ressourcen in der Nähe, z. B. eines S3-Buckets, einer anderen CloudFormation-Ressource oder einer benutzerdefinierten Ressource im Allgemeinen .

6. Wir fahren mit der Bereitstellung fort


Um unser Projekt zusammenzustellen, werden wir nicht Gradle, sondern SAM verwenden.
Versammlung
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


Wenn Sie den Befehl sam build im Stammverzeichnis des Projekts ausführen, werden automatisch alle erforderlichen Dateien im Ordner .aws-sam gesammelt : Klassen, Abhängigkeiten, SAM-Vorlage.
Als Nächstes müssen Sie einen S3-Bucket erstellen, in den SAM anschließend alle gesammelten Artefakte hochlädt.
Dies kann entweder über die browserbasierte AWS-Konsole oder mit dem Befehl erfolgen
aws s3 mb s3://bucket-name

Hinweis: Alle Buckets werden in einem globalen Kontext erstellt und zwischen allen Konten durchsucht. Sie können also keinen Bucket erstellen, wenn jemand ihn bereits in Ihrem Konto erstellt hat.

Wenn der Bucket bereit ist, führen Sie den folgenden Befehl aus:
sam package --output-template-file packaged.yaml --s3-bucket <YOUR_BACKET>

Paketergebnis
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>


Ich zeigte auf meinen Kontakt-uns-Sam-App-Bucket und SAM lud alle Ressourcen an den angegebenen Speicherort hoch. Als Nächstes weist SAM Sie bereits an, einen Stapel mit Ressourcen zu erstellen und damit Ihre Entscheidung einzubetten. Wir führen den Befehl aus, nachdem wir ein wenig abgeschlossen haben:
sam deploy --template-file packaged.yaml --region eu-west-1 --capabilities CAPABILITY_IAM --stack-name contact-us-sam-app

Wie Sie sehen können, habe ich --capabilities CAPABILITY_IAM hinzugefügt . Dadurch kann CloudFormation IAM-Ressourcen erstellen. Andernfalls erhalten Sie beim Erstellen einen InsufficientCapabilities-Fehler.
Das Folgende ist das Protokoll der Arbeit dieses Befehls (das Bild ist anklickbar). Details wie der Status jeder Ressource und die Werte der Ausgaben wurden nur in einer der neuesten Versionen von SAM CLI verfügbar.


7. Überprüfen Sie den Status von CloudFormation


Wir können warten, bis die Bereitstellung in der Konsole abgeschlossen ist, bis eine Meldung angezeigt wird, dass der Stapel bereitgestellt wurde (die Bereitstellungsbefehlsprotokolle im vorherigen Absatz):
Successfully created/updated stack - contact-us-sam-app in eu-west-1

In unserem Fall wird diese Meldung aufgrund des kanarischen Bereitstellungsmodus jedoch erst nach zehn Minuten angezeigt. Daher ist es einfacher, eine Browserkonsole zu öffnen und den Stapel dort anzusehen.

Nach einiger Zeit ändert sich der Status in CREATE_COMPLETE , was einen erfolgreichen Abschluss bedeutet.
Auf der Registerkarte Ereignisse können Sie den Status aller Ressourcen anzeigen. Wenn Ihr Stack floppt, finden Sie hier detaillierte Fehlermeldungen.
Beispiel: UPDATE_FAILED - Wenn Sie die Gateway-API in der Vorlage falsch konfiguriert haben.


Auf der Registerkarte Ressourcen finden Sie alle erstellten Ressourcen. Seien Sie nicht überrascht über ihre Menge. Obwohl wir in der SAM-Vorlage nur eine Funktion und eine Datenbanktabelle deklariert haben, hat CloudFormation viele andere Ressourcen für uns erstellt. Wenn Sie sich ihren Typ ansehen, können Sie verstehen, zu welchem ​​Dienst sie gehören.
Für Api Gateway wurde implizit Folgendes erstellt:
  • ServerlessRestApi
  • ServerlessRestApiDeployment
  • ServerlessRestApiProdStage

Auch für Lambda wurden mehrere zusätzliche Objekte erstellt.
Öffnen Sie nun Outputs und suchen Sie die URL unserer API. Kopieren Sie es, es wird bald nützlich sein.

8. HTML ContactUs-Formular


Wie Sie sich erinnern, habe ich beschlossen, ein ContactUs-Formular zu erstellen, und jetzt müssen wir es irgendwie nicht nur auf dem lokalen Computer verfügbar machen.

Konfiguration
Für das Formular selbst habe ich mich beispielsweise für das einfachste HTML-Formular entschieden und Gateway-API-Aufrufe über Ajax hinzugefügt.

Zusätzlich zum Formular selbst habe ich einige Schaltflächen zum Debuggen und Vereinfachen des Ausfüllens hinzugefügt:
Standard festlegen - Ersetzt erwartungsgemäß die in HTML angegebenen Standardparameter.
$("#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?");

Wenn Sie diese Funktionalität verwenden möchten, ändern Sie die URL-Eingabe in den Pfad zu Ihrer Gateway-API, den Sie aus den Ausgaben kopiert haben .

Hosting
  • Erstellen Sie einen neuen S3-Bucket.
  • Wir laden die HTML-Datei in den Bucket, indem wir die Option auswählen, die Datei öffentlich zu machen.
  • Wir gehen in den Eimer, in den wir die Datei hochgeladen haben.
  • Gehen Sie zu Eigenschaften, aktivieren Sie das Hosting der statischen Website und sehen Sie Ihren neuen öffentlich verfügbaren Endpunkt.


9. Gesundheitscheck


Standardanforderung
Jetzt können Sie dem Link zu Ihrem Formular folgen, auf Standard festlegen und Senden klicken.
Auf diese Weise stellen Sie eine Anfrage, die über die Gateway-API an AWS Lambda gesendet wird.

Wenn Sie nach einer Weile alles richtig konfiguriert haben, erhalten Sie eine Nachricht wie:
Erfolgreich: Die Nachricht 0102016f28b06243-ae897a1e-b805-406b-9987-019f21547682-000000 wurde erfolgreich gesendet.

Dies bedeutet, dass die Nachricht erfolgreich zugestellt wurde. Überprüfen Sie Ihr Postfach, das Sie in der SAM-Vorlage angegeben haben. Wenn Sie die Vorlage nicht geändert haben, hat der Brief das folgende Format:

Sie können auch DynamoDB öffnen und sicherstellen, dass ein neuer Eintrag angezeigt wurde.

Merkmale eines Kaltstarts
Ich denke, Sie haben bemerkt, dass die Nachricht über das erfolgreiche Senden nach ziemlich langer Zeit kam. Dies liegt an der Tatsache, dass der AWS Lambda-Dienst, nachdem er eine Anforderung zur Verarbeitung erhalten hat, die Instanz Ihrer Java-Anwendung ausgelöst hat. Dazu gehört das Aufheben des Containers mit dem Betriebssystem und der JRE, das Laden des Klassenpfads, das Initialisieren aller Variablen und erst danach der Start von handleRequest ( ) . Dies wird als Kaltstart bezeichnet.

Versuchen Sie erneut, das Formular auszufüllen und einzureichen. Diesmal kam die Antwort fast sofort, oder? Wenn zwischen der ersten und der zweiten Anforderung mehr als 20 bis 30 Minuten vergangen sind, kann das Ergebnis variieren.
Was ist der Grund dafür? Und mit der Tatsache, dass AWS Lambda-Caches bereits Container mit Lambdas für ihre Wiederverwendung verwendet haben. Dies reduziert die Initialisierungszeit des gesamten Kontexts zum Starten der Methode.
Es gibt keine eindeutige Korrelation dafür, wie lange Lambdas je nach Art zwischengespeichert werden, aber einige Leute haben experimentell festgestellt, dass dies direkt vom gewählten RAM-Wert abhängt. Das heißt, ein Lambda mit 128 MB Speicher ist länger verfügbar als mit 3 GB. Möglicherweise gibt es andere Parameter, z. B. die durchschnittliche Belastung der Region, in der Ihre Lambdas ausgeführt werden, aber dies ist ungenau.
Experimentieren Sie daher selbst und planen Sie die Caching-Zeit, wenn Sie synchrone Anforderungen verwenden.

Aufwärmen
Als zusätzliche Option können Sie die Lambda-Heizung verwenden. Im Code habe ich eine X-WAMP-UP-Header- Prüfung hinzugefügt . Wenn es eine gibt, gibt das Lambda einfach eine Antwort zurück, ohne eine Geschäftslogik auszuführen, aber der Container ist für nachfolgende Aufrufe bereit.
Sie können Ihr Lambda beispielsweise selbst über den Kronen-Timer mit CloudWatch aufrufen. Dies ist hilfreich, wenn Sie nicht möchten, dass Ihre Kunden Cold Start beitreten.
Im HTML-Formular finden Sie die Schaltfläche WarmUp-Modus, mit der der Anforderung dieser spezielle Header hinzugefügt wird. Sie können überprüfen, ob weder ein Brief gesendet noch in die Datenbank geschrieben wird, aber die Antwort vom Lambda eintrifft und der nachfolgende Aufruf der tatsächlichen Anfrage nicht viel Zeit in Anspruch nimmt.

Zusammenfassen


Während des Artikels haben wir alle Hauptphasen vom Anwendungsdesign bis zur Veröffentlichung in der sogenannten Produktion durchlaufen .
Ich hoffe, dass diejenigen Personen, die bereits von Serverless und AWS Lambda gehört haben, aber keine praktische Erfahrung haben, diesen Leitfaden verwenden können und das Gefühl haben, dass dies erhebliche Vorteile für die Geschwindigkeit der Bereitstellung einiger Softwarelösungen bietet und nicht nur das.

Vorteile
Für mich habe ich die wertvollsten Vorteile identifiziert:
  • Free Tier , , .
  • 1-2 . /.
  • , , .
  • Serverless . , — . SAM AWS. AWS , , .
  • Lambda , .
  • Serverless , , . ELK/Promethes/Grafana .
  • , API. API Key, IAM , .
  • Nun, wahrscheinlich ist das Grundlegendste Serverlos. Sie müssen nicht darüber nachdenken, für welche EC2-Instanz Sie bezahlen müssen, wie Sie den Zugriff darauf konfigurieren, wie Sie Warnungen im Falle eines Anwendungsabsturzes konfigurieren, die automatische Skalierung konfigurieren usw.


Nachteile
Leider bedeutet dies nicht, dass Sie diese Lösung jetzt als Teil eines Unternehmenssystems anstelle Ihrer bevorzugten Microservices im Cuber verwenden können. Sehr oft müssen Sie tiefer graben, um in einem bestimmten Fall eine geeignetere Lösung zu finden.
Und ich habe auch Kommentare, auf die Sie bei der Auswahl von Lambda achten sollten:
  • Kaltstart. Besonders wichtig bei Verwendung synchroner Abfragen und Sprachen wie Java oder C #. Das Problem wird durch Aufwärmen mäßig gelöst, aber es ist besser, im Voraus über eine solche Lösung nachzudenken und die Kosten und möglichen Vorteile zu vergleichen
  • . 3GB , 2 , , - Fargate, , .
  • API, , .
  • / 1-2 Serverless, .
  • Wenn Sie bereits über die Infrastruktur für die Entwicklung von Microservices und CI / CD in Coober verfügen, ist es erneut problematisch, (insbesondere für Manager) die Notwendigkeit zu argumentieren, einen anderen CI / CD-Prozess zu unterstützen.
  • Nun, wo ohne zu testen. Und das Lambda auf Durchsatz zu testen ist ziemlich schwierig, da es sich immer noch von den üblichen Leistungstests unterscheidet und viele Faktoren berücksichtigt werden müssen.


Verwendung
Im Allgemeinen passt die Verwendung von AWS Lambda und anderen serverlosen Diensten sehr gut zu den Prinzipien der asynchronen und ereignisgesteuerten Entwicklung. Durch die Verwendung der asynchronen Verarbeitung können optimale Ergebnisse sowohl hinsichtlich der Leistung als auch der Kosten erzielt werden. Hier ist eine Liste von Lösungen, bei denen Serveress-Lösungen eine wichtige Rolle spielen werden:


PS


Da sich der Artikel als ziemlich umfangreich herausstellte und ich ihn nicht noch mehr aufblasen möchte, werde ich höchstwahrscheinlich eine Fortsetzung vorbereiten, in der ich zeigen werde, wie die Entwicklung mit allen Funktionen der AWS-Konsole, des SAM-Frameworks und sogar von IntelljIDEA effizienter durchgeführt werden kann. Da ich den Testteil weggelassen habe, werde ich versuchen, diesen Aspekt der Entwicklung genauer zu beschreiben. Wenn Sie Wünsche haben, was Sie zum nächsten Artikel oder zu Fragen hinzufügen möchten, können Sie diese gerne in die Kommentare oder in private Nachrichten schreiben.

Hinzugefügt: AWS Lambda in Aktion. Teil 2

Einige wichtige und nützliche Links aus dem Artikel:

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


All Articles