AWS Lambda en acción en Java 11. Ir sin servidor a producción

Este artículo es una guía sobre cómo comenzar a usar AWS Lambda de manera rápida y sin problemas con un simple ejemplo. Adecuado tanto para un desarrollador que no trabajó con Lambda en absoluto, y que conocía a Cloud para evaluar otra visión para desarrollar aplicaciones sin servidor.

imagen


Introducción


Hola a todos.
Mi nombre es Alexander Gruzdev, soy jefe de equipo de Java en DINS. Durante más de dos años, he estado trabajando estrechamente con la infraestructura de AWS y tengo experiencia tanto en la escritura de aplicaciones para AWS como en la implementación de estas mismas aplicaciones. En mi trabajo tuve que usar ElasticBeanstalk, ECS, Fargate, EKS y, por supuesto, AWS Lambda.

Cada uno de estos servicios es bueno a su manera y, en el marco de este artículo, no instaré a usar solo AWS Lambda como reemplazo de todas las demás opciones. Solo quiero mostrar lo fácil que es comenzar a usar Lambda, y en qué casos obtendrá los beneficios de usarlo.

Todos, desde desarrolladores hasta gerentes, quieren un proceso claro para entregar cambios a los clientes. Cuanto más transparente sea este camino y se requiera menos esfuerzo, más rentable. Con mayor frecuencia, los desarrolladores y evaluadores no quieren saber dónde se implementará la aplicación, qué pieza de hardware elegir, en qué región desea colocar réplicas, etc. En el siguiente diagrama, puede ver la clase de servicios "aaS" (como un servicio) donde AWS Lambda representa la categoría FaaS.

imagen

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


Idea


Para demostrar cuál es el desarrollo de una solución sin servidor, decidí tomar un ejemplo bastante banal. Creo que ha visto el formulario de comentarios de contacto en muchos sitios, donde puede dejar su correo o teléfono y hacer una pregunta para recibir una respuesta más tarde. La pantalla a continuación, el formulario no es super, pero el tema de este artículo no es el diseño material.


Al completar este formulario, un cliente potencial estará esperando una llamada o carta suya.
Desde el lado técnico necesitas:
  • guardar datos del formulario en la base de datos;
  • enviar una carta a un agente que responderá la pregunta;
  • quizás escriba otro servicio donde el agente marcará el trabajo realizado sobre la base de la misma base de datos (no incluida en el alcance del artículo).


La implementación de la idea está disponible en GitHub .

Arquitectura


Lo primero que viene a la mente durante la implementación es lo que necesitamos:
  • máquina debajo de la espalda, frente, imágenes, etc.
  • coche con base
  • máquina con servicio de correo electrónico.




La parte posterior y la base, en el buen sentido, necesitan ser replicadas, pero supongamos que hacemos esto para nuestra mini-tienda y la carga en este formulario es mínima. Por lo tanto, todas estas máquinas pueden combinarse en una y desplegarse en un solo conjunto.
Pero como estamos considerando Serverless, construyamos la arquitectura completamente en componentes sin servidor.
No importa cuán prometedor pueda parecer Serverless, todos entendemos que los servidores todavía están aquí, simplemente desaparecieron de su campo de visión. Y ahora ese trabajo en la nube que podría hacer las tareas que podría hacer por su cuenta. Pero intentemos usar solo componentes sin servidor:


el diagrama muestra una separación muy clara de la funcionalidad de cada componente.
  • AWS Lambda es el componente principal que contiene código / lógica,
  • S3 es responsable de almacenar recursos estáticos y scripts JS,
  • CloudFront: para mecanismos de almacenamiento en caché y soporte multirregional,
  • SES - servicio de correo electrónico,
  • DynamoDB: la base para almacenar datos del formulario (de quién, qué pregunta, dónde enviar la respuesta),
  • API de puerta de enlace: API HTTP para nuestra lambda,
  • Route53: necesario si desea agregar un hermoso nombre de dominio.


