Розгортаємо 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-файлу запишемо такий код:

версія: '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);
}

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

...
// Message to Publish 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.397 Z", "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 і спробувати взаємодія з ним в тестовому проекті, прошу в репозиторій .

Опубліковано: 10/01/20 @ 12:21
Розділ Різне

Рекомендуємо:

Асинхронність в C#. Руйнування легенд
Чи є життя після macOS, або Як я переїхав на Linux десктоп і не шкодую
Коли в добі досить годин, або Чому варто навчитися грамотному плануванню
DOU Hobby: авіамоделювання - від розробки моделі літака до запуску в небо
iOS дайджест #35: курс Combine, Redux + SwiftUI, Vapor 4