michimani.net

API Gateway のカスタムドメインを CloudFormation で設定してみた

2020-02-16

ここ 2 週間くらいで CloudFormation のテンプレートをいくつか書く機会があり、だんだんと CloudFormation 楽しいおじさんになりつつあります。
今回は、 API Gateway のカステムドメインを CloudFormation のテンプレートで設定してみた時の話です。

目次

前提

API Gateway のカスタムドメインを CloudFormation を使って設定します。
API 自体は既に存在しており、その API に対してカステムドメインを設定します。

また、今回は下記の状態を前提とします。

API Gateway でのカスタムドメイン設定

まずは手でマネジメントコンソールをぽちぽちしてやる場合を想定して、 API Gateway でのカスタムドメイン設定の手順をおさらいしておきます。

  1. カスタムドメイン名作成

API Gateway のコンソールで左のメニューにある カスタムドメイン名 (Custom domain names) からカスタムドメイン名を作成します。

Create API Gateway custom domain name

項目を埋めて 保存 を押すと、 SSL 証明書の初期化が始まります。この処理には最大で 40 分ほどかかります。

  1. ベースパスマッピングを追加

カスタムドメイン名を保存したら、続いては ベースパスマッピング を追加します。これは何かというと、作成したカスタムドメイン名と、 API Gateway の API を紐づける設定のことです。

項目は パス送信先ステージ があります。

Create API Gateway custom domain name set base path mapping 1
Create API Gateway custom domain name set base path mapping 2

例えば、 API Gateway の生の URL が https://aabbcc123456.execute-api.us-east-1.amazonaws.com/prod だとして、カスタムドメインを適用後に https://api.example.com/ でアクセスしたい場合は、それぞれを下記のように設定します。

  1. 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 ではないことに注意してください。

上の間違ったテンプレートでは HostedZoneIdHostZoneId を指定しています。よく見ると全然違いますよね。 ホステッド なのか ホスト なのか、全然違います。
ここで指定すべきなのは、作成されたカスタムドメイン名が持っているターゲットドメイン名がホストされている ゾーンの 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 テンプレート書こうと思っている方でまだプラグインをいれていない方は、今すぐに入れましょう!


comments powered by Disqus