いちはじCDK第12回 API GateWay④

AWS

4回目になりました~

今回はカスタムドメイン設定が目標になります~

改めてアーキテクト図です

今回は赤い四角枠のところを設定していきますね。

カスタムドメイン

今回はAPI GatewayのエンドポイントをリージョンAPIエンドポイントで作成しているので、カスタムドメインもリージョン別カスタムドメインを使用します。
まずは公式HPからです。以下を見るのが良いと思います。
API Gateway でリージョン別カスタムドメイン名を設定する
でここを読むと以下が分かります。
・ACM 証明書が必要。
・ドメイン名が必要。
です。
ドメイン名については、「いちはじCDK第10回 Route53-ドメイン登録」で取得したので、ACM証明書は追加で作らないといけないですね。
必要なConstructを調べてるうちに気が付いたのですが、「aws_cdk.aws_apigateway」には「v2」があるんですね・・・v2ってなんなんでしょうか?って思ったのでチラッと説明を読んでみました。

To create a custom domain name for private APIs, use AWS::ApiGateway::DomainNameV2 .

プライベート API のカスタムドメイン名を作成するには、AWS::ApiGateway::DomainNameV2を使用します。

と言う事らしいので、今回はパブリックなものなので、v2は使わなくてよいのかなと、思いましたが、調べてるうちに、v2も使わないとだめっぽいですね・・・ちょっと脱線しましたが・・・

あとはACM証明書はお金かかるのか?ってところですね。AWS内利用であれば無料には見えますが・・・
AWS Certificate Manager 料金」を見ると、

AWS Certificate Manager (ACM) を通じてプロビジョニングされたパブリック SSL/TLS 証明書は無料です。

