Разворачиваем AWS для разработки локально на базе LocalStack

Сейчас все больше компаний уходит в облака для запуска своих приложений. Мы в компании Namecheap не стали исключением и уже довольно долго используем сервисы AWS. В связи с этим перед нами встала задача упростить работу с сервисами AWS в условиях локальной разработки. Как приблизить локальное окружение к условиям прода?

В этой статье мы с вами поднимем небольшой проект, который будет взаимодействовать со стабами сервисов AWS, таких как: DynamoDB, SNS/SQS и S3.

Одним из самых распространённых решений для стабов сервисов AWS является LocalStack. Ранее этот проект разрабатывался Atlassian, но теперь брошен в дикий open-source и монетизируется за поддержку ряда дополнительных сервисов и саппорт.

TL; DR

  1. Поднимаем LocalStack при помощи docker-compose.
  2. Переключаем проект на эндпоинт сервиса LocalStack.

Холодный старт на Windows

Самый простой путь развернуть LocalStack локально — запустить его при помощи Docker Compose.

Для начала нам нужно установить рабочую среду разработчика Docker for Windows. Установка и настройка этого инструмента выходит за пределы статьи, так что оставлю вам ссылочку на хороший официальный мануал.

В содержимое docker-compose-файла запишем такой код:

version: '2.1'
services:
localstack:
image: localstack/localstack:latest
ports:
- "4567-4584:4567-4584"
- "8080:8080"
volumes:
- "//var/run/docker.sock:/var/run/docker.sock"
environment:
- SERVICES=dynamodb
- PORT_WEB_UI=8080
- DOCKER_HOST=unix:///var/run/docker.sock

Осталось только поднять docker-compose-сервис:

docker-compose -f docker-compose.yml up -d localstack

Обратите внимание на установленную переменную окружения SERVICES. С ее помощью сейчас включён сервис DynamoDB. Чтобы включить другие сервисы, настроить Debug-трейсы и кое-что ещё, настоятельно рекомендую взглянуть в мануал.

Если у вас вылетает ошибка о занятом порте по типу такой, как описана ниже, можно убрать из файла порты неиспользуемых сервисов или перебиндить на другой порт.

docker: Error response from daemon: driver failed programming external connectivity on endpoint localstack_main (a156a7ce6d590937504c17b1f37f4634e7eaec09a9f8ba20cdf37b94424db39f): Error starting userland proxy: listen tcp 0.0.0.0:8080: bind: address already in use.

На одной из испытуемых систем это выглядело как-то так:

...
ports:
- "4567-4584:4567-4584"
- "9090:8080"
...
environment:
- PORT_WEB_UI=9090
...

Можно попробовать запустить LocalStack, как по мануалу — localstack start —docker. Но есть ряд минусов. Во-первых, вам придётся установить окружение Python, для того чтобы при помощи pip установить LocalStack. А во-вторых, вам понадобится либо установить докер, либо установить Java-окружение, для того чтобы заработали некоторые стабы сервисов.

Работа с DynamoDB

Итак, у нас уже запустился и работает LocalStack. Теперь мы можем проверить работоспособность и заодно подготовить сервисы, с которыми будем работать. Для настройки этих сервисов придётся использовать AWS CLI. Надеюсь, он уже у вас установлен. Для того, чтобы подключиться к нашим сервисам, нужно будет указать в конце команды кастомный эндпоинт при помощи следующего параметра —endpoint-url=http://localhost:4578, где номер порта мы можем взять из таблицы официального мануала.

Для начала проверим, что скажет LocalStack о состоянии таблиц:

aws dynamodb list-tables --endpoint-url=http://localhost:4569
{
    "TableNames": []
}

После чего создадим таблицу:

aws dynamodb create-table --table-name Todo \
--key-schema AttributeName=Id,KeyType=HASH --attribute-definitions AttributeName=Id,AttributeType=N AttributeName=Name,AttributeType=S \
--provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 --endpoint-url=http://localhost:4569 

