- Что вы увидите в этой статье
- Что мы собираемся делать
- Определение нашего POC (Proof of Concept)
- Репозиторий
- TL;DR
- Настройка среды:
- Запуск приложения:
- Убийство приложения:
- Расширенная версия: все необходимое для создания собственного POC
- Настройка среды
- Docker
- Docker Compose
- Python
- AWS CLI
- LocalStack
- Terraform
- NodeJs
- Облако AWS
- LocalStack
- Terraform
- API
- Лямбды
- Очередь SQS
- DynamoDB
- Код
- Запуск нашего приложения
- Заключение
Что вы увидите в этой статье
- Сервисы AWS: APIGateway, Lambda, DynamoDB
- Сообщество LocalStack
- NodeJs
- Docker Compose
- Terraform
Что мы собираемся делать
Определение нашего POC (Proof of Concept)
Целью этой статьи является демонстрация использования LocalStack и того, что необходимо для запуска простого проекта. Помимо того, что мы используем здесь, инструмент имеет множество других функций, а также другие услуги, предлагаемые AWS, которые я не буду рассматривать.
Поэтому наш POC очень прост. Это решение, включающее APIGateway, который получает запросы на поиск, составление списка и создание сообщений известных злодеев. Запросы на создание обрабатываются Lambda, созданной на NodeJs, которая сохраняет эти сообщения в очереди SQS, которая, в свою очередь, имеет триггер для другой Lambda, чтобы получить эту фразу и сохранить ее в базе данных DynamoDB. Для перечисления и детализации сообщения мы используем другую лямбду, которая извлекает сообщение из базы данных и возвращает его пользователю.
Вся конфигурация приложения была выполнена с помощью Terraform с конфигурационными файлами, очень простыми для понимания.
Репозиторий
Весь этот проект и полные коды доступны в следующем репозитории. Этот текст содержит некоторые
фрагменты кода, которые могут быть неполными.
TL;DR
Для тех, кто хочет получить быстрый пошаговый инструктаж о том, как проверить и запустить этот POC, продолжайте в этой сессии. Если вы предпочитаете достаточно подробное объяснение решений и способов достижения результата проекта, переходите к следующему занятию.
Настройка среды:
- Клонируйте проект из репозитория;
- Установите Docker, руководствуясь документацией;
- Установите docker-compose:
sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- Установите Python, следуя официальной документации;
- Установите AWS CLI: официальная страница;
- Установите Localstack:
sudo python3 -m pip install localstack
. - Установите awscli-local:
pip install awscli-local
. - Следуйте официальному руководству для установки Terraform;
- NodeJs:
- NVM: официальный репозиторий
- NodeJs: команда, описанная в репозитории
Запуск приложения:
- В корневом каталоге выполните команду:
docker-compose up -d
. - В каталоге terraform запустите:
terraform init
terraform apply --auto-approve
- Для проверки:
- Установите расширение REST Client для VSCode;
- Откройте файл
test.http
. - Измените переменную
API_ID
на id, введенный в выводе terraform; - Нажмите кнопку отправить запрос, чтобы отправить POST-запрос;
- Проверьте службы и журналы вашего приложения на сайте https://app.localstack.cloud/;
- Проверьте через несколько секунд, что предложение сохранено в базе данных, отправив GET-запрос с вашим любимым злодеем.
Убийство приложения:
- Используйте команду
terraform destroy --auto-approve
из каталога terraform, чтобы удалить службы AWS - Из корневого каталога используйте команду
docker-compose down -v
, чтобы удалить все ресурсы, созданные с помощью docker-compose.
Расширенная версия: все необходимое для создания собственного POC
На предыдущем занятии мы выполнили быстрый пробный запуск приложения. На этом занятии мы более подробно расскажем обо всем, что необходимо для создания собственного POC с нуля.
Настройка среды
Я использую машину под управлением Ubuntu 20.04.3 LTS. Все, что сделано здесь, также возможно при использовании машины Windows или Mac. Однако методы установки меняются.
Docker
Давайте начнем с установки Docker. Я точно следовал документации на официальной странице для установки на Ubuntu. Вы можете проверить версию, установленную в вашем окружении, с помощью команды docker -v
. Для меня он возвращает: Docker версии 20.10.12, сборка e91ed57.
.
Docker Compose
Docker Compose было немного сложнее установить. Страница Docker указывает на очень старую версию compose. Я предпочел зайти в репозиторий github, чтобы проверить и изменить адрес в команде, указанной на странице Docker. Таким образом, я выполнил следующие команды:
sudo curl -L "https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
Сейчас у меня установлена версия docker-compose:
leandro@leandro-desktop ~> docker-compose -v
Docker Compose version v2.2.3
Python
LocalStack использует компилятор Python. Поэтому мне нужно было установить его в дополнение к менеджеру пакетов. Я следовал официальной документации. Я установил следующие версии:
leandro@leandro-desktop:~$ python3 --version
Python 3.8.10
leandro@leandro-desktop:~$ pip --version
pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)
AWS CLI
Чтобы выполнять команды в терминале для доступа к данным из служб AWS, даже в LocalStack, необходимо использовать AWS CLI. Для его установки я следовал пошаговой инструкции на официальной странице. Проверив версию, я получил:
leandro@leandro-desktop ~> aws --version
aws-cli/2.4.11 Python/3.8.8 Linux/5.4.0-94-generic exe/x86_64.ubuntu.20 prompt/off
LocalStack
Для установки LocalStack я попытался следовать официальному репозиторию, в котором говорится, что его не следует устанавливать с помощью sudo, но это не сработало. Я установил его с помощью следующей команды:
sudo python3 -m pip install localstack
и версия, которую я установил:
leandro@leandro-desktop:~$ LocalStack --version
0.13.3.1
Я также установил awscli-local с помощью команды pip install awscli-local
.
Terraform
Terraform – это инструмент инфраструктурного кода или инфраструктура как код. Он имеет открытый исходный код и поддерживается компанией HashCorp. Чтобы установить его, я следовал официальному руководству. После установки я проверил версию на:
leandro@leandro-desktop:~$ terraform -v
Terraform v1.1.3
on linux_amd64
NodeJs
Выбор NodeJs для этого учебника был вполне конкурентным по отношению к Python. Но это утяжеляет знание и возможность повторного использования JavaScript в front-end. Вот очень интересный анализ возможных языков для разработки AWS Lambdas.
Для установки Node в своей среде я решил использовать NVM, менеджер версий NodeJs. Этот менеджер можно загрузить из официального репозитория. После установки NVM просто выполните команду, описанную в репозитории.
При этом версия Node на моей машине была:
leandro@leandro-desktop:~$ node -v
v16.13.2
leandro@leandro-desktop:~$ npm -v
8.1.2
Облако AWS
Я решил использовать следующие службы AWS:
- API-шлюз: позволяет создавать конечные точки и связывать их с бэк-эндом.
- Cloudwatch: позволяет отслеживать работу приложения с помощью сигналов тревоги и журналов.
- Lambda: позволяет выполнить функцию без необходимости предоставления или управления сервером.
- DynamoDB: нереляционная база данных NoSQL от AWS.
- SQS – Simple Queue Service: Как видно из названия, это служба очередей сообщений.
Выбор основывается на личных потребностях и на том, что LocalStack предлагает бесплатно.
LocalStack
Чтобы запустить наш проект, необходимо загрузить LocalStack в контейнер с подходящими для него конфигурациями. Для этого я использовал docker-compose и создал скрипт, следуя официальной странице. Кроме того, я пытался понять и изменить некоторые моменты согласно документации по конфигурации. После этого мой docker-compose
выглядел следующим образом:
version: "3.8"
services:
localstack:
# Determino o nome do container principal da aplicação.
container_name: ${LOCALSTACK_DOCKER_NAME-localstack-terraform-tutorial}
image: localstack/localstack
network_mode: bridge
ports:
- "127.0.0.1:4566:4566"
- "127.0.0.1:4571:4571"
environment:
# Como eu resolvi trocar o nome do container principal eu preciso informar em uma
# variável de ambiente.
- MAIN_CONTAINER_NAME=${LOCALSTACK_DOCKER_NAME-localstack-terraform-tutorial}
# Informo os serviços da AWS que desejo usar.
- SERVICES=${SERVICES-dynamodb,lambda,apigateway,sqs,cloudwatch}
# Diretório para salvar dados localmente.
- DATA_DIR=${DATA_DIR-tmp/localstack/data}
# Como nossas funções lambda serão executadas. Nesse caso escolho o padrão
# rodar as funções dentro de containers para cada uma.
- LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR-docker}
# Esse parâmetro diz respeito a como a função será passada para o container.
# Se ela será montada no container ou será copiada. Nesse caso, escolhi copiar
# todo o arquivo zip para dentro do container.
- LAMBDA_REMOTE_DOCKER=true
volumes:
- "${TMPDIR:-/tmp}/localstack:/tmp/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
Чтобы запустить docker-compose, я использовал команду docker-compose up
, она вызовет всю среду. Если вы хотите продолжать использовать тот же терминал для других целей, добавьте -d
detach. Чтобы завершить работу и избавиться от всего окружения, просто выполните команду docker-compose down -v
. Символ -v
говорит вам, что вы также хотите, чтобы созданные тома были удалены, освободив все ресурсы компьютера.
После запуска вы можете проверить, что все работает правильно, через URL http://localhost:4566/health и приборную панель, предоставляемую LocalStack.
Terraform
Теперь мы предоставим сервисы и их настройки через Terraform, указав ресурсы в файлах .tf
, которые я поместил в папку terraform
.
Следуя документации LocalStack, мы сначала объявляем провайдера "aws"
:
provider "aws" {
access_key = "test"
secret_key = "test"
region = "us-east-1"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
apigateway = var.default_endpoint
cloudwatch = var.default_endpoint
dynamodb = var.default_endpoint
iam = var.default_endpoint
lambda = var.default_endpoint
sqs = var.default_endpoint
}
}
Обратите внимание, что необходимо указать службы AWS на LocalStack. Здесь я предпочел создать переменную default_endpoint
для хранения адреса:
variable "default_endpoint" {
description = "Endpoint padrão para os serviços AWS local."
default = "http://localhost:4566"
type = string
}
API
Декларация api, функция котировок и методы довольно просты для понимания. И есть два способа сделать это. Первый – объявить блоки для каждого api, ресурса, интеграции, метода:
# Declarando nossa api para acesso de frases e os métodos
resource "aws_api_gateway_rest_api" "quotes" {
name = "Quotes"
description = "Api para consumo e envio de frases para a aplicação."
}
resource "aws_api_gateway_resource" "quotes" {
rest_api_id = aws_api_gateway_rest_api.quotes.id
parent_id = aws_api_gateway_rest_api.quotes.root_resource_id
path_part = "quotes"
}
resource "aws_api_gateway_method" "get_quotes" {
rest_api_id = aws_api_gateway_rest_api.quotes.id
resource_id = aws_api_gateway_resource.quotes.id
http_method = "GET"
authorization = "NONE"
}
resource "aws_api_gateway_method" "post_quote" {
rest_api_id = aws_api_gateway_rest_api.quotes.id
resource_id = aws_api_gateway_resource.quotes.id
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "quote_receiver" {
rest_api_id = aws_api_gateway_rest_api.quotes.id
resource_id = aws_api_gateway_resource.quotes.id
http_method = aws_api_gateway_method.post_quote.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.quote_receiver.invoke_arn
}
resource "aws_api_gateway_deployment" "quotes" {
rest_api_id = aws_api_gateway_rest_api.quotes.id
stage_name = "dev"
}
Более интересный способ сделать это – использовать OpenAPI для объявления нашего api. Это значительно упрощает наш файл rest-api.tf
:
resource "aws_api_gateway_rest_api" "quotes" {
name = "Quotes API"
body = templatefile("./openapi.json",
{
quote_receiver = "${aws_lambda_function.quote_receiver.invoke_arn}",
quote_recover = "${aws_lambda_function.quote_recover.invoke_arn}"
}
)
}
resource "aws_api_gateway_deployment" "quotes" {
rest_api_id = aws_api_gateway_rest_api.quotes.id
}
resource "aws_api_gateway_stage" "quotes" {
deployment_id = aws_api_gateway_deployment.quotes.id
rest_api_id = aws_api_gateway_rest_api.quotes.id
stage_name = "dev"
}
Я предпочитаю объявлять OpenApi с помощью yaml, но по какой-то причине Terraform не принимает yaml в определении тела. Поэтому я установил расширение openapi-designer, которое компилирует yaml-файлы в один json-файл. Мое определение API выглядит следующим образом:
openapi: 3.0.3
info:
title: Quotes Api
description: Api para consumo e envio de frases para a aplicação.
version: "1.0"
paths:
/quotes:
post:
summary: Permite gravar uma nova frase vilanesca!
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Quote"
x-amazon-apigateway-auth:
type: none
x-amazon-apigateway-integration:
uri: ${quote_receiver}
httpMethod: POST
type: AWS_PROXY
responses:
"201":
description: Frase vilanesca gravada com sucesso!
get:
summary: Retorna as frases vilanesca de um vilão.
parameters:
- name: author
in: query
required: true
description: O grade vilão por trás das frases.
schema:
type: string
x-amazon-apigateway-auth:
type: none
x-amazon-apigateway-integration:
uri: ${quote_recover}
httpMethod: POST
type: AWS_PROXY
responses:
"200":
description: As frases vilanescas para o vilão selecionado.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Quote"
Поэтому наш API имеет ресурс quote
, который доступен по пути /quotes
и позволяет использовать методы POST
и GET
, не требуя авторизации доступа.
Как мы видели на нашей диаграмме, цель проекта заключается в том, чтобы предложения отправлялись в очередь функцией Lambda, а затем извлекались другой функцией и сохранялись в базе данных. Здесь мы уже объявили интеграцию с лямбда-функцией. Стоит отметить:
- Обратите внимание, что интеграция теперь передается как часть метода через свойство
x-amazon-apigateway-integration
. То же самое для авторизации с помощью свойстваx-amazon-apigateway-auth
. - Terraform обрабатывает файл методом templatefile, который обменивает значения типа
${quote_receiver}
на значение, переданное в качестве параметра. - В приведенном выше yaml-файле обратите внимание, что отсутствует часть
components
, которую можно найти в репозитории.
Лямбды
Чтобы получить сообщение от API, мы объявляем в нашем lambda.tf
:
# Lambdas para processar as frases
data "archive_file" "quote_receiver" {
type = "zip"
output_path = "../lambdas/dist/quote_receiver.zip"
source_dir = "../lambdas/quote-receiver/"
}
resource "aws_lambda_function" "quote_receiver" {
function_name = "quote_receiver"
filename = data.archive_file.quote_receiver.output_path
source_code_hash = data.archive_file.quote_receiver.output_base64sha256
handler = "index.handler"
runtime = "nodejs14.x"
role = "fake_role"
environment {
variables = {
SQS_URL = "${resource.aws_sqs_queue.quotes.url}"
}
}
}
---
resource "aws_lambda_event_source_mapping" "quotes" {
event_source_arn = aws_sqs_queue.quotes.arn
function_name = aws_lambda_function.quote_persister.arn
}
Здесь мы генерируем файл, который был создан при выполнении terraform в результате сжатия данных внутри папки lambdas/quote-receiver
. На сжатый файл ссылаются при создании функции. То же самое справедливо и для других наших функций – сохранения и извлечения данных.
Как видите, у нас есть сопоставление функции quote_persister
с очередью SQS
. Это позволяет нам получать события с информацией, вставленной в очередь, всякий раз, когда в нее добавляются новые сообщения.
Интересным моментом является возможность передачи переменных окружения в функцию, как это видно при передаче переменной SQS_URL
.
Очередь SQS
Объявление очереди также очень просто:
resource "aws_sqs_queue" "quotes" {
name = "Quotes"
}
DynamoDB
Для создания новой таблицы в dynamo требуются только следующие поля:
resource "aws_dynamodb_table" "quotes" {
name = "Quotes"
hash_key = "author"
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "author"
type = "S"
}
}
Мы могли бы ввести другие атрибуты, но только hash_key
является обязательным.
Для AWS этот атрибут эквивалентен partition key
. Если бы я хотел создать sort key
, я должен был бы передать его как
от того же автора.
Код
У нас есть только три очень простые функции, написанные с использованием NodeJs: одна для получения котировок, одна для сохранения и одна для получения. Полный код находится в репозитории, но
стоит обратить внимание на некоторые детали:
const { DynamoDBClient, BatchWriteItemCommand } = require("@aws-sdk/client-dynamodb");
const { Marshaller } = require("@aws/dynamodb-auto-marshaller");
const client = new DynamoDBClient({ endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:4566` });
const marshaller = new Marshaller();
exports.save = (quotes) => client.send(new BatchWriteItemCommand({
RequestItems: {
"Quotes": quotes
.map(quote => marshaller.marshallItem(quote))
.map(item => new Object({ PutRequest: { Item: item } }))
}
}));
- Код для персистенции данных в Dynamo демонстрирует использование SDK для JavaScript V3.
- В отличие от V2, эта версия позволяет импортировать только те модули, которые действительно понадобятся для приложения, что делает Lambda намного легче.
- Необходимо настроить конечную точку для служб AWS.
- Я использовал библиотеку
Marshaller
, которая является связующим звеном между собственными значениями JavaScript и AttributeValues из DynamoDB. - Лучший способ просмотра журналов приложений – это панель LocalStack.
Запуск нашего приложения
Как только среда будет запущена throughcker-compose, мы можем перейти в папку terraform
и запустить команду terraform init
. Эта команда создаст папку .terraform
и другие файлы в проекте. После завершения работы наступает очередь команды terraform apply --auto-approve
, которая фактически обеспечивает все ресурсы, которые мы объявили в наших файлах .tf
. В конце команда предоставит в качестве возврата API_ID
, необходимый нам для тестирования api в http-клиенте.
Как только приложение будет запущено, мы можем протестировать его функциональность с помощью коллекции запросов, доступных в файле test.http
. Для запуска тестов на этом файле необходимо установить расширение REST Client для Visual Studio Code. После установки измените значение переменной @API_ID на то, которое возвращает terraform, и нажмите кнопку отправить запрос.
Чтобы завершить работу нашего приложения, просто выполните команду docker-compose down -v
из корневого каталога проекта.
Заключение
LocalStack – это отличный инструмент для запуска приложения в локальной среде во время разработки. Это устраняет беспокойство о
испортить среду, разделяемую большим количеством разработчиков, или понести финансовые расходы за неправильное использование AWS, значительно улучшив процесс создания кода. Эти функции становятся еще более
интересно, когда вы являетесь стартапом Cloud, потому что нет риска заплатить за то, что было между строк.
Один из аспектов, который не был освещен, но заслуживает внимания, – это библиотеки LocalStack и интеграция с интеграционным тестированием. Можно проводить автоматизированные тесты, не нагружая облако.