No todos estos componentes se utilizarán en nuestra próxima guía, solo para no estirar el artículo.
Route53 y CloudFront son componentes bastante simples sobre los que puede leer por separado.
Unos pequeños spoilers que nos darán tal solución:
  • Nos estamos alejando del soporte de máquinas EC2, sin depuración a través de ssh,
  • Configuración sencilla: configure la limitación / almacenamiento en caché / accesos con un clic,
  • Admite políticas de acceso: restringe los derechos y da acceso,
  • Registro / monitoreo fuera de la caja,
  • Pague solo por los recursos / solicitudes utilizados.


Manifestación


Formación


Para comenzar a desarrollar nuestra solución Serverless, debe cumplir con los siguientes requisitos:


Después de instalar todas las herramientas anteriores, debe configurar su aws-cli para el acceso remoto a AWS. Sigue las instrucciones . Esto requerirá crear un nuevo usuario y descargar su clave de acceso y clave secreta.

Asamblea de proyecto


1. Crear un proyecto a partir de una plantilla


Abra el directorio para el proyecto futuro y desde allí inicie SAM CLI. Siga las instrucciones:


Nota: Dependiendo de la versión de SAM-CLI, los comandos pueden ser ligeramente diferentes, pero todos siguen siendo intuitivos. Simplemente elija los más similares a los que se usaron anteriormente. También puede elegir una herramienta de compilación diferente si Gradle no es adecuado para usted.

El proyecto "¡Hola Mundo!" Listo. Ahora puede trabajar en el nombre del proyecto y los paquetes, las dependencias y el código fuente.

2. Tratemos con las adicciones.


Agregue las siguientes dependencias a 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'
}

Los principales son el SDK de AWS. Le permitirán trabajar con servicios específicos, como SES, DynamoDB, etc.

3. Escribimos una lambda


  • Cambiamos la solicitud de plantilla y las clases de respuesta de RequestHandler a AwsProxyRequest y 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 lógica básica es bastante simple, por lo que no creo que valga la pena describirla en detalle. Hay varios puntos a los que vale la pena prestarles atención.

El primero es el registro. Uno de los argumentos del método Context lambda contiene mucha información auxiliar, así como un registrador. Por lo tanto, no puede crear un registrador separado, sino utilizar el contexto lambda proporcionado. Para hacer esto, es suficiente llamar antes de usar:
LambdaLogger logger = context.getLogger();


El segundo punto es el calentamiento. Dado que crear un entorno ejecutable para un lambda es lento, iniciar la JVM, cargar el classpath y ejecutar el código lleva algo de tiempo. La primera llamada puede tardar varios segundos, lo que no es bueno si escribe diferentes API síncronas. Para tales casos, usted mismo puede decirle a AWS que necesita mantener varias instancias lambda en alerta. Pero esto requiere que alguien llame a una lambda. Si hacemos esto con la versión básica del código, de hecho, enviaremos una carta y escribiremos algunos datos inexactos en la base de datos.
Para evitar esto, podemos agregar algún tipo de procesamiento de solicitud para distinguir la solicitud real de la solicitud de calentamiento. Por ejemplo, podemos agregar un encabezado especial a la solicitud. En nuestro caso, el encabezado "X-WARM-UP" se usará con cualquier valor, para comprender que esta es una solicitud de calentamiento y que solo necesitamos devolver algún tipo de respuesta sin ejecutar la lógica empresarial.

Lo último que me gustaría llamar la atención es el uso de variables estáticas. Esto no hace que la instancia lambda con estado en nuestro caso, simplemente permite reutilizar objetos existentes e inicializados. Si los objetos que desea inicializar cambian estáticamente durante la ejecución del código, intente pensar nuevamente si esto interferirá con la operación de llamadas posteriores a la lambda. Además, si estos objetos usan variables de entorno durante la inicialización, no debe hacerlos estáticos.

4. Escribimos pruebas


