こんにちは。 エキサイト株式会社で内定者アルバイトをしています。
今回は、AWS CDKの勉強をしていてハマった、リソース生成の順番が保証されないことがある件についてまとめます。
発生したエラー
コード
今回は複数のアベイラビリティゾーンに一つずつDBインスタンスをおくようなAWS CDKアプリケーションを作成していました。
// importは省略 public class SampleDbStack extends Stack { public SampleDbStack(final Construct scope, final String id) { this(scope, id, null); } public SampleDbStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); // VPCやパブリックサブネットの定義は省略 // Subnet Subnet privateSubnetA = PrivateSubnet.Builder.create(this, "PrivateSubnetA") .availabilityZone("ap-northeast-1a") .vpcId(vpc.getVpcId()) .cidrBlock("cidrBlcok") .build(); // Subnet Subnet privateSubnetB = PrivateSubnet.Builder.create(this, "PrivateSubnetB") .availabilityZone("ap-northeast-1c") .vpcId(vpc.getVpcId()) .cidrBlock("cidrBlcok") .build(); // RDS // DBインスタンス用のサブネットグループ CfnDBSubnetGroup dbSubnetGroup = CfnDBSubnetGroup.Builder.create(this, "DBSubnetGroup") .dbSubnetGroupDescription("subnet-group-for-rds") .subnetIds(List.of(privateSubnetA.getSubnetId(), privateSubnetB.getSubnetId())) .dbSubnetGroupName("sample-db-subnet-group") .build(); CfnDBCluster dbCluster = CfnDBCluster.Builder.create(this, "DBCluster") .engine("aurora-mysql") .engineMode("provisioned") .databaseName("sampledb") .dbClusterIdentifier("sample-db-cluster") .engineVersion("5.7.mysql_aurora.2.10.2") .masterUsername("iammaster") .masterUserPassword("idontknow") .port(3306) .preferredMaintenanceWindow("wed:20:15-wed:20:45") .preferredBackupWindow("19:30-20:00") .storageEncrypted(true) .deletionProtection(true) .enableIamDatabaseAuthentication(false) .vpcSecurityGroupIds(List.of(vpcSecurityGroup.getSecurityGroupId())) .dbSubnetGroupName(dbSubnetGroup.getDbSubnetGroupName()) .build(); // Primary DB instance CfnDBInstance dbInstancePrimary = CfnDBInstance.Builder.create(this, "DBInstancePrimary") .dbInstanceClass("db.r5.large") .autoMinorVersionUpgrade(true) .availabilityZone("ap-northeast-1a") .dbClusterIdentifier(dbCluster.getDbClusterIdentifier()) .dbInstanceIdentifier("sample-db-01") .dbSubnetGroupName(dbSubnetGroup.getDbSubnetGroupName()) .engine("aurora-mysql") .preferredMaintenanceWindow("wed:19:35-wed:20:05") .build(); // Replica DB instance CfnDBInstance dbInstanceReplica = CfnDBInstance.Builder.create(this, "DBInstanceReplica") .dbInstanceClass("db.r5.xlarge") .autoMinorVersionUpgrade(true) .availabilityZone("ap-northeast-1c") .sourceDbInstanceIdentifier(dbInstancePrimary.getSourceDbInstanceIdentifier()) .dbClusterIdentifier(dbCluster.getDbClusterIdentifier()) .dbInstanceIdentifier("sample-db-02") .dbSubnetGroupName(dbSubnetGroup.getDbSubnetGroupName()) .engine("aurora-mysql") .preferredMaintenanceWindow("wed:19:35-wed:20:05") .build(); } }
エラー内容
このスタックをアプリケーションで生成し、AWSにデプロイするために
cdk synth cdk deploy
を実行すると、以下のようなエラーが発生しました。
SampleDbStack: creating CloudFormation changeset... 14:51:43 | CREATE_FAILED | AWS::RDS::DBInstance | DBInstancePrimary Resource handler returned message: "DBSubnetGroup 'sample-db-subnet-group' not found. (Service: Rds, Status Code: 404, Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)" (RequestToken: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, HandlerErrorCode: NotFound) 14:51:44 | CREATE_FAILED | AWS::RDS::DBInstance | DBInstanceReplica Resource handler returned message: "DBSubnetGroup 'sample-db-subnet-group' not found. (Service: Rds, Status Code: 404, Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)" (RequestToken: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, HandlerErrorCode: NotFound)
エラーメッセージを解読すると、DBInstancePrimary
とDBInstanceReplica
において .dbSubnetGroupName(dbSubnetGroup.getDbSubnetGroupName())
で参照しているサブネットグループ sample-db-subnet-group
が見つからないとのこと。
どちらもsample-db-subnet-group
よりも後に記述しているのに...。
参照の方法が違うのか?名前に使ってはならない文字が含まれているのか?といろいろ考えましたが、どれも原因ではないようで...。
解決方法
結論から申し上げますと、Sさんのご指摘もあり、どうやらAWS CDKであっても明示的に指定しないと実行順序が担保されないことがあるということがわかりました。
今回の例だと、DBクラスタやDBインスタンスの作成に必要なサブネットグループが作成されるよりも前に、DBクラスタを作成しようとしたことで、サブネットグループを参照できないことが原因でした。
以下の公式ドキュメントによるとa.addDependsOn(b)
でa
がb
に依存していることを明示的に指定できるようです。
そこで、以下のようなコードを加えました。
dbCluster.addDependsOn(dbSubnetGroup); dbInstancePrimary.addDependsOn(dbCluster); dbInstanceReplica.addDependsOn(dbCluster); dbInstanceReplica.addDependsOn(dbInstancePrimary);
DBクラスタを作成する前にサブネットグループが作成されていなければならないという依存関係、DBインスタンスが作成される前にDBクラスタが作成されていなければならないという依存関係、レプリカのDBインスタンスが作成される前にプライマリのDBインスタンスが作成されていなければならないという依存関係を追加しました。これらはそれぞれ、リソース作成の際に依存元のリソースを参照しているためです。
これで、再び
cdk synth cdk deploy
を実行すると...
無事、AWSにデプロイできました!!
念のため、AWSのmanagement consoleを確認してみると...
データベースが作成されていました。
Sさんたまんねえ!
補足1
AWS CloudFormationをご存知の方は、cdk synth
を実行することで表示されるテンプレートを確認すると、
DBInstanceReplica: ~~~省略~~~ DependsOn: - DBCluster - DBInstancePrimary Metadata: aws:cdk:path: SampleDbStack/DBInstanceReplica
とあり、テンプレートのDependsOn
とaddDependsOn()
が対応していることがわかるかと思います。
補足2
addDependsOn()
メソッドはL1 construct(最も低レベルなコンストラクトで先頭にCfn
がついているもの)にしかなく、L2 construct(L1 constructをカプセル化し、適切なデフォルト値やセキュリティポリシーを提供してくれる)やL3 construct(特定のユースケースのために複数のリソースを宣言できる)にはありません。また、addDependsOn()
メソッドの引数に指定できる依存先もL1 constructのみです。
これはあくまで推測ですが、L1 constructは「AWS CloudFormationによって定義されたリソースに直接対応している」そうなので、テンプレートと同じ設定が必要になるからかと思います。
ちなみに、DBクラスタやRDSを作成するだけならL2 constructを使用することができ、多くの場合はこちらの方が容易ですが、詳細な設定が必要な場合にはL1 constructを使う方がよさそうです。
終わりに
今回は、AWS CDKであっても(L1 constructでは)明示的に依存関係を指定しないとデプロイがうまくいかないことがあるという内容の記事でした。
まだまだAWS CDKも勉強を始めたばかりでハマりポイントも多くあると思いますが、引き続き勉強を頑張っていきたいと思います。
では、また次回。