と言う事らしいのでパブリックな環境では無料・・・だよね・・・不安だな・・・
(´_ゝ`)フフフ・・・いくら調べても不安だよね・・・
とりあえずやってきますかね・・・

作業の流れ

やってみると、手順が面倒でした。
大きな流れから説明していきます。
API Gateway本体と、カスタムドメインを作る作業は、別けて作業できます。
ただ順番的には、API Gateway本体を先に作ってからカスタムドメイン設定を入れるというのが良いと思いました。全体的にやらないといけないことは以下の感じです。
①API Gatewayを作る。
②ACM証明書を作る。
③カスタムドメイン名を作る。
④カスタムドメインのルールを作る。
 ※API Gateway本体(ステージ)とカスタムドメインの紐づけ
⑤Route53のホストゾーンにAレコード追加
になります。
AWS公式HPは「API GatewayでのパブリックREST APIのカスタムドメイン名」を見ながらやりました。ほぼこれに書いてある手順をそのまま追っていった感じです。

ACM証明書(CfnCertificate)から~

まずは、これがまず手こずりましたね。
使うConstructは
aws_cdk.aws_certificatemanagerのCfnCertificateですね。
パラメータは、以下の感じです。
内容については、以下からの引用です。
AWS Certificate Managerで証明書を準備する」の「SSL/TLS証明書を作成またはACMにインポートするには」の「To request a certificate provided by ACM for a domain name」を見ながら考えてます。

パラメータ設定値
id好きな名前を付けましょう。Stack内では一意である必要あります。
domain_name付けたいカスタムドメインの名前
例:test.example.com
certificate_authority_arnプライベート証明機関 (CA) の Amazon リソースネーム (ARN)と言う事なので、こちらは、AWS以外の外部機関(CA)を使うときに指定するんじゃないかと思います。
こちらは、指定しないです。
certificate_export省略すると、DISABLEDになります。
外部機関(CA)を使う場合は、ENABLEDにするんじゃないですかね。
ここは、省略です。
certificate_transparency_logging_preferenceDISABLEDオプションを指定することで、証明書の透明性のログ記録をオプトアウトできます。
ENABLEDを指定してオプトインします。
と書いてあるので、どっちがいいんでしょうね?「証明書の透明性のログ記録」はログ記録しない方がいいんでしょうか?そう思って、DISABLEDにしておきます。
domain_validation_optionsCfnCertificate.DomainValidationOptionPropertyで詳細を設定します。
domain_name:
 上記のdomain_nameと同じで良いか迷いましたが、同じにしました。
hosted_zone_id:★重要★
 Route53で登録した時にでいたホストゾーンのIDを設定します。これがないとdeployした時にデプロイが途中で止まってしまいます。
 どこに追加したら良いかが分からないとCDK側が困るみたいですね。
key_algorithmこれはAWSの公式HPの例と同じ、RSA_2048を指定。
tags好きに設定したら良いと思います。
validation_methodオプションです。
指定するときはDNS検証になります。このパラメータを指定しないとメール認証になります。
推奨はDNS認証です。のでDNS認証を指定します。

最終的には以下のような感じになります。
詳細はGITのcertificate.yamlを見て下さい。

id: "pwork_certificate"
domain_name: "{{common.mydomain}}"
certificate_transparency_logging_preference: "DISABLED"
domain_validation_options:
  - domain_name: "{{common.mydomain}}"
    hosted_zone_id: "{{common.hosted_zone_id}}"
key_algorithm: "RSA_2048"
tags:
  - key: "memo"
    value: "Nothing in particular"
validation_method: "DNS"

これでACM証明書はおしまいです。

カスタムドメインの作成

AWS公式HPは「API Gatewayでリージョン別カスタムドメイン名を設定する」を参考に設定値を考えてます。
Constructはaws_cdk.aws_apigatewayv2のCfnDomainNameを使います。
aws_cdk.aws_apigatewayにも同じ名前のCfnDomainNameがあり、かなりややこしいことになっていますが、aws_apigatewayv2の方を使うのは、上記のAWS公式HPがそうしているのでそうしました。

パラメータ設定値
id好きな名前を付けましょう。Stack内では一意である必要あります。
domain_name付けたいカスタムドメインの名前
例:test.example.com
domain_name_configurationsDomainNameConfigurationProperty
certificate_arn:
 CfnCertificateで作ったACM証明書のARN
 CfnCertificateにはattr_arnのようなAttributesがないので、refを設定すると良い。
endpoint_type:
 REGIONAL, EDGE, PRIVATEの何れかを指定する。
 今回はリージョン別カスタムドメインなのでREGIONALを使います。
security_policy:
 TLS_1_0、TLS_1_2の何れかを指定する。今回はTLS_1_2を使います。
routing_mode以下の何れかを指定する。
・ROUTING_RULE_THEN_API_MAPPING
・ROUTING_RULE_ONLY
・API_MAPPING_ONLY
詳しい説明はこちらを参照。
今回はROUTING_RULE_ONLYを使います。
 tags好きに設定したら良いと思います。

最終的には以下のような感じになります。
詳細はGITのdomain.yamlを見て下さい。
ここでハマったのは、tagsのところ・・・他のConstructと設定の仕方が違います。と言うか型がMap型になっているようで違うんですよね。

id: "apigw_test_domain"
domain_name: "{{common.mydomain}}"
domain_name_configurations:
  - certificate_arn: "{{certificate.certificates.pworks.ref}}"
    endpoint_type: "REGIONAL"
    security_policy: "TLS_1_2"
    routing_mode: "ROUTING_RULE_ONLY"
    tags:
      "memo": "Nothing in particular"

カスタムドメインルールを作る

Constructはaws_cdk.aws_apigatewayv2のCfnRoutingRuleを使います。

パラメータ設定値
id好きな名前を付けましょう。Stack内では一意である必要あります。
actionsActionProperty
 invoke_api:
  ActionInvokeApiProperty
  api_id:対象のAPI Gatewayを指定します。
  stage:対象のAPI Gatewayのステージを指定します。
  strip_base_path:
   ベースパスの削除設定。true の場合、API Gatewayはリクエストをターゲット APIに転送するときに、
   一致する受信ベースパスを削除します。
   ↑直訳ですが、何言っているのかわからないですね。
   今回はここは、指定しません。
conditions必須パラメータでリストを指定します。
ただ、今回は使わないので詳細の設定内容は省略します。
指定しないときは、空リスト([])を指定します。
domain_name_arnCfnDomainNameで作ったカスタムドメイン名のARNを指定します。
Attributesは、attr_domain_name_arnです。
priority優先度です。CfnRoutingRuleでは、複数のステージに振り分けるようなルールも作れます。
複数の振り分け先のステージがある場合、priorityパラメータで優先度を指定できます。
新規サービスのリリースで、カナリアリリースの手法で行う場合に、新規ステージの優先度を全体の10%にして、動作確認を行い問題ないか確認できます。
優先度は1-1000000までの間で指定できます。
今回はステージが100を使います。一つしかないので値はなんでも良いです。

最終的には以下のような感じになります。
詳細はGITのdomain.yamlを見て下さい。
conditionsの方は要注意ですね。コメントで残して置きましたが、必須パラメータで指定しないときは、空のリスト([])を指定する必要あります。yamlで空リストとか指定できないので、パラメータを指定しない時は、MyRoutingRuleで空リストを設定するようにしています。
詳細はGITのmyapigatewayv2.pyのMyRoutingRuleを見て下さい。

id: "apigw_test_routingrule"
actions:
  - invoke_api:
      api_id: "{{apigwlambda.apigws.apigw_test.attr_rest_api_id}}"
      stage: "test"
#conditions:
domain_name_arn: "{{domain.domainnames.apigw_test_domain.attr_domain_name_arn}}"
priority: 100

Route53のホストゾーンにAレコード追加

これは、コンソールからやっちゃえばいいんじゃないの?とも思いましたがCDKでやりました。
Constructはaws_cdk.aws_route53のCfnRecordSetを使います。
私はこういった、DNSサーバーの設定については、全くの素人なので、AWSの公式HPの内容をそのまま使って、パラメータ設定値を考えています。「リージョン別カスタムドメイン名の DNS レコードを作成する」の辺りを参考にしています。
やる事ととしては、ホストゾーンにAレコードを追加するというものになります。

パラメータ設定値
id好きな名前を付けましょう。Stack内では一意である必要あります。
name付けたいカスタムドメインの名前
例:test.example.com
typeDNS レコードの種類です。たくさんありますね。書くのが面倒なので省略します。
今回は”A”を指定します。Aレコードなんで!
alias_targetAliasTargetProperty
dns_name:
 リージョン別カスタムドメインの場合、作ったCfnDomainNameのattr_regional_domain_nameを指定します。
hosted_zone_id:
 リージョン別カスタムドメインの場合、作ったCfnDomainNameのattr_regional_hosted_zone_idを指定します。
evaluate_target_health:
 エッジロケーションの時にtrueにして使うようです。
 今回はFlaseにします。
comment何かわかりやすいメモを書きます。
hosted_zone_idRoute53で作ったホストゾーンのIDを指定します。hosted_zone_idとhosted_zone_nameはどちらか指定すます。両方指定するとエラーになります。今回は、hosted_zone_idを指定します。
hosted_zone_nameホストゾーンの名前を指定します。hosted_zone_idとhosted_zone_nameはどちらか指定すます。両方指定するとエラーになります。今回は指定しないです。

最終的には以下のような感じになります。
詳細はGITのdomain.yamlを見て下さい。
ここでハマったのは、alias_targetのdns_nameにカスタムドメインのattr_regional_domain_nameを使わないといけないってところですね。ここ間違えるとdeployが失敗するので注意しましょう。

id: "apigw_test_recordset"
name: "{{common.mydomain}}"
type: "A"
alias_target:
  dns_name: "{{domain.domainnames.apigw_test_domain.attr_regional_domain_name}}"
  hosted_zone_id: "{{domain.domainnames.apigw_test_domain.attr_regional_hosted_zone_id}}"
  evaluate_target_health: False
comment: "memo"
hosted_zone_id: "{{common.hosted_zone_id}}"

パラメータ設定のところはこれで大体おしまいです。

スタックをサラッと

Stackの作成は、どんなときでも大体同じコーディングになりますね。
今回ハマったのは、aws-cdk-libのバージョンが古すぎて失敗したって感じですね。
これまでの回では、2.155.0を使っていました。今回2.215.0にアップデートしています。
requirements.txtの以下の部分ですね。

aws-cdk-lib==2.215.0
constructs>=10.0.0,<11.0.0
pyyaml==6.0.1
pydantic==2.5.3
simplejson==3.19.3

その他にCDK CLI自体のバージョンも低くいとダメでした。ので2.1029.2にアップデートしています。
スタックは、以下の4つに分けました。
・PworkVpcStack※VPCとSubnet
・ApigwLambdaStack3※API Gateway本体
・CertificateStack※ACM証明書
・DomainStack※カスタムドメイン名
深い理由はないんですけど、なんとなく同じ塊にした感じです。
まず
・pwork_vpc_stack.py
・apigw_lambda_stack3.py
は変更ないです。
・certificate_stack.py
・domain_stack.py
を追加しました。
certificate_stack.pyは以下になります。

01:from aws_cdk import Stack
02:from constructs import Construct
03:
04:from core.myconstructs.mycertificatemanager import MyCertificate
05:from core.myconstructs.myctrl import MyCtrl
06:
07:
08:class CertificateStack(Stack):
09:    def __init__(
10:        self, scope: Construct, construct_id: str, myctrl: MyCtrl, **kwargs
11:    ) -> None:
12:        super().__init__(scope, construct_id, **kwargs)
13:        self.myctrl = myctrl
14:        myctrl.add_common_tags(self)
15:        # Certificate
16:        certificate = myctrl.create(
17:            MyCertificate(obj=self, name="certificate.certificates.pworks")
18:        )

domain_stack.py

01:from aws_cdk import Stack
02:from constructs import Construct
03:
04:from core.myconstructs.myapigatewayv2 import MyDomainName, MyRoutingRule
05:from core.myconstructs.myctrl import MyCtrl
06:from core.myconstructs.myroute53 import MyRecordSet
07:
08:
09:class DomainStack(Stack):
10:    def __init__(
11:        self, scope: Construct, construct_id: str, myctrl: MyCtrl, **kwargs
12:    ) -> None:
13:        super().__init__(scope, construct_id, **kwargs)
14:        self.myctrl = myctrl
15:        myctrl.add_common_tags(self)
16:        # DomainName
17:        domainname = myctrl.create(
18:            MyDomainName(obj=self, name="domain.domainnames.apigw_test_domain")
19:        )
20:        # Routing
21:        routing = myctrl.create(
22:            MyRoutingRule(obj=self, name="domain.routingrules.apigw_test_routingrule")
23:        )
24:        routing.add_dependency(domainname)
25:
26:        # route53
27:        route53_record_set = myctrl.create(
28:            MyRecordSet(obj=self, name="domain.route53.apigw_test_recordset")
29:        )
30:        route53_record_set.add_dependency(domainname)

どちらも説明すべき点ないですね。詳しくはGITの方見て下さい。

deployして確認です

まずはACM証明書です。

ACM証明書をdeployするとACM証明書ができるのと、Route53にCNAMEレコードが追加されます。その辺を確認します。

ACM証明書はできてますね。CNAMEは以下の感じで追加されます。

次はカスタムドメイン名です。

カスタムドメイン名は以下の感じでできています。

動作確認です。

動作確認するときのURLですが、API Gatewayが作るURLは以下の形式です。
v********4はAPI Gatewayのapi-idですね。testはステージ名です。

https://v********4.execute-api.ap-northeast-1.amazonaws.com/test/

でカスタムドメイン名は以下になります。

https://poeny.******.click/

で最初CURLに指定するときはステージ名(test)を含めて、以下の感じになるかと思っていました。

https://poeny.******.click/test/v2/first/cat/1020

これは間違えでしたね。
正解はこうでした。

https://poeny.******.click/v2/first/cat/1020

この部分を理解できてたら上手くできます。
CURL全体は以下になります。前回組み込んだ、カスタムオーソライザーもあるので、HTTPヘッダーにAuthorization: bearer testtestパラメータも含める必要あります。
実行した結果は以下になります。

> curl.exe -v POST 'https://poeny.******.click/v2/first/cat/1020' -H 'content-type: application/json' -H 'Authorization: bearer testtest' -d '{ \"greeter\": \"John\" }'
* Could not resolve host: POST
* shutting down connection #0
curl: (6) Could not resolve host: POST
* Host poeny.******.click:443 was resolved.
* IPv6: (none)
* IPv4: ~ 省略 ~
*   Trying xx.xxx.xxx.xx:443...
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* Connected to poeny.******.click (xx.xxx.xxx.xx) port 443
* using HTTP/1.x
> POST /v2/first/cat/1020 HTTP/1.1
> Host: poeny.******.click
> User-Agent: curl/8.14.1
> Accept: */*
> content-type: application/json
> Authorization: bearer testtest
> Content-Length: 21
>
* upload completely sent off: 21 bytes
* schannel: remote party requests renegotiation
* schannel: renegotiating SSL/TLS connection
* schannel: SSL/TLS connection renegotiated
< HTTP/1.1 200 OK
< Date: Sun, 21 Sep 2025 13:03:56 GMT
< Content-Type: */*
< Content-Length: 3
< Connection: keep-alive
< x-amzn-RequestId: c******f-f***-4***-9***-7**********d
< x-amz-apigw-id: ****************
< X-Amzn-Trace-Id: Root=1-6******b-************************;Parent=****************;Sampled=0;Lineage=2:********:0
<
cat* Connection #1 to host poeny.******.click left intact

これで、今回のミッションはコンプリートです。
全体はこちらのGITを見て下さい。
では、次回は・・・決まったら更新します。がEC2か、ECSですかね!

コメント

タイトルとURLをコピーしました