LocalStack, або Як налаштувати локальне AWS-середовище

Усі статті, обговорення, новини про DevOps — в одному місці. Підписуйтеся на DOU | DevOps!

Усім привіт, мене звати Богдан, я Intermediate Software Engineer у компанії VITech. З Amazon Web Services (AWS) я познайомився два роки тому, з того часу активно працюю з багатьма їхніми сервісами кожен день.

AWS надає дуже потужні інструменти, але є один нюанс — локальне середовище з використанням цих сервісів. Звісно, ми можемо використовувати щось на зразок відокремленого dev/sandbox середовища, але й тут не без проблем: швидкість отримання відгуку щодо нових змін, ціна, використання багатьма розробниками одночасно, обмежені можливості модифікації середовища.

Саме тут на допомогу приходить LocalStack. Що ж це таке та як його використовувати?

LocalStack — це локальний емулятор AWS, написаний на Python, який дає змогу працювати відокремлено та без підключення до інтернету.

Як використовувати

Є дві можливості запуску та використання:

  • localstack cli
python3 -m pip install --upgrade localstack
localstack start
  • docker
docker run -d -p 4566:4566 localstack/localstack:2.2.0

Особисто я надаю перевагу docker-у, тому, що він незалежний щодо операційної системи та не вимагає встановлення додаткових речей, а ще можна зробити docker-compose та використовувати одну конфігурацію запуску на всьому проєкті. На відміну від docker-у, localstack cli використовує Python та вимагає попереднього встановлення.

Якщо ми вже згадали docker-compose, то розгляньмо приклад:

version: '3.9'

services:
  localstack:
    container_name: localstack
    image: localstack/localstack:2.2.0
    network_mode: bridge
    ports:
      - "4566:4566"
    environment:
      - LAMBDA_DOCKER_NETWORK=bridge
      - AWS_DEFAULT_REGION=us-east-2
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - ./init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh
      - /var/run/docker.sock:/var/run/docker.sock

З важливого:

  • я використовую network_mode: bridge (можна й host), бо якщо ми будемо запускати lambda локально в docker, я хочу мати можливість туди підключатись (container-to-container);
  • init-aws.sh — це скрипт, який виконується після успішного запуску контейнера, тут ми можемо підготувати створення необхідних ресурсів за допомогою AWS CLI.

Приклади використання

AWS CLI

Тут дуже легко. Все, що потрібно, це додати --endpoint-url http://localhost:4566

aws s3 mb s3://local-bucket --endpoint-url http://localhost:4566
aws s3api list-buckets --query "Buckets[].Name" --endpoint-url http://localhost:4566

Дуже зручно використовувати alias - alias awslocal="aws --endpoint-url=http://localhost:4566”. Після цього можна використовувати команду awslocal (або ж іншу, залежно від того, як ви назвете alias), яка ****автоматично буде додавати це поле.

awslocal s3 mb s3://local-bucket2
awslocal s3api list-buckets --query "Buckets[].Name"

AWS SDK (Java)

Для роботи з AWS нам необхідно підтягнути залежності, які містять імплементацію клієнтів для роботи з AWS-сервісами. Ми розглядаємо приклад на Gradle та Java, але всюди логіка однакова.

implementation platform('software.amazon.awssdk:bom:2.20.115')
implementation 'software.amazon.awssdk:s3'

Створення S3 клієнта:

private static final S3Client S3 = S3Client.builder().build();

Отож, так само як і в попередньому прикладі, нам потрібно змінити ендпоінт, на який буде звертатись клієнт:

private static final S3Client S3 = S3Client.builder()
            .endpointOverride(URI.create("https://localhost:4566"))
            .build();

Spring

Варто почати з того, що всі налаштування, які підходять для Java, підходять і тут, але додатково ми отримуємо нові та спрощені можливості для налаштування. Перша така можливість — це анотація @Configuration, яка додається до класу, в якому описані створення необхідних bean-нів. Друга — @Profile, за допомогою цієї анотації дуже зручно розділити конфігураційні класи на ті, які працюють із AWS, і ті, які працюють із localStack.

Конфігурація:

@Configuration
@Profile("localstack")
public class Configurations {
		@Bean
    public S3Client s3Client() {
        return S3Client.builder()
                .endpointOverride(URI.create("https://s3.localhost.localstack.cloud:4566"))
                .build();
    }
}

Зверніть увагу, що тут використовується DNS https://s3.localhost.localstack.cloud:4566, це аналогічно до https://localhost:4566/.

Spring Cloud

На цей час актуальна версія провайдера для AWS — v3. Тут нам стає ще зручніше працювати з клієнтами. Ми можемо задати ендпоінт через параметр.

spring:
  cloud:
    aws:
      region:
        static: us-east-2
      s3:
        endpoint: http://s3.localhost.localstack.cloud:456

Необхідні залежності:

implementation platform('io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1')
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3'

Testcontainers

Сьогодні всі активно використовують тестові контейнери для інтеграційних тестів, давайте й ми оглянемо такий приклад із Spring.

Необхідні залежності:

implementation platform('org.testcontainers:testcontainers-bom:1.18.3')
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:localstack'

Тест:

@Testcontainers
@SpringBootTest
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ApplicationTests.TestConfig.class)
class ApplicationTests {