El código Lambda puede estar cubierto por los mismos tipos de pruebas que usa en la mayoría de los casos. Pero para evitar que el artículo crezca aún más, cubriré las pruebas de aplicaciones sin servidor en el próximo artículo sobre pruebas de AWS Lambda y desarrollo local. Aunque las pruebas unitarias ya están disponibles en el repositorio .

5. Escribimos una plantilla de recursos SAM


Después de que escribimos y probamos nuestro lambda localmente, necesitamos escribir una plantilla SAM para implementar todos estos recursos en AWS.

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

En realidad, veamos qué recursos necesitamos declarar en la plantilla SAM .

AWS :: Serverless :: Function
Las funciones principales ya están completas para nosotros si utilizamos SAM init para crear el proyecto. Pero así es como se ven en mi ejemplo:
CodeUri: contact-us-function
Handler: com.gralll.sam.App::handleRequest
Runtime: java8
MemorySize: 256

Aquí creo que todo está lo suficientemente claro.
CodeUri es el directorio con nuestro
controlador lambda : la ruta completa al método
Runtime , en lo que está escrito
el lambda MemorySize , habla por sí mismo

Hablando de memoria : hasta 3 GB de RAM están disponibles en el lambda, que corresponde a un recurso de 2 CPU. Es decir, no puede ajustar la CPU por separado, solo aumenta / disminuye la cantidad de memoria.

El siguiente bloque es necesario para seleccionar el método de implementación.
AutoPublishAlias: live
DeploymentPreference:
  Type: Canary10Percent10Minutes

AutoPublishAlias : le permite agregar un alias a cada nueva versión implementada . Esto es necesario para implementar el despliegue canario.
Canary10Percent10Minutes : un tipo de implementación que le permitirá mantener simultáneamente dos versiones de lambda: la antigua y la nueva, pero solo redirigirá el 10% del tráfico a la nueva. Si en diez minutos no hay problemas, el resto del tráfico también se redirigirá a la nueva versión.
Puede leer más sobre el uso de funciones avanzadas en la página SAM .

A continuación están las variables de entorno que se utilizarán a partir del código. Después de eso, otro bloque para lambda:
Events:
  ContactUs:
    Type: Api
    Properties:
      Path: /contact
      Method: post

En él, debemos describir los desencadenantes para llamar a una lambda. En nuestro caso, estas serán solicitudes de API Gateway .
Esta es una forma bastante simplificada de describir la API, pero es suficiente para que la API de Gateway recién creada redirija todas las solicitudes POST / contacto a nuestra lambda.

Por supuesto, necesitamos describir los aspectos de seguridad. Fuera de la caja, el lambda creado no tendrá acceso a la base de datos o al servicio de correo electrónico. Por lo tanto, debemos prescribir explícitamente lo que se permitirá. Hay varias formas de dar acceso dentro de AWS. Utilizaremos políticas basadas en 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'

Tenga en cuenta que para la tabla de la base de datos especificamos un nombre específico, por lo tanto, necesitaremos crear una tabla con el mismo nombre.
En cuanto a SES: ves mi dirección de correo electrónico. En su caso, debe ser su propia dirección confirmada. Cómo hacer esto, mira aquí .
Justo después de eso, puede encontrar el ARN de identidad de este recurso haciendo clic en la dirección creada y reemplazarla con el correo electrónico en el ejemplo anterior.
Como que descubrieron la lambda. Ahora pasemos a la base de datos.

AWS :: Serverless :: SimpleTable
Para nuestras tareas, crearemos solo una tabla ContactUsTable :
ContactUsTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: Id
      Type: String
    TableName: ContactUsTable
    ProvisionedThroughput:
      ReadCapacityUnits: 2
      WriteCapacityUnits: 2

De los campos obligatorios, solo Id, y también indican ReadCapacityUnits y WriteCapacityUnits. No nos detendremos en detalle sobre qué valores elegir, ya que este también es un tema bastante extenso. Puedes leerlo aquí . Para una aplicación de prueba, también son suficientes pequeños valores del orden de 1-2.

