AWS Lambda em ação no Java 11. Indo sem servidor para produção

Este artigo é um guia sobre como começar de maneira rápida e indolor usando o AWS Lambda com um exemplo simples. Adequado para um desenvolvedor que não trabalhou com o Lambda e que conhecia a Cloud para avaliar outra visão para o desenvolvimento de aplicativos sem servidor.

imagem


Introdução


Olá a todos.
Meu nome é Alexander Gruzdev, sou Java Team Lead no DINS. Por mais de dois anos, trabalho em estreita colaboração com a infraestrutura da AWS e tenho experiência tanto na criação de aplicativos para a AWS quanto na implantação desses mesmos aplicativos. No meu trabalho, tive que usar o ElasticBeanstalk, ECS, Fargate, EKS e, é claro, o AWS Lambda.

Cada um desses serviços é bom à sua maneira e, dentro da estrutura deste artigo, não insistirei em usar apenas o AWS Lambda como substituto de todas as outras opções. Eu só quero mostrar como é fácil começar a usar o Lambda e em quais casos você terá os benefícios de usá-lo.

Todos, de desenvolvedores a gerentes, desejam um processo claro para entregar alterações aos clientes. Quanto mais transparente for esse caminho e menos esforço for necessário, mais lucrativo. Na maioria das vezes, os desenvolvedores e testadores não querem saber onde o aplicativo será implantado, qual hardware escolher, em que região você deseja colocar réplicas etc. No diagrama a seguir, você pode ver a classe de serviços “aaS” (como um serviço), em que o AWS Lambda representa a categoria FaaS.

imagem

FaaS em poucas palavras
FaaS , . , , CRUD C, R, U, D Create Read. , — .


Idéia


Para demonstrar o que é o desenvolvimento de uma solução sem servidor, decidi dar um exemplo bastante banal. Acho que você já viu o formulário de comentários Fale conosco em muitos sites, onde você pode deixar seu e-mail ou telefone e fazer uma pergunta para ser respondida mais tarde. Na tela abaixo, o formulário não é super, mas o assunto deste artigo não é o design do material.


Ao preencher este formulário, um cliente em potencial estará aguardando uma ligação ou carta sua.
Do lado técnico, você precisa:
  • salve dados do formulário no banco de dados;
  • envie uma carta a um agente que responderá à pergunta;
  • talvez escreva outro serviço em que o agente marque o trabalho realizado com base no mesmo banco de dados (não incluído no escopo do artigo).


A implementação da ideia está disponível no GitHub .

Arquitetura


A primeira coisa que vem à mente durante a implementação é o que precisamos:
  • máquina nas costas, frente, imagens, etc.,
  • carro com base
  • máquina com serviço de e-mail.




A parte traseira e a base, no bom sentido, precisam ser replicadas, mas suponha que façamos isso para nossa mini-loja e a carga nesse formulário seja mínima. Portanto, todas essas máquinas podem ser combinadas em uma e implantadas em um único conjunto.
Mas, como estamos considerando o Serverless, vamos construir a arquitetura inteiramente em componentes sem servidor.
Não importa o quão promissor o Serverless possa parecer, todos entendemos que os servidores ainda estão aqui, eles simplesmente desapareceram do seu campo de visão. E agora essa nuvem funciona e você pode fazer as tarefas que você pode fazer por conta própria. Mas vamos tentar usar apenas esses componentes sem servidor:


O diagrama mostra uma separação muito clara da funcionalidade de cada componente.
  • O AWS Lambda é o principal componente que contém código / lógica,
  • O S3 é responsável por armazenar recursos estáticos e scripts JS,
  • CloudFront - para mecanismos de cache e suporte multi-regional,
  • SES - serviço de e-mail,
  • DynamoDB - a base para armazenar dados do formulário (de quem, qual pergunta, para onde enviar a resposta),
  • API de gateway - API HTTP para nossa lambda,
  • Route53 - necessário se você deseja adicionar um belo nome de domínio.


