AWS Lambda en action dans Java 11. Passer à la production sans serveur

Cet article est un guide sur la façon de commencer rapidement et sans douleur à utiliser AWS Lambda avec un exemple simple. Convient à la fois à un développeur qui n'a pas du tout travaillé avec Lambda et qui connaissait le Cloud pour évaluer une autre vision du développement d'applications sans serveur.

image


introduction


Bonjour à tous.
Je m'appelle Alexander Gruzdev, je suis chef d'équipe Java chez DINS. Depuis plus de deux ans, je travaille en étroite collaboration avec l'infrastructure AWS et j'ai de l'expérience à la fois dans l'écriture d'applications pour AWS et dans le déploiement de ces mêmes applications. Dans mon travail, j'ai dû utiliser ElasticBeanstalk, ECS, Fargate, EKS et, bien sûr, AWS Lambda.

Chacun de ces services est bon à sa manière, et dans le cadre de cet article, je ne recommanderai pas d'utiliser uniquement AWS Lambda en remplacement de toutes les autres options. Je veux juste montrer à quel point il est facile de commencer à utiliser Lambda, et dans quels cas vous en tirerez les avantages.

Tout le monde, des développeurs aux gestionnaires, veut un processus clair pour apporter des changements aux clients. Plus cette voie est transparente et moins d'efforts sont nécessaires, plus elle est rentable. Le plus souvent, les développeurs et les testeurs ne veulent pas savoir où l'application sera déployée, quel matériel choisir, dans quelle région vous souhaitez placer les répliques, etc. Dans le diagramme suivant, vous pouvez voir la classe de services «aaS» (en tant que service), où AWS Lambda représente la catégorie FaaS.

image

FaaS en bref
FaaS , . , , CRUD C, R, U, D Create Read. , — .


Idée


Pour démontrer ce qu'est le développement d'une solution sans serveur, j'ai décidé de prendre un exemple assez banal. Je pense que vous avez vu le formulaire de commentaires Contactez-nous sur de nombreux sites, où vous pouvez laisser votre courrier ou votre téléphone et poser une question à laquelle il sera répondu plus tard. L'écran ci-dessous, le formulaire n'est pas super, mais le sujet de cet article n'est pas la conception matérielle.


En remplissant ce formulaire, un client potentiel attendra un appel ou une lettre de votre part.
Du point de vue technique, vous avez besoin de:
  • enregistrer les données du formulaire dans la base de données;
  • envoyer une lettre à un agent qui répondra à la question;
  • peut-être écrire un autre service où l'agent marquera le travail effectué sur la base de la même base de données (non incluse dans la portée de l'article).


L'implémentation de l'idée est disponible sur GitHub .

Architecture


La première chose qui vient à l'esprit lors de la mise en œuvre est ce dont nous avons besoin:
  • machine sous le dos, le devant, les images, etc.,
  • voiture avec base
  • machine avec service de messagerie.




Le dos et la base, dans le bon sens, doivent être répliqués, mais supposons que nous le fassions pour notre mini-magasin et que la charge sur ce formulaire soit minime. Par conséquent, toutes ces machines peuvent être combinées en une seule et déployées en un seul ensemble.
Mais puisque nous envisageons Serverless, construisons l'architecture entièrement sur des composants sans serveur.
Peu importe à quel point Serverless peut sembler prometteur, nous comprenons tous que les serveurs sont toujours là, ils ont juste disparu de votre champ de vision. Et maintenant que le cloud fonctionne, vous pouvez faire les tâches que vous pouvez faire vous-même. Mais essayons d'utiliser uniquement ces composants sans serveur:


le diagramme montre une séparation très claire de la fonctionnalité de chaque composant.
  • AWS Lambda est le composant principal qui contient du code / de la logique,
  • S3 est responsable du stockage des ressources statiques et des scripts JS,
  • CloudFront - pour les mécanismes de mise en cache et le support multi-régional,
  • SES - service de messagerie,
  • DynamoDB - la base de stockage des données du formulaire (de qui, quelle question, où envoyer la réponse),
  • API de passerelle - API HTTP pour notre lambda,
  • Route53 - nécessaire si vous souhaitez ajouter un beau nom de domaine.