И ещё раз взглянем в lLocalStack на список. Он покажет только что созданную таблицу:

aws dynamodb list-tables --endpoint-url=http://localhost:4569
{
    "TableNames": [
        "Todo"
    ]
}

Ну что же поздравляю, теперь у вас есть свой первый локальный сервис амазона.

HINT: Для тех, кому нужно сетапать инфраструкту не в ручном режиме, а с помощью терраформ, есть отличный механизм сделать это, задав маппинг ендпоинтов в модуле AWS:

provider "aws" {
    skip_credentials_validation = true
    skip_metadata_api_check = true
    s3_force_path_style = true
    access_key = "mock_access_key"
    secret_key = "mock_secret_key"
    endpoints {
        dynamodb = "http://localhost:4569"
    }
}

Чуть больше инфы по этому вопросу можно взять здесь.

Теперь давайте попробуем подключиться к DynamoDB из нашего тестового приложения. В любимой IDE создаём консольное dotnet core приложение. И сразу же устанавливаем пакет AWSSDK.DynamoDBv2. Общие правила для большинства подключения к сервисам LocalStack:

  1. Переключаемся на использование HTTP-протокола (LocalStack из коробки работает через HTTP, хотя и поддерживает https).
  2. Устанавливаем ServiceURL на порт этого стаба.

Давайте настроим подключение:

var clientConfig = new AmazonDynamoDBConfig()
{
    UseHttp = true,
    ServiceURL = "http://localhost:4569"
};

_dynamoClient = new AmazonDynamoDBClient(clientConfig);

После этого мы можем положить в нашу таблицу первое значение:

var putItemRequest = new PutItemRequest()
{
    TableName = TableName,
    Item = 
    {
        {
            "Id", new AttributeValue() { N = "42"}
        },
        {
            "Name", new AttributeValue() {S = "Get Up Early"}
        }
    }
};
await _dynamoClient.PutItemAsync(putItemRequest);

Можем проверить, что же сохранилось в LocalStack следующей командой:

aws dynamodb get-item --table-name Todo --key '{"Id":{"N":"42"}}' --endpoint-url=http://localhost:4569
{
    "Item": {
        "Id": {
            "N": "42"
        },
        "Name": {
            "S": "Get Up Early"
        }
    }
}

Добавляем SNS & SQS

Представим, что теперь нам нужно добавить SNS и SQS. Начнём с SNS. Для начала включим сервис и создадим топик. Для этого в compose-файле добавим в переменную окружения SERVICES разделённые запятой имена сервисов, как это сделано ниже:

...
environment:
- SERVICES=dynamodb,sns,sqs,s3
...

и перезапускаем его, чтобы подтянулись эти значения, следующей командой:

docker-compose -f docker-compose.yml restart localstack

В проект добавим nuget-пакеты AWSSDK.SimpleNotificationService и AWSSDK.SimpleNotificationService, для того чтобы получить возможность взаимодействовать с этими сервисами.

Как и для предыдущего случая настраиваем подключения:

var snsConfig = new AmazonSimpleNotificationServiceConfig()
{
    UseHttp = true,
    ServiceURL = "http://localhost:4575"
};

snsClient = new AmazonSimpleNotificationServiceClient(snsConfig);

var sqsConfig = new AmazonSQSConfig()
{
    UseHttp = true,
    ServiceURL = "http://localhost:4576"
};

sqsClient = new AmazonSQSClient(sqsConfig);

Теперь мы можем работать с этими двумя сервисами локально. Давайте сразу же создадим очередь и топик и подпишемся очередью на него:

private void CreateQueue()
{
    var queueCreationResult = await sqsClient.CreateQueueAsync("MyQueue");
    var queueUrl = queueCreationResult.QueueUrl;
    var topicCreationResult = await snsClient.CreateTopicAsync(new  CreateTopicRequest("TopicName"));
    var topicArn = topicCreationResult.TopicArn;
    var subscribeRequest = new SubscribeRequest(topicArn, "sqs", queueUrl);
    var subscribeResponse = await snsClient.SubscribeAsync(subscribeRequest);
}

