
はじめに
こんにちは。アプリケーションサービス本部ディベロップメントサービス1課の森山です。
今回は案件の中で学んだことを紹介します。
AWS CDK等を利用したIaC(Infrastructure as Code)で開発していると、別CDKアプリケーションや別スタックで管理している既存リソースに対して、新たにリソース追加をしたいケースがあります。
例えば、以下のようなケースです。
- Elastic Load Blancing(ALB)のリスナーにリスナールールを追加
- Amazon Route53のホストゾーンにレコード追加
- Amazon CloudFront ディストリビューションにオリジン、ビヘイビアの追加
前職での経験から、1と2については対応可能だと知っていました。そのため3についても同様に実施できると考えていたのですが、実際に試してみると実施できませんでした。
この記事では、なぜ似たようなケースで挙動の差異が出てしまうのかを調査した結果を紹介します。
CDKにおけるリソース追加方法
まずは上記3パターンをCDKで対応する方法を紹介します。
ALBにリスナールールの追加
ALBのリスナーにリスナールールを追加する場合、以下のようなコードで対応可能です。
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"; import * as cdk from "aws-cdk-lib/core"; import type { Construct } from "constructs"; export class AlbStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // 外部のHTTPSリスナーを参照 const httpsListener: elbv2.IApplicationListener = elbv2.ApplicationListener.fromLookup(this, "ExternalHTTPSListener", { listenerArn: "arn:aws:elasticloadbalancing:ap-northeast-1:111122223333:listener/app/test/9a67cd7e5a16071c/8c2b52d73d37c7df", }); // 固定レスポンスのリスナールール new elbv2.ApplicationListenerRule(this, "FixedResponseRule", { listener: httpsListener, priority: 400, conditions: [elbv2.ListenerCondition.pathPatterns(["/maintenance"])], action: elbv2.ListenerAction.fixedResponse(503, { contentType: "text/html", messageBody: "<h1>Under Maintenance</h1>", }), }); } }
Route53のホストゾーンにレコード追加
こちらも以下の方法で対応可能です。
import * as cdk from 'aws-cdk-lib/core'; import { Construct } from 'constructs'; import * as route53 from 'aws-cdk-lib/aws-route53'; export class Route53Stack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // 外部のRoute53ホストゾーンを参照 const hostedZone:route53.IHostedZone = route53.HostedZone.fromHostedZoneAttributes(this, 'ExternalHostedZone', { hostedZoneId: 'ZZZZZXXXXXXCCCCCCCC', zoneName: 'moriyama.example', }); // TXTレコードを追加(例:ドメイン検証用) new route53.TxtRecord(this, 'SimpleTxtRecord', { zone: hostedZone, values: ['verification-token-12345'], ttl: cdk.Duration.minutes(5), }); } }
リスナーと同じく、fromLookup,fromHostedZoneAttributes等、外部リソースを参照するメソッドの戻り値の型はIが付与されたインタフェース型になってますね。
CloudFront ディストリビューションにオリジン、ビヘイビアの追加
今回はビヘイビアで試してみます。
以下のようなソースを書いてみましたが、addBehaviorが存在せず、エラーになってしまいます。
import * as cloudfront from "aws-cdk-lib/aws-cloudfront"; import * as origins from "aws-cdk-lib/aws-cloudfront-origins"; import * as s3 from "aws-cdk-lib/aws-s3"; import * as cdk from "aws-cdk-lib/core"; import type { Construct } from "constructs"; export class CloudFrontStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // 既存のCloudFrontディストリビューションを参照 const distribution: cloudfront.IDistribution = cloudfront.Distribution.fromDistributionAttributes( this, "ExistingDistribution", { distributionId: "XXXXXXXXXX", domainName: "xxxxxxxxxxxx.cloudfront.net", }, ); // 新しいオリジンを定義 const bucket = s3.Bucket.fromBucketName( this, "OriginBucket", "my-api-bucket", ); const newOrigin = origins.S3BucketOrigin.withOriginAccessControl(bucket); // 新しいビヘイビアを追加(ここでエラー) distribution.addBehavior("/api/*", newOrigin, {}); } }
これはaddBehaviorメソッドが、Distributionには存在するが、IDistributionには存在しないためです。
なぜ、addBehaviorメソッドが、Distributionには存在するのに、インタフェース型であるIDistributionには存在しないのでしょうか。
インターフェース型は読み取り専用
インタフェース型について、公式ページを調べてみると以下記載がありました。
The AWS CDK supports using resources defined outside CDK applications using methods such as
Bucket.fromBucketArn(). External resources cannot be modified and may not have all the functionality available with resources defined in your CDK app using e.g. theBucketclass. Interfaces, then, represent the bare minimum functionality available in the CDK for a given AWS resource type, including external resources.
CDKアプリケーション外で定義されたリソースを参照する際は、リソースの変更が行えない最小限の機能のみを提供するインターフェース型で取得する模様です。
では、なぜRoute53のレコードや、ALBのリスナールールは追加できるのに対し、CloudFrontディストリビューションのビヘイビアは追加できないのでしょうか。
CLIで比較するリソースの単位
AWS CLIで上記3パターンの操作を実施してみると理解が深まったので、この方法で紹介してみます。
ALBにリスナールールの追加
リスナールールを追加するコマンドは以下です。
aws elbv2 create-rule \
--listener-arn arn:aws:elasticloadbalancingv2:ap-northeast-1:111122223333:listener/app/test/9a67cd7e5a16071c/8c2b52d73d37c7df \
--priority 400 \
--conditions Field=path-pattern,Values="/maintenance" \
--actions Type=fixed-response,FixedResponseConfig='{StatusCode=503,ContentType="text/html",MessageBody="<h1>Under Maintenance</h1>"}'
create-ruleという名前の通り、リスナー自体を変更するのではなく、リスナーに紐づくリスナールールを新規作成する、といった感じですね。
CloudFormationのリソース定義も確認してみましたが、下記の通り、別々に存在しています。
AWS::ElasticLoadBalancingV2::ListenerAWS::ElasticLoadBalancingV2::ListenerRule
つまり、この操作は参照したリソースに対する変更ではない、と考えることができそうです。
Route53ホストゾーンへのレコード追加
次に指定したホストゾーンにレコードを追加するコマンドです。
aws route53 change-resource-record-sets \
--hosted-zone-id ZZZZZXXXXXXCCCCCCCC \
--change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "_verification.moriyama.example",
"Type": "TXT",
"TTL": 300,
"ResourceRecords": [{"Value": "\"verification-token-12345\""}]
}
}]
}'
コマンドchange-resource-record-setsはリソース変更をイメージさせる名前ですが、Actionで作成・削除・更新を選ぶことができ、今回は作成を指す、CREATEを選択しています。
こちらもホストゾーンを変更ではなく、ホストゾーンに紐づくレコードセットを作成しているように見えます。
リソースも同様、ホストゾーンとレコードセットは個別に定義されていました。
AWS::Route53::HostedZoneAWS::Route53::RecordSetAWS::Route53::RecordSetGroup
CloudFront ディストリビューションにオリジン、ビヘイビアの追加
最後にビヘイビアを追加するコマンドです。
# ディストリビューション全体の設定を取得 aws cloudfront get-distribution-config --id XXXXXXXXXX > distribution-config.json # 設定ファイルを手動で編集してオリジン、ビヘイビアを追加 # ディストリビューション全体を更新 aws cloudfront get-distribution --id XXXXXXXXXX --query 'ETag' --output text aws cloudfront update-distribution \ --id XXXXXXXXXX \ --distribution-config file://distribution-config-update.json \ --if-match <ETag>
少し複雑なのですが、一度ディストリビューション全体の設定をファイルに出力します。
その後ファイルに変更したい内容を反映し、update-distributionコマンドによる更新で設定が反映できます。
update-distributionという名前の通り、これまで紹介したALB、Route53とは異なり、ディストリビューションそのものを更新しています。
リソースを調べても、ビヘイビア・オリジンを個別に定義はしておらず、ディストリビューションのみです。
- AWS::CloudFront::Distribution
この操作のみ、個別のリソースを新規作成しているのではなく、参照したリソースそのものを更新するため、エラーとなる模様です。
まとめ
以下3つの比較でした。
- Elastic Load Blancing(ALB)のリスナーにリスナールールを追加
- Amazon Route53のホストゾーンにレコード追加
- Amazon CloudFront ディストリビューションにオリジン、ビヘイビアの追加
日本語で列挙すると似た操作に見えますが、リソース単位がサービスごとに異なることで挙動の違いが出ることに気づきました。
外部参照したリソースに対し、この操作は実施できるかどうかと悩んだ時にはCloudFormationのリソース定義を確認することが有効そうでした。
また、実施したい作業に対応するCLIのコマンドリファレンスを確認する方法でも事前に気づくことがですね。
この記事が誰かのお役に立つと幸いです。
そして今年最後の記事になりそうです。 ご一読いただきありがとうございました。