API Gateway のカスタムドメインを CloudFormation で設定してみた
2020-02-16ここ 2 週間くらいで CloudFormation のテンプレートをいくつか書く機会があり、だんだんと CloudFormation 楽しいおじさんになりつつあります。
今回は、 API Gateway のカステムドメインを CloudFormation のテンプレートで設定してみた時の話です。
目次
前提
API Gateway のカスタムドメインを CloudFormation を使って設定します。
API 自体は既に存在しており、その API に対してカステムドメインを設定します。
また、今回は下記の状態を前提とします。
- カスタムドメインとして使用するドメインが Route 53 にてホストゾーンとして作成されている
- カスタムドメインとして使用するドメイン (サブドメイン) に対する SSL 証明書が ACM にて発行されている
API Gateway でのカスタムドメイン設定
まずは手でマネジメントコンソールをぽちぽちしてやる場合を想定して、 API Gateway でのカスタムドメイン設定の手順をおさらいしておきます。
- カスタムドメイン名作成
API Gateway のコンソールで左のメニューにある カスタムドメイン名 (Custom domain names) からカスタムドメイン名を作成します。
項目を埋めて 保存 を押すと、 SSL 証明書の初期化が始まります。この処理には最大で 40 分ほどかかります。
- ベースパスマッピングを追加
カスタムドメイン名を保存したら、続いては ベースパスマッピング を追加します。これは何かというと、作成したカスタムドメイン名と、 API Gateway の API を紐づける設定のことです。
項目は パス 、 送信先 と ステージ があります。
例えば、 API Gateway の生の URL が https://aabbcc123456.execute-api.us-east-1.amazonaws.com/prod
だとして、カスタムドメインを適用後に https://api.example.com/
でアクセスしたい場合は、それぞれを下記のように設定します。
- パス :
/
- 送信先
- API : API ID が
aabbcc123456
の API - ステージ :
prod
- API : API ID が
- Route 53 にレコードセットを追加
API Gateway 側でカスタムドメイン名の作成とベースパスマッピングの追加が終わったら、最後に Route 53 にレコードセットを追加します。
先ほどの例の通り api.example.com
を使用したい場合は、ドメイン名 api.example.com
の A レコード (エイリアス) を作成します。エイリアスのターゲットは、先ほど作成したカスタムドメインの ターゲットドメイン名 にです。これは API Gateway のコンソールでも確認できますが、 Route 53 のエイリアスのターゲット候補にカスタムドメイン名とともに出てくるので、そこから選択すれば OK です。ただし、カスタムドメイン名を作成してすぐには出てこないので、出てこない場合は出てくるまで待ちます。
CloudFormation でさくっとやる
上記でやった手順を CloudFormtaion でさくっとやってしまいます。
早速ですが、実行するテンプレートは下記の通りです。
AWSTemplateFormatVersion: "2010-09-09"
Description: "Setup API Gateway custom domain name"
Parameters:
AcmArn:
Type: String
CustomDomainName:
Type: String
ApiID:
Type: String
DomainHostZoneId:
Type: String
Resources:
ApiGatewayCustomDomainName:
Type: "AWS::ApiGateway::DomainName"
Properties:
CertificateArn: !Ref AcmArn
DomainName: !Ref CustomDomainName
BasePathMapping:
Type: "AWS::ApiGateway::BasePathMapping"
DependsOn: ApiGatewayCustomDomainName
Properties:
DomainName: !Ref CustomDomainName
RestApiId: !Ref ApiID
Stage: api
CustomDomainRecord:
Type: "AWS::Route53::RecordSet"
Properties:
Name: !Ref CustomDomainName
Type: A
HostedZoneId: !Ref DomainHostZoneId
AliasTarget:
DNSName: !GetAtt ApiGatewayCustomDomainName.DistributionDomainName
HostedZoneId: !GetAtt ApiGatewayCustomDomainName.DistributionHostedZoneId
リソースを 3 つ定義していますが、それぞれが上で説明した手順と対応しています。また、 Parameters
の各値については環境変数など、外から設定することを想定しています。
実際にこのテンプレートを実行してリソースが生成されるまでには 5 分程度の時間がかかります。
とは言っても手でぽちぽちやるより圧倒的に楽なので、やっぱり CloudFormation 良いなっていう感じです。
ハマったポイント
上で紹介したテンプレートは無事にリソースが作成された時のテンプレートですが、これに至るまでに AWS::Route53::RecordSet AliasTarget
部分の記述でハマったポイントがあったので紹介しておきます。基本的にリファレンスをちゃんと読めば解決する問題でした。
まず、一番最初に書いていたのはこんなテンプレートでした。
CustomDomainRecord:
Type: "AWS::Route53::RecordSet"
Properties:
Name: !Ref CustomDomainName
Type: A
HostedZoneId: !Ref DomainHostZoneId
AliasTarget:
DNSName: !Ref ApiGatewayCustomDomainName
HostedZoneId: !Ref DomainHostZoneId
AliasTarget.DNSName
には上で作成したカスタムドメイン名のリソースを参照するようにしています。無茶してますね。
AliasTarget.HostedZoneId
には、設定するドメインのホストゾーン ID を指定しています。これはなんか良さそうな気もします。
ではこのテンプレートを実行してどうなったでしょうか。
“creates a CNAME or alias loop in the zone.”
まず、 AliasTarget.DNSName
の設定ミスでスタックのエラーになりました。(適宜、改行しています)
[Tried to create an alias that targets api.example.net.,
type A in zone ZZ88889999AAAA, but that target was not found,
RRSet of type A with DNS name api.example.net. is not permitted
as it creates a CNAME or alias loop in the zone.]
理由としては、 DNS の名前解決がループしてしまっていたからです。冷静に考えると、 ApiGatewayCustomDomainName
のリソースはカスタムドメイン名なので、それを DNS のターゲットとして指定するとループするのは容易にわかります。
というこ実行しました。
CustomDomainRecord:
Type: "AWS::Route53::RecordSet"
Properties:
Name: !Ref CustomDomainName
Type: A
HostedZoneId: !Ref DomainHostZoneId
AliasTarget:
DNSName: !GetAtt ApiGatewayCustomDomainName.DistributionDomainName
HostedZoneId: !Ref DomainHostZoneId
ホストゾーン内にターゲットのドメインが存在しない
今度は別のエラーになりました。(適宜、改行しています)
[Tried to create an alias that targets d11234efghabcd.cloudfront.net.,
type A in zone ZZ88889999AAAA, but the alias target name does not lie within the target zone,
Tried to create an alias that targets d11234efghabcd.cloudfront.net.,
type A in zone ZZ88889999AAAA, but that target was not found]
理由としては、ターゲットドメイン名が指定された Hosted Zone に存在しないというものでした。
答えは下記のように公式リファレンスにも書かれていました。
設定ファイルで AWS リソースに使用している HostedZoneId の値が正しくありません。HostedZoneId キーの値は、各リージョンの AWS リソースの一意な ID であり、ドメイン名のホストゾーン ID ではないことに注意してください。
上の間違ったテンプレートでは HostedZoneId
に HostZoneId
を指定しています。よく見ると全然違いますよね。 ホステッド なのか ホスト なのか、全然違います。
ここで指定すべきなのは、作成されたカスタムドメイン名が持っているターゲットドメイン名がホストされている ゾーンの ID です。Route 53 で管理しているドメインの ゾーン ID とは違うわけです。(それはそう)
というこ正解でした。
CustomDomainRecord:
Type: "AWS::Route53::RecordSet"
Properties:
Name: !Ref CustomDomainName
Type: A
HostedZoneId: !Ref DomainHostZoneId
AliasTarget:
DNSName: !GetAtt ApiGatewayCustomDomainName.DistributionDomainName
HostedZoneId: !GetAtt ApiGatewayCustomDomainName.DistributionHostedZoneId
まとめ
API Gateway のカスタムドメインを CloudFormation を使って設定した話でした。
CloudFormation 初心者で設定値のところでいろいろ失敗しましたが、やっぱり最終的に自分の考えているリソースが出来上がるのは最高に楽しいですね。これまでは CDK を使って CloudFormation テンプレートを実行してたのですが、これを機に生のテンプレートを書くこともチャレンジしていこうと思います。
その際、 VS Code のプラグインを入れることでテンプレートの記述がめちゃくちゃ楽になるので、もしこれから CFn テンプレート書こうと思っている方でまだプラグインをいれていない方は、今すぐに入れましょう!
- vscode-cfn-lint - Visual Studio Marketplace
- awslabs/aws-cfn-lint-visual-studio-code: Provides IDE specific integration to cfn-lint. https://github.com/awslabs/cfn-python-lint
- Linterを使ってCloudFormationの間違いに爆速で気づく | Developers.IO
comments powered by Disqus