O blog da AWS

Aproveitando as novas extensões do AWS Lambda

Por Servio Reyes, Arquiteto de Soluções AWS México e
Iván González, Arquiteto de Soluções AWS México

 

A AWS lançou seu primeiro serviço de computação sem servidor (sem servidor) em 2014, conhecido como AWS Lambda, um serviço que permite a execução de código sem a necessidade de provisionar ou gerenciar servidores, altamente focado na criação de microsserviços. E desde a sua criação, muitos outros serviços foram adicionados à AWS, como o AWS Fargate ou o Amazon Aurora Serverless, com as vantagens da abordagem sem servidor. Sempre procurando criar soluções com base nas necessidades e nos comentários dos clientes que usam a AWS. Portanto, não apenas novos serviços foram lançados, mas também os serviços existentes, como o AWS Lambda, melhoraram e incorporaram novos recursos. Somente até agora este ano adicionaram novos recursos como: integração com o Amazon EFS, criação de funções definidas pelo usuário compatíveis com o Redshift e também a incorporação de Extensões do AWS Lambda, funcionalidade descrita abaixo.

 

Noções básicas sobre extensões do AWS Lambda

As extensões do AWS Lambda surgiram em resposta à solicitação dos desenvolvedores de incorporar ferramentas de monitoramento, segurança e governança mais diretamente nas invocações de recursos para que pudessem centralizar o monitoramento em ferramentas que já possuíam. Antes das extensões do AWS Lambda, havia duas maneiras principais de monitorar o recurso:

  • Forma síncrona: Envie logs durante a execução de itens de recurso, o que pode aumentar o tempo de execução do AWS Lambda. Derivando em maior latência para o usuário e também aumentando o custo de invocação.
  • Forma assíncrona: Os logs são enviados após a execução, principalmente usando o AWS CloudWatch. O que, ao mesmo tempo em que melhora a eficiência do AWS Lambda, requer pós-processamento para filtrar os logs mais relevantes (se desejado) e enviá-los para a ferramenta de monitoramento. Gerar atrasos na atualização de informações, considerando que o envio de logs para o AWS CloudWatch pode aumentar o custo médio de execução da função. Finalmente, há também a possibilidade de perder registros se o ambiente fechar imediatamente antes de enviar as informações.

As extensões permitem que o desenvolvedor seja capaz de gerar processos separados para que ele possa enviar informações de monitoramento de forma síncrona e também que eles possam personalizar ou preparar o ambiente de execução do AWS Lambda antes de sua invocação. Dividindo em dois tipos:

  • Extensões Internas: Este tipo pode ser identificado como invólucros do código, o que significa que eles compartilharão o mesmo processo e, por isso, devem ser a mesma linguagem de programação que a função principal. Sua finalidade é modificar o início da função, permitindo adicionar ou modificar argumentos de execução, variáveis de ambiente, obter e fornecer dados secretos ou dados necessários para a execução adequada da função.
  • Extensões Externas: Eles permitem um thread de execução separado, mas mantendo o mesmo ambiente em execução da função do AWS Lambda. Esta separação permite que eles sejam executados em uma linguagem de programação diferente da definida na função. Além disso, eles podem ser invocados antes e continuar a ser executado após o tempo de execução da função.

O Ambiente de Execução consiste principalmente nas APIs e Processos dos Endpoints. Onde os processos são criados pela execução do código de extensão e o tempo de execução junto com a função. O serviço do AWS Lambda se comunica via HTTP com o Runtime usando a Runtime API , permitindo que as informações sejam enviadas e inseridas para outros elementos da função ou serviço do AWS Lambda durante a existência do processo.

Por outro lado, as extensões podem se comunicar com os outros componentes de duas maneiras:

  • Extensões de API: Através desta API HTTP é que as extensões recebem os sinais de tempo de execução da função, permitindo-lhes executar diferentes tipos de lógica dependendo do estado geral da função.
  • Registros de API: Essa API HTTP permite que a extensão envie logs diretamente para o AWS CloudWatch ou se inscreva para recebimento.

 

 

