Как постепенно и безопасно перейти от старого унаследованного монолита PHP к бессерверной архитектуре?
В этом посте я покажу вам, как мы использовали AWS CDK для развертывания двух стеков, кросс-аккаунтов, которые использовали правила ALB и квоты для постепенного переключения трафика, конечная точка за конечной точкой, на Lambdas и DynamoDB.


Немного контекста

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

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

Как мы могли безопасно внести изменения в что? Я даже не был уверен, что смогу проверить репозиторий и повторно развернуть его, учитывая, что многие из его зависимостей и фреймворк также устарели, если не полностью устарели?

потливость

Несмотря на отсутствие у нас знаний PHP, прочитать код и понять, что он делает, было не так уж сложно, и, немного повозившись с перепроектированные абстракции ООП (в основном из-за того, что там используется Dependency Injection Framework) мы пришли к выводу, что если бы было намного лучше написать новую функцию (и, возможно, постепенно переписать все это) с более современным подходом в техническом стеке, у нас есть понимание из.

Вместо громоздкого монолита PHP, развернутого на EC2, мы бы выбрали Машинописный и бессерверный!

Ура! Я был в восторге от участия в таком масштабном переписывании, но у нас была проблема безопасное развертывание изменения и самое главное откат в случае проблем.

В качестве дополнительной сложности у нас было желание / требование, возможно, постепенно деактивировать все службы, связанные с этим приложением, из учетной записи исходной команды.


Решение

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

Для совершенно новых конечных точек мы создали новых прослушивателей с условиями правила, соответствующими методу и пути, и направили трафик непосредственно в нашу Lambda — в предыдущем приложении этой конечной точки не существовало, и в развернутом PHP не было доступной реализации. Легкий!

Что делать с существующими конечными точками и существующей кодовой базой, которую нам нужно было изменить?

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

Нам, конечно, пришлось переписать первоначальный функционал, но поскольку мы понимали бизнес-логику, писать новый код на Typescript было намного проще, чем возиться со старой и недокументированной кодовой базой PHP.

ALB в лямбда

Сила этого выбора заключалась в том, что мы могли развернуть лямбду и целевую группу с очень очень низкой квотой, например, 99% и 1%, и отслеживать результаты в течение нескольких часов или дней. Если бы все шло гладко, мы бы постепенно увеличивали квоту, пока не достигли бы 100%. Если в какой-то момент мы поняли, что допустили какие-то ошибки в бизнес-логике или допустили ошибки, мы могли уменьшить квоту или полностью деактивировать целевую группу и сосредоточиться на решении проблемы.

Как вы можете себе представить, процесс длился несколько недель, но мы смогли перенести значительное количество конечных точек и постепенно отказаться от PHP-приложения, улучшив систему, представив более современная, надежная и даже более дешевая архитектура: мы перешли на DynamoDB, разветвляя нагрузку с помощью SQS, а для более сложной логики, охватывающей разные системы и процессы, которые могут длиться несколько дней (например, запросы на утверждение и подписки), мы использовали пошаговые функции!


Как насчет проблемы с кросс-аккаунтом?

Что ж, передача приложения в собственность нашей команды потребовала от нас переноса сервисов, на которых оно работало, под нашей собственной учетной записью.
То, что мы сделали, хотя и немного увеличили затраты, а в некоторых случаях и задержку, заключались в том, чтобы иметь одну лямбду в качестве цели LoadBalancer в старой учетной записи, вызывая лямбда-выражения в нашей учетной записи или прокси-запросы к Dynamo или SQS в нашей учетной записи.

К сожалению, невозможно (или я не смог найти способ), чтобы Listener указывал на целевую группу в другой учетной записи (или чтобы целевая группа указывала на Lambda в другой учетной записи).

Перекрестная учетная запись ALB Lambda to Lambda

Хотя это и не оптимально, и у нас были некоторые головные боли из-за политик, ролей и токенов — я уже писал пост, вы можете прочитать его здесь), это решение позволило нам отделить систему и постепенно взять ее во владение, в том числе с точки зрения затрат. .

В дальнейшем, когда процесс будет завершен, Балансировщик нагрузки, Слушатели и Целевая группа, указывающая на этот проксирование lambda, будет удален и заменен APIGateway в нашей учетной записи.

Интеграция со шлюзом API

Шлюз API может использовать различные типы интеграции для реагирования на конкретный метод или конечную точку, что позволяет, например, выполнять чтение и запись непосредственно в DynamoDB в лямбдалесс способом, как я описал здесь) или для отправки непосредственно в SQS. Затем, благодаря фильтрам сопоставления потоков и источников событий, будут запускаться разные лямбда-выражения (как описано здесь — пост о DynamoDB, но идея та же), что еще больше разъединяет логику.

покажи мне код


Как это было сделано

