背景

Amazon Aurora MySQL 提供了比社区 MySQL 5.7 更长达一年的免费扩展支持,但该版本将于 2023 年 10 月 31 日终止。如果您需要更多时间完成升级,可以选择 Amazon RDS 扩展支持,提供最多 28 个月的额外支持,直到 2027 年 2 月扩展支持结束。

RDS 蓝绿升级

在进行 RDS 升级时,使用蓝绿部署是一种最佳实践,特别是在原集群开启了二进制日志记录(binlog_format)的情况下。升级操作主要涉及升级主要或次要数据库引擎版本、更改数据库参数或在暂存环境中更改模式。准备就绪后,可以将暂存环境提升为新的生产数据库环境,通常情况下停机时间不会超过一分钟。更多详细信息请参考 AWS 官方文档

注意事项

  1. 确保满足升级的先决条件:
  • 原集群开启二进制日志记录 (binlog_format)。
  • 原实例类型最小为 db.t3.medium
  • 原写入程序实例与集群参数组同步状态。
  • 确保蓝色环境中不包含任何名为 tmp 的数据库,因为具有此名称的数据库将不会复制到绿色环境中。
  1. 全面测试绿色环境:
  • 在绿色环境中进行全面测试,包括数据库查询、事务和应用程序功能。
  • 谨慎启用写入操作,因为可能导致复制冲突,并可能导致切换后生产数据库中出现意外数据。
  1. 保持绿色环境只读:
  • 在绿色环境中配置数据库为只读,以防止写入操作对复制和切换的影响。
  • 避免在绿色环境中进行写入操作,除非确信这不会影响升级过程。
  1. 进行与复制兼容的更改:
  • 确保在蓝绿部署实现模式更改时,只进行与复制兼容的更改,以避免复制问题。
  1. 使用集群端点或读取器端点连接:
  • 为两个环境中的所有连接使用集群端点、读取器端点或自定义端点,以确保连接的无缝切换。
  • 避免使用带有静态或排除列表的实例端点或自定义端点。
  1. 版本兼容性:
  • 确保不使用 Aurora MySQL 版本 2.08 和 2.09 作为升级源版本或目标版本。
  1. 蓝色环境数据库命名规范:
  • 确保蓝色环境中的数据库不包含任何名为 tmp 的数据库。

创建参数组

在进行升级之前,首先需要创建新的数据库参数组。以下是通过 Python 脚本创建数据库参数组的示例:

创建 DB 参数组
import boto3

def create_db_parameter_group(cluster_name):
    # 创建 Boto3 客户端
    client = boto3.client('rds')

    # 构建 CreateDBParameterGroup 的参数
    params = {
        "DBParameterGroupFamily": "aurora-mysql8.0",
        "DBParameterGroupName": f"{cluster_name}-mysql8",
        "Description": f"{cluster_name} db parameter group"
    }

    # 发送 CreateDBParameterGroup 请求
    response = client.create_db_parameter_group(**params)

    # 检查响应结果
    if response['ResponseMetadata']['HTTPStatusCode'] == 200:
        print("DB parameter group created successfully!")
    else:
        print("Failed to create DB parameter group. Error:", response['ResponseMetadata']['HTTPStatusCode'])

create_db_parameter_group("test-cluster-v1")
创建集群参数组
import boto3

def create_db_cluster_parameter_group(cluster_name):
    # 创建 Boto3 客户端
    client = boto3.client('rds')

    # 构建 CreateDBClusterParameterGroup 的参数
    params = {
        "DBClusterParameterGroupName": f"{cluster_name}-mysql8",
        "DBParameterGroupFamily": "aurora-mysql8.0",
        "Description": f"{cluster_name} cluster parameter group"
    }

    # 发送 CreateDBClusterParameterGroup 请求
    response = client.create_db_cluster_parameter_group(**params)

    # 检查响应结果
    if response['ResponseMetadata']['HTTPStatusCode'] == 200:
        print("DB cluster parameter group created successfully!")
    else:
        print("Failed to create DB cluster parameter group. Error:", response['ResponseMetadata']['HTTPStatusCode'])

create_db_cluster_parameter_group("test-cluster-v1")

修改参数组

在创建参数组后,可以根据需要修改参数(启用性能详情为例子)。以下是通过 Python 脚本修改数据库参数组的示例:

修改 DB 参数组
import boto3