    private static final DockerImageName LOCALSTACK_IMAGE = DockerImageName.parse("localstack/localstack:2.2.0");

    @Container
    private static final LocalStackContainer LOCAL_STACK_CONTAINER = new LocalStackContainer(LOCALSTACK_IMAGE)
            .withEnv("AWS_DEFAULT_REGION", Region.US_EAST_2.toString())

    @Autowired
    private S3Client s3Client;

    @Test
    void test() {
			// GIVEN

			// WHEN

			// THEN
    }

    @TestConfiguration
    public static class TestConfig {
        @Bean("s3ClientTest")
        @Primary
        public S3Client s3Client() {
            return S3Client.builder()
                    .endpointOverride(LOCAL_STACK_CONTAINER.getEndpointOverride(LocalStackContainer.Service.S3))
                    .build();
        }
    }
}

Із важливого:

  • анотація @Container запускає контейнер перед тестами;
  • анотація @TestConfiguration описує налаштування клієнтів для тестів;
  • анотація @ContextConfiguration(classes = ApplicationTests.TestConfig.class) дозволяє використати налаштування клієнтів, які ми змінили.

Terraform

Для використання Terraform та LocalStack, нам необхідно встановити спеціальний сервіс:

pip install terraform-local

Для роботи з AWS основна вимога — це provider:

provider "aws" {
  region = "us-east-2"
}

Якщо раніше ми змінювали ендпоінт для клієнтів, то тепер нам необхідно змінити ендпоінт для провайдера:

provider "aws" {
  region = "us-east-2"

	endpoints {
    s3  = "https://s3.localhost.localstack.cloud:4566"
    sqs = "https://sqs.localhost.localstack.cloud:4566"
  }
}

Наступними кроками нам потрібно виконувати tflocal init та tflocal apply (замість tf init та tf apply).

Підсумок

Це був швидкий огляд, з чого можна та варто почати впровадження LocalStack-у на вашому проєкті, але я можу з впевненістю сказати, що ця річ дійсно пришвидшує розробку.

Також ось репозиторій із готовою аплікацією та вище зазначеними сервісами.

👍ПодобаєтьсяСподобалось10
До обраногоВ обраному8
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Я встановив LocalStack у Docker контейнері. Серед сервісів працює лише S3, решта у статусі available. Як саме запустити потрібний мені сервіс? Наприклад, мені потрібний SQS.

там є додаткове значення EAGER_SERVICE_LOADING, за замовчуванням воно 0. Якщо його не змінювати на 1, то будь-який серві буду активовуватись автоматично, коли ви зробите реквест до цього. У вашому випадко, щоб працювати з SQS, достатньо просто зробити реквест

Дякую. Бачу там таку поведінку. Є запит — є активний сервіс.

Достатньо прописати key, secret key, region в ini файл AWS і просто використовувати всю потужність aws cli на реальному сервісі, на локальному середовищі. Бакет можна земулювати, як це зробити, наприклад, з load balancer, AWS dms. Та купа всякого. Ви там в API потонете.
Але так, цікаво, напевно комусь згодиться, можливо я не зрозумів проблему, дякую за статтю.

на реальному сервісі,

І платити за це гроші? Дякую, не потрібно

Додайте будь ласка LocalKMS бо реалізація KMS in LocalStack далека від ідеалу...

Мені не доводилось працювати з KMS, можете підказати конкретний випадок? Хочу спробувати

Encryption context не підтримується. Хоча апв на енкріпшн нібито працює. ЛокалКмс прям робить шифрування дешифрування даних

це локальний емулятор AWS

тут важливо додати, що емулюються тільки AWS api без емуляції роботи сервісів.
Тобто записати і прочитати файл на s3 бакет не получиться

Що саме ви маєте на увазі під «записати/прочитати» файл? Query object?

aws s3 cp file s3://bucket_name/file
видалити файл «file»
aws s3 cp s3://bucket_name/file file
при повній емуляції ми б отримали файл, при емуляції апі — команда спрацює успішно — але файл буде пустий

це у вас з localstack cli таке чи з контейнером?
якщо з контейнером то ви щось робите не так.
за cli хз (але підозрюю те саме)

$ docker run -d -p 4566:4566 -p 4510-4559:4510-4559 --name localstack localstack/localstack
d9ab187a24fa98182047ffe6f8317131325c0a362d5f4bef84f295dbeff9facc
$ echo 'testme' > test.txt
$ aws --endpoint-url http://localhost:4566 s3 mb s3://mybucket
make_bucket: mybucket
$ aws --endpoint-url http://localhost:4566 s3 cp test.txt s3://mybucket/test.txt
upload: ./test.txt to s3://mybucket/test.txt
$ aws --endpoint-url http://localhost:4566 s3 cp s3://mybucket/test.txt -
testme
$ docker rm -f localstack

Ви точно щось робите не так. Звичайно, localstack — це емуляція, але не настільки
Якщо ж потрібен «справжнісінький S3» — то hub.docker.com/r/minio/minio

о... це прогресс, три роки тому такого 100% не було
файл при повторному скачуванні був пустий
але це не заважало використовувати localstack в unit тестах

без емуляції роботи сервісів

Та ладно? Все там працює норм. Памятаю навіть івенти через чергу пересилались

Підписатись на коментарі