Тема этого поста больше в том, чтобы поделиться этим опытом (и, возможно, также получение обратной связи от сообществао различных возможных подходах или советах по решению некоторых болевых точек), а не предоставлять огромный шаблон формирования облака или стек CDK. Но я постараюсь предоставить несколько фрагментов и объяснить кое-что из волшебства!

const ALBListener = ApplicationListener.fromApplicationListenerAttributes(this,`${id}-albListener`, {
    listenerArn: props?.envConfig.elbListenerArn, 
    securityGroup})

Войти в полноэкранный режим

Выйти из полноэкранного режима

Как упоминалось выше, наше устаревшее приложение уже использовало Listeners и ALB, поэтому здесь мы просто ссылаемся на ALBListener, используя ARN, который мы поместили в cdk.context.json.

const targetGroup = new ApplicationTargetGroup(this, `${id}-ELBTargetGroup`, {
    targetGroupName: `${id}-tg`,
    targets: [new LambdaTarget(myProxyingLambda)],
})
Войти в полноэкранный режим

Выйти из полноэкранного режима

Затем мы создаем TargetGroup, которая указывает на функцию Lambda, отвечающую за передачу каждого действия Lambda в новой учетной записи.

ALBListener.addTargetGroups(`${id}-rule-for-endpoint-we-are-migrating-to-serverless`, {
    priority: priorityInt++,
    conditions: [
        ListenerCondition.hostHeaders([ourHost]),
        ListenerCondition.httpRequestMethods(['POST']),             
        ListenerCondition.pathPatterns([OUR_ENDPOINT_PATTERN])
    ],
    targetGroups: [targetGroup],
})

ALBListener.addTargetGroups(`${id}-rule-for-another-endpoint`, {
    priority: priorityInt++,
    conditions: [
        ListenerCondition.hostHeaders([ourHost]),
        ListenerCondition.httpRequestMethods(['GET']),
   ListenerCondition.pathPatterns([ANOTHER_ENDPOINT_PATTERN]),
    ],
    targetGroups: [targetGroup],
})

Войти в полноэкранный режим

Выйти из полноэкранного режима

Наконец, мы добавляем целевую группу в прослушиватель, указав правила/условия, в соответствии с которыми трафик будет направлен на цель.
Как видите, здесь мы указываем условия по хосту, по HTTP-методу и по пути.

ENDPOINT_PATTERN может быть реальным путем, например /subscriptions или, как следует из названия, такой узор, как /users/*/address например (так что это условие будет работать всякий раз, когда мы вызываем конечную точку адреса для любого идентификатора пользователя.)
приоритет это уникальный целое число, определяющее, какое правило будет использоваться для каждого запроса (правило с наименьшим номером имеет приоритет).

В этом, по сути, и суть.
Для краткости я пропускаю, как настроить лямбду (вы можете найти тысячи примеров в Интернете) и как настроить роли и политики (вы можете проверить мои сообщения, перечисленные ниже).


Некоторые болевые точки, которые у нас были (и еще не решены)

  • Слишком много жестко закодированного материала: мы действительно хотели максимально упростить развертывание и централизовать репозиторий, не теряясь в 2 учетных записях aws и другом репозитории gitlab (там у нас тоже были разные пространства имен), поэтому мы просто жестко закодировали ARN LoadBalancer и Listeners, а также информацию о кросс-аккаунте внутри файла cdk.context.json. Но, по крайней мере, мы смогли (благодаря нашему SSO-логину и профилям (о работе с несколькими учетными записями и профилями я писал здесь) развернуть Правила и изменения в проксирование lambda непосредственно из того же проекта repo/cdk реального приложения. Просто переключите профиль и выберите правильный стек:
AWS_DEFAULT_PROFILE=account-A npm run cdk deploy dismantle-the-monolith-changes

AWS_DEFAULT_PROFILE=account-B npm run cdk deploy awesome-serverless-implementation
Войти в полноэкранный режим

Выйти из полноэкранного режима

  • Приоритеты: при настройке правил вам нужны уникальные значения, так как мы использовали CDK для добавления новых правил и целевых групп к уже существующему ALB (который я даже не могу вспомнить, был ли он создан вручную из консоли или какого-то IaC), мы пришли с каким-то прилавок но иметь способ получения текущих правил было бы чище.

  • Правила и работа с квотами

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

Консоль пользовательского интерфейса — квоты целевой группы

  • Конструкции CDK В конце концов наш стек CDK был слишком запутанным, и мне бы очень хотелось использовать конструкции, с другой стороны, хотя по той же причине, что и выше, мы предпочли сосредоточиться на выполнении работы, а затем после миграции избавьтесь от промежуточного стека, а не замедляйте разработку с помощью Constructs и CDK Tests.

Я надеюсь, что вы найдете этот пост полезным, и мне бы очень хотелось услышать от вас в комментариях о различных возможных подходах или советах по решению проблем, которые у нас были!

Другие сообщения о бессерверной архитектуре, которые могут вас заинтересовать:


фото Золтан Таси на Скрыть