Nem todos esses componentes serão usados ​​em nosso próximo guia, para não esticar o artigo.
O Route53 e o CloudFront são componentes bastante simples sobre os quais você pode ler separadamente.
Alguns spoilers que nos darão uma solução:
  • Estamos nos afastando do suporte a máquinas EC2, sem depuração via ssh,
  • Configuração fácil: configure a otimização / armazenamento em cache / acessos em um clique,
  • Oferece suporte a políticas de acesso: restrinja direitos e conceda acesso,
  • Log / monitoramento fora da caixa,
  • Pague apenas pelos recursos / solicitações usadas.


Demo


Treinamento


Para começar a desenvolver nossa solução sem servidor, você deve atender aos seguintes requisitos:


Depois de instalar todas as ferramentas acima, você precisa configurar o aws-cli para acesso remoto à AWS. Siga as instruções . Isso exigirá a criação de um novo usuário e o descarregamento de sua Chave de acesso e Chave secreta.

Montagem do projeto


1. Crie um projeto a partir de um modelo


Abra o diretório para o projeto futuro e, a partir dele, inicie o SAM CLI. Siga as instruções:


Nota: Dependendo da versão do SAM-CLI, os comandos podem ser um pouco diferentes, mas todos permanecem intuitivos. Basta escolher os mais semelhantes aos que foram usados ​​acima. Você também pode escolher uma ferramenta de compilação diferente se o Gradle não for adequado para você.

O projeto "Olá, mundo!" pronto. Agora você pode trabalhar no nome do projeto e pacotes, dependências e código fonte.

2. Vamos lidar com vícios


Adicione as seguintes dependências ao 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'
}

Os principais são o AWS SDK. Eles permitirão que você trabalhe com serviços específicos, como SES, DynamoDB, etc.

3. Nós escrevemos uma lambda


  • Alteramos as classes de solicitação e resposta do modelo para RequestHandler para AwsProxyRequest e 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()));
    }
    



A lógica básica é bastante simples, então não acho que valha a pena descrevê-la em detalhes. Vale a pena prestar atenção em vários pontos.

O primeiro é o log. Um dos argumentos do método lambda Context contém muitas informações auxiliares, além de um criador de logs. Portanto, você não pode criar um criador de logs separado, mas use o contexto lambda fornecido. Para fazer isso, basta ligar antes do uso:
LambdaLogger logger = context.getLogger();


O segundo ponto está esquentando. Como criar um ambiente executável para um lambda é lento, iniciar a JVM, carregar o caminho de classe e executar o código leva algum tempo. A primeira chamada pode levar alguns segundos, o que não é bom se você escrever APIs síncronas diferentes. Para esses casos, você mesmo pode informar à AWS que precisa manter várias instâncias lambda em alerta. Mas isso exige que alguém chame um lambda. Se fizermos isso com a versão básica do código, enviaremos uma carta e escreveremos alguns dados imprecisos no banco de dados.
Para evitar isso, podemos adicionar algum tipo de processamento de solicitação para distinguir a solicitação real da solicitação de aquecimento. Por exemplo, podemos adicionar um cabeçalho especial à solicitação. No nosso caso, o cabeçalho “X-WARM-UP” será usado com qualquer valor - para entender que essa é uma solicitação de aquecimento e precisamos apenas retornar algum tipo de resposta sem executar a lógica de negócios.

A última coisa que gostaria de chamar a atenção é o uso de variáveis ​​estáticas. Isso não cria a instância lambda com estado no nosso caso, simplesmente permite reutilizar objetos existentes e inicializados. Se os objetos que você deseja inicializar mudar estaticamente durante a execução do código, tente pensar novamente se isso interferirá na operação de chamadas subseqüentes ao lambda. Além disso, se esses objetos usarem variáveis ​​de ambiente durante a inicialização, você não deve torná-los estáticos.

4. Nós escrevemos testes


O código Lambda pode ser coberto pelos mesmos tipos de testes usados ​​na maioria dos casos. Mas, para impedir que o artigo cresça ainda mais, abordarei o teste de aplicativos sem servidor no próximo artigo sobre o teste do AWS Lambda e o desenvolvimento local. Embora os testes de unidade já estejam disponíveis no repositório .

