LocalStack: мощь AWS прямо из вашей машины


Что вы увидите в этой статье

  1. Сервисы AWS: APIGateway, Lambda, DynamoDB
  2. Сообщество LocalStack
  3. NodeJs
  4. Docker Compose
  5. Terraform

Что мы собираемся делать

Определение нашего POC (Proof of Concept)

Целью этой статьи является демонстрация использования LocalStack и того, что необходимо для запуска простого проекта. Помимо того, что мы используем здесь, инструмент имеет множество других функций, а также другие услуги, предлагаемые AWS, которые я не буду рассматривать.

Поэтому наш POC очень прост. Это решение, включающее APIGateway, который получает запросы на поиск, составление списка и создание сообщений известных злодеев. Запросы на создание обрабатываются Lambda, созданной на NodeJs, которая сохраняет эти сообщения в очереди SQS, которая, в свою очередь, имеет триггер для другой Lambda, чтобы получить эту фразу и сохранить ее в базе данных DynamoDB. Для перечисления и детализации сообщения мы используем другую лямбду, которая извлекает сообщение из базы данных и возвращает его пользователю.

Вся конфигурация приложения была выполнена с помощью Terraform с конфигурационными файлами, очень простыми для понимания.

Репозиторий

Весь этот проект и полные коды доступны в следующем репозитории. Этот текст содержит некоторые
фрагменты кода, которые могут быть неполными.

TL;DR

Для тех, кто хочет получить быстрый пошаговый инструктаж о том, как проверить и запустить этот POC, продолжайте в этой сессии. Если вы предпочитаете достаточно подробное объяснение решений и способов достижения результата проекта, переходите к следующему занятию.

Настройка среды:

  1. Клонируйте проект из репозитория;
  2. Установите Docker, руководствуясь документацией;
  3. Установите docker-compose:
    1. 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
    2. sudo chmod +x /usr/local/bin/docker-compose
  4. Установите Python, следуя официальной документации;
  5. Установите AWS CLI: официальная страница;
  6. Установите Localstack: sudo python3 -m pip install localstack.
  7. Установите awscli-local: pip install awscli-local.
  8. Следуйте официальному руководству для установки Terraform;
  9. NodeJs:
    1. NVM: официальный репозиторий
    2. NodeJs: команда, описанная в репозитории

Запуск приложения:

  1. В корневом каталоге выполните команду: docker-compose up -d.
  2. В каталоге terraform запустите:
    1. terraform init
    2. terraform apply --auto-approve
  3. Для проверки:
    1. Установите расширение REST Client для VSCode;
    2. Откройте файл test.http.
    3. Измените переменную API_ID на id, введенный в выводе terraform;
    4. Нажмите кнопку отправить запрос, чтобы отправить POST-запрос;
    5. Проверьте службы и журналы вашего приложения на сайте https://app.localstack.cloud/;
    6. Проверьте через несколько секунд, что предложение сохранено в базе данных, отправив GET-запрос с вашим любимым злодеем.

Убийство приложения:

  1. Используйте команду terraform destroy --auto-approve из каталога terraform, чтобы удалить службы AWS
  2. Из корневого каталога используйте команду 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. Таким образом, я выполнил следующие команды:

  1. 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
  2. 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 и интеграция с интеграционным тестированием. Можно проводить автоматизированные тесты, не нагружая облако.

Оцените статью
Procodings.ru
Добавить комментарий