Introduction#
Github shows basics of AWS RDS
- launch a RDS database instance
- test connector with credentials from secret manager or config.json
- read/write iops multi-thread
- launch a multi-az db and check failover handle
- please ensure having enough EIP address before deploy cdk
Create VPC#
- create a vpc with at least two AZs
- one public, private, isolated subnet per each zone
- create security groups for rds
this.vpc = new aws_ec2.Vpc(this, 'FabbiVpc', {vpcName: 'fabbi',maxAzs: 2,cidr: props.cidr,subnetConfiguration: [{name: 'public',subnetType: aws_ec2.SubnetType.PUBLIC,cidrMask: 24},{name: 'private-nat',subnetType: aws_ec2.SubnetType.PRIVATE_WITH_NAT,cidrMask: 24},{name: 'private-isolated',subnetType: aws_ec2.SubnetType.PRIVATE_ISOLATED,cidrMask: 24}]})
create a security group for rds
const databaseSG = new aws_ec2.SecurityGroup(this, 'DbSecurityGroup', {securityGroupName: 'DbSecurityGroup',vpc: this.vpc})databaseSG.addIngressRule(aws_ec2.Peer.securityGroupId(webServerSG.securityGroupId),aws_ec2.Port.tcp(3306))databaseSG.addIngressRule(aws_ec2.Peer.securityGroupId(webServerSG.securityGroupId),aws_ec2.Port.tcp(1403))
create security group for the database read replica with internet facing
const replicaSG = new aws_ec2.SecurityGroup(this, 'DbReplicaSecurityGroup', {securityGroupName: 'DbReplicaSecurityGroup',vpc: this.vpc})replicaSG.addIngressRule(aws_ec2.Peer.anyIpv4(), aws_ec2.Port.tcp(3306))replicaSG.addIngressRule(aws_ec2.Peer.anyIpv4(), aws_ec2.Port.tcp(1403))
create webserver security group
const webServerSG = new aws_ec2.SecurityGroup(this, 'WebServerSecurityGroup', {securityGroupName: 'WebServerSecurityGroup',vpc: this.vpc})
Multi-AZ RDS#
Enable multi-az so there is a standby instance in another zone
new aws_rds.DatabaseInstance(this, 'RdsDatabaseInstance', {databaseName: 'covid',deletionProtection: false,// enable multiAz so there is a standby onemultiAz: true,engine: aws_rds.DatabaseInstanceEngine.mysql({version: aws_rds.MysqlEngineVersion.VER_8_0_23}),vpc: props.vpc,port: 3306,instanceType: aws_ec2.InstanceType.of(aws_ec2.InstanceClass.BURSTABLE3,aws_ec2.InstanceSize.MEDIUM),credentials: aws_rds.Credentials.fromGeneratedSecret('admin', {secretName: 'rds-secrete-name'}),iamAuthentication: false,removalPolicy: RemovalPolicy.DESTROY,securityGroups: [props.sg],storageEncrypted: false,vpcSubnets: {subnetType: aws_ec2.SubnetType.PRIVATE_WITH_NAT}})
Read Replica#
Let create a read replica for performance purpose
const replica = new aws_rds.DatabaseInstanceReadReplica(this,'DatabaseInstanceReadReplicaDemo',{sourceDatabaseInstance: db,instanceType: aws_ec2.InstanceType.of(aws_ec2.InstanceClass.BURSTABLE2,aws_ec2.InstanceSize.MEDIUM),vpc: props.vpc,removalPolicy: RemovalPolicy.DESTROY,securityGroups: [props.replicaSG],port: 3306,vpcSubnets: {subnetType: aws_ec2.SubnetType.PUBLIC},storageEncrypted: false,backupRetention: Duration.days(7),deleteAutomatedBackups: true,deletionProtection: false,publiclyAccessible: true// availabilityZone: ""})
Install DB CLI#
sudo yum install mariadb
connect via terminal
mysql --version
mysql -h $ENDPOINT -P 3306 -u $USER -p
show databases
show databases
create a table
TODO
insert data into a table
TODO
Python Connector#
save database configuration in a config.json or retrieve from secrete manager service
{"host": "","username": "","password": "","dbname": ""}
install dependencies
python3 -m pip install -r requirements.txt
content of the requirements.txt
mysql-connector-python==8.0.30names==0.3.0boto3==1.26.0
retrieve datbase credentials from secrete manager service
def get_db_credentials_from_sm():"""retrieve db credentials from secrete manager"""# sm clientsecrete_client = boto3.client("secretsmanager", region_name=REGION)# get secret stringtry:resp = secrete_client.get_secret_value(SecretId=SECRET_ID)credentials = json.loads(resp["SecretString"])print(credentials)except:print("error retrieving secret manager")credentials = None# returnreturn credentials
create a connector
def get_connect():"""test connecting to db endpoint"""# get db credentialscredentials = get_db_credentials_from_sm()# connectorconn = mysql.connector.connect(host=credentials["host"],user=credentials["username"],password=credentials["password"],database=credentials["dbname"],)return conn
then create a table and write/read data using thread
Employees Table#
create an employee table
def create_table() -> None:"""create a rds table"""# get connectionconn = get_connect()# cursorcur = conn.cursor()# drop table if existsdrop = "DROP TABLE IF EXISTS employees"cur.execute(drop)# create tableemployee_table = ("CREATE TABLE employees ("" id VARCHAR(36) UNIQUE, "" name VARCHAR(200) DEFAULT '' NOT NULL, "" age INT, "" time TEXT, ""PRIMARY KEY (id))")cur.execute(employee_table)cur.close()conn.close()
write some random data to the table
def write_to_table():"""write random data to table"""# get connectionconn = get_connect()conn.autocommit = Trueprint(f"thread {current_thread().name} connect {conn}")cursor = conn.cursor()print(f"thread {current_thread().name} cursor {cursor}")for k in range(NUM_ROW):print(f"{current_thread().name} insert item {k}")stmt_insert = ("INSERT INTO employees (id, name, age, time) VALUES (%s, %s, %s, %s)")cursor.execute(stmt_insert,(str(uuid.uuid4()),f"{str(uuid.uuid4())}-{str(uuid.uuid4())}",30,datetime.datetime.now().strftime("%Y-%M-%D-%H-%M-%S"),),)if k % CHUNK_SIZE == 0:print(f"{current_thread().name} commit chunk {k // CHUNK_SIZE}")conn.commit()# close connectioncursor.close()conn.close()
fetch data for the flask web app
def fetch_data():"""create a rds table"""# table dataemployees = []# initoutputs = []# connectconn = conect_db()# cursorcur = conn.cursor()# querystmt_select = "SELECT id, name, age, time FROM employees ORDER BY id LIMIT 1000"cur.execute(stmt_select)# parsefor row in cur.fetchall():outputs.append(row)print(row)# item objectfor output in outputs:employees.append(Item(output[0], output[1], output[2], output[3]))# close connectcur.close()conn.close()# returnreturn ItemTable(employees)
Test Failover#
connect and play with the database
mysql --host=$host --user=$user --password=$password
you will see DNS of the database endpoint changes after reboot (click the failover box and rds multi-az assumed enlabed before)
while true; do host database-1.cxa01z0gy4dn.ap-northeast-1.rds.amazonaws.com; sleep 3; done;
Run WebServer#
- clone or download the repository
- check the secret name in test_basic_db_connect.py
- run test_basic_db_connect.py to get config => create table => write data
- run the app.y web server
Please check and update the environment variables in user data before running
# set environment variablesexport REGION=ap-southeast-1export SECRET_ID=rds-secrete-name# vim configurationwget -O ~/.vimrc https://raw.githubusercontent.com/cdk-entest/basic-vim/main/.vimrc# install packagesyum install -y mariadb# download repositorywget https://github.com/cdk-entest/rds-failover-demo/archive/refs/heads/main.zipunzip main.zipcd rds-failover-demo-main# install dependenciespython3 -m pip install -r requirements.txtcd web# create table and data to databasepython3 test_rds.py# run the flask apppython3 -m app
Troubleshooting#
When using CDK to look up supporting mysql rds version, please check form the docs here