def modify_db_parameter_group(DBParameterGroupName):
    # 创建Boto3客户端
    client = boto3.client('rds')
    
    # 构建ModifyDBParameterGroup的参数
    params = {
        "DBParameterGroupName": DBParameterGroupName,
        "Parameters": [
            {
                "ApplyMethod": "immediate", #'immediate'|'pending-reboot'
                "ParameterName": "performance_schema",
                "ParameterValue": "1"
            },
            {
                "ApplyMethod": "pending-reboot",
                "ParameterName": "performance-schema-consumer-events-waits-current",
                "ParameterValue": "ON"
            },
            {
                "ApplyMethod": "pending-reboot",
                "ParameterName": "performance-schema-instrument",
                "ParameterValue": "wait/% = ON"
            },
            {
                "ApplyMethod": "pending-reboot",
                "ParameterName": "performance_schema_consumer_global_instrumentation",
                "ParameterValue": "1"
            },
            {
                "ApplyMethod": "pending-reboot",
                "ParameterName": "performance_schema_consumer_thread_instrumentation",
                "ParameterValue": "1"
            }
        ]
    }
    
    # 发送ModifyDBParameterGroup请求
    response = client.modify_db_parameter_group(**params)

    # 检查响应结果
    if response['ResponseMetadata']['HTTPStatusCode'] == 200:
        print("DB parameter group modified successfully!")
    else:
        print("Failed to modify DB parameter group. Error:", response['ResponseMetadata']['HTTPStatusCode'])

modify_db_parameter_group("test-cluster-v1-mysql8")
修改集群参数组
import boto3

def modify_db_cluster_parameter_group(DBClusterParameterGroupName):
    # 创建Boto3客户端
    client = boto3.client('rds')

    # 构建ModifyDBClusterParameterGroup的参数
    params = {
    "DBClusterParameterGroupName": DBClusterParameterGroupName,
    "Parameters": [
    {
    "ApplyMethod": "pending-reboot",
    "ParameterName": "binlog_format",
    "ParameterValue": "ROW"
    },
    {
    "ApplyMethod": "pending-reboot",
    "ParameterName": "performance_schema",
    "ParameterValue": "1"
    },
    {
    "ApplyMethod": "pending-reboot",
    "ParameterName": "performance-schema-consumer-events-waits-current",
    "ParameterValue": "ON"
    },
    {
    "ApplyMethod": "pending-reboot",
    "ParameterName": "performance-schema-instrument",
    "ParameterValue": "wait/% = ON"
    },
    {
    "ApplyMethod": "pending-reboot",
    "ParameterName": "performance_schema_consumer_global_instrumentation",
    "ParameterValue": "1"
    },
    {
    "ApplyMethod": "pending-reboot",
    "ParameterName": "performance_schema_consumer_thread_instrumentation",
    "ParameterValue": "1"
    }
    ]
    }

    # 发送ModifyDBClusterParameterGroup请求
    response = client.modify_db_cluster_parameter_group(**params)

    # 检查响应结果
    if response['ResponseMetadata']['HTTPStatusCode'] == 200:
        print("DB cluster parameter group modified successfully!")
    else:
        print("Failed to modify DB cluster parameter group. Error:", response['ResponseMetadata']['HTTPStatusCode'])

modify_db_cluster_parameter_group("test-cluster-v1-mysql8")

参数组比较

新旧参数组比较,确保与旧的一致

修改实例参数组

import boto3

def edit_db_parameters(source_db_parameter_group_name,target_db_parameter_group_name,region_name):
    # 创建一个RDS客户端
    rds = boto3.client('rds', region_name=region_name)

    # 获取源参数组的详细信息
    source_response = rds.describe_db_parameters(
        DBParameterGroupName=source_db_parameter_group_name,
        Source='user'  # 只返回用户修改过的参数
    )

    # 获取目标参数组的详细信息
    target_response = rds.describe_db_parameters(
        DBParameterGroupName=target_db_parameter_group_name,
        Source='user'  # 只返回用户修改过的参数
    )

    # 获取目标参数组的所有参数名称
    target_param_names = [param['ParameterName'] for param in target_response['Parameters']]

    # 遍历源参数组的参数,并更新目标参数组
    for param in source_response['Parameters']:
        if 'ParameterValue' in param:  # 检查'ParameterValue'键是否存在
            param_name = param['ParameterName']
            param_value = param['ParameterValue']

            # 检查参数名称是否存在于目标参数组
            if param_name in target_param_names:
                # 更新目标参数组
                rds.modify_db_parameter_group(
                    DBParameterGroupName=target_db_parameter_group_name,
                    Parameters=[
                        {
                            'ParameterName': param_name,
                            'ParameterValue': param_value,
                            'ApplyMethod': 'immediate'
                        },
                    ]
                )

edit_db_parameters('test-cluster-v1','test-cluster-v1-mysql8','us-east-1')

修改集群参数组

import boto3

def edit_db_cluster_parameters(source_db_cluster_parameter_group_name,target_db_cluster_parameter_group_name,region_name):
    # 创建一个RDS客户端
    rds = boto3.client('rds', region_name=region_name)

    # 获取源参数组的详细信息
    source_response = rds.describe_db_cluster_parameters(
        DBClusterParameterGroupName=source_db_cluster_parameter_group_name,
        Source = 'user'  # 只返回用户修改过的参数
    )

    # 获取目标参数组的详细信息
    target_response = rds.describe_db_cluster_parameters(
        DBClusterParameterGroupName=target_db_cluster_parameter_group_name,
        Source='user'  # 只返回用户修改过的参数
    )

    # 获取目标参数组的所有参数名称
    target_param_names = [param['ParameterName'] for param in target_response['Parameters']]

    # 遍历源参数组的参数,并更新目标参数组
    for param in source_response['Parameters']:
        if 'ParameterValue' in param:  # 检查'ParameterValue'键是否存在
            param_name = param['ParameterName']
            param_value = param['ParameterValue']

            # 检查参数名称是否存在于目标参数组
            if param_name in target_param_names:
                # 更新目标参数组
                rds.modify_db_cluster_parameter_group(
                    DBClusterParameterGroupName=target_db_cluster_parameter_group_name,
                    Parameters=[
                        {
                            'ParameterName': param_name,
                            'ParameterValue': param_value,
                            'ApplyMethod': 'immediate'
                        },
                    ]
                )

edit_db_cluster_parameters('test-cluster-v1','test-cluster-v1-mysql8', 'us-east-1')


创建绿环境

如果存在报错先解决,再重新创建绿环境是蓝绿部署中的关键步骤,以下是通过 Python 脚本创建蓝绿环境的示例:

import boto3

def create_blue_green_deployment(cluster_name):
    # 创建 Boto3 客户端
    client = boto3.client('rds')

    # 构建 CreateBlueGreenDeployment 的参数
    params = {
        "BlueGreenDeploymentName": f"{cluster_name}-green",
        "Source": f"arn:aws:rds:us-east-1:830710710665:cluster:{cluster_name}",
        "TargetDBClusterParameterGroupName": f"{cluster_name}-mysql8",
        "TargetDBParameterGroupName": f"{cluster_name}-mysql8",
        "TargetEngineVersion": "8.0.mysql_aurora.3.05.0"
    }

    # 发送 CreateBlueGreenDeployment 请求
    response = client.create_blue_green_deployment(**params)

    # 检查响应结果
    if response['ResponseMetadata']['HTTPStatusCode'] == 200:
        print("Blue-green deployment created successfully!")
    else:
        print("Failed to create blue-green deployment. Error:", response['Responsemetadata']['HTTPStatusCode'])

create_blue_green_deployment("test-cluster-v1")

重启绿环境实例和修改证书

重启绿环境实例使参数组生效,修改证书使过期时间变长

创建默认的证书颁发机构为rds-ca-2019,以下更新为rds-ca-rsa2048-g1,修改不影响业务

import boto3

def modify_db_instance(instance_identifier, ca_certificate_identifier):
    rds_client = boto3.client('rds')
    
    response = rds_client.modify_db_instance(
    DBInstanceIdentifier=instance_identifier,
    CACertificateIdentifier=ca_certificate_identifier,
    ApplyImmediately=True
    )
    
    print(f"Modification request for instance {instance_identifier} submitted successfully.")

# 调用函数来修改RDS实例
modify_db_instance('test-cluster-v1-c1-green-vap0g2', 'rds-ca-rsa2048-g1')

重启绿环境实例使参数组生效

import boto3
def reboot_db_instance(db_instance_identifier):
    # Create a low-level client with the service name
    rds = boto3.client('rds')

    # Call reboot_db_instance method to reboot the instance
    response = rds.reboot_db_instance(
        DBInstanceIdentifier=db_instance_identifier
    )

    print(response)
reboot_db_instance('test-cluster-v1-c1-green-vap0g2')

切换环境

import boto3

def switchover_blue_green_deployment(blue_green_deployment_identifier):
    client = boto3.client('rds')
    response = client.switchover_blue_green_deployment(
        BlueGreenDeploymentIdentifier=blue_green_deployment_identifier,
        SwitchoverTimeout=300
    )
    # 检查响应结果
    if response['ResponseMetadata']['HTTPStatusCode'] == 200:
        print("Blue-green switchover successfully!")
    else:
        print("Failed to switchover blue-green. Error:", response['ResponseMetadata']['HTTPStatusCode'])


switchover_blue_green_deployment('bgd-bltpowzwckbinsim')

排查创建绿环境报错