O novo ciclo de vida do AWS Lambda consiste em três fases distintas:

  • init: Dividida em três estágios internos, essa separação permite que você execute os diferentes componentes de forma independente e em momentos diferentes. Os estágios são:

    • Inicialização de extensões.
    • Inicialização do tempo de execução.
    • Inicialização da função.
  • invocação: O AWS Lambda executa o código de função e extensão em resposta a gatilhos. Nesta fase, as extensões podem estar ouvindo os eventos de vida da função para obter ou enviar informações, com a possibilidade até mesmo de influenciar a invocação congelada e descongelada. Este estágio é limitado pelo tempo limite definido na configuração. Se você definir um tempo máximo de espera de 30 segundos, o recurso e as extensões devem terminar nesse momento.
  • desligamento: Depois que a execução da função for concluída, nesta parte a extensão pode continuar a ser executada para limpar, enviar informações e encerrar seu processo. Antes do encerramento das extensões, o tempo de execução envia um sinal de desligamento para extensões que podem ser usadas para determinar o ponto final de todo o AWS Lambda. Esta fase é limitada a um máximo de 2 segundos.

 

 

Casos de uso

Dada a abordagem de segurança com a qual as extensões foram criadas, seus principais casos de uso em que é recomendável implementá-las estão relacionados a:

  • Monitoramento: Geração de logs com informações relacionadas aos recursos usados (CPU, memória, rede) e envio de informações para outros serviços ou solicitações da Web.
  • Segurança: Limitação das ações ou comunicações que o código executará em sua execução.
  • Configuração: Padronize o ambiente de execução das funções.

Além disso, um benefício que pode ser derivado de seu uso, é uma otimização de custos. Porque as extensões em vários casos estarão sendo executadas como o plano de fundo da função de invocação. Isso reduz o tempo de execução em comparação com o envio síncrono de informações de monitoramento do AWS Lambda’s.

 

Melhores Práticas

O modelo de definição de preço para extensões é o mesmo do AWS Lambda, onde é cobrado pela solicitação, memória usada e tempo de execução. Nesse caso, o tempo paralelo da extensão não será cobrado, desde que seja menor ou igual à invocação do AWS Lambda. Quando o tempo de processamento for maior, a duração total do tempo de execução será cobrada. Sempre arredondando para o milissegundo mais próximo.

 

 

No momento deste blog, as extensões do AWS Lambda estavam em um estágio preliminar em que somente o tempo de execução será cobrado na fase de chamada. Embora mais tarde o faturamento de extensões passará por todas as fases da função onde elas são executadas (init, invoque e shutdown). Para obter a política de faturamento mais atualizada, consulte a seção Perguntas frequentes do AWS Lambda.

Para aproveitar os tempos de execução, tanto quanto possível, recomenda-se que você procure fazer as mudanças ambientais mais relevantes para a execução. Para extensões externas, use pacotes compilados em pacotes binários independentes que são mais eficientes para execução. Além de identificar os elementos para os quais o monitoramento será feito, evitando o envio de informações possivelmente não relevantes para o rastreamento de funções. Em conjunto com o fato de que as extensões podem ser executadas após a conclusão da função, é importante antes de implantar uma função com extensões para produção para verificar se estas terminam corretamente e evitar custos de funcionamento extras.

 

Características técnicas

Para fazer uso das extensões, você precisa levar em conta os seguintes recursos:

  • Extensões e recursos de compartilhamento de recursos, como CPU, memória, armazenamento e variáveis de ambiente.
  • As permissões atribuídas pelo IAM são compartilhadas pela função e pelas extensões.
  • Um máximo de 10 extensões podem ser configuradas.
  • O número máximo de camadas em execução simultânea é 5.
  • Várias extensões podem ser configuradas em uma única camada respeitando os limites acima.
  • O uso de extensões em conjunto com o código da função descompactada não pode exceder 250 MB.

 

Extensões do AWS Lambda e do AWS EFS  

Este exemplo usará um arquivo de configuração YAML que está em um Amazon EFS para carregar o local e o nome do arquivo para o qual os logs serão enviados com as informações de execução do AWS Lambda. Os logs são tratados por uma extensão externa do AWS Lambda.

Pré-requisitos

  • Para o funcionamento correto do exemplo, todos os serviços devem estar na mesma região. Neste exemplo, tudo será feito no N. Virgínia (US-east-1).
  • Você precisará de uma instância do AWS Cloud9 com Amazon Linux 2 para montar o AWS EFS.
  • As instâncias do AWS EFS, AWS Lambda e AWS Cloud9, criadas, devem compartilhar o mesmo security group para se comunicar corretamente.
  • Instale o manipulador NFS para Linux com sudo yum -y install nfs-utils
  • Instale a biblioteca boto3 com sudo python3 -m pip install boto3

Arquitetura final do exemplo

 

 

Passos de desenvolvimento

  1. Criamos e editamos o seguinte arquivo com o editor de sua escolha, neste caso usaremos o Linux nano editor:

nano ~/environment/efs_creation.py

  1. Nós adicionamos o seguinte código a ele. Que criará um sistema de arquivos no AWS EFS, seus destinos de montagem e no ponto de acesso.