Tous ces composants ne seront pas utilisés dans notre prochain guide, juste pour ne pas étirer l'article.
Route53 et CloudFront sont des composants assez simples que vous pouvez lire séparément.
Un petit spoilers qui nous donnera une telle solution:
  • Nous nous éloignons de la prise en charge des machines EC2, pas de débogage via ssh,
  • Configuration facile: configurez la limitation / la mise en cache / les accès en un seul clic,
  • Prise en charge des politiques d'accès: restreindre les droits et donner accès,
  • Journalisation / surveillance hors de la boîte,
  • Payez uniquement pour les ressources / demandes utilisées.


Démo


Entraînement


Pour commencer à développer notre solution sans serveur, vous devez répondre aux exigences suivantes:


Après avoir installé tous les outils ci-dessus, vous devez configurer votre aws-cli pour un accès à distance à AWS. Suivez les instructions . Cela nécessitera la création d'un nouvel utilisateur et le déchargement de sa clé d'accès et de sa clé secrète.

Montage du projet


1. Créez un projet à partir d'un modèle


Ouvrez le répertoire du futur projet et lancez-le SAM CLI. Suivez les instructions:


Remarque: Selon la version de SAM-CLI, les commandes peuvent être légèrement différentes, mais elles restent toutes intuitives. Choisissez simplement les plus similaires à ceux qui ont été utilisés ci-dessus. Vous pouvez également choisir un outil de construction différent si Gradle ne vous convient pas.

Le projet "Hello, World!" prêt. Vous pouvez maintenant travailler sur le nom du projet et des packages, les dépendances et le code source.

2. Traitons les dépendances


Ajoutez les dépendances suivantes à 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'
}

Les principaux sont le kit SDK AWS. Ils vous permettront de travailler avec des services spécifiques, tels que SES, DynamoDB, etc.

3. Nous écrivons un lambda


  • Nous modifions les classes de demande et de réponse de modèle pour RequestHandler en AwsProxyRequest et 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()));
    }
    



La logique de base est assez simple, donc je ne pense pas qu'il soit utile de la décrire en détail. Il y a plusieurs points auxquels il faut prêter attention.

Le premier est la journalisation. L'un des arguments de la méthode lambda Context contient de nombreuses informations auxiliaires, ainsi qu'un enregistreur. Par conséquent, vous ne pouvez pas créer un enregistreur distinct, mais utiliser le contexte lambda fourni. Pour ce faire, il suffit d'appeler avant utilisation:
LambdaLogger logger = context.getLogger();


Le deuxième point se réchauffe. Étant donné que la création d'un environnement exécutable pour un lambda est paresseuse, le démarrage de la JVM, le chargement du chemin de classe et l'exécution du code prennent un certain temps. Le premier appel peut prendre plusieurs secondes, ce qui n'est pas bon si vous écrivez différentes API synchrones. Dans de tels cas, vous pouvez dire à AWS que vous devez garder plusieurs instances lambda en alerte. Mais cela nécessite que quelqu'un appelle un lambda. Si nous le faisons avec la version de base du code, alors, en fait, nous enverrons une lettre et écrirons des données inexactes dans la base de données.
Pour éviter cela, nous pouvons ajouter une sorte de traitement de demande pour distinguer la demande réelle de la demande d'échauffement. Par exemple, nous pouvons ajouter un en-tête spécial à la demande. Dans notre cas, l'en-tête «X-WARM-UP» sera utilisé avec n'importe quelle valeur - pour comprendre qu'il s'agit d'une demande de préchauffage et que nous devons uniquement renvoyer une sorte de réponse sans exécuter la logique métier.

La dernière chose sur laquelle je voudrais attirer l'attention est l'utilisation de variables statiques. Cela ne rend pas l'instance lambda avec état dans notre cas, cela permet simplement de réutiliser des objets existants et initialisés. Si les objets que vous souhaitez initialiser changent statiquement lors de l'exécution du code, essayez de réfléchir à nouveau si cela va interférer avec le fonctionnement des appels suivants au lambda. De plus, si ces objets utilisent des variables d'environnement lors de l'initialisation, vous ne devez pas les rendre statiques.

4. Nous écrivons des tests


Le code lambda peut être couvert par les mêmes types de tests que vous utilisez dans la plupart des cas. Mais pour éviter que l'article ne se développe encore plus, je couvrirai le test des applications sans serveur dans le prochain article sur les tests AWS Lambda et le développement sur site. Bien que les tests unitaires soient déjà disponibles dans le référentiel .

5. Nous écrivons un modèle de ressources SAM


Après avoir écrit et testé notre lambda localement, nous devons écrire un modèle SAM pour déployer toutes ces ressources dans AWS.

Un peu sur SAM
Serverless Application Model – serverless . , CloudFormation DSL, – Lambda, Gateway, DynamoDB . .
SAM, CloudFormation, Terraform – IaC-. SAM Serverless-, Terraform .
Terraform DINS.

En fait, regardons quelles ressources nous devons déclarer dans le modèle SAM .

AWS :: Serverless :: Function
Les principales fonctionnalités sont déjà remplies pour nous si nous avons utilisé SAM init pour créer le projet. Mais voici à quoi ils ressemblent dans mon exemple:
CodeUri: contact-us-function
Handler: com.gralll.sam.App::handleRequest
Runtime: java8
MemorySize: 256

Ici, je pense que tout est assez clair.
CodeUri est le répertoire de notre
gestionnaire lambda - le chemin d'accès complet à la méthode
Runtime - sur quoi le lambda
MemorySize est écrit - parle de lui-même

En parlant de mémoire : jusqu'à 3 Go de RAM sont disponibles dans le lambda, ce qui correspond à une ressource de 2 CPU. Autrement dit, vous ne pouvez pas régler le processeur séparément, augmentant / diminuant uniquement la quantité de mémoire.

Le bloc suivant est nécessaire pour sélectionner la méthode de déploiement.
AutoPublishAlias: live
DeploymentPreference:
  Type: Canary10Percent10Minutes

AutoPublishAlias - vous permet d' ajouter un alias à chaque nouvelle version déployée . Cela est nécessaire pour mettre en œuvre le déploiement des canaris.
Canary10Percent10Minutes - un type de déploiement qui vous permettra de tenir simultanément deux versions du lambda: l'ancienne et la nouvelle, mais ne redirigera que 10% du trafic vers la nouvelle. Si dans dix minutes il n'y a pas de problème, le reste du trafic sera également redirigé vers la nouvelle version.
Vous pouvez en savoir plus sur l'utilisation des fonctionnalités avancées sur la page SAM .

Viennent ensuite les variables d'environnement qui seront utilisées à partir du code. Après cela, un autre bloc pour lambda:
Events:
  ContactUs:
    Type: Api
    Properties:
      Path: /contact
      Method: post

Dans ce document, nous devons décrire les déclencheurs pour appeler un lambda. Dans notre cas, il s'agira de requêtes API Gateway .
Il s'agit d'une forme plutôt simplifiée de description de l'API, mais il suffit que l'API de passerelle nouvellement créée redirige toutes les demandes POST / contact vers notre lambda.

Bien sûr, nous devons décrire les aspects de sécurité. Hors de la boîte, le lambda créé n'aura accès ni à la base de données ni au service de messagerie. Nous devons donc explicitement prescrire ce qui sera autorisé. Il existe plusieurs façons de donner accès à l'intérieur d'AWS. Nous utiliserons des politiques basées sur les ressources:
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'

Veuillez noter que pour la table de base de données, nous avons spécifié un nom spécifique, par conséquent, nous devrons créer une table avec le même nom.
Concernant SES: vous voyez mon adresse e-mail. Dans votre cas, il doit s'agir de votre propre adresse confirmée. Comment faire cela, voir ici .
Juste après cela, vous pouvez trouver l'ARN d'identité de cette ressource en cliquant sur l'adresse créée et la remplacer par l'e-mail dans l'exemple ci-dessus.
Ils ont en quelque sorte compris la lambda. Passons maintenant à la base de données.

AWS :: Serverless :: SimpleTable
Pour nos tâches, nous ne créerons qu'une seule table ContactUsTable :
ContactUsTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: Id
      Type: String
    TableName: ContactUsTable
    ProvisionedThroughput:
      ReadCapacityUnits: 2
      WriteCapacityUnits: 2

Parmi les champs obligatoires - uniquement l'ID, et indiquent également ReadCapacityUnits et WriteCapacityUnits. Nous ne nous attarderons pas en détail sur les valeurs à choisir, car il s'agit également d'un sujet assez vaste. Vous pouvez le lire ici . Pour une application de test, de petites valeurs de l'ordre de 1-2 sont également suffisantes.

