Блог Amazon Web Services

Представляем новый бессерверный LAMP-стек, часть 2: Реляционные базы данных

Оригинал статьи: ссылка (Benjamin Smith, Senior Developer Advocate)

Из этой статьи вы узнаете, как использовать реляционную базу данных Amazon Aurora, совместимую с MySQL, в ваших бессерверных приложениях. Я покажу, как с помощью Amazon RDS Proxy объединить подключения к базе данных в пулы и использовать их повторно. Все примеры исходного кода в этой статье написаны на PHP и выложены в GitHub репозитории. Аналогичным образом можно настроить и другие среды запуска, поддерживаемые AWS Lambda.

Бессерверный LAMP-стек

Данная архитектура более подробно описана в статье по ссылке. В ней используется одна или несколько Lambda-функций на языке PHP для чтения и записи в базу данных Amazon Aurora MySQL.

Amazon Aurora обеспечивает высокую производительность и доступность баз данных и имеет совместимость с MySQL и PostgreSQL. Она использует систему хранения, которая при необходимости автоматически масштабируется до 128 тебибайт (ТиБ). Каждый экземпляр Amazon Aurora создаётся внутри виртуальной сети Virtual Private Cloud (VPC) для предотвращения публичного доступа. Чтобы Lambda-функция могла подключиться к базе данных Aurora, она должна быть настроена с доступом к той же VPC.

При использовании прямого подключения к базе данных RDS доступная память может закончиться. Это может быть вызвано, например, резким повышением количества подключений к базе или большим количеством подключений, которые открываются и закрываются с высокой скоростью. В итоге это может привести к замедлению запросов и ограниченной масштабируемости всего приложения. Для решения этой проблемы применяется Amazon RDS Proxy. RDS Proxy – это функциональность Amazon RDS, реализующая полностью управляемый прокси баз данных. Он создаёт пул подключений к базе данных, который расположен между вашим приложением и самой базой, и повторно использует подключения из этого пула. Такой подход позволяет защитить базу данных от слишком большого количества подключений и дополнительных затрат CPU и памяти, вызванных обработкой новый подключений. Учётные данные для подключения к базе данных надёжно хранятся в AWS Secrets Manager. Доступ к ним осуществляется на основе роли IAM (AWS Identity and Access Management). Это обеспечивает высокие требования по аутентификации к приложениям, работающим с базой данных, без дополнительных трудозатрат на миграцию самих экземпляров баз данных.

Следующие шаги показывают, как осуществляется подключение к базе данных Amazon Aurora MySQL, запущенной внутри VPC. Этот процесс начинается в Lambda-функции, написанной на PHP. Она подключается к базе данных через RDS Proxy. Учётные данные базы данных, которые использует RDS Proxy, хранятся в Secrets Manager. Доступ к ним осуществляется с помощью аутентификации через IAM.

RDS Proxy с IAM-аутентификацией

Начало работы

Создание базы данных Amazon RDS Aurora MySQL

Перед тем как создавать кластер Aurora, необходимо сначала выполнить подготовительные действия, такие как создание VPC и группы подсетей для базы данных (DB subnet group). Более подробную информацию о том, как это сделать, можно найти в документации в разделе DB cluster prerequisites. После этого, для создания базы данных выполните следующие действия.

  1. Выполните команду create-db-cluster в AWS CLI для создания кластера Aurora MySQL.
    aws rds create-db-cluster \
    --db-cluster-identifier sample-cluster \
    --engine aurora-mysql \
    --engine-version 5.7.12 \
    --master-username admin \
    --master-user-password secret99 \
    --db-subnet-group-name default-vpc-6cc1cf0a \
    --vpc-security-group-ids sg-d7cf52a3 \
    --enable-iam-database-authentication true
  2. Добавьте новый экземпляр базы данных в кластер.
    aws rds create-db-instance \
        --db-instance-class db.r5.large \
        --db-instance-identifier sample-instance \
        --engine aurora-mysql  \
        --db-cluster-identifier sample-cluster
  3. Сохраните учётные данные от созданной базы в виде секрета в AWS Secrets Manager.
    aws secretsmanager create-secret \
    --name MyTestDatabaseSecret \
    --description "My test database secret created with the CLI" \
    --secret-string '{"username":"admin","password":"secret99","engine":"mysql","host":"<REPLACE-WITH-YOUR-DB-WRITER-ENDPOINT>","port":"3306","dbClusterIdentifier":"<REPLACE-WITH-YOUR-DB-CLUSTER-NAME>"}'

    Сохраните ARN секрета, который будет возвращён после выполнения команды. Он понадобится на следующем шаге.

    {
        "VersionId": "eb518920-4970-419f-b1c2-1c0b52062117", 
        "Name": "MySampleDatabaseSecret", 
        "ARN": "arn:aws:secretsmanager:eu-west-1:1234567890:secret:MySampleDatabaseSecret-JgEWv1"
    }

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

  4. Создайте политику IAM, которая предоставляет права на вызовы secretsmanager к указанному секрету (замените поле <the-arn-of-the-secret> на ARN из предыдущего шага).
    aws iam create-policy \
    --policy-name my-rds-proxy-sample-policy \
    --policy-document '{
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetResourcePolicy",
            "secretsmanager:GetSecretValue",
            "secretsmanager:DescribeSecret",
            "secretsmanager:ListSecretVersionIds"
          ],
          "Resource": [
            "<the-arn-of-the-secret>”
          ]
        },
        {
          "Sid": "VisualEditor1",
          "Effect": "Allow",
          "Action": [
            "secretsmanager:GetRandomPassword",
            "secretsmanager:ListSecrets"
          ],
          "Resource": "*"
        }
      ]
    }'

    Сохраните ARN созданной политики. Он понадобится, чтобы связать эту политику с новой ролью, которую мы создадим.

    {
        "Policy": {
            "PolicyName": "my-rds-proxy-sample-policy", 
            "PermissionsBoundaryUsageCount": 0, 
            "CreateDate": "2020-06-04T12:21:25Z", 
            "AttachmentCount": 0, 
            "IsAttachable": true, 
            "PolicyId": "ANPA6JE2MLNK3Z4EFQ5KL", 
            "DefaultVersionId": "v1", 
            "Path": "/", 
            "Arn": "arn:aws:iam::1234567890112:policy/my-rds-proxy-sample-policy", 
            "UpdateDate": "2020-06-04T12:21:25Z"
         }
    }
  5. Создайте роль IAM, которая будет использована сервисом RDS Proxy. С помощью этой роли RDS Proxy сможет получить доступ к учётным данным базы данных.
    aws iam create-role --role-name my-rds-proxy-sample-role --assume-role-policy-document '{
     "Version": "2012-10-17",
     "Statement": [
      {
       "Sid": "",
       "Effect": "Allow",
       "Principal": {
        "Service": "rds.amazonaws.com"
       },
       "Action": "sts:AssumeRole"
      }
     ]
    }'
  6. Добавьте созданную ранее политику в роль:
    aws iam attach-role-policy \
    --role-name my-rds-proxy-sample-role \
    --policy-arn arn:aws:iam::123456789:policy/my-rds-proxy-sample-policy

Создание RDS Proxy

  1. Используйте AWS CLI, чтобы создать новый RDS Proxy. Замените значения -role-arn и SecretArn на соответствующие ARN из предыдущих шагов.
    aws rds create-db-proxy \
    --db-proxy-name sample-db-proxy \
    --engine-family MYSQL \
    --auth '{
            "AuthScheme": "SECRETS",
            "SecretArn": "arn:aws:secretsmanager:eu-west-1:123456789:secret:exampleAuroraRDSsecret1-DyCOcC",
             "IAMAuth": "REQUIRED"
          }' \
    --role-arn arn:aws:iam::123456789:role/my-rds-proxy-sample-role \
    --vpc-subnet-ids  subnet-c07efb9a subnet-2bc08b63 subnet-a9007bcf

    Для принудительной аутентификации пользователей RDS Proxy через IAM значение IAMAuth установлено в REQUIRED. Это более безопасная альтернатива встраиванию учётных данных базы данных в исходный код приложения.

    Кластер баз данных Aurora и его экземпляры называются целями (targets) созданного прокси.

  2. Добавьте кластер баз данных в прокси с помощью команды register-db-proxy-targets.
    aws rds register-db-proxy-targets \
    --db-proxy-name sample-db-proxy \
    --db-cluster-identifiers sample-cluster

Развёртывание Lambda-функции на PHP с доступом к VPC

В GitHub репозитории находится Lambda-функция со средой запуска PHP, настроенной с использованием слоя Lambda. Эта функция использует расширение MySQLi для подключения к RDS Proxy. Расширение было установлено и собрано вместе с исполняемым файлом PHP с помощью следующей команды:

Исполняемый файл PHP был упакован вместе с bootstrap-файлом для создания среды запуска PHP в Lambda. Более подробную информацию о создании своей среды запуска для PHP вы можете найти в предыдущей статье серии.

Установите стек приложения с помощью AWS Serverless Application Model (AWS SAM) CLI:

sam deploy -g

При запросе введите значения SecurityGroupIds и SubnetIds для вашего кластера Aurora.

Шаблон SAM передаёт параметры SecurityGroupIds и SubnetIds в Lambda-функцию, используя подресурс VpcConfig.

Lambda создаёт эластичный сетевой интерфейс (elastic network interface, ENI) для каждой комбинации группы безопасности (security group) и подсети в конфигурации VPC соответствующей функции. Функция может получить доступ к другим ресурсам (и интернету) только через этот VPC.

Добавление RDS Proxy к Lambda-функции

  1. Перейдите в консоль Lambda.
  2. Выберите только что созданную функцию PHPHelloFunction.
  3. Нажмите Add database proxy внизу страницы.
  4. Выберите опцию Choose an existing database proxy, затем выберите sample-db-proxy.
  5. Нажмите Add.

Использование RDS Proxy из Lambda-функции

Lambda-функция импортирует три библиотеки из AWS SDK для PHP. Они используются для генерации токена для подключения к базе данных на основе учётных данных, хранящихся в Secrets Manager.

Библиотеки AWS SDK для PHP предоставляются слоем PHP-example-vendor. Использование слоёв Lambda таким образом позволяет создать механизм по встраиванию дополнительных библиотек и зависимостей по мере развития приложения.

Обработчик функции под названием index представляет собой точку входа в код функции. Вначале вызывается getenv() для того, чтобы получить переменные окружения, установленные при развёртывании приложения через SAM. После этого они доступны в локальных переменных до окончания вызова Lambda-функции.

Класс AuthTokenGenerator используется при аутентификации IAM и генерирует токен для аутентификации в RDS. Для его инициализации необходимо передать в конструктор объект credential provider. Затем вызывается метод createToken(), в который в качестве параметров передаются адрес конечной точки прокси, порт, регион и имя пользователя базы данных. Полученный таким образом токен впоследствии используется для подключения к прокси.

Класс mysqli в PHP используется для подключения к базе данных MySQL. Метод real_connect() используется для открытия подключения к базе данных через RDS Proxy. Вместо адреса базы данных первым параметром необходимо передать адрес конечной точки прокси. Также передаются имя пользователя базы данных, временный токен, название базы данных и порт. Кроме того, указана константа MYSQLI_CLIENT_SSL, чтобы убедиться, что подключение будет использовать шифрование SSL.

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

Вывод после успешного запуска функции выглядит следующим образом:

Мониторинг RDS Proxy и настройка производительности

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

Установите максимальное время ожидания в соответствии с требованиями вашего приложения через настройку Connection borrow timeout. Она указывает на то, как долго сервис будет ждать доступного подключения из пула, перед тем как вернёт ошибку.

Измените интервал Idle client connection timeout, чтобы помочь вашим приложениям обрабатывать устаревшие ресурсы. С помощью него можно уберечь ваши приложения от ошибочного оставления открытых подключений к базе данных, которые тратят важные системные ресурсы.

Несколько приложений, использующих одну базу данных, могут каждый использовать RDS Proxy для разделения общей квоты подключений между ними. Вы можете установить максимальное количество подключений для каждого прокси в виде процента от значения max_connections (для MySQL).

В следующем примере показано, как можно поменять MaxConnectionsPercent для целевой группы в прокси.

aws rds modify-db-proxy-target-group \
--db-proxy-name sample-db-proxy \
--target-group-name default \
--connection-pool-config '{"MaxConnectionsPercent": 75 }'

Ответ:

{
    "TargetGroups": [
        {
            "DBProxyName": "sample-db-proxy",
            "TargetGroupName": "default",
            "TargetGroupArn": "arn:aws:rds:eu-west-1:####:target-group:prx-tg-03d7fe854604e0ed1",
            "IsDefault": true,
            "Status": "available",
            "ConnectionPoolConfig": {
            "MaxConnectionsPercent": 75,
            "MaxIdleConnectionsPercent": 50,
            "ConnectionBorrowTimeout": 120,
            "SessionPinningFilters": []
        	},            
"CreatedDate": "2020-06-04T16:14:35.858000+00:00",
            "UpdatedDate": "2020-06-09T09:08:50.889000+00:00"
        }
    ]
}

Если RDS Proxy обнаружит, что сессия находится в таком состоянии, что её нельзя повторно использовать, он будет использовать для неё одно и то же подключение до завершения этой сессии. Такое поведение называется закреплением (pinning). При настройке производительности RDS Proxy необходимо максимизировать повторное использование подключений путём минимизации количества закреплений.

Вы можете следить за метрикой Amazon CloudWatch под названием DatabaseConnectionsCurrentlySessionPinned, чтобы понять как часто в вашем приложении происходит закрепление.

Amazon CloudWatch собирает и обрабатывает сырые данные из RDS Proxy и строит на их основе удобочитаемые метрики практически в реальном времени. Используйте указанные в документации метрики, чтобы следить за количеством подключений и расходуемой на управление подключениями памятью. Благодаря им, вы можете понять, будет ли польза вашему экземпляру или кластеру базы данных от использования RDS Proxy. Например, в случае если он обрабатывает много коротких подключений или подключений, которые открываются и закрываются с высокой скоростью.

Заключение

Из этой статьи вы узнали, как создавать и настраивать RDS Proxy для управления подключениями из Lambda-функции на языке PHP к базе данных Aurora MySQL. Вы увидели, как можно обеспечить соблюдение строгих требований аутентификации с использованием Secrets Manager и аутентификации IAM. Вы развернули Lambda-функцию, которая использует слои Lambda для хранения AWS SDK для PHP в качестве зависимости.

Вы можете создавать безопасные, масштабируемые и производительные бессерверные приложения, использующие реляционные базы данных. Для этого расположите RDS Proxy между вашей базой данных и функциями Lambda. Вы можете осуществить миграцию базы данных MySQL в кластер Aurora без изменения самой базы. С помощью RDS Proxy и Lambda вы можете создавать бессерверные PHP-приложения быстрее и с меньшим количеством кода.

Больше примеров на PHP вы можете найти в репозитории Serverless LAMP stack.