Globals
Los parámetros generales se pueden extraer en este bloque si, por ejemplo, declara varios recursos de tipo Function o API.
Globals:
  Function:
    Timeout: 15
  Api:
    Cors:
      AllowOrigin: "'*'"
      AllowHeaders: "'Content-Type,X-WARM-UP,X-Amz-Date,Authorization,X-Api-Key'"

Lo usé para establecer un tiempo de espera para la función y algunas configuraciones de Cors para llamar a la API de Gateway más tarde desde mi página estática con el formulario ContactUs.

Salidas
Este bloque le permite definir dinámicamente algunas variables en el contexto global de 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 ejemplo, declaramos una variable ContactUsApi, que se establecerá en un valor, como la dirección pública de nuestro punto final API creado.
Como estamos usando $ {ServerlessRestApi} , AWS insertará el identificador único de nuestra nueva API de Gateway en la cadena. Como resultado, cualquier aplicación que tenga acceso a CloudFormation podrá obtener esta dirección, por lo que no puede codificar la URL de sus servicios. Bueno, otra ventaja es que es muy conveniente ver la lista de resultados, algo de metainformación sobre su pila. Aquí
puede encontrar una lista completa de funciones y qué parámetros puede usar . Parámetros Además de todo lo anterior, puede agregar un bloque de Parámetros


. Estas opciones ayudarán a que la plantilla sea más versátil. En cualquier otro bloque de la plantilla, puede hacer referencias a estos parámetros, por lo tanto, no codificar algunos valores. Por ejemplo, en mi plantilla pueden ser correos electrónicos, SES ARN, cantidad de memoria, etc.

Esa es toda la plantilla. Nadie prohíbe agregar un par de recursos más cerca, por ejemplo, un depósito S3, cualquier otro recurso de CloudFormation o un recurso personalizado en general .

6. Procedemos al despliegue


Para armar nuestro proyecto, usaremos no Gradle, sino SAM.
Montaje
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


La ejecución del comando sam build desde la raíz del proyecto recopilará automáticamente todos los archivos necesarios en la carpeta .aws-sam : clases, dependencias, plantilla SAM.
A continuación, debe crear un depósito S3, donde SAM posteriormente carga todos los artefactos recopilados.
Esto se puede hacer a través de la consola de AWS basada en navegador o con el comando
aws s3 mb s3://bucket-name

Nota: todos los depósitos se crean en un contexto global, y se analiza entre todas las cuentas. Por lo tanto, no puede crear un depósito si alguien ya lo ha creado en su cuenta.

Cuando el cubo esté listo, ejecute el comando:
sam package --output-template-file packaged.yaml --s3-bucket <YOUR_BACKET>

Resultado del paquete
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>


Apunté el bucket de nuestra aplicación contact-us-sam y SAM cargó todos los recursos en la ubicación especificada. A continuación, SAM ya le dice un comando para crear una pila con recursos y, por lo tanto, incorporar su decisión. Ejecutamos el comando, habiendo finalizado un poco:
sam deploy --template-file packaged.yaml --region eu-west-1 --capabilities CAPABILITY_IAM --stack-name contact-us-sam-app

Como puede ver, agregué --capabilities CAPABILITY_IAM . Esto permitirá que CloudFormation cree recursos de IAM. De lo contrario, recibirá un error InsufficientCapabilities al crear.
El siguiente es el registro del trabajo de este comando (se puede hacer clic en la imagen). Detalles como el estado de cada recurso y los valores de las salidas solo estuvieron disponibles en una de las últimas versiones de SAM CLI.


7. Verifique el estado de CloudFormation


Podemos esperar a que se complete la implementación en la consola hasta que aparezca un mensaje que indique que la pila se ha implementado (el comando de implementación registra en el párrafo anterior):
Successfully created/updated stack - contact-us-sam-app in eu-west-1

Pero en nuestro caso, verá este mensaje solo después de diez minutos debido al modo de implementación canario. Por lo tanto, es más fácil abrir una consola del navegador y ver la pila allí.