5. Escrevemos um modelo de recursos SAM


Depois que escrevemos e testamos nossa lambda localmente, precisamos escrever um modelo SAM para implantar todos esses recursos na AWS.

Um pouco sobre o SAM
Serverless Application Model – serverless . , CloudFormation DSL, – Lambda, Gateway, DynamoDB . .
SAM, CloudFormation, Terraform – IaC-. SAM Serverless-, Terraform .
Terraform DINS.

Na verdade, vejamos quais recursos precisamos declarar no modelo SAM .

AWS :: Serverless :: Function
Os principais recursos já serão preenchidos se usarmos o SAM init para criar o projeto. Mas aqui está como eles se parecem no meu exemplo:
CodeUri: contact-us-function
Handler: com.gralll.sam.App::handleRequest
Runtime: java8
MemorySize: 256

Aqui eu acho que tudo está claro o suficiente.
CodeUri é o diretório com nosso
manipulador lambda - o caminho completo para o método
Runtime - no qual o lambda
MemorySize está escrito - fala por si mesmo

Falando em memória : até 3 GB de RAM estão disponíveis no lambda, o que corresponde a um recurso de 2 CPUs. Ou seja, você não pode ajustar a CPU separadamente, apenas aumentando / diminuindo a quantidade de memória.

O próximo bloco é necessário para selecionar o método de implantação.
AutoPublishAlias: live
DeploymentPreference:
  Type: Canary10Percent10Minutes

AutoPublishAlias - permite adicionar um alias a cada nova versão implantada . Isso é necessário para implementar a implantação de canários.
Canary10Percent10Minutes - um tipo de implantação que permitirá a você segurar simultaneamente duas versões do lambda: a antiga e a nova, mas redirecionar apenas 10% do tráfego para a nova. Se em dez minutos não houver problemas, o restante do tráfego também será redirecionado para a nova versão.
Você pode ler mais sobre o uso de recursos avançados na página SAM .

A seguir, estão as variáveis ​​de ambiente que serão usadas no código. Depois disso, outro bloco para lambda:
Events:
  ContactUs:
    Type: Api
    Properties:
      Path: /contact
      Method: post

Nele, devemos descrever os gatilhos para chamar um lambda. No nosso caso, essas serão solicitações do API Gateway .
Essa é uma forma bastante simplificada de descrever a API, mas é suficiente para a API de gateway recém-criada redirecionar todas as solicitações de POST / contato para nossa lambda.

Obviamente, precisamos descrever os aspectos de segurança. Fora da caixa, o lambda criado não terá acesso ao banco de dados ou ao serviço de email. Portanto, precisamos prescrever explicitamente o que será permitido. Existem várias maneiras de conceder acesso à AWS. Usaremos políticas baseadas em recursos:
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'

Observe que, para a tabela do banco de dados, especificamos um nome específico; portanto, precisaremos criar uma tabela com o mesmo nome.
Em relação ao SES: você vê meu endereço de e-mail. No seu caso, deve ser o seu próprio endereço confirmado. Como fazer isso, veja aqui .
Logo em seguida, você poderá encontrar o ARN de identidade desse recurso clicando no endereço criado e substituí-lo pelo email no exemplo acima.
Eles meio que descobriram a lambda. Agora vamos para o banco de dados.

AWS :: Serverless :: SimpleTable
Para nossas tarefas, criaremos apenas uma tabela ContactUsTable :
ContactUsTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: Id
      Type: String
    TableName: ContactUsTable
    ProvisionedThroughput:
      ReadCapacityUnits: 2
      WriteCapacityUnits: 2

Dos campos obrigatórios - apenas ID, e também indicam ReadCapacityUnits e WriteCapacityUnits. Não vamos nos deter em detalhes sobre quais valores escolher, pois esse também é um tópico bastante extenso. Você pode ler aqui . Para um aplicativo de teste, pequenos valores da ordem de 1-2 também são suficientes. Os parâmetros gerais

