← назад к разделу

Допустим, ваше приложение на Spring Boot должно отправить файл в облачное хранилище, прочитать сообщение из очереди или достать пароль из хранилища секретов AWS. Как это сделать из кода? Хорошая новость: один и тот же код будет работать и на вашем ноутбуке, и на сервере в облаке — без единой правки. Чтобы это получилось, нужно понять три вещи: SDK (библиотеку для разговора с AWS), credentials chain (как код узнаёт, под чьими правами он работает) и LocalStack (как тестировать всё это локально, не трогая настоящее облако).

Эта статья — про интеграцию именно из Spring Boot, поэтому примеры будут на Java. Но сами идеи (SDK, цепочка получения прав, локальная имитация) одинаковы для любого языка — у AWS есть SDK для Python, Node.js, Go и других.

SDK v2 и клиенты как бины

SDK (Software Development Kit) — это библиотека от AWS, через которую ваш код общается с облаком. Вместо того чтобы вручную собирать HTTP-запросы к API, вы вызываете методы вроде s3Client.putObject(...), а SDK сам формирует запрос, подписывает его и отправляет.

Важная деталь: у AWS SDK для Java два поколения. Старое (v1, пакеты начинаются с com.amazonaws) считается устаревшим — новые проекты на нём не пишут. Актуальное — v2, его пакеты начинаются с software.amazon.awssdk. Дальше речь только про v2.

Для каждого сервиса AWS — свой клиент: S3Client для хранилища, SqsClient для очередей и так далее. Эти клиенты потокобезопасны (их можно безопасно использовать из нескольких потоков одновременно) и тяжеловесны при создании, поэтому создавать их нужно один раз и переиспользовать. В Spring это означает: оформить клиент как бин.

@Configuration
public class AwsConfig {

    @Bean
    SqsClient sqsClient() {
        return SqsClient.builder()
            .region(Region.EU_CENTRAL_1)
            .build();
    }

    @Bean
    S3Client s3Client() {
        return S3Client.builder()
            .region(Region.EU_CENTRAL_1)
            .build();
    }
}

Здесь region — географический регион AWS (например, eu-central-1 — Франкфурт), где живут ваши данные. Через overrideConfiguration настраиваются таймауты и встроенные повторы запросов (retry). По умолчанию SDK сам повторяет неудачные запросы с нарастающей паузой между попытками. Если поверх этого добавить ещё свой механизм повторов, получится «повтор поверх повтора» — при сбое сервиса это превращается в лавину запросов. Поэтому retry настраивают либо в SDK, либо свой — но не оба сразу.

Credentials chain: откуда берутся права доступа

Посмотрите на код выше ещё раз. Где там логин и пароль для AWS? Их нет — и это не упущение, а главная идея. Credentials chain (цепочка получения учётных данных) — это механизм, по которому SDK сам ищет, под чьими правами работать, проверяя источники по порядку, пока не найдёт первый подходящий.

Аналогия: вы приходите в офис и прикладываете пропуск к турникету. Вы не несёте с собой сейф с правами — система сама знает, что вам можно. Credentials chain работает похоже: код не хранит ключи, он просто «прикладывает пропуск», который ему выдаёт окружение.

Порядок, в котором SDK v2 ищет права (упрощённо):

  1. Java System Propertiesaws.accessKeyId и aws.secretAccessKey.
  2. Переменные окруженияAWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY.
  3. Web identity token — так роль выдаётся приложению в Kubernetes/EKS (это называется IRSA — роль, привязанная к ServiceAccount).
  4. Профиль из файла ~/.aws/credentials — то, что вы настраиваете на своём ноутбуке через aws configure или SSO.
  5. Роль контейнера (ECS task role) — если задана переменная AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.
  6. Роль инстанса (EC2) — права запрашиваются у служебного сервиса метаданных машины.

Эффект мощный: один и тот же код на ноутбуке работает под вашим личным профилем, в Kubernetes — под ролью пода, на EC2 — под ролью машины. Меняется не код, а окружение.

Самая частая ошибка — прописать ключи прямо в application.yml:

aws:
  accessKeyId: AKIA...
  secretKey: wJalr...

Так делать нельзя. Ключи попадут в git, в логи, в копии конфигов; их никто не ротирует (не меняет периодически), а перенос сервиса в другое окружение становится мукой. Если в репозитории встречается строка вроде aws.secret — считайте, что утечка уже произошла. Права отдавайте credentials chain, ключи в коде не держите никогда.

Spring Cloud AWS: меньше шаблонного кода

Поверх SDK существует Spring Cloud AWS (проект awspring) — надстройка, которая берёт на себя рутину: создаёт клиентов из настроек в application.yml и даёт удобные высокоуровневые инструменты. Самое полезное — слушатель очереди SQS в привычном Spring-стиле, как обработчик сообщений:

@Component
public class OrderEventsSqsConsumer {

    @SqsListener("order-events")
    public void handle(OrderEventPayload payload) {
        orderService.register(payload);
    }
}

Аннотация @SqsListener сама опрашивает очередь, превращает JSON-сообщение в объект и удаляет сообщение после успешной обработки. От вас требуется одно — сделать обработчик идемпотентным, то есть безопасным к повторной обработке одного и того же сообщения. Это важно, потому что SQS гарантирует доставку «хотя бы один раз» (at-least-once): одно сообщение может прийти дважды, и ваш код не должен от этого ломаться (например, создавать дубль заказа).