Después de un tiempo, el estado cambiará a CREATE_COMPLETE , lo que significará una finalización exitosa.
En la pestaña Eventos , puede ver el estado de todos los recursos. Si su pila falla, aquí es donde puede encontrar mensajes de error detallados.
Por ejemplo, esto: UPDATE_FAILED : si configura incorrectamente la API de Gateway en la plantilla.


En la pestaña Recursos, puede encontrar todos los recursos creados. No se sorprenda de su cantidad. Aunque solo declaramos una función y una tabla de base de datos en la plantilla SAM, CloudFormation creó muchos otros recursos para nosotros. Si observa su tipo, puede comprender a qué servicio pertenecen.
Para Api Gateway, se creó implícitamente:
  • ServerlessRestApi
  • ServerlessRestApiDeployment
  • ServerlessRestApiProdStage

También para lambda se crearon varios objetos adicionales.
Ahora abra Salidas y encuentre la URL de nuestra API. Cópielo, será útil pronto.

8. Formulario de contacto HTML


Como recordarán, decidí hacer un formulario de ContactUs, y ahora necesitamos ponerlo a disposición de alguna manera no solo en la máquina local.

Configuración
En cuanto al formulario en sí, por ejemplo, decidí tomar el formulario HTML más simple y agregué llamadas de API de Gateway a través de ajax.

Además del formulario en sí, agregué un par de botones para depurar y simplificar el relleno:
Establecer predeterminado : sustituye, como se esperaba, los parámetros predeterminados especificados dentro de 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 tiene la intención de utilizar esta funcionalidad, cambie la entrada de URL a la ruta de acceso a su API de puerta de enlace, que copió de Salidas .

Hospedaje
  • Crea un nuevo cubo S3.
  • Cargamos el archivo HTML en el depósito eligiendo la opción para hacer público el archivo.
  • Entramos en el cubo donde cargamos el archivo.
  • Vaya a Propiedades, luego active el alojamiento de sitios web estáticos y vea su nuevo Endpoint disponible públicamente.


9. Control de salud


Solicitud estándar
Ahora puede seguir el enlace a su formulario, hacer clic en Establecer predeterminado y Enviar.
De esta forma, realizará una solicitud que pasará por la API de Gateway a AWS Lambda.

Si configuró todo correctamente después de un tiempo, recibirá un mensaje como:
Correcto: el mensaje 0102016f28b06243-ae897a1e-b805-406b-9987-019f21547682-000000 se ha enviado correctamente.

Esto significa que el mensaje se entregó correctamente. Verifique el buzón que especificó en la plantilla SAM. Si no ha cambiado la plantilla, la carta tendrá este formato:

también puede abrir DynamoDB y asegurarse de que haya aparecido una nueva entrada.

Características de un arranque en frío
Creo que notó que el mensaje sobre el envío exitoso llegó después de un tiempo bastante largo. Esto se debe al hecho de que el servicio AWS Lambda, después de recibir una solicitud de procesamiento, comenzó a generar la instancia de su aplicación Java, y esto incluye levantar el contenedor con el sistema operativo y JRE, cargar el classpath, inicializar todas las variables, y solo después de eso es el inicio de handleRequest ( ) . Esto se llama un arranque en frío.

Intente completar y enviar el formulario nuevamente. Esta vez la respuesta llegó casi al instante, ¿verdad? Si han transcurrido más de 20-30 minutos entre la primera y la segunda solicitud, el resultado puede variar.
¿Cuál es la razón para esto? Y con el hecho de que los cachés AWS Lambda ya usaban contenedores con lambdas para su reutilización. Esto reduce el tiempo de inicialización de todo el contexto para iniciar el método.
No existe una correlación clara sobre cuánto tiempo se almacenan en caché las lambdas, según lo que sea, pero algunas personas descubrieron experimentalmente que esto depende directamente del valor elegido de RAM. Es decir, una lambda con 128 MB de memoria estará disponible durante más tiempo que con 3 GB. Quizás haya otros parámetros, por ejemplo, la carga promedio en la región en la que se ejecutan sus lambdas, pero esto es inexacto.
Por lo tanto, experimente y planifique el tiempo de almacenamiento en caché si utiliza solicitudes sincrónicas.