#!/usr/bin/python3
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0
import boto3, json, random, time, subprocess, time

REGION_NAME = "us-east-1"

def get_default_vpc():
    """Get the default VPC id from the region and account"""
    client = boto3.client('ec2')
    vpcs = client.describe_vpcs(Filters=[{'Name' : 'isDefault', 'Values' : ['true',]}])
    vpcs_str = json.dumps(vpcs)
    resp = json.loads(vpcs_str)
    data = json.dumps(resp['Vpcs'])
    vpcs = json.loads(data)
    return(vpcs[0]["VpcId"])

def get_subnets_ids():
    """Get the subnet ids from the default VPC"""
    default_vpc = get_default_vpc()
    session = boto3.Session(region_name=REGION_NAME)
    ec2_resource = session.resource("ec2")
    ec2_client = session.client("ec2")
    subnet_ids = []
    for vpc in ec2_resource.vpcs.all():
        if vpc.id in default_vpc:
            for subnet in vpc.subnets.all():
                subnet_ids.append(subnet.id)
    subnets_ids = []
    for subnet in ec2_client.describe_subnets(SubnetIds=subnet_ids)["Subnets"]:
        subnets_ids.append(subnet.get("SubnetId"))
    return subnet_ids

def create_efs():
    """Create the EFS"""
    print("Creating EFS")
    client = boto3.client('efs', region_name=REGION_NAME)
    response = client.create_file_system(
        CreationToken=f'first-efs{random.random()}',
        PerformanceMode='generalPurpose',
        Encrypted=True,
        ThroughputMode='bursting',
        Tags=[{'Key': 'Name','Value': 'lambda-efs-test'}]
    )
    time.sleep(5)
    return response.get("FileSystemId")

def add_mounting_targets_and_access_point(efs_id):
    """Adds the mounting targets using each default subnet, ending with the creation of the access point"""
    print("Adding mounting targets")
    client = boto3.client('efs', region_name=REGION_NAME)
    subnets = get_subnets_ids()
    for subnet in subnets:
        client.create_mount_target(FileSystemId=efs_id, SubnetId=subnet)
    print("Adding access point")
    client.create_access_point(
        Tags=[{'Key': 'Name', 'Value': 'lambda-efs'}],
        FileSystemId=efs_id
    )
    
    while True:
        print("Waiting for mounting points creation...")
        mounting_targets = client.describe_mount_targets(FileSystemId=efs_id,)["MountTargets"]
        if all(target["LifeCycleState"].lower() in "available" for target in mounting_targets):
            break
        time.sleep(10)
            
def link_EFS(efs_id):
    bash_command = "mkdir efs"
    process = subprocess.Popen(bash_command.split(), stdout=subprocess.PIPE)
    process.communicate()

def main():
    efs_id = create_efs()
    add_mounting_targets_and_access_point(efs_id)
    print(f"Creation Completed.")
    link_EFS(efs_id)
    print(f"EFS linked. EFS link command: \nsudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport {efs_id}.efs.us-east-1.amazonaws.com:/ efs")

if __name__ == "__main__":
    main()
  1. Nós executamos o código com

sudo python3 ~/environment/efs_creation.py

  1. Vinculamos o AWS EFS ao nosso ambiente AWS Cloud9 com o comando que retorna o script. Que é semelhante ao seguinte:

sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-XXXXXXXX.efs.us-east-1.amazonaws.com:/ efs

  1. Executamos o seguinte comando que dará permissão de gravação ao nosso AWS Lambda

sudo chmod 777 efs

  1. Criamos uma pasta em nossa instância do AWS Cloud9 chamada Dentro da pasta, criamos duas outras pastas: extensions e python-example-extension . Comandos:

a. mkdir ~/environment/Extensions_dir

b. mkdir ~/environment/Extensions_dir/extensions

c. mkdir ~/environment/Extensions_dir/python-example-extension

 

 

  1. Criamos e editamos o arquivo com

nano ~/environment/Extensions_dir/extensions/python-example-extension

  1. O conteúdo será o seguinte script, que relaciona e invoca a extensão do AWS Lambda:
#!/bin/bash
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

set -euo pipefail
OWN_FILENAME="$(basename $0)"
LAMBDA_EXTENSION_NAME="$OWN_FILENAME" # (external) extension name has to match the filename
echo "${LAMBDA_EXTENSION_NAME} launching extension"exec "/opt/${LAMBDA_EXTENSION_NAME}/extension.py"
  1. Criamos e editamos um novo arquivo com