globais
podem ser removidos neste bloco se, por exemplo, você declarar vários recursos do tipo Função ou API.
Globals:
  Function:
    Timeout: 15
  Api:
    Cors:
      AllowOrigin: "'*'"
      AllowHeaders: "'Content-Type,X-WARM-UP,X-Amz-Date,Authorization,X-Api-Key'"

Usei-o para definir um tempo limite para a função e algumas configurações do Cors para chamar a API do Gateway posteriormente na minha página estática com o formulário ContactUs.

Saídas
Esse bloco permite definir dinamicamente algumas variáveis ​​no contexto global do 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

Por exemplo, declaramos uma variável ContactUsApi, que será definida como um valor, como o endereço público do nosso terminal de API criado.
Como estamos usando $ {ServerlessRestApi} , a AWS inserirá o identificador exclusivo de nossa nova API de gateway na cadeia de caracteres. Como resultado, qualquer aplicativo que tenha acesso ao CloudFormation poderá obter esse endereço - assim, você não pode codificar a URL de seus serviços. Bem, outra vantagem é que é muito conveniente ver a lista de saídas - algumas informações meta sobre sua pilha.
Uma lista completa de funções e quais parâmetros você pode usar pode ser encontrada aqui .

Parâmetros
Além de todas as opções acima, você pode adicionar um bloco Parâmetros. Essas opções ajudarão a tornar o modelo mais versátil. Em qualquer outro bloco do modelo, você pode fazer referências a esses parâmetros, não codificando alguns valores. Por exemplo, no meu modelo, podem ser emails, SES ARN, quantidade de memória etc.

Esse é o modelo inteiro. Ninguém proíbe adicionar mais alguns recursos por perto, por exemplo, um bucket S3, qualquer outro recurso do CloudFormation ou um recurso personalizado em geral .

6. Prosseguimos com a implantação


Para montar nosso projeto, usaremos não o Gradle, mas o SAM.
Montagem
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


A execução do comando sam build a partir da raiz do projeto coletará automaticamente todos os arquivos necessários na pasta .aws-sam : classes, dependências, modelo SAM.
Em seguida, você precisa criar um depósito S3, em que o SAM carrega posteriormente todos os artefatos coletados.
Isso pode ser feito através do console da AWS baseado em navegador ou com o comando
aws s3 mb s3://bucket-name

Nota: todos os buckets são criados em um contexto global e são vasculhados entre todas as contas. Portanto, você não pode criar um bloco se alguém já o criou na sua conta.

Quando o bucket estiver pronto, execute o comando:
sam package --output-template-file packaged.yaml --s3-bucket <YOUR_BACKET>

Resultado do pacote
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>


Apontei meu balde entre em contato conosco e o aplicativo e o SAM carregou todos os recursos no local especificado. Em seguida, o SAM já informa um comando para criar uma pilha com recursos e, assim, incorporar sua decisão. Executamos o comando, finalizando um pouco:
sam deploy --template-file packaged.yaml --region eu-west-1 --capabilities CAPABILITY_IAM --stack-name contact-us-sam-app

Como você pode ver, eu adicionei --capabilities CAPABILITY_IAM . Isso permitirá que o CloudFormation crie recursos do IAM. Caso contrário, você receberá um erro InsufficientCapabilities ao criar.
A seguir, é apresentado o log do trabalho deste comando (a imagem é clicável). Detalhes como o status de cada recurso e os valores das saídas ficaram disponíveis apenas em uma das versões mais recentes do SAM CLI.


7. Verifique o status do CloudFormation


Podemos esperar a conclusão da implantação no console até aparecer uma mensagem informando que a pilha foi implantada (o comando deploy registra no parágrafo anterior):
Successfully created/updated stack - contact-us-sam-app in eu-west-1

Mas, no nosso caso, você verá esta mensagem somente após dez minutos devido ao modo de implantação do canário. Portanto, é mais fácil abrir um console do navegador e observar a pilha lá.