Calentamiento
Como opción adicional, puede usar el calentamiento lambda. En el código, agregué una verificación de encabezado X-WAMP-UP . Si hay uno, el lambda simplemente devuelve una respuesta sin ejecutar ninguna lógica de negocios, pero el contenedor estará listo para llamadas posteriores.
Puede llamar a su lambda usted mismo, por ejemplo, mediante el temporizador de corona, usando CloudWatch. Esto ayudará si no desea que sus clientes se unan a Cold Start.
En el formulario HTML, puede encontrar el botón de modo WarmUp, que agrega este encabezado especial a la solicitud. Puede verificar que no se envíe ni una carta ni se escriba a la base de datos, pero llega la respuesta del lambda y la llamada posterior a la solicitud real no tomará mucho tiempo.

Resumiendo


Durante el artículo, pasamos por todas las etapas principales desde el diseño de la aplicación hasta su lanzamiento en la llamada producción .
Espero que las personas que ya han escuchado acerca de Serverless y AWS Lambda, pero que no tenían experiencia práctica, puedan usar esta guía y sentir que esto ofrece ventajas significativas en la velocidad de implementación de algunas soluciones de software y no solo eso.

Beneficios
Para mí, he identificado las ventajas más valiosas:
  • Free Tier , , .
  • 1-2 . /.
  • , , .
  • Serverless . , — . SAM AWS. AWS , , .
  • Lambda , .
  • Serverless , , . ELK/Promethes/Grafana .
  • , API. API Key, IAM , .
  • Bueno, probablemente lo más básico es sin servidor. No es necesario pensar en qué instancia EC2 debe pagar, cómo configurar el acceso a ella, cómo configurar alertas en caso de un bloqueo de la aplicación, configurar el autoescalado, etc.


Desventajas
Desafortunadamente, esto no significa que ahora puede tomar esta solución y usarla como parte de un sistema empresarial en lugar de sus microservicios favoritos en el cubo. Muy a menudo, debe profundizar para determinar una solución más adecuada en un caso particular.
Y también tengo comentarios a los que debes prestar atención al elegir Lambda:
  • Inicio fresco. Especialmente significativo cuando se utilizan consultas sincrónicas y lenguajes como Java o C #. El problema se resuelve moderadamente calentando, pero es mejor pensar de antemano en tal solución y comparar el costo y los posibles beneficios.
  • . 3GB , 2 , , - Fargate, , .
  • API, , .
  • / 1-2 Serverless, .
  • Si ya tiene la infraestructura para desarrollar microservicios y CI / CD en Coober, nuevamente será problemático argumentar (especialmente para los gerentes) la necesidad de soportar otro proceso de CI / CD.
  • Bueno, donde sin pruebas. Y probar el rendimiento de la lambda es bastante difícil, porque sigue siendo diferente de las pruebas de rendimiento habituales, y deben tenerse en cuenta muchos factores.


Uso
En general, el uso de AWS Lambda y otros servicios sin servidor encaja muy bien con los principios de desarrollo asíncrono y basado en eventos. Y es mediante el uso del procesamiento asincrónico que se pueden lograr resultados óptimos tanto en términos de rendimiento como de costo. Aquí hay una lista de soluciones en las que las soluciones Serveress desempeñarán un papel importante:


PD


Dado que el artículo resultó ser bastante extenso y no quiero inflarlo aún más, lo más probable es que prepare una continuación en la que muestre cómo llevar a cabo el desarrollo de manera más eficiente utilizando todas las características de la consola AWS, el marco SAM e incluso IntelljIDEA. Bueno, como omití la parte de prueba, intentaré describir este aspecto del desarrollo con más detalle. Además, si tiene algún deseo de agregar al siguiente artículo o preguntas, no dude en escribir en los comentarios o en mensajes privados.

Agregado: AWS Lambda en acción. Parte 2

Algunos enlaces importantes y útiles del artículo:

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


All Articles