こんにちは。 エキサイト株式会社で内定者アルバイトをしています。
今回も、実際のリソースの作成を行いたいと思います。第4回は、RDSです。
前回の記事 tech.excite.co.jp
RDSとは?
Amazon RDSはAmazon Relational Database Serviceの略で、クラウド内でデータベースのセットアップ、運用、およびスケールを簡単に行うことのできるマネージド型サービスの集合体です。MySQLとの互換性を持つAmazon Aurora、PostgreSQLとの互換性を持つAmazon Aurora、MySQL、MariaDB、PostgreSQL、Oracle、SQL Serverの7つのエンジンから選択することができます。
要するにクラウド上で簡単に使える関係データベースです。
実際に作成してみる
今回は、2つのデータベースインスタンス(一つがPrimary、もう一つがReplica)からなる、データベースクラスタを作成します。ちなみに、RDSではパフォーマンスと耐久性を向上させるために、一つのDBインスタンスに対してレプリカを(複数)作成することが多いです。Primaryが書き込みも読み出しも行えるソースDBインスタンス、Replicaが読み出しのみ許可されたリードレプリカです。
データベースクラスタの設定は以下の通りです。
項目 | 設定 |
---|---|
エンジン | Aurora MySQL |
エンジンバージョン | 5.7.mysql_aurora.2.10.2 |
マスターユーザ名 | user |
マスターパスワード | password |
ポート | 3306 |
メンテナンスウィンドウ | wed:20:15-wed:20:45 UTC (GMT) |
バックアップウインドウ | 19:30-20:00 UTC (GMT) |
暗号化 | 有効 |
CloudWatch Logsへの出力 | スロークエリ |
データベースインスタンスの設定は以下の通りです。
項目 | Primary | Replica |
---|---|---|
サイズ | db.t3.micro | db.t3.small |
アベイラビリティゾーン | ap-northeast-1a | ap-northeast-1c |
前回までのコード
前回はApplication Load Balancerを作成しました。これまでのコードは以下の通りです。
package com.myorg; import software.amazon.awscdk.CfnTag; import software.amazon.awscdk.Duration; import software.amazon.awscdk.services.ec2.CfnInternetGateway; import software.amazon.awscdk.services.ec2.CfnVPCGatewayAttachment; import software.amazon.awscdk.services.ec2.IMachineImage; import software.amazon.awscdk.services.ec2.Instance; import software.amazon.awscdk.services.ec2.InstanceClass; import software.amazon.awscdk.services.ec2.InstanceSize; import software.amazon.awscdk.services.ec2.InstanceType; import software.amazon.awscdk.services.ec2.MachineImage; import software.amazon.awscdk.services.ec2.Peer; import software.amazon.awscdk.services.ec2.Port; import software.amazon.awscdk.services.ec2.PrivateSubnet; import software.amazon.awscdk.services.ec2.PublicSubnet; import software.amazon.awscdk.services.ec2.SecurityGroup; import software.amazon.awscdk.services.ec2.SubnetSelection; import software.amazon.awscdk.services.ec2.Vpc; import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationListener; import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationLoadBalancer; import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationProtocol; import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationProtocolVersion; import software.amazon.awscdk.services.elasticloadbalancingv2.ApplicationTargetGroup; import software.amazon.awscdk.services.elasticloadbalancingv2.HealthCheck; import software.amazon.awscdk.services.elasticloadbalancingv2.IpAddressType; import software.amazon.awscdk.services.elasticloadbalancingv2.ListenerAction; import software.amazon.awscdk.services.elasticloadbalancingv2.Protocol; import software.amazon.awscdk.services.elasticloadbalancingv2.TargetType; import software.amazon.awscdk.services.elasticloadbalancingv2.targets.InstanceTarget; import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import java.util.List; public class MyProjectStack extends Stack { public MyProjectStack(final Construct scope, final String id) { this(scope, id, null); } public MyProjectStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); Vpc vpc = Vpc.Builder.create(this, "SampleVPC") .vpcName("sample-vpc") .cidr("cidr") .subnetConfiguration(List.of()) .build(); // Availability zone: ap-northeast-1a PrivateSubnet privateSubnetA = PrivateSubnet.Builder.create(this, "PrivateSubnetA") .availabilityZone("ap-northeast-1a") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); PublicSubnet publicSubnetA = PublicSubnet.Builder.create(this, "PublicSubnetA") .availabilityZone("ap-northeast-1a") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); // Availability zone: ap-northeast-1c PrivateSubnet privateSubnetB = PrivateSubnet.Builder.create(this, "PrivateSubnetB") .availabilityZone("ap-northeast-1c") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); PublicSubnet publicSubnetB = PublicSubnet.Builder.create(this, "PublicSubnetB") .availabilityZone("ap-northeast-1c") .vpcId(vpc.getVpcId()) .cidrBlock("cidr") .build(); // NAT gatewayの追加 publicSubnetA.addNatGateway(); publicSubnetB.addNatGateway(); // Internet Gateway CfnInternetGateway internetGateway = CfnInternetGateway.Builder.create(this, "InternetGateway") .tags(List.of(CfnTag.builder() .key("Name") .value("sample-internet-gateway") .build())) .build(); // Internet gatewayをVPCにattachする CfnVPCGatewayAttachment.Builder.create(this, "VpcGateAwayAttachment") .vpcId(vpc.getVpcId()) .internetGatewayId(internetGateway.getAttrInternetGatewayId()) .build(); SecurityGroup securityGroup = SecurityGroup.Builder.create(this, "SampleSecurityGroup") .securityGroupName("sample-security-group") .vpc(vpc) .build(); IMachineImage machineImage = MachineImage.latestAmazonLinux(); Instance instance01 = Instance.Builder.create(this, "ec2-instance-01") .instanceName("ec2-instance-01") .vpcSubnets(SubnetSelection.builder().subnets(List.of(privateSubnetA)).build()) .machineImage(machineImage) .vpc(vpc) .availabilityZone("ap-northeast-1a") .instanceType(InstanceType.of(InstanceClass.T2, InstanceSize.SMALL)) .securityGroup(securityGroup) .build(); Instance instance02 = Instance.Builder.create(this, "ec2-instance-02") .instanceName("ec2-instance-02") .vpcSubnets(SubnetSelection.builder().subnets(List.of(privateSubnetB)).build()) .machineImage(machineImage) .vpc(vpc) .availabilityZone("ap-northeast-1c") .instanceType(InstanceType.of(InstanceClass.T2, InstanceSize.SMALL)) .securityGroup(securityGroup) .build(); // Load Balancer // Load Balancer用のSecurity Group SecurityGroup securityGroupEC2LB = SecurityGroup.Builder.create(this, "SecurityGroupEC2LoadBalancer") .securityGroupName("ec2-lb-security-group") .vpc(vpc) .description("security-group-of-ec2-load-balancer") .allowAllOutbound(true) .build(); securityGroupEC2LB.addIngressRule(Peer.ipv4("0.0.0.0/0"), Port.tcp(80)); ApplicationLoadBalancer applicationLoadBalancer = ApplicationLoadBalancer.Builder.create(this, "SampleEC2ApplicationLoadBalancer") .loadBalancerName("sample-ec2-load-balancer") .deletionProtection(false) .vpc(vpc) .vpcSubnets(SubnetSelection.builder().subnets(List.of(privateSubnetA, privateSubnetB)).build()) .securityGroup(securityGroupEC2LB) .ipAddressType(IpAddressType.IPV4) .idleTimeout(Duration.seconds(120)) .http2Enabled(true) .build(); ApplicationTargetGroup ec2TargetGroup = ApplicationTargetGroup.Builder.create(this, "EC2TargetGroup") .targetGroupName("ec2-target-group") .targetType(TargetType.INSTANCE) .port(80) .vpc(vpc) .targets(List.of(new InstanceTarget(instance01, 80), new InstanceTarget(instance02, 80))) .protocol(ApplicationProtocol.HTTP) .protocolVersion(ApplicationProtocolVersion.HTTP1) .healthCheck(HealthCheck.builder() .enabled(true) .path("/") .healthyThresholdCount(3) .unhealthyThresholdCount(2) .interval(Duration.seconds(60)) .timeout(Duration.seconds(5)) .protocol(Protocol.HTTP) .port("80") .build()) .build(); ApplicationListener applicationListener = ApplicationListener.Builder.create(this, "ApplicationListener") .loadBalancer(applicationLoadBalancer) .protocol(ApplicationProtocol.HTTP) .port(80) .defaultAction(ListenerAction.forward(List.of(ec2TargetGroup))) .build(); } }
データベースクラスタを作成するコード
// ① SubnetGroup databaseSubnetGroup = SubnetGroup.Builder.create(this, "DatabaseSubnetGroup") .vpc(vpc) .vpcSubnets(SubnetSelection.builder() .subnets(List.of(privateSubnetA, privateSubnetB)) .build()) .description("subnet-group-for-rds") .subnetGroupName("sample-db-subnet-group") .build(); // DBインスタンス用のセキュリティグループ SecurityGroup vpcSecurityGroup = SecurityGroup.Builder.create(this, "VPCSecurityGroup") .securityGroupName("sample-db-sg") .vpc(vpc) .allowAllOutbound(true) .description("sample_db_sg") .build(); vpcSecurityGroup.addIngressRule(Peer.ipv4("0.0.0.0/0"), Port.tcp(3306)); // ② CfnDBParameterGroup dbParameterGroup = CfnDBParameterGroup.Builder.create(this, "DBParameterGroup") .family("aurora-mysql5.7") .description("parameter-group-for-sample-db") .tags(List.of(CfnTag.builder() .key("Name") .value("sample-db-parameter-group") .build())) .parameters(Map.of("slow_query_log", "1")) .build(); CfnDBClusterParameterGroup dbClusterParameterGroup = CfnDBClusterParameterGroup.Builder.create(this, "DBClusterParameterGroup") .family("aurora-mysql5.7") .description("parameter-group-for-sample-db-cluster") .tags(List.of(CfnTag.builder() .key("Name") .value("sample-db-Cluster-parameter-group") .build())) .parameters(Map.of("slow_query_log", "1")) .build(); // ③ CfnDBCluster dbCluster = CfnDBCluster.Builder.create(this, "DBCluster") .engine("aurora-mysql") .engineMode("provisioned") .databaseName("samplecfndb") .dbClusterIdentifier("sample-cfn-db-cluster") .engineVersion("5.7.mysql_aurora.2.10.2") .masterUsername("user") .masterUserPassword("password") .port(3306) .dbClusterParameterGroupName(dbClusterParameterGroup.getRef()) .preferredMaintenanceWindow("wed:20:15-wed:20:45") .preferredBackupWindow("19:30-20:00") .storageEncrypted(true) .deletionProtection(false) .enableIamDatabaseAuthentication(false) .vpcSecurityGroupIds(List.of(vpcSecurityGroup.getSecurityGroupId())) .dbSubnetGroupName(databaseSubnetGroup.getSubnetGroupName()) .enableCloudwatchLogsExports(List.of("slowquery")) .build(); // ④ CfnDBInstance dbInstancePrimary = CfnDBInstance.Builder.create(this, "DBInstancePrimary") .dbInstanceClass("db.t3.small") .autoMinorVersionUpgrade(false) .availabilityZone("ap-northeast-1a") .dbClusterIdentifier(dbCluster.getDbClusterIdentifier()) .dbInstanceIdentifier("sample-cfn-db-01") .dbSubnetGroupName(databaseSubnetGroup.getSubnetGroupName()) .dbParameterGroupName(dbParameterGroup.getRef()) .engine("aurora-mysql") .preferredMaintenanceWindow("wed:19:35-wed:20:05") .build(); dbInstancePrimary.addDependsOn(dbCluster); CfnDBInstance dbInstanceReplica = CfnDBInstance.Builder.create(this, "DBInstanceReplica") .dbInstanceClass("db.t3.medium") .autoMinorVersionUpgrade(false) .availabilityZone("ap-northeast-1c") .sourceDbInstanceIdentifier(dbInstancePrimary.getSourceDbInstanceIdentifier()) .dbClusterIdentifier(dbCluster.getDbClusterIdentifier()) .dbInstanceIdentifier("sample-cfn-db-02") .dbSubnetGroupName(databaseSubnetGroup.getSubnetGroupName()) .dbParameterGroupName(dbParameterGroup.getRef()) .engine("aurora-mysql") .preferredMaintenanceWindow("wed:19:35-wed:20:05") .build(); dbInstanceReplica.addDependsOn(dbCluster); dbInstanceReplica.addDependsOn(dbInstancePrimary); // ⑤ ScalableTarget scalableTarget = ScalableTarget.Builder.create(this, "SampleDBScalableTarget") .maxCapacity(4) .minCapacity(1) .serviceNamespace(ServiceNamespace.RDS) // 何をオートスケールするか .scalableDimension("rds:cluster:ReadReplicaCount") .resourceId("cluster:" + dbCluster.getDbClusterIdentifier()) .build(); TargetTrackingScalingPolicy.Builder.create(this, "TargetTrackingScalingPolicy") .policyName("sample-db-autoscaling") .scalingTarget(scalableTarget) .predefinedMetric(PredefinedMetric.RDS_READER_AVERAGE_CPU_UTILIZATION) .targetValue(70) .scaleInCooldown(Duration.seconds(120)) .scaleOutCooldown(Duration.seconds(300)) .build();
かなり長くなりましたので、以下で各リソースの作成および設定について簡単に解説します。
まず①では、作成するデータベースクラスタおよびデータベースインスタンスに割り当てるサブネットグループを作成しています。第1回で作成したPrivateサブネット二つで構成されたグループを作成しています。
②では、パラメータグループを作成しています。データベースクラスタやデータベースインスタンスのパラメータの値を変更するにはこのパラメータグループを作成します。
データベースクラスタとデータベースインスタンスの両方のパラメータグループで、スロークエリログを出力するためにslow_query_log
を1に設定します。
特に、データベースクラスタのパラメータグループのL1コンストラクトであるCfnDBClusterParameterGroup
ではparameters()
に渡せる引数がObject
型になっていますが、データベースインスタンスのパラメータグループのL1コンストラクトであるCfnDBParameterGroup
と同様にMap
型を渡せばOKのようです。
③、④ではデータベースクラスタとデータベースインスタンスを作成しています。dbClusterIdentifier()
でデータベースクラスタとデータベースインスタンスを紐づけます。
⑤ではオートスケーリングの設定を行なっています。ScalableTarget
では何をどの程度までオートスケールできるかを指定します。このコードでは、ここで定義したデータベースクラスタのリードレプリカの数を最大4つまで増やせるように設定しています。
また、TargetTrackingScalingPolicy
では何に基づいてどのような詳細設定でオートスケーリングするかを設定します。このコードでは、平均CPU使用率(70%が閾値)をもとにオートスケーリングを行うことを設定しています。
デプロイ
ここまで、記述できたらいつものようにデプロイします。
cdk deploy
エラーなくデプロイできれば成功です。
補足
addDependsOn()
メソッドについて
お気づきの方も多いかと思いますが、コード中にはaddDependsOn()
メソッドが何度か出現しました。
このメソッドが何をしているかと言いますと、a.addDependsOn(b)
でリソースaの作成がリソースbの作成に依存していることを明示的に示すためのメソッドです。
詳細は別の記事で解説していますので、こちらもご覧ください。
L2コンストラクトの使用
今回のコードでは、同じデータベースクラスタ内のデータベースインスタンスを異なるサイズにするためにL1コンストラクトを使用しました。
しかし、そのような制約がない場合にはL2コンストラクトを使用して簡単にデータベースを作成することができます。L2コンストラクトを使用したデータベースクラスタの作成については別記事にて紹介しようと思います。
終わりに
今回は、AWS CDKを用いてRDSのデータベースクラスタを作成しました。
では、また次回。
次の記事 tech.excite.co.jp