В тестовых целях отправим в топик оповещение и вычитаем его из очереди:

...
// Publish message to topic
var request = new PublishRequest
{
    TopicArn = topicArn,
    Message = "Test Message"
};

await snsClient.PublishAsync(request);

...
// Read message from queue
var result = await sqsClient.ReceiveMessageAsync(queueUrl);
foreach (var message in result.Messages)
{
    Console.WriteLine(message.Body);
}
...

В консоли мы видим следующее:

{"MessageId": "e4e6ef59-107a-479d-952d-2a9b9e2da15c", "Type": "Notification", "Timestamp": "2019-10-05T13:27:36.397Z", "Message": "hello", "TopicArn": "arn:aws:sns:eu-west-3:000000000000:test"}

Это говорит о том, что всё успешно работает.

Сервис S3

Давайте примемся за самый используемый сервис — S3. Так как ранее мы его уже включили, можем оставить compose-файл в покое.

Устанавливаем nuget AWSSDK.S3 и создаём следующий конфиг для использования LocalStack-овского S3. Ничего нового — HTTP и кастомный порт, на котором крутится сервис:

var clientConfig = new AmazonS3Config()
{
    UseHttp = true,
    ServiceURL = "http://localhost:4572"
};
s3Client = new AmazonS3Client(clientConfig);

Давайте посмотрим, как этот сервис работает. Для этого создадим ведёрко и зальём на него файл.

await s3Client.PutBucketAsync(BucketName);
var putRequest = new PutObjectRequest()
{
    BucketName = BucketName,
    Metadata = { ["x-amz-meta-title"] = "Title" },
    FilePath = Path.GetFileName(FileName),
    ContentType = "text/plain"
};
await s3Client.PutObjectAsync(putRequest);

Можем взглянуть на его содержимое:

var result = await s3Client.ListObjectsAsync(BucketName);
foreach (var s3Object in result.S3Objects)
{
    Console.WriteLine(s3Object.Key);
}

Попробуем скачать:

using (GetObjectResponse response = await s3Client.GetObjectAsync(BucketName, FileName))
using (Stream responseStream = response.ResponseStream)
using (StreamReader reader = new StreamReader(responseStream))
{
    Console.WriteLine("Object metadata, Title: {0}", response.Metadata["x-amz-meta-title"]);
    Console.WriteLine("Content type: {0}", response.Headers["Content-Type"]);
    Console.WriteLine(reader.ReadToEnd());
}

Осталось только подчистить за собой состояние сервиса:

await s3Client.DeleteObjectAsync(BucketName, FileName);
await s3Client.DeleteBucketAsync(BucketName);

Выводы

Это всё! Вот так мы подключили приложение, настроили и поработали со стабами трёх сервисов AWS — DynamoDB, SNS/SQS и S3. Теперь, зная, как пользоваться этим инструментом, мы можем вести разработку приложения локально, а не реальный демо-аккаунт AWS. Это даёт нам возможность с самого начала разработки задать высокий уровень development experience. Всем, кому интересно чуть больше поиграться с LocalStack и попробовать взаимодействие с ним в тестовом проекте, прошу в репозиторий.

Похожие статьи:
У Міністерстві економіки обговорюють можливості, як зробити ФОП і гіг-спеціалістів доступними для бронювання від мобілізації. Про...
От редакции:В рубрике DOU Проектор все желающие могут презентовать свой продукт (как стартап, так и ламповый pet-проект). Если вам есть...
У вересні 2022 року ми провели традиційне опитування щодо того, як українські ІТ-спеціалісти шукають роботу, чи стежать...
Привіт! Мене звати Богдан, і я продакт-менеджер у компанії Ring, де працюю над R&D нових технологій, що стануть...
Привет! Меня зовут Андрей Губский, и теперь я буду готовить выпуски .NET дайджеста вместо Андрея Литвинова,...
Яндекс.Метрика