Как создать мультитенантные внутренние сервисы в AWS и CDK (часть 1): API Gateway и AppSync

Как создать мультитенантные внутренние сервисы в AWS и CDK (часть 1): API Gateway и AppSync

19 сентября 2023 г.

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

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

Я разделю серию публикаций в блоге на три части для каждого типа интеграции между службами: синхронная, асинхронная и пакетная интеграция.

В части 1 будет обсуждаться мультитенантная архитектура для двух сервисов AWS: API Gateway и AppSync. На протяжении всей статьи я ссылаюсь на код из примера приложения, созданного для этой статьи на Typescript и AWS CDK: https://github.com/filletofish/aws-cdk-multi-tenant-api-example/tree/main.

Обзор контента

  1. Мультиарендность для внутренних служб
  2. 1.1. Изоляция арендатора

    1.2. Мультитенантный мониторинг

    1.3. Масштабирование 2. Мультиарендность для внутренних служб

    2.1. Изоляция арендатора – контроль доступа

    2.2 Изоляция арендаторов — проблема с шумными соседями

    2.3 Многопользовательский мониторинг

    2.4 Показатели, сигналы тревоги, информационные панели

    2.5 Подключение и отключение клиентов API 3. Мультиарендность с помощью AWS AppSync 4. Заключение

    1. Мультиарендность для внутренних сервисов

    <блок-цитата>

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

    Если вы разрешите нескольким командам вызывать ваш сервис API, ваш сервис станет мультитенантным. Мультитенантная архитектура усложняет ваши услуги, например изоляцию клиентов, мониторинг на уровне клиентов и масштабирование.

    Example of internal multi-tenant service

    1.1. Изоляция арендатора

    Как правило, изоляция арендаторов решает проблемы безопасности, гарантируя, что арендаторы не смогут получить доступ к ресурсам другого арендатора. Кроме того, реализована изоляция клиентов, чтобы гарантировать, что любые сбои, вызванные одним клиентом, не повлияют на других клиентов вашего сервиса. Это также часто называют проблемой шумного соседа. Дополнительную информацию см. в официальном документе AWS о стратегиях изоляции клиентов https://d1.awsstatic.com/whitepapers/ saas-tenant-isolation-strategies.pdf.

    1.2. Мультитенантный мониторинг

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

    1. Устранение неполадок: упрощает выявление и решение проблем, отличая проблемы, связанные с конкретным клиентом, от более широких.
    2. Распределение ресурсов и планирование мощности. Мультитенантный мониторинг может помочь вам отслеживать потребление ресурсов каждым арендатором для распределения ресурсов и планирования мощности. Даже если ваш сервис является бессерверным, вам все равно необходимо понимать потребление ресурсов вашего клиента, чтобы понять, собираетесь ли вы в ближайшее время достичь каких-либо ограничений AWS (типичным примером является ограничение одновременного выполнения функции Lambda).
    3. Управление соглашениями об уровне обслуживания: позволяет отслеживать производительность отдельных арендаторов в соответствии с соглашениями об уровне обслуживания.
    4. Биллинг. Маловероятно, что вы начнете выставлять счет другим командам за использование вашего внутреннего сервиса. Однако на определенном этапе роста компании выставление счетов другим командам может быть хорошей идеей, чтобы обеспечить экономное использование службы.
    5. 1.3. Масштабирование

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

      2. Мультитенантность с API Gateway

      Если вы создаете веб-сервис AWS с помощью REST, HTTP или WebSocket API в AWS, скорее всего, вы используете API Gateway.

      2.1. Tenant-изоляция — контроль доступа

      AWS рекомендует развертывать каждый сервис в отдельных учетных записях AWS, чтобы изолировать ресурсы и данные сервиса, упростить управление затратами и разделить тестовую и производственную среды (подробности см. в Информационный документ AWS «Организация среды AWS с использованием нескольких учетных записей»).

      Если сервисы вашей компании развернуты в AWS, то наиболее очевидным решением для управления доступом к вашему шлюзу API является AWS IAM. AWS Cognito — еще один вариант управления доступом к мультитенантному API (см. Регулирование многоуровневого многотенантного REST API в масштабе с помощью API Gateway, Аргументы за и против Amazon Cognito).

      Сравнение AWS IAM и AWS Cognito заслуживает отдельного подробного рассмотрения. Но в этой статье я бы остановился на AWS IAM, поскольку это самый простой способ управления доступом, когда сервисы вашей компании находятся в AWS.

      После включения авторизации AWS IAM для метода шлюза API (см. CFN), все запросы API для этого метода должны быть подписаны учетными данными IAM, которым разрешено вызывать ваш API-шлюз.

      По умолчанию доступ между учетными записями AWS запрещен. Например, вызов API-шлюза с учетными данными другой учетной записи AWS не удастся. Чтобы интегрировать своих клиентов с вашим API, вам необходимо настроить доступ к нескольким аккаунтам. Для предоставления доступа к вашему API-шлюзу между учетными записями вы можете использовать два метода: авторизацию на основе ресурсов (недоступно для HTTP API API-шлюза) и авторизацию на основе личности (подробнее см. на https://repost.aws/knowledge-center/access-api-gateway-account):

      1. Подключение клиента с авторизацией на основе ресурсов. Для доступа на основе ресурсов вам необходимо обновить политику ресурсов шлюза API и добавить учетную запись AWS вашего клиента. Основным недостатком этого метода является то, что после обновления политики ресурсов необходимо повторно развернуть этап API Gateway, чтобы изменения вступили в силу (см. документацию AWS [1] и [2]). Однако если вы используете CDK, вы можете автоматизировать развертывание новых этапов (см. Документация AWS CDK для шлюза Api). Еще одним недостатком является ограничение максимальной длины ресурсной политики.
      2. 2. Подключение клиента с авторизацией на основе личности. Для управления доступом на основе удостоверений вам необходимо создать роль IAM для клиента и разрешить клиенту взять на себя ее использование, обновив политику ресурсов роли (доверенные отношения). Вы можете использовать пользователей IAM, но роли IAM лучше с точки зрения безопасности. Роли допускают аутентификацию с использованием временных учетных данных и не требуют хранения учетных данных пользователя IAM. Существует ограничение в 1000 ролей на одну учетную запись, но это ограничение можно изменить. Кроме того, еще одним недостатком ролевого метода получения доступа к вашему API между учетными записями является то, что вам необходимо создавать роль IAM для каждого нового клиента API. Однако управление ролями можно автоматизировать с помощью CDK (см. пример кода из предоставленного приложения CDK).

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

        export const handler = async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
          // IAM Principal ARN of the api caller
          const callerArn = event.requestContext.identity.userArn!;
        
          // .. business logic based on caller
          return {
            statusCode: 200,
            body: JSON.stringify({
              message: `Received API Call from ${callerArn}`,
            })
          };
        };
        

        2.2. Tenant-изоляция — проблема шумных соседей

        Ограничение шлюза API по умолчанию составляет 10 000 TPS (Квоты и ограничения шлюза API) . Однако из-за ваших нисходящих зависимостей вашему сервису может потребоваться более низкий предел TPS. Чтобы избежать перегрузки запросов API от одного клиента, которая повлияет на доступность всей системы, вам следует реализовать ограничение скорости API для каждого клиента (также называемое «регулированием» или «контролем доступа»).

        Вы можете использовать планы использования API шлюза API и ключи для настройки лимитов для каждого клиента отдельно (подробнее см. документацию AWS [1], [2] и [3])

        2.3. Мультитенантный мониторинг

        API Gateway имеет два типа журналов:

        1. Журналы выполнения шлюза API: содержат такие данные, как значения параметров запроса или ответа, какие ключи API требуются, включены ли планы использования и т. д. Не включено по умолчанию, но его можно настроить.
        2. 2. Функция журналов доступа к шлюзу API: позволяет регистрировать, кто обращался к вашему API, как к нему обращались, к какой конечной точке осуществлялся доступ, а также результат вызова API. Вы можете указать свой формат журнала и выбрать, что именно регистрировать, с помощью контекстных переменных (см. документацию в CDK).

          Для мониторинга запросов ваших API-клиентов я бы рекомендовал включить ведение журнала доступа. Вы можете зарегистрировать как минимум AWS IAM ARN вызывающего абонента ($context.identity.userArn), путь запроса ($context.path), код состояния ответа вашей службы. $context.status и задержка вызова API ( $context.responseLatency).

          Лично для сервиса с функцией AWS IAM Auth и функцией Lambda в качестве вычисления я нашел полезной следующую конфигурацию ведения журнала доступа к шлюзу API:

          const formatObject = {
            requestId: '$context.requestId',
            extendedRequestId: '$context.extendedRequestId',
            apiId: '$context.apiId',
            resourceId: '$context.resourceId',
            domainName: '$context.domainName',
            stage: '$context.stage',
            path: '$context.path',
            resourcePath: '$context.resourcePath',
            httpMethod: '$context.httpMethod',
            protocol: '$context.protocol',
            accountId: '$context.identity.accountId',
            sourceIp: '$context.identity.sourceIp',
            user: '$context.identity.user',
            userAgent: '$context.identity.userAgent',
            userArn: '$context.identity.userArn',
            caller: '$context.identity.caller',
            cognitoIdentityId: '$context.identity.cognitoIdentityId',
            status: '$context.status',
            integration: {
              // The status code returned from an integration. For Lambda proxy integrations, this is the status code that your Lambda function code returns.
              status: '$context.integration.status',
              // For Lambda proxy integration, the status code returned from AWS Lambda, not from the backend Lambda function code.
              integrationStatus: '$context.integration.integrationStatus',
              // The error message returned from an integration
              // A string that contains an integration error message.
              error: '$context.integration.error',
              latency: '$context.integration.latency',
            },
            error: {
              responseType: '$context.error.responseType',
              message: '$context.error.message',
            },
            requestTime: '$context.requestTime',
            responseLength: '$context.responseLength',
            responseLatency: '$context.responseLatency',
          };
          
          const accessLogFormatString = JSON.stringify(formatObject);
          const accessLogFormat = apigw.AccessLogFormat.custom(accessLogFormatString);
          

          После включения ведения журнала вы можете использовать CloudWatch Insights, чтобы легко получать последние вызовы от выбранного клиента API с помощью:

          fields @timestamp, path, status, responseLatency, userArn
          | sort @timestamp desc
          | filter userArn like 'payment-service'
          | limit 20
          

          2.4. Метрики, сигналы тревоги, информационные панели

          Метрики CloudWatch, поддерживаемые API Gateway, по умолчанию агрегируются для всех запросов. Но вы можете проанализировать журналы доступа к API-шлюзу, чтобы опубликовать пользовательские метрики CloudWatch с дополнительным измерением имени вашего клиента, чтобы иметь возможность отслеживать использование клиентом (арендатором) вашего API. Как минимум я бы рекомендовал публиковать метрики CloudWatch для каждого клиента: Count, 4xx, 5xx, Latency, разделенные на Dimension=${Client. Вы также можете добавить такие параметры, как код статуса и путь к API.

          2.4.1. Использование фильтров журнала показателей для публикации показателей по каждому клиенту

          Фильтры журналов метрик CloudWatch (см. документацию) позволяют вам предоставить собственный фильтр и извлекать значения метрик из журналов доступа к шлюзу API (см. пример ниже). Фильтры журналов метрик также позволяют извлекать значения для пользовательских измерений метрик из журналов. Для мониторинга с несколькими арендаторами измерение «Клиент» может быть IAM ARN вызывающего абонента.

          Основными преимуществами фильтров журнала метрик являются (1) отсутствие вычислительных затрат для управления (2) простота и удобство использования. дешевый. Но вы не можете вносить какие-либо изменения в данные (например, устанавливать более читаемые имена клиентов вместо IAM ARN), и существует ограничение в 100 фильтров метрик на одну группу журналов (документы).

          Пример фильтра журнала метрик CloudWatch для публикации Count с измерением Client и Path

          new logs.MetricFilter(this, 'MultiTenantApiCountMetricFilter', {
            logGroup: accessLogsGroup,
            filterPattern: logs.FilterPattern.exists('$.userArn'),
            metricNamespace: metricNamespace,
            metricName: 'Count',
            metricValue: '1',
            unit: cloudwatch.Unit.COUNT,
            dimensions: {
              client: '$.userArn',
              method: '$.httpMethod',
              path: '$.path',},});
          });
          

          См. все метрики фильтруются по метрикам ошибок 4xx, 5xx и задержки в предоставленном образце приложения CDK.

          2.4.2. Использование функции Lambda для публикации показателей по каждому клиенту

          Альтернативный вариант — создать лямбда-функцию для анализа журналов, извлечения показателей и их публикации. Это позволяет вам выполнять дополнительные действия, например фильтровать неизвестных клиентов или извлекать имя клиента из userArn.

          Всего лишь пара строк кода CDK для подписки функции Lambda на журналы доступа к шлюзу API:

          const logProcessingFunction = new lambda.NodejsFunction(
            this, 
            'log-processor-function',
            {
              functionName: 'multi-tenant-api-log-processor-function',
            }
          );
          
          new logs.SubscriptionFilter(this, 'MultiTenantApiLogSubscriptionFilter', {
            logGroup: accessLogsGroup,
            destination: new logsd.LambdaDestination(logProcessingFunction),
            filterPattern: logs.FilterPattern.allEvents(),
          });
          

          Полный пример см. в коде, а также реализацию Лямбда-функция процессора журналов.

          Adding Usage Keys and Lambda function to publish per-client metrics

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

          Example of per-client metrics in CloudWatch Dashboard

          2.5. Подключение и отключение клиентов API

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

          Используя эту конфигурацию, приложение CDK может затем создать роль IAM, ключ использования шлюза API и передать имя клиента функции Lambda, которая анализирует журналы доступа (см. это в примере кода приложения).

          3. Мультитенантность с AWS AppSync

          Если у вашего сервиса есть API GraphQL, вы, вероятно, используете AppSync. Как и в случае с API-шлюзом, вы можете использовать IAM Auth для авторизации запросов AppSync. В AppSync нет политики использования ресурсов (см. проблема GH), поэтому вы можете использовать только роль -основанная авторизация для настройки контроля доступа к AppSync API. Как и в случае с API Gateway, вам потребуется создать отдельную роль IAM для каждого нового клиента вашего сервиса.

          К сожалению, AppSync имеет ограниченную поддержку регулирования для каждого клиента, которая нам необходима для изоляции и мониторинга клиентов. Хотя вы можете настроить ограничения TPS для AppSync с помощью WAF, вы не можете создавать отдельные ограничения для каждого клиента, чтобы изолировать ваших клиентов службы. Аналогично, AppSync не предоставляет журналы доступа, в отличие от API Gateway.

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

          Proxying requests to AppSync with API Gateway

          4. Заключение

          Вот и все. Если у вас есть какие-либо вопросы или идеи, дайте мне знать в комментариях или свяжитесь со мной напрямую. В следующей части этой серии я рассмотрю лучшие практики асинхронной внутренней интеграции с AWS Event Bridge и AWS SQS/SNS.

          Если вы хотите углубиться в тему создания мультитенантных сервисов на базе AWS, мне пригодятся следующие ресурсы:

          1. Информационный документ AWS о стратегиях изоляции клиентов SaaS
          2. Справедливость в многопользовательских системах
          3. AWS re:Invent 2021 — шаблоны архитектуры SaaS: от концепции к реализации
          4. :::информация Также опубликовано здесь.

            :::


            Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE