Бэкэнд -приложения имеют много движущихся частей. Помимо проектирования ваших доменных моделей, вы также должны подумать о том, что разоблачить и как сохранить ваши данные.
Для приложений, которые используют реляционные данные данных, такие как MySQL, PostgreSQL, мигрирующая схема имеет решающее значение для успешного развертывания.
Мне нравится думать о миграциях как коде, который обновляет схему базы данных. В этом посте поддерживается GitHub Project Вы узнаете один способ обработки миграции схемы без сервера с использованием пользовательского ресурса.
Пользовательские ресурсы
Пользовательские ресурсы Позвольте вам написать пользовательскую логику обеспечения в шаблонах, которые AWS CloudFormation работает в любое время, когда вы создаете, обновляете (если вы измените пользовательский ресурс) или удаляете стек.
Мы могли бы написать функцию Lambda с помощью пользовательского ресурса, который запускает миграции после обеспечения базы данных.
Следующий вопрос, который приходит на ум, заключается в том, как сделать сценарии схемы доступными для Lambda, не включив его в слой развертывания. Здесь входит слой AWS Lambda.
Lambda Layer Позволяет вам архивировать зависимости и использовать их в ваших функциях, не включив их в ваш уровень развертывания.
Без лишних слов давайте посмотрим, как это сделать с CDK.
Создание обработчика миграции
migration_handler.py
это функция, которая выполняется во время создания пользовательского ресурса. Он использует Данные API Чтобы подключить экземпляр базы данных, прочитайте файлы миграции и запустите их в базе данных.
import boto3 import os import logging as log from botocore import exceptions import cfnresponse import glob log.getLogger().setLevel(log.INFO) def main(event, context): SQL_PATH = "/opt" # Layers are extracted to the /opt directory in the function execution environment. # This needs to change if there are to be multiple resources # in the same stack physical_id = "SchemaMigrationResource" # If this is a Delete event, do nothing. The schema will be destroyed along with the cluster. if event['RequestType'] == 'Delete': cfnresponse.send(event, context, cfnresponse.SUCCESS, {"Response": "Deleted successfully"}, physical_id) try: log.info("Input event: %s", event) sqlfiles = glob.glob(os.path.join(SQL_PATH, "*.sql")) log.info(sqlfiles) for file_path in sqlfiles: log.info(f"Found an SQL script in path:{file_path}") execute_sql_file(file_path) log.info("Ran migration successfully") attributes = {"Response": f"Ran migration successfully for these files:{sqlfiles}"} cfnresponse.send(event, context, cfnresponse.SUCCESS, attributes, physical_id) except Exception as e: log.exception(e) # cfnresponse's error message is always "see CloudWatch" cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id) raise RuntimeError("Create failure requested") def execute_statement(sql, sql_parameters=[], transaction_id=None): log.info(f"sql query:{sql}") client = boto3.client("rds-data") parameters = { "secretArn": os.getenv("DB_SECRET_ARN"), "database": os.getenv("DB_NAME"), "resourceArn": os.getenv("DB_CLUSTER_ARN"), "sql": sql, "parameters": sql_parameters, } if transaction_id is not None: parameters["transactionId"] = transaction_id try: response = client.execute_statement(**parameters) return response except client.exceptions.BadRequestException as e: log.exception(e) raise RuntimeError("Create failure requested") def execute_sql_file(file_path: str): log.info(f"executing file in : {file_path}") with open(file_path, "r") as script: script_content = script.read() queries = script_content.split(";") for query in queries: sql = query.strip() if sql: execute_statement(query) log.info(f"executed the file : {file_path} successfully")
Создание пользовательского ресурса
migration.py
Содержит код для создания функции Lambda и Lambda -слоя. Мы передали некоторые переменные среды (например, ссылка на базу данных, секрет и имя базы данных), необходимые функции Lambda для подключения к базе данных.
from aws_cdk import ( core, custom_resources as cr, aws_lambda as _lambda, aws_cloudformation as cfn, aws_iam as _iam, ) from aws_cdk.aws_lambda import Runtime SQL_SCRIPTS_PATH = "scripts/schema" class SchemaMigrationResource(core.Construct): def __init__( self, scope: core.Construct, id: str, secret_arn: str, db_name: str, db_ref: str, **kwargs, ): super().__init__( scope, id, **kwargs, ) with open("migration_handler.py", encoding="utf-8") as fp: code_body = fp.read() lambda_function = _lambda.SingletonFunction( self, "Singleton", uuid="f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc", code=_lambda.InlineCode(code_body), handler="index.main", timeout=core.Duration.seconds(300), runtime=_lambda.Runtime.PYTHON_3_7, layers=[ _lambda.LayerVersion( scope, id="migrationscripts", code=_lambda.Code.from_asset(SQL_SCRIPTS_PATH), description="Database migration scripts", ) ], environment={ "DB_NAME": db_name, "DB_SECRET_ARN": secret_arn, "DB_CLUSTER_ARN": db_ref, }, ) # Allow lambda to read database secret lambda_function.add_to_role_policy( _iam.PolicyStatement( resources=[secret_arn], actions=["secretsmanager:GetSecretValue"], ) ) # allow lambda to execute query on the database lambda_function.add_to_role_policy( _iam.PolicyStatement( resources=[db_ref], actions=[ "rds-data:ExecuteStatement", "rds-data:BatchExecuteStatement", ], ) ) # assign policies to the Lambda function so it can output to CloudWatch Logs. lambda_function.add_to_role_policy( _iam.PolicyStatement( resources=["*"], actions=[ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents", ], ) ) resource = cfn.CustomResource( self, "Resource", provider=cfn.CustomResourceProvider.lambda_(lambda_function), properties=kwargs, ) self.response = resource.get_att("Response").to_string()
Создание без сервера экземпляра Aurora
С помощью определенного пользовательского ресурса мы можем создать стек Aurora без сервера с помощью этого кода:
# filename: migration.py import os from aws_cdk import ( aws_ec2 as ec2, aws_rds as rds, core, aws_secretsmanager as sm, ) from .migration import SchemaMigrationResource class RDSStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) vpc = ec2.Vpc(self, "VPC") db_master_user_name = os.getenv("DB_USERNAME", "admin_user") self.secret = rds.DatabaseSecret( self, id="MasterUserSecret", username=db_master_user_name ) rds.CfnDBSubnetGroup( self, "rdsSubnetGroup", db_subnet_group_description="private subnets for rds", subnet_ids=vpc.select_subnets( subnet_type=ec2.SubnetType.PRIVATE ).subnet_ids, ) db_name = os.getenv("DB_NAME", "anonfed") self.db = rds.CfnDBCluster( self, "auroraCluster", engine="aurora-mysql", engine_version="5.7.mysql_aurora.2.08.1", db_cluster_parameter_group_name="default.aurora-mysql5.7", # snapshot_identifier="", # your snapshot engine_mode="serverless", scaling_configuration=rds.CfnDBCluster.ScalingConfigurationProperty( auto_pause=True, min_capacity=1, max_capacity=4, seconds_until_auto_pause=300, ), db_subnet_group_name=core.Fn.ref("rdsSubnetGroup"), database_name=db_name, master_username=self.secret.secret_value_from_json("username").to_string(), master_user_password=self.secret.secret_value_from_json( "password" ).to_string(), enable_http_endpoint=True, ) secret_attached = sm.CfnSecretTargetAttachment( self, id="secret_attachment", secret_id=self.secret.secret_arn, target_id=self.db.ref, target_type="AWS::RDS::DBCluster", ) secret_attached.node.add_dependency(self.db) db_ref = f"arn:aws:rds:{self.region}:{self.account}:cluster:{self.db.ref}" migration = SchemaMigrationResource( self, "schemamigration", self.secret.secret_arn, db_name, db_ref ) # Publish the custom resource output core.CfnOutput( self, "ResponseMessage", description="Database Migration", value=migration.response, )
Наконец, мы можем подключить все вместе в app.py
env_EU = core.Environment( account=os.environ.get("CDK_DEPLOY_ACCOUNT", os.environ["CDK_DEFAULT_ACCOUNT"]), region=os.environ.get("CDK_DEPLOY_REGION", os.environ["CDK_DEFAULT_REGION"]), ) app = core.App() db = RDSStack(scope=app, id="aurora", env=env_EU) ap_stack = Api(scope=app, id="api", env=env_EU, db=db) app.synth()
Вывод
В этом посте мы видели, как перенести схему базы данных, используя пользовательский ресурс Cloud Formation. Чтобы опираться на эти знания, я призываю вас взглянуть на полный проект в этом Репозиторий Анкет
Рекомендации
Оригинал: “https://dev.to/aws-builders/handling-serverless-aurora-schema-migration-using-custom-resource-with-python-cdk-5fpc”