Depois de algum tempo, o status será alterado para CREATE_COMPLETE , o que significará a conclusão bem-sucedida.
Na guia Eventos , você pode ver o status de todos os recursos. Se sua pilha fracassar, é aqui que você pode encontrar mensagens de erro detalhadas.
Por exemplo, este: UPDATE_FAILED - se você configurar incorretamente a API do Gateway no modelo.


Na guia Recursos, você pode encontrar todos os recursos criados. Não se surpreenda com a quantidade. Embora tenhamos declarado apenas uma função e uma tabela de banco de dados no modelo SAM, o CloudFormation criou muitos outros recursos para nós. Se você olhar para o tipo deles, poderá entender a que serviço eles pertencem.
Para o Api Gateway, ele foi criado implicitamente:
  • ServerlessRestApi
  • ServerlessRestApiDeployment
  • ServerlessRestApiProdStage

Também para o lambda, vários objetos adicionais foram criados.
Agora abra Saídas e encontre o URL da nossa API. Copie-o, em breve será útil.

8. Formulário ContactUs HTML


Como você se lembra, decidi criar um formulário ContactUs e agora precisamos disponibilizá-lo de alguma forma, não apenas na máquina local.

Configuração
Quanto ao próprio formulário, por exemplo, decidi usar o formulário HTML mais simples e adicionei chamadas à API do Gateway através do ajax.

Além do próprio formulário, adicionei alguns botões para depuração e simplificação do preenchimento:
Definir padrão - substitui, como esperado, os parâmetros padrão especificados dentro do 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?");

Se você pretende usar essa funcionalidade, altere a entrada da URL para o caminho da API do Gateway, que você copiou das Saídas .

Hospedagem
  • Crie um novo bucket S3.
  • Carregamos o arquivo HTML no bucket, escolhendo a opção para tornar o arquivo público.
  • Entramos no balde onde carregamos o arquivo.
  • Vá para Propriedades, ative a hospedagem de site estático e veja seu novo Endpoint disponível ao público.


9. Exame de Saúde


Solicitação padrão
Agora você pode seguir o link para seu formulário, clique em Definir padrão e em Enviar.
Dessa forma, você fará uma solicitação que passará pela API do Gateway para o AWS Lambda.

Se você configurou tudo corretamente depois de um tempo, receberá uma mensagem como:
Êxito: A mensagem 0102016f28b06243-ae897a1e-b805-406b-9987-019f21547682-000000 foi enviada com sucesso.

Isso significa que a mensagem foi entregue com sucesso. Verifique sua caixa de correio que você especificou no modelo SAM. Se você não alterou o modelo, a letra estará neste formato:

Você também pode abrir o DynamoDB e verificar se uma nova entrada foi exibida.

Características de um começo frio
Acho que você percebeu que a mensagem sobre o envio bem-sucedido veio após um longo período de tempo. Isso ocorre porque o serviço AWS Lambda, após receber uma solicitação de processamento, começou a aumentar a instância do seu aplicativo Java, e isso inclui levantar o contêiner com o sistema operacional e o JRE, carregar o caminho de classe, inicializar todas as variáveis ​​e somente depois disso é o início do handleRequest ( ) . Isso é chamado de partida a frio.

Tente preencher e enviar o formulário novamente. Desta vez, a resposta veio quase instantaneamente, certo? Se tiverem passado mais de 20 a 30 minutos entre a primeira e a segunda solicitações, o resultado poderá variar.
Qual é a razão para isto? E com o fato de o AWS Lambda caches já usarem contêineres com lambdas para reutilização. Isso reduz o tempo de inicialização de todo o contexto para iniciar o método.
Não há uma correlação clara de quanto tempo as lambdas são armazenadas em cache, dependendo do tipo, mas algumas pessoas descobriram experimentalmente que isso depende diretamente do valor escolhido da RAM. Ou seja, um lambda com 128 MB de memória estará disponível por mais tempo do que com 3 GB. Talvez haja outros parâmetros, por exemplo, a carga média na região na qual suas lambdas são executadas, mas isso é impreciso.
Portanto, experimente você mesmo e planeje o tempo de armazenamento em cache se você usar solicitações síncronas.