Вторая полезная вещь — подгрузка секретов и настроек из AWS прямо как обычных свойств Spring. Достаточно одной строки в конфиге:

spring:
  config:
    import: aws-secretsmanager:/prod/order-service/

После этого пароли к базе и прочие секреты живут только в AWS Secrets Manager, а приложение получает их при старте. В коде вы обращаетесь к ним как к обычным @Value-свойствам — и нигде не храните сами значения. Подробнее про секреты и метрики — в статье про безопасность и наблюдаемость.

Тестирование через LocalStack

Как протестировать код, который ходит в AWS, не платя за настоящее облако и не завися от интернета? Ответ — LocalStack: программа, которая запускается у вас локально (в Docker) и притворяется набором сервисов AWS. Ваш код думает, что говорит с настоящим S3 или SQS, а на самом деле — с локальной имитацией.

Связка LocalStack + Testcontainers (библиотека, которая поднимает Docker-контейнеры прямо из теста и гасит их после) делает интеграционный тест против «AWS» таким же простым, как тест против локальной базы данных:

@Testcontainers
class OrderEventsSqsConsumerTest {

    @Container
    static LocalStackContainer localstack =
        new LocalStackContainer(DockerImageName.parse("localstack/localstack:3"))
            .withServices(SQS);

    @DynamicPropertySource
    static void aws(DynamicPropertyRegistry registry) {
        registry.add("spring.cloud.aws.endpoint", () -> localstack.getEndpoint().toString());
        registry.add("spring.cloud.aws.region.static", localstack::getRegion);
    }

    @Test
    void processesOrderEvent() {
        sendToQueue("order-events", paidOrderPayload());
        await().untilAsserted(() -> assertThat(repository.findEvents()).hasSize(1));
    }
}

Ключевая строка — spring.cloud.aws.endpoint: она говорит Spring Cloud AWS обращаться не к реальному облаку, а к адресу LocalStack. Контейнер сам выбирает свободный порт, поэтому адрес берём из localstack.getEndpoint().

Важно понимать границы: LocalStack хорошо имитирует поведение сервисов (очереди принимают и отдают сообщения, бакеты хранят файлы), но не проверяет права доступа (IAM-политики) и не воспроизводит лимиты и тарифы настоящего AWS. Поэтому правильное распределение проверок такое: чистую бизнес-логику тестируем вообще без AWS (через подмену зависимости заглушкой), интеграцию с облачными сервисами — на LocalStack, а реальные права и сеть — уже на тестовом стенде в настоящем облаке.

Цена вызовов

В облаке привычки, безобидные на своём сервере, стоят денег: каждый запрос к AWS — строчка в счёте. Два места, где это заметнее всего:

  • Запросы к S3 в цикле по одному объекту — лучше пакетами или листингом по префиксу, иначе каждый объект превращается в отдельный платный запрос.
  • Трафик через NAT — обращения к AWS-сервисам наружу тарифицируются; чтобы их убрать, есть VPC endpoints (см. networking).

Это не преждевременная оптимизация, а гигиена: цену за запрос видно в прайс-листе заранее.

Типичные ошибки

ОшибкаЧем кончаетсяЧто делать
Ключи в application.yml или gitУтечка, ручная ротация, инцидентПолагаться на credentials chain: IRSA, task role, SSO
SDK v1 в новом кодеДве библиотеки в одном проекте, старые багиТолько software.amazon.awssdk (v2)
Свой retry поверх retry SDKЛавина повторов при сбоеНастроить retry в SDK, свой — выключить
Тесты против настоящего AWSМедленно, дорого, нестабильно, нужен интернетЗаглушка для логики, LocalStack для интеграции
Короткий опрос SQSОплата пустых ответов круглосуточноLong polling, waitTimeSeconds: 20
Новый клиент на каждый вызовСотни TLS-рукопожатий, утечка ресурсовОдин клиент-бин на сервис

Где это применяется

Связка «SDK + credentials chain + LocalStack» — это базовый каркас любого приложения, которое живёт в AWS: микросервис, читающий заказы из очереди; backend, складывающий загруженные пользователем файлы в хранилище; сервис, который при старте достаёт пароли из Secrets Manager. Везде один и тот же приём: код не знает про ключи, права приходят из окружения, а локальные тесты идут через имитацию.

Самые частые грабли: захардкоженные ключи доступа (главная причина утечек), создание нового клиента на каждый запрос (исчерпание ресурсов под нагрузкой), забытая идемпотентность обработчика очереди (дубли заказов и платежей при повторной доставке) и попытка тестировать прямо против настоящего AWS (флаки-тесты и неожиданный счёт).

Что изучить дальше:

  • Fundamentals — как устроены аккаунты, регионы и базовые сервисы.
  • IAM — роли, политики и то, откуда credentials chain берёт права.
  • Безопасность и наблюдаемость — Secrets Manager и метрики CloudWatch из кода.
  • Работа с объектным хранилищем — загрузка файлов, временные ссылки, multipart.
  • DynamoDB — если из приложения нужна управляемая NoSQL-база.