Globals
Les paramètres généraux peuvent être supprimés dans ce bloc si, par exemple, vous déclarez plusieurs ressources de type Function ou API.
Globals:
  Function:
    Timeout: 15
  Api:
    Cors:
      AllowOrigin: "'*'"
      AllowHeaders: "'Content-Type,X-WARM-UP,X-Amz-Date,Authorization,X-Api-Key'"

Je l'ai utilisé pour définir un délai d'expiration pour la fonction et certains paramètres Cors pour appeler l'API de passerelle plus tard à partir de ma page statique avec le formulaire ContactUs.

Sorties
Ce bloc vous permet de définir dynamiquement certaines variables dans le contexte global 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

Par exemple, nous avons déclaré une variable ContactUsApi, qui sera définie sur une valeur, comme adresse publique de notre point de terminaison API créé.
Puisque nous utilisons $ {ServerlessRestApi} , AWS insérera l'identifiant unique de notre nouvelle API de passerelle dans la chaîne. Par conséquent, toute application ayant accès à CloudFormation pourra obtenir cette adresse - vous ne pourrez donc pas coder en dur l'URL de vos services. Eh bien, un autre avantage est qu'il est très pratique de voir la liste des sorties - quelques méta-informations sur votre pile.
Une liste complète des fonctions et des paramètres que vous pouvez utiliser peut être trouvée ici .

Paramètres
En plus de tout ce qui précède, vous pouvez ajouter un bloc Paramètres. Ces options aideront à rendre le modèle plus polyvalent. Dans tout autre bloc du modèle, vous pouvez faire référence à ces paramètres, et donc ne pas coder en dur certaines valeurs. Par exemple, dans mon modèle, il peut s'agir d'e-mails, d'ARN SES, de la quantité de mémoire, etc.

C'est tout le modèle. Personne n'interdit d'ajouter quelques ressources supplémentaires à proximité, par exemple, un compartiment S3, toute autre ressource CloudFormation ou une ressource personnalisée en général .

6. Nous procédons au déploiement


Afin d'assembler notre projet, nous n'utiliserons pas Gradle, mais SAM.
Assemblée
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


L'exécution de la commande sam build à partir de la racine du projet collectera automatiquement tous les fichiers nécessaires dans le dossier .aws-sam : classes, dépendances, modèle SAM.
Ensuite, vous devez créer un compartiment S3, où SAM télécharge ensuite tous les artefacts collectés.
Cela peut être fait soit via la console AWS basée sur un navigateur, soit avec la commande
aws s3 mb s3://bucket-name

Remarque: tous les compartiments sont créés dans un contexte global et ils sont fouillés entre tous les comptes. Vous ne pouvez donc pas créer de compartiment si quelqu'un l'a déjà créé dans votre compte.

Lorsque le compartiment est prêt, exécutez la commande:
sam package --output-template-file packaged.yaml --s3-bucket <YOUR_BACKET>

Résultat du package
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>


J'ai pointé mon compartiment contact-us-sam-app et SAM a téléchargé toutes les ressources à l'emplacement spécifié. Ensuite, SAM vous indique déjà une commande pour créer une pile avec des ressources et ainsi intégrer votre décision. Nous exécutons la commande, ayant un peu finalisé:
sam deploy --template-file packaged.yaml --region eu-west-1 --capabilities CAPABILITY_IAM --stack-name contact-us-sam-app