nano ~/environment/Extensions_dir/python-example-extension/extension.py

  1. O arquivo pyterá a implementação de extensão manipulando os eventos recebidos da API de tempo de execução. Seu código:
#!/usr/bin/env python3
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

import json
import datetime
import os
import requests
import signal
import sys
import datetime
import fcntl
from pathlib import Path


# GLOBAL VARIABLES
# extension name has to match the file's parent directory name
LAMBDA_EXTENSION_NAME = Path(__file__).parent.name
# Duration text string
DURATION = "DURATION"

def handle_signal(signal, frame):
    # if needed pass this signal down to child processes
    print(f"[{LAMBDA_EXTENSION_NAME}] Received signal={signal}. Exiting.", flush=True)
    sys.exit(0)

def register_extension():
    print(f"[{LAMBDA_EXTENSION_NAME}] Registering...", flush=True)
    headers = {
        'Lambda-Extension-Name': LAMBDA_EXTENSION_NAME,
    }
    payload = {
        'events': [
            'INVOKE',
            'SHUTDOWN'
        ],
    }
    response = requests.post(
        url=f"http://{os.environ['AWS_LAMBDA_RUNTIME_API']}/2020-01-01/extension/register",
        json=payload,
        headers=headers
    )
    ext_id = response.headers['Lambda-Extension-Identifier']
    print(f"[{LAMBDA_EXTENSION_NAME}] Registered with ID: {ext_id}", flush=True)

    return ext_id

def write_log(log_path, data_type, data):
    with open(log_path, 'a') as f:
        f.write(f"Lambda API: {os.environ['AWS_LAMBDA_RUNTIME_API']}, {data_type}: {data}\n")
    if data_type in DURATION:
        sys.exit(0)

def process_events(ext_id):
    headers = {
        'Lambda-Extension-Identifier': ext_id
    }
    time = datetime.datetime.now()
    log_path = "/mnt/efs/lambda_logs.txt"
    print("Downloaded data path for logs in EFS, path: ", log_path)
    while True:
        print(f"[{LAMBDA_EXTENSION_NAME}] Waiting for event...", flush=True)
        response = requests.get(
            url=f"http://{os.environ['AWS_LAMBDA_RUNTIME_API']}/2020-01-01/extension/event/next",
            headers=headers,
            timeout=None
        )
        event = json.loads(response.text)
        if event['eventType'] == 'INVOKE':
            print(f"[{LAMBDA_EXTENSION_NAME}] Received INVOKE event. Exiting.", flush=True)
            write_log(log_path, "INVOCATION ON DATE", datetime.datetime.now())
        elif event['eventType'] == 'SHUTDOWN':
            print(f"[{LAMBDA_EXTENSION_NAME}] Received SHUTDOWN event. Exiting.", flush=True)
            write_log(log_path, DURATION, datetime.datetime.now() - time)

def main():
    # handle signals
    signal.signal(signal.SIGINT, handle_signal)
    signal.signal(signal.SIGTERM, handle_signal)

    # execute extensions logic
    extension_id = register_extension()
    process_events(extension_id)


if __name__ == "__main__":
    main()
  1. Nós mudamos o diretório com

cd ~/environment/Extensions_dir/python-example-extension/

  1. Executamos o download de dependências com

pip3 install "requests==2.24.0" -t .

  1. Alteramos as permissões de execução da pasta

sudo chmod -R 777 ~/environment/Extensions_dir/

  1. Mudamos para o diretório Extensions_Dir com

cd ~/environment/Extensions_dir/ 

  1. Nós embalamos o conteúdo com

zip -r extension.zip .

  1. Publicamos esta camada com a extensão usando o seguinte comando. O comando retornará um ARN da extensão criada, copiá-lo em um editor de texto para uso posterior

