В этой серии публикаций в блоге я хотел бы обсудить лучшие практики создания мультитенантных сервисов в AWS . Существующая литература о том, как создавать мультитенантные сервисы, обычно ориентирована на приложения SaaS с сотнями клиентов (например ).
Я разделю серию сообщений в блоге на три части для каждого типа интеграции между сервисами: синхронная, асинхронная и пакетная интеграция.
Мультитенантность для внутренних сервисов
1.1. Изоляция арендатора 1.2. Мультитенантный мониторинг 1.3. МасштабированиеМультитенантность для внутренних сервисов
2.1. Изоляция арендатора - контроль доступа 2.2 Изоляция арендатора – проблема шумного соседа 2.3 Мультиарендный мониторинг 2.4 Метрики, сигналы тревоги, информационные панели 2.5 Подключение и отключение клиентов APIМультитенантность с AWS AppSync
Заключение
Мультиарендность — это способность программного обеспечения обслуживать нескольких клиентов или арендаторов с помощью одного экземпляра программного обеспечения.
Как только вы разрешите нескольким командам вызывать API вашего сервиса, ваш сервис станет мультитенантным. Мультитенантная архитектура усложняет ваши услуги, например изоляцию клиентов, мониторинг на уровне клиентов и масштабирование.
Если вы создаете свой веб-сервис AWS с помощью REST , HTTP или WebSocket API в AWS, вы, скорее всего, используете API Gateway.
Подключение клиента с авторизацией на основе ресурсов . Для доступа на основе ресурсов вам необходимо обновить политику ресурсов шлюза API и добавить учетную запись AWS вашего клиента. Основным недостатком этого метода является то, что после обновления политики ресурсов необходимо повторно развернуть этап API Gateway, чтобы изменения вступили в силу (см. документы AWS и ). Однако если вы используете CDK, вы можете автоматизировать развертывание новых этапов (см. ). Еще одним недостатком является ограничение максимальной длины ресурсной политики.
Подключение клиента с авторизацией на основе личности . Для управления доступом на основе удостоверений вам необходимо создать роль IAM для клиента и разрешить клиенту взять на себя ее использование, обновив политику ресурсов роли (доверенные отношения). Вы можете использовать пользователей IAM, но роли IAM лучше с точки зрения безопасности. Роли допускают аутентификацию с использованием временных учетных данных и не требуют хранения учетных данных пользователя IAM. Существует ограничение в 1000 ролей на одну учетную запись, но это ограничение можно изменить. Кроме того, еще одним недостатком ролевого метода получения доступа к вашему API между учетными записями является то, что вам необходимо создавать роль IAM для каждого нового клиента API. Однако управление ролями можно автоматизировать с помощью 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}`, }) }; };
API Gateway имеет два типа журналов:
Для мониторинга запросов ваших API-клиентов я бы рекомендовал включить ведение журнала доступа. Вы можете зарегистрировать как минимум AWS IAM ARN вызывающего абонента ( $context.identity.userArn
), путь запроса ( $context.path
), код состояния ответа вашего сервиса $context.status
и задержку вызова API ( $context.responseLatency
). .
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);
fields @timestamp, path, status, responseLatency, userArn | sort @timestamp desc | filter userArn like 'payment-service' | limit 20
Метрики CloudWatch, поддерживаемые API Gateway, по умолчанию агрегируются для всех запросов. Но вы можете проанализировать журналы доступа к API-шлюзу, чтобы опубликовать пользовательские метрики CloudWatch с дополнительным измерением имени вашего клиента, чтобы иметь возможность отслеживать использование клиентом (арендатором) вашего API. Как минимум я бы рекомендовал публиковать метрики CloudWatch для каждого клиента Count, 4xx, 5xx, Latency, разделенные по Dimension=${Client}
. Вы также можете добавить такие измерения, как код состояния и путь к API.
2.4.1. Использование фильтров журнала метрик для публикации метрик по каждому клиенту.
Пример фильтра журнала метрик 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',},}); });
2.4.2. Использование функции Lambda для публикации показателей по каждому клиенту
Альтернативный вариант — создать функцию Lambda для анализа журналов, извлечения метрик и их публикации. Это позволяет вам делать больше пользовательских действий, таких как фильтрация неизвестных клиентов или извлечение имени клиента из userArn.
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(), });
interface ApiClientConfig { name: string; awsAccounts: string[]; rateLimit: number; burstLimit: number; } const apiClients: ApiClientConfig[] = [ { name: 'payment-service', awsAccounts: ['3','444455556666'], rateLimit: 10, burstLimit: 2, }, { name: 'order-service', awsAccounts: ['777788889999'], rateLimit: 1, burstLimit: 1, }, ];
Если у вашего сервиса есть API GraphQL , вы, вероятно, используете AppSync. Как и в случае с API-шлюзом, вы можете использовать IAM Auth для авторизации запросов AppSync. У AppSync нет политики ресурсов (см. ), поэтому для настройки контроля доступа к AppSync API можно использовать только авторизацию на основе ролей. Как и в случае с API Gateway, вам потребуется создать отдельную роль IAM для каждого нового клиента вашего сервиса.