Здесь, в I Love My Local Farmer, мы переносим большую часть нашей инфраструктуры и кода в облако. На данный момент мы создали совершенно новый сервис с использованием бессерверных технологий в AWS, а также выяснили несколько хороших способов развертывания и тестирования приложений такого рода. Однако сейчас мы пытаемся справиться с гораздо более крупным зверем — переносом одного из наших существующих локальных приложений в облако.

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

В этой статье я расскажу о нескольких различных темах, в том числе о некоторых ограничениях, с которыми мы столкнулись в определенных технологиях. Во-первых, я собираюсь рассказать о стратегиях, которые мы изначально пытались использовать для нашей миграции, и о недостатках наших решений, которые привели к тому, что мы выбрали другой путь. Затем я объясню наше окончательное рабочее решение и почему мы сочли правильным отказаться от некоторых из наших первоначальных идей.

На заметку: наш первоначальный план использования созданных нами шаблонов App2Container в нашем коде CDK оказался не таким управляемым, как мы изначально думали, из-за необходимости изменять части шаблонов после их включения. Однако существуют нативные конструкции CDK, которые мы могли бы использовать для точного воспроизведения того, что мы создали из App2Container, с гораздо меньшими трудностями. Если вы просто хотите увидеть окончательное решение, смело переходите к концу статьи, где мы рассматриваем это решение.

Кроме того, вы можете ознакомиться с полным исходным кодом этого решения в репозитории I Love My Local Farmer для получения дополнительной информации и других полезных примеров развертывания.

Отказ от ответственности
I Love My Local Farmer — это вымышленная компания, вдохновленная взаимодействием клиентов с AWS Solutions Architects. Любые истории, рассказанные в этом блоге, не связаны с конкретным клиентом. Сходства с любыми реальными компаниями, людьми или ситуациями чисто случайны. Истории в этом блоге представляют точку зрения авторов и не одобрены AWS.

Включение шаблонов App2Container в CDK

Сразу после создания нового сервиса в AWS мы уже знаем, как настраивать базы данных и VPC в AWS. Если вы не знакомы с этими темами, ознакомьтесь с другими нашими сообщениями о создании баз данных и соединений с базами данных и связанных с ними сетевых аспектах. Однако наша текущая миграция отличается от созданной нами предыдущей службы тем, что наше приложение не предназначено для использования в облаке. Вместо этого это приложение Spring MVC, которое ранее использовалось для прямого подключения к нашей локальной базе данных.

«В нашем последнем посте мы рассказали, как мы смогли использовать App2Container для создания контейнерной версии нашего приложения с нашего работающего сервера Tomcat. Этот процесс был довольно простым, и в результате были созданы пригодные для использования шаблоны CloudFormation, которые мы могли просто развернуть в наших учетных записях AWS. Однако мы знали, что хотели бы автоматизировать развертывание всей нашей инфраструктуры, а не развертывать вручную как шаблоны приложений, так и инфраструктуру базы данных.

Чтобы упростить процесс развертывания нашей инфраструктуры, мы изначально решили просто включить шаблоны App2Container в тот же код CDK, который развернул нашу инфраструктуру базы данных. Этот процесс довольно прост, все, что нужно было сделать, это сохранить сгенерированные шаблоны App2Container в репозитории, содержащем наш код CDK, и сослаться на них с помощью конструкции CfnInclude, доступной в CDK. Единственная сложная вещь, которую следует иметь в виду, заключается в том, что большинство сгенерированных шаблонов вложены в более крупный стек, и их необходимо включить как вложенные стеки, чтобы на них можно было правильно ссылаться.

Еще одна проблема, с которой мы столкнулись при первой попытке, заключалась в том, что синтаксический анализатор, используемый CfnInclude, не мог правильно прочитать формат YAML, сгенерированный App2Container. Чтобы обойти это, нам пришлось преобразовать основной стек в схему JSON с помощью инструмента под названием cfn-flip. Этот инструмент позволяет преобразовывать JSON- и YAML-версии шаблонов CloudFormation и более удобен, чем развертывание шаблона в учетной записи и загрузка JSON-версии развернутой схемы.

Теперь эта практика импорта шаблонов хорошо работает, когда мы хотим развернуть точную копию нашей локальной системы в облаке. В этом отношении он работает как отличный инструмент начальной загрузки, позволяющий нам создать необходимую инфраструктуру для контейнеризации нашего приложения. Однако мы поняли, что App2Container на самом деле был нацелен на полностью созданные приложения, которые просто нужно было поднять и перенести в облако. Для наших целей, когда мы хотели продолжать вносить изменения в наше приложение, мы поняли, что нам придется внести некоторые изменения в то, что предоставляет App2Container, чтобы избежать необходимости запускать его каждый раз, когда мы хотим обновить нашу службу. В частности, мы поняли, что нам нужно сделать создание нашего образа контейнера более переносимым, чтобы мы могли создавать его без работающего экземпляра Tomcat.

Создание образов контейнеров в CDK

К счастью для нас, создать образ контейнера и загрузить его в репозиторий эластичных контейнеров в CDK очень просто — если вы умеете писать свой собственный Dockerfile. Все, что вам нужно сделать, это поместить файл Dockerfile в свой репозиторий, чтобы вы могли ссылаться на него из CDK, а встроенная конструкция выполнит тяжелую работу по фактическому созданию образа контейнера и публикации в репозитории.

Здесь каталог ./src/main/container содержит наш файл Dockerfile и сгенерированный файл .WAR, который мы создаем при сборке нашего приложения. Изначально мы собирались просто использовать Dockerfile, созданный App2Container. Однако App2Container создает точную копию среды работающего приложения, копируя все необходимые файлы из работающей системы в контейнер в файле TAR. Мы поняли, что это не совсем наш вариант использования, поскольку нам действительно нужна была только работающая среда Tomcat и не обязательно заботиться о том, чтобы все остальное, например конфигурации портов и разрешения пользователей, было точно таким же. В конце концов, мы просто использовали официальный образ Tomcat и скопировали наш встроенный военный файл в соответствующее место в контейнере.