Comme vous pouvez le voir, j'ai ajouté --capabilities CAPABILITY_IAM . Cela permettra à CloudFormation de créer des ressources IAM. Sinon, vous recevrez une erreur InsufficientCapabilities lors de la création.
Voici le journal des travaux de cette commande (l'image est cliquable). Des détails tels que l'état de chaque ressource et les valeurs des sorties sont devenus disponibles uniquement dans l'une des dernières versions de SAM CLI.


7. Vérifiez l'état de CloudFormation


Nous pouvons attendre la fin du déploiement dans la console jusqu'à ce qu'un message apparaisse indiquant que la pile a été déployée (la commande deploy se connecte dans le paragraphe précédent):
Successfully created/updated stack - contact-us-sam-app in eu-west-1

Mais dans notre cas, vous ne verrez ce message qu'après dix minutes en raison du mode de déploiement canari. Par conséquent, il est plus facile d'ouvrir une console de navigateur et de regarder la pile là-bas.

Après un certain temps, le statut passera à CREATE_COMPLETE , ce qui signifiera l'achèvement réussi.
Dans l'onglet Événements , vous pouvez voir l'état de toutes les ressources. Si votre pile s'effondre, c'est ici que vous pouvez trouver des messages d'erreur détaillés.
Par exemple, ceci: UPDATE_FAILED - si vous configurez incorrectement l'API de passerelle dans le modèle.


Dans l'onglet Ressources, vous pouvez trouver toutes les ressources créées. Ne soyez pas surpris de leur quantité. Bien que nous ayons uniquement déclaré une fonction et une table de base de données dans le modèle SAM, CloudFormation a créé de nombreuses autres ressources pour nous. Si vous regardez leur type, vous pouvez comprendre à quel service ils appartiennent.
Pour Api Gateway, il a été implicitement créé:
  • ServerlessRestApi
  • ServerlessRestApiDeployment
  • ServerlessRestApiProdStage

Aussi pour lambda, plusieurs objets supplémentaires ont été créés.
Ouvrez maintenant Outputs et trouvez l'URL de notre API. Copiez-le, il vous sera bientôt utile.

8. Formulaire HTML ContactUs


Comme vous vous en souvenez, j'ai décidé de créer un formulaire ContactUs, et maintenant nous devons en quelque sorte le rendre disponible non seulement sur la machine locale.

Configuration
En ce qui concerne le formulaire lui-même, par exemple, j'ai décidé de prendre le formulaire HTML le plus simple et d'ajouter des appels d'API de passerelle via ajax.

En plus du formulaire lui-même, j'ai ajouté quelques boutons pour déboguer et simplifier le remplissage:
Définir par défaut - remplace, comme prévu, les paramètres par défaut spécifiés dans 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?");

Si vous avez l'intention d'utiliser cette fonctionnalité, remplacez url-input par le chemin d'accès à votre API de passerelle, que vous avez copié à partir des sorties .

Hébergement
  • Créez un nouveau compartiment S3.
  • Nous chargeons le fichier HTML dans le compartiment en choisissant l'option pour rendre le fichier public.
  • Nous allons dans le seau où nous avons téléchargé le fichier.
  • Accédez à Propriétés, puis activez l'hébergement de sites Web statiques et voyez votre nouveau point de terminaison accessible au public.


9. Bilan de santé


Demande standard
Vous pouvez maintenant suivre le lien vers votre formulaire, cliquez sur Définir par défaut et Envoyer.
De cette façon, vous ferez une demande qui passera par l'API de passerelle à AWS Lambda.

Si vous avez tout configuré correctement après un certain temps, vous recevrez un message comme:
Réussi: le message 0102016f28b06243-ae897a1e-b805-406b-9987-019f21547682-000000 a été envoyé avec succès.

Cela signifie que le message a été remis avec succès. Vérifiez votre boîte aux lettres que vous avez spécifiée dans le modèle SAM. Si vous n'avez pas modifié le modèle, la lettre sera dans ce format:

vous pouvez également ouvrir DynamoDB et vous assurer qu'une nouvelle entrée est apparue.

Caractéristiques d'un démarrage à froid
Je pense que vous avez remarqué que le message concernant l'envoi réussi est venu après un temps assez long. Cela est dû au fait que le service AWS Lambda, après avoir reçu une demande de traitement, a commencé à augmenter l'instance de votre application Java, ce qui comprend le levage du conteneur avec le système d'exploitation et JRE, le chargement du chemin de classe , l'initialisation de toutes les variables, et ce n'est qu'après que le début de la demande de poignée ( ) . C'est ce qu'on appelle un démarrage à froid.

Essayez de remplir et d'envoyer à nouveau le formulaire. Cette fois, la réponse est venue presque instantanément, non? Si plus de 20 à 30 minutes se sont écoulées entre la première et la deuxième demande, le résultat peut varier.
Quelle est la raison pour ça? Et avec le fait que les caches AWS Lambda utilisaient déjà des conteneurs contenant des lambdas pour leur réutilisation. Cela réduit le temps d'initialisation de l'ensemble du contexte pour démarrer la méthode.
Il n'y a pas de corrélation claire pour combien de temps les lambdas sont mis en cache selon quoi que ce soit, mais certaines personnes ont expérimentalement constaté que cela dépend directement de la valeur choisie de RAM. Autrement dit, un lambda avec 128 Mo de mémoire sera disponible plus longtemps qu'avec 3 Go. Il y a peut-être d'autres paramètres, par exemple, la charge moyenne sur la région dans laquelle vos lambdas sont exécutés, mais cela est inexact.
Par conséquent, testez-vous et planifiez le temps de mise en cache si vous utilisez des demandes synchrones.

Échauffement
En option, vous pouvez utiliser le chauffage lambda. Dans le code, j'ai ajouté une vérification d'en -tête X-WAMP-UP . S'il y en a une, le lambda renvoie simplement une réponse sans exécuter de logique métier, mais le conteneur sera prêt pour les appels suivants.
Vous pouvez appeler votre lambda vous-même, par exemple, avec la minuterie de la couronne, à l'aide de CloudWatch. Cela vous sera utile si vous ne souhaitez pas que vos clients rejoignent Cold Start.
Dans le formulaire HTML, vous pouvez trouver le bouton du mode WarmUp, qui ajoute cet en-tête spécial à la demande. Vous pouvez vérifier que ni l'envoi d'une lettre ni l'écriture dans la base de données n'ont lieu, mais la réponse du lambda arrive, et l'appel suivant à la demande réelle ne prendra pas beaucoup de temps.

Résumer


Au cours de l'article, nous sommes passés par toutes les étapes principales de la conception de l'application à sa sortie dans la soi-disant production .
J'espère que les personnes qui ont déjà entendu parler de Serverless et d'AWS Lambda, mais qui n'ont pas d'expérience pratique, pourront utiliser ce guide et sentir que cela offre des avantages significatifs dans la vitesse de déploiement de certaines solutions logicielles et pas seulement.

Avantages
Pour moi, j'ai identifié les avantages les plus précieux:
  • Free Tier , , .
  • 1-2 . /.
  • , , .
  • Serverless . , — . SAM AWS. AWS , , .
  • Lambda , .
  • Serverless , , . ELK/Promethes/Grafana .
  • , API. API Key, IAM , .
  • Eh bien, la chose la plus fondamentale est probablement sans serveur. Il n'est pas nécessaire de penser à l'instance EC2 pour laquelle vous devez payer, comment configurer l'accès à celle-ci, comment configurer les alertes en cas de panne d'une application, configurer la mise à l'échelle automatique, etc.


Inconvénients
Malheureusement, cela ne signifie pas que vous pouvez maintenant prendre cette solution et l'utiliser dans le cadre d'un système d'entreprise au lieu de vos microservices préférés dans le cuber. Très souvent, vous devez creuser plus profondément pour déterminer une solution plus appropriée dans un cas particulier.
Et j'ai également des commentaires auxquels vous devez faire attention lors du choix de Lambda:
  • Démarrage à froid. Particulièrement important lors de l'utilisation de requêtes synchrones et de langages tels que Java ou C #. Le problème est modérément résolu par l'échauffement, mais il vaut mieux réfléchir à une telle solution à l'avance et comparer le coût et les avantages possibles
  • . 3GB , 2 , , - Fargate, , .
  • API, , .
  • / 1-2 Serverless, .
  • Si vous disposez déjà de l'infrastructure pour développer des microservices et CI / CD dans Coober, il sera à nouveau problématique de discuter (en particulier pour les gestionnaires) de la nécessité de prendre en charge un autre processus CI / CD.
  • Eh bien, où sans tests. Et pour tester le lambda pour le débit est assez difficile, car il est toujours différent des tests de perfarmance habituels, et de nombreux facteurs doivent être pris en compte.


Utilisation
En général, l'utilisation d'AWS Lambda et d'autres services sans serveur correspond très bien aux principes de développement asynchrone et événementiel. Et c'est en utilisant un traitement asynchrone que des résultats optimaux peuvent être obtenus à la fois en termes de performances et de coût. Voici une liste de solutions dans lesquelles les solutions Serveress joueront un rôle important:


PS


Étant donné que l'article s'est avéré assez étendu et que je ne veux pas le gonfler encore plus, je préparerai très probablement une suite dans laquelle je montrerai comment mener le développement plus efficacement en utilisant toutes les fonctionnalités de la console AWS, du framework SAM et même d'IntelljIDEA. Eh bien, puisque j'ai omis la partie test, je vais essayer de décrire cet aspect du développement plus en détail. De plus, si vous avez des souhaits à ajouter au prochain article ou des questions, n'hésitez pas à écrire dans les commentaires ou dans des messages privés.

Ajouté: AWS Lambda en action. Partie 2

Quelques liens importants et utiles de l'article:

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


All Articles