在创建绿环境时,可能会遇到一些错误。以下是通过 Python 脚本排查错误并获取详细信息的示例:

import boto3

def detect_abnormal_green_environment(db_instance_identifier):
    # 创建 Boto3 客户端
    client = boto3.client('rds')

    # 查询 upgrade-prechecks.log 文件是否存在
    response = client.describe_db_log_files(DBInstanceIdentifier=db_instance_identifier)
    log_files = response['DescribeDBLogFiles']
    log_file_exists = any(log_file['LogFileName'] == 'upgrade-prechecks.log' for log_file in log_files)

    if log_file_exists:
        # 下载日志文件
        params = {
            'DBInstanceIdentifier': db_instance_identifier,
            'LogFileName': 'upgrade-prechecks.log',
        }
        response = client.download_db_log_file_portion(**params)

        # 将日志内容写入文件
        with open('upgrade_prechecks.log', 'w') as file:
            file.write(response['LogFileData'])

        # 读取日志文件内容
        with open('upgrade_prechecks.log', 'r') as file:
            log_content = file.readlines()

        # 查找包含特定内容的行及后续两行
        error_lines = []
        for i, line in enumerate(log_content):
            if '"level": "Error"' in line:
                error_lines.append(line.strip())
                error_lines.extend(log_content[i + 1:i + 3])

        # 打印结果
        for line in error_lines:
            print(line)
    else:
        print("upgrade-prechecks.log file does not exist.")

detect_abnormal_green_environment('test-cluster-v1-green-ce44ac')

删除蓝绿环境

出现错误后需要解决错误,并删除绿环境,直到完成创建操作

import boto3

def delete_blue_green_deployment(blue_green_deployment_identifier):
    try:
        # 创建 AWS RDS 客户端
        client = boto3.client('rds')

        # 删除蓝/绿部署
        response = client.delete_blue_green_deployment(
            BlueGreenDeploymentIdentifier=blue_green_deployment_identifier,
            DeleteTarget=True
        )
        print(f"Successfully deleted blue/green deployment with ID {blue_green_deployment_identifier}")
        return response
    except Exception as e:
        print(f"Error deleting blue/green deployment with ID {blue_green_deployment_identifier}: {str(e)}")
        raise

    # 调用函数并传入参数
delete_blue_green_deployment('bgd-mw0x1z5n1gme5nh9')

FAQ

在排查错误后,可能需要针对不同的错误进行处理。以下是一些可能遇到的错误和解决方案的示例:

FULLTEXT 索引问题

如果遇到 FULLTEXT 索引问题,错误信息可能如下:

"level": "Error",
"dbObject": "bi-test.posting_copy",
"description": "Table `bi-test.posting_copy` contains dangling FULLTEXT index. Kindly recreate the table before upgrade."

解决方案:

CREATE TABLE `bi-test`.`posting_copy_new` LIKE `bi-test`.`posting_copy`;
INSERT INTO `bi-test`.`posting_copy_new` SELECT * FROM `bi-test`.`posting_copy`;
RENAME TABLE `bi-test`.`posting_copy` TO `bi-test`.`posting_copy_old`;
RENAME TABLE `bi-test`.`posting_copy_new` TO `bi-test`.`posting_copy`;

DROP TABLE `bi-test`.`posting_copy_old`;
Index 长度问题

如果遇到 Index 长度问题,错误信息可能如下:

"level": "Error",
"dbObject": "bi-pda.posting",
"description": "Index `title` from `bi-pda.posting` has Index length 2000 > 767 bytes, which will cause error after upgradation. Consider changing the row_format of the tables to dynamic and restart the upgrade."

解决方案:

  1. 登录到数据库服务器上,并打开数据库客户端。
  2. 执行以下命令以更改表的行格式为 dynamic:
ALTER TABLE `bi-pda`.`posting` ROW_FORMAT=DYNAMIC;
  1. 确保命令执行成功后,重新启动数据库升级过程。
FTS 索引问题

如果遇到 FTS 索引问题,错误信息可能如下:

"level": "Error",
"dbObject": "bi-test.reco_eval_2",
"description": " The auxiliary tables of FTS indexes on the table 'bi-test.reco_eval_2' are created in system table-space due to https://bugs.mysql.com/bug.php?id=72132. In MySQL8.0, DDL queries executed on this table shall cause database unavailability. To avoid that, drop and recreate all the FTS indexes on the table or rebuild the table using ALTER TABLE query before the upgrade."

解决方案:

使用 ALTER TABLE 查询重建表:

ALTER TABLE `bi-test`.`reco_eval_2` ENGINE=InnoDB;

根据以上内容,您可以撰写一篇详细的博文,帮助他人更好地进行 RDS 升级和蓝绿部署。