FROM tomcat:9.0.52
RUN mv /usr/local/tomcat/webapps.dist/* /usr/local/tomcat/webapps/
COPY provman.war /usr/local/tomcat/webapps/
EXPOSE 8080

Наконец, последнее, что нам нужно было сделать, это обновить наши включенные шаблоны CloudFormation, чтобы они использовали только что опубликованный образ контейнера. Опять же, это оказалось довольно просто, так как образ контейнера является одним из параметров, включенных во вложенные шаблоны App2Container, и его можно переопределить непосредственно при включении шаблона.

Недостатки CfnInclude в Java CDK

До сих пор все, что мы реализовывали, было довольно просто. Мы можем напрямую использовать шаблоны App2Container и относительно легко переопределять некоторые необходимые поля. Однако это работает только тогда, когда у нас есть приложение, которое собрано и упаковано со всей конфигурацией, необходимой для его запуска. Как насчет того, когда нам нужно включить эту конфигурацию во время развертывания? Например, в случае, если нам нужно получить учетные данные базы данных из вновь созданной базы данных и предоставить их нашему приложению Spring? Именно тогда мы начали замечать некоторые недостатки прямого использования сгенерированных шаблонов App2Container. Ниже приведены некоторые болевые точки, с которыми мы столкнулись, когда нам пришлось изменять наши шаблоны во время развертывания.

Невозможность приведения типов при извлечении элементов шаблона

При написании кода Typescript CDK свойства, полученные из шаблона CfnInclude, можно напрямую привести к соответствующему типу ресурсов CDK. Это связано с тем, что Typescript может напрямую создавать классы из своих объектов JSON. Однако в Java это невозможно из-за используемой в языке системы типизации. При попытке получить свойства из шаблона вы получите только объект CfnResource, который нельзя напрямую привести к соответствующему объекту Java.

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

Зависимости между ресурсами больше не управляются автоматически

Когда вы пишете код исключительно в CDK, он создаст все необходимые зависимости между различными ресурсами и включит их в окончательный шаблон. Однако при прямой перезаписи свойств CloudFormation это уже не так. Попытка сослаться на объекты между двумя разными шаблонами стека может привести к ошибкам, поскольку один стек может не иметь ссылки на ресурсы других стеков.

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

Разрешения и сети не настраиваются автоматически для определенных ресурсов

Одна из самых больших проблем, с которой мы столкнулись при попытке использовать сгенерированные шаблоны, заключалась в том, что мы пытались импортировать секретные значения в наши контейнеры. Мы хотели сохранить учетные данные нашей базы данных в AWS Secrets Manager, а затем загрузить эти учетные данные в контейнер нашего приложения при его запуске. Хотя это возможно, указав параметр Secrets в определении вашего контейнера, это связано с множеством других соображений, необходимых для обеспечения того, чтобы роль выполнения контейнера действительно имела правильные разрешения и сетевую конфигурацию для правильного извлечения секрета. Это не так просто, как просто указать секрет, и мы не смогли заставить это работать за день или два, которые мы потратили, пытаясь убедиться, что наша конфигурация была правильной. Необходимость иметь дело с этой проблемой больше, чем что-либо еще, вдохновила нас на поиск альтернатив жесткому кодированию вещей в наших шаблонах CloudFormation.

Использование эквивалентов CDK для шаблонов App2Container

Столкнувшись с только что описанными проблемами с CfnInclude, мы начали искать другие решения для достижения той же конечной цели без необходимости прямого взаимодействия с шаблонами Cloudformation. Именно тогда мы обнаружили библиотеку ecs-patterns в CDK. Эта библиотека содержит несколько общих архитектурных проектов, которые используются в ECS, как в версиях EC2, так и Fargate.

В частности, один из шаблонов — ApplicationLoadBalancedFargateService — привлек наше внимание, когда мы проходили семинар по использованию Secret Manager с ECS, потому что поняли, что он почти точно отражает то, что делается в наших шаблонах App2Container. Мало того, это также упростило процесс передачи учетных данных и секретов нашему приложению, предоставив базовые ресурсы CloudFormation, которыми мы манипулировали, как методы и поля самого класса.

В конце концов, использовать эту новую конструкцию оказалось даже проще, чем включать шаблоны App2Container. Все, что нам нужно было сделать, это сымитировать конфигурацию, которую мы видели в наших исходных шаблонах — большая часть которых уже была обработана самим шаблоном — и включить наши учетные данные в качестве секретов для загрузки в контейнер. Ниже приведено все, что потребовалось для преобразования нашей установки App2Container в новый шаблон.

Заключительные мысли и подведение итогов

В целом, мы извлекли много ценных уроков из процесса миграции нашего приложения в ECS. Хотя наша первоначальная стратегия оказалась не той, которую мы использовали, она привела нас к гораздо более управляемому конечному результату и послужила основой для наших решений на этом пути. Ниже приведены некоторые из основных выводов, которые мы сделали из этого опыта.

  1. App2Container — отличный вариант, если вы хотите скопировать приложение и его существующую конфигурацию в ECS.
  2. Даже если вам не нужна точная копия вашей локальной среды, App2Container может отлично подойти для поддержки определенных ресурсов, которые вы можете расширить и использовать в качестве примеров для своего приложения.
  3. CfnInclude может хорошо работать, если у вас уже есть конфигурация инфраструктуры и вам не нужно динамически изменять многие свойства или ресурсы.
  4. Существует множество готовых архитектурных конфигураций, которые можно использовать в CDK.
  5. Гораздо проще взять что-то вроде шаблона App2Container и преобразовать его в код CDK, чем использовать CfnInclude и пытаться модифицировать шаблон Cloudformation в коде.

Основываясь на этом опыте, теперь у нас есть еще несколько инструментов, которые помогут нам перенести наши приложения в AWS. Мы считаем, что в будущем будет еще проще перемещать наши существующие сервисы, особенно теперь, когда мы знаем о существующих готовых шаблонах проектирования, которые мы можем использовать.

Мы также рассмотрим еще несколько тем, над которыми мы работали во время переноса базы данных нашего провайдера, в том числе о том, как мы настроили конвейер CI/CD в CDK и как мы реализовали пакетный экспорт содержимого нашей базы данных в другое веб-приложение. Обязательно подпишитесь на «Я люблю своего местного фермера», чтобы увидеть последние шаги в нашем облачном путешествии!