aws lambda publish-layer-version \
 --layer-name "python-example-extension" \
 --region us-east-1 \
 --zip-file "fileb://extension.zip" \
 --query "LayerVersionArn" \
 --output text \
 --compatible-runtime python3.8

  1. Sem fechar a guia com nosso ambiente AWS Cloud9. Abrimos o console do IAM em uma nova guia. No painel lateral, selecionamos Funções e, em seguida, clique em Criar uma função.

 

 

  1. Nós escolhemos o caso de uso do Lambda e clique em Próximo: Permissões.

 

 

  1. Na barra de pesquisa, colocamos AWSLambdaBasicExecutionRole e selecione a política. Repetimos a etapa anterior, mas com o AmazonEC2fullAccess, selecione a política e clique em Próximo: Tags

 

 

  1. Ignoramos as tags add clicando em Próximo: Revisar

 

 

  1. Colocamos em nome do papel: AWSLambdaEFSExtension e clique em Criar função.

 

 

  1. Abrimos o console do AWS Lambda e criamos um novo recurso.

 

 

  1. Selecione Criar função a partir do zero, dê-lhe um nome, neste caso lambda-extension. Runtime Python 3.8, a função de execução: Use uma função existente e selecione AWSLambdaEFSExtension.

 

 

  1. Na seção Configurações avançadas, selecione a mesma AWS VPC do nosso AWS EFS. Selecionamos as sub-redes nas quais o AWS Lambda’s pode ser criado e o security group.

 

 

  1. Na parte inferior, clique em Criar uma função.

 

 

  1. Após a conclusão do processo de criação (pode levar até 5 minutos). Atribuímos a extensão ao AWS Lambda. Para isso, na tela principal do AWS Lambda, clicamos em Layers:

 

 

  1. Abaixo, você verá uma caixa como a seguinte e clique em Adicionar uma camada.

 

 

  1. Na próxima janela, selecionamos a opção para Especificar um ARN e no campo colocamos o ARN o que copiamos na etapa 14 para criar a extensão. Clique em Adicionar.

 

 

  1. Agora vamos vincular o AWS EFS ao nosso AWS Lambda, primeiro na tela principal do AWS Lambdapara, na parte inferior, procuramos a opção Sistema de arquivos e clique em Adicionar sistema de arquivos. Na nova tela do sistema de arquivos, selecionamos o sistema de arquivos que criamos, o ponto de acesso e o caminho de montagem (essa será a maneira como o AWS Lambda acessa os arquivos) /mnt/efs. Clique em Salvar.

 

 

  1. De volta à tela principal, procuramos a seção Configurações básicas e alteramos o tempo de execução máximo (Timeout) para 30 segundos e clique no botão Salvar:

 

 

31. Selecione Selecionar um evento de teste, na parte superior da configuração e, em seguida, Configurar eventos de teste.

 

 

  1. Selecionamos o modelo hello-world, atribuímos o nome do evento de teste e criamos.

 

 

  1. Nós testamos a extensão clicando em Testar.

 

 

  1. Se a configuração for bem-sucedida, devemos ver algo semelhante ao seguinte:

 

 

  1. Voltando à nossa instância do AWS Cloud9, mudamos para o diretório cd ~/environment/efs e, se a execução bem-sucedida, deve haver um arquivo chamado txt (aberto com nano lambda_logs.txt) com conteúdo semelhante ao seguinte.

 

 

  1. Parabéns você configurou seu AWS Lambda com extensões de gravação usando o Amazon EFS!

 

Extensões disponíveis para implantação

Este é um exemplo das múltiplas abordagens que poderiam ser dadas às extensões, mas muitos outros estudos de caso podem ser derivados do propósito inicial deles. Uma fonte de referência para implantações é o repositório de amostra no GitHub.

Vários parceiros da AWS desenvolveram extensões dentro de suas soluções para permitir a integração do monitoramento do AWS Lambda com plataformas existentes de muitas empresas e desenvolvedores. Alguns são:

Conclusão

O desenvolvimento usando o AWS Lambda tem os benefícios da abordagem sem servidor, oferecendo aos seus negócios uma grande agilidade no desenvolvimento de suas ideias, maior elasticidade e escalabilidade para atender às demandas de seus clientes, bem como uma melhor utilização de recursos. Além disso, você obtém as vantagens de monitoramento, a partir de suas execuções, centralizado usando suas extensões. Onde os benefícios das extensões incluem redução de custos por não exigir um daemon de execução longa para obter logs, ou menos uso de ferramentas intermediárias para o seu processo, como o AWS CloudWatch poderia ser.

As Extensões do AWS Lambda são ferramentas que visam facilitar a integração de ferramentas relacionadas ao monitoramento, segurança e governança, mantendo a abordagem sem servidor que distingue o AWS Lambda. Se você desenvolve suas extensões para configurar seu ambiente de execução e/ou monitoramento, ou usando ferramentas de terceiros. Recursos que mudarão e continuarão a se adaptar de acordo com as novas necessidades que os desenvolvedores têm.

 

 


Sobre os Autores

Servio Reyes é Arquiteto de Soluções na AWS México.

 

 

 

 

Iván González é Arquiteto de Soluções na AWS México.

 

 

 

 

Sobre os revisores

Arturo Velasco é um Arquiteto de Soluções Especializado em Mídia e Entretenimento.

 

 

 

 

Rodrigo Cabrera é Arquiteto de Soluções na AWS México.

 

 

 

 

Sobre o tradutor

Thiago Paiva Instrutor de Tech U.