Aquecimento
Como opção adicional, você pode usar o aquecimento lambda. No código, adicionei uma verificação de cabeçalho X-WAMP-UP . Se houver, o lambda simplesmente retorna uma resposta sem executar nenhuma lógica de negócios, mas o contêiner estará pronto para chamadas subseqüentes.
Você mesmo pode chamar seu lambda, por exemplo, pelo cronômetro de coroa, usando o CloudWatch. Isso ajudará se você não desejar que seus clientes participem do Cold Start.
No formulário HTML, você pode encontrar o botão do modo WarmUp, que adiciona esse cabeçalho especial à solicitação. Você pode verificar se não ocorre o envio de uma carta nem a gravação no banco de dados, mas a resposta do lambda chega e a chamada subsequente à solicitação real não levará muito tempo.

Resumindo


Durante o artigo, passamos por todas as etapas principais, do design do aplicativo ao seu lançamento na chamada produção .
Espero que as pessoas que já ouviram falar sobre Serverless e AWS Lambda, mas que não tiveram experiência prática, possam usar este guia e possam sentir que isso oferece vantagens significativas na velocidade de implantação de algumas soluções de software e não apenas isso.

Benefícios
Para mim, identifiquei as vantagens mais valiosas:
  • Free Tier , , .
  • 1-2 . /.
  • , , .
  • Serverless . , — . SAM AWS. AWS , , .
  • Lambda , .
  • Serverless , , . ELK/Promethes/Grafana .
  • , API. API Key, IAM , .
  • Bem, provavelmente a coisa mais básica é sem servidor. Não é necessário pensar em qual instância do EC2 você precisa pagar, como configurar o acesso a ela, como configurar alertas no caso de uma falha do aplicativo, configurar o dimensionamento automático, etc.


Desvantagens
Infelizmente, isso não significa que agora você pode pegar esta solução e usá-la como parte de um sistema corporativo, em vez de seus microsserviços favoritos no cubo. Muitas vezes, você precisa se aprofundar para determinar uma solução mais adequada em um caso específico.
E também tenho comentários aos quais você deve prestar atenção ao escolher o Lambda:
  • Partida a frio. Especialmente significativo ao usar consultas síncronas e linguagens como Java ou C #. O problema é moderadamente resolvido com o aquecimento, mas é melhor pensar em uma solução com antecedência e comparar o custo e os possíveis benefícios
  • . 3GB , 2 , , - Fargate, , .
  • API, , .
  • / 1-2 Serverless, .
  • Se você já possui a infraestrutura para desenvolver microsserviços e CI / CD na Coober, será problemático novamente argumentar (especialmente para gerentes) a necessidade de oferecer suporte a outro processo de CI / CD.
  • Bem, onde sem testes. E testar o lambda quanto à taxa de transferência é bastante difícil, porque ainda é diferente do teste de desempenho usual e muitos fatores devem ser levados em consideração.


Uso
Em geral, o uso do AWS Lambda e de outros serviços sem servidor se encaixa muito bem com os princípios de desenvolvimento assíncrono e orientado a eventos. E é usando o processamento assíncrono que os melhores resultados podem ser alcançados tanto em termos de desempenho quanto de custo. Aqui está uma lista de soluções nas quais as soluções Serveress desempenharão um papel significativo:


PS


Como o artigo acabou sendo bastante extenso e não quero aumentar ainda mais, provavelmente prepararei uma sequência em que mostrarei como conduzir o desenvolvimento com mais eficiência usando todos os recursos do console da AWS, estrutura SAM e até IntelljIDEA. Bem, como omiti a parte de teste, tentarei descrever esse aspecto do desenvolvimento em mais detalhes. Além disso, se você deseja saber o que adicionar ao próximo artigo, ou perguntas, sinta-se à vontade para escrever nos comentários ou em mensagens privadas.

Adicionado: AWS Lambda em ação. Parte 2

Alguns links importantes e úteis do artigo:

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


All Articles