API GWにカスタムドメインを設定する。
前提条件
- ドメインを取得済み(今回はFreenom利用)。
- Route53にホストゾーン作成済み。
- FreenomへNSレコードの値を登録済み。
- 東京リージョンでAWS ACMで証明書を発行済み。(Route53へCNAMEも登録済み)
- CDKのversion
1.134.0 (build dd5e12d)
必要なリソースの準備と設定(CDK)
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import * as apigw from '@aws-cdk/aws-apigateway';
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as route53 from '@aws-cdk/aws-route53';
import * as route53Tragts from '@aws-cdk/aws-route53-targets';
export class CustomdomainapiStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// const
const ApigwCustomDomainName = "my-custom-domain.tk";
const ApigwCertificateArn = "arn:aws:acm:ap-northeast-1:************:certificate/*********";
const hostzoneId = "*******************";
const apiName = "test211124";
const apiStageName = "v1";
const apiUsagePlanName = "testusage211124"
const apiKeyName = "testusage211124"
const lambdaName = "test211124"
// == Lambda ==
const testLambda = new lambda.Function(this, `Lambda`, {
code: lambda.Code.fromAsset(`src/${lambdaName}`),
functionName: lambdaName,
handler: "index.handler",
runtime: lambda.Runtime.NODEJS_14_X,
})
// == API Gateway ==
const testApi = new apigw.RestApi(this, "APIGW", {
restApiName: apiName,
deployOptions: {
stageName: apiStageName,
},
endpointTypes: [
apigw.EndpointType.REGIONAL
],
apiKeySourceType: apigw.ApiKeySourceType.HEADER,
domainName: {
domainName: ApigwCustomDomainName,
certificate: acm.Certificate.fromCertificateArn(this, "Certificate", ApigwCertificateArn),
securityPolicy: apigw.SecurityPolicy.TLS_1_2,
endpointType: apigw.EndpointType.REGIONAL,
}
});
// apiUsagePlan
const apiUsagePlan = testApi.addUsagePlan("UsagePlan", {
name: apiUsagePlanName,
throttle: {
rateLimit: 10,
burstLimit: 2
}
});
apiUsagePlan.addApiStage({
stage: testApi.deploymentStage
})
// apiKey
const key = new apigw.ApiKey(this, "Key", {
apiKeyName: apiKeyName
});
apiUsagePlan.addApiKey(key);
// Path
const testPath = testApi.root.addResource("test");
const deeptestPath = testPath.addResource("test");
// Method
// root path
testApi.root.addMethod("GET", new apigw.LambdaIntegration(testLambda), {
methodResponses: [
{
statusCode: "200",
}
],
apiKeyRequired: true
});
// /test path
testPath.addMethod("GET", new apigw.LambdaIntegration(testLambda), {
methodResponses: [
{
statusCode: "200",
}
],
apiKeyRequired: true
});
// /test/test path
deeptestPath.addMethod("GET", new apigw.LambdaIntegration(testLambda), {
methodResponses: [
{
statusCode: "200",
}
],
apiKeyRequired: true
});
const hostZone = route53.PublicHostedZone.fromHostedZoneAttributes(this, "HostZone",{
hostedZoneId: hostzoneId,
zoneName: ApigwCustomDomainName,
}
);
new route53.ARecord(this, "ARecord", {
zone: hostZone,
recordName: ApigwCustomDomainName,
target: route53.RecordTarget.fromAlias(
new route53Tragts.ApiGateway(testApi),
),
}
);
}
}
200返せば何でもいい(Mockでも十分)だが、一応Lambdaのコードも。
"use strict";
exports.handler = main;
async function main(event) {
try {
console.log("start function");
console.log(`event : ${JSON.stringify(event)}`);
return {
statusCode: 200,
body: "hello"
};
} catch (err) {
console.error(`[Error] ${JSON.stringify(err)}`);
return {
statusCode: 500,
body: JSON.stringify(err.message)
};
}
}
検証
postmanで実行。
https://my-custom-domain.tk/
https://my-custom-domain.tk/test
https://my-custom-domain.tk/test/test
上記にアクセスできれば成功。
他
手動でRoute53のホストゾーンにAレコードを作成する場合
レコード作成時にエイリアス
を指定しAPI GWの選択一覧に出てくるAPIエンドポイントは、カスタムエンドポイントのコンソールの「設定」タブに表示されているもので、ステージ作成時に発行されているものとは異なる。
パス(Optional)
カスタムドメインのAPIマッピングの際に指定可能でOptionalとなっているパス。
ルートの位置を指定するものと思っていたが、イメージと違ったのでメモ。
(今考えてみると複数指定できる時点で元の考え方はおかしいのだが、、)
例えば、以下のようなAPIを作成し、2つのリソースを持たせたとする。
http://abcdef012.execute-api.ap-northeast-1.amazonaws.com/v1/
http://abcdef012.execute-api.ap-northeast-1.amazonaws.com/v1/test
このAPIにカスタムドメインmy-custom-domain.com
を設定し、上記のようにマッピング(パス指定なし)すると、それぞれ以下のようにしてアクセスする必要がある。
https://my-custom-domain.tk/
https://my-custom-domain.tk/test
つまりデフォルトでv1にマッピングされるのでカスタムドメインでのアクセス時に/v1/
を入れる必要がなくなる。
ここで、v1を明示して指定させたい時、このパス(Optional)にてv1
と書く。
すると、上記のURLではアクセスできなくなり、以下のように指定するようになる。
https://my-custom-domain.tk/v1
https://my-custom-domain.tk/v1/test
もちろんパス部には何を書いてもいい(/とかは入れられない)のでパスにv1
ではなくhoge
と入力すると
https://my-custom-domain.tk/hoge
https://my-custom-domain.tk/hoge/test
となる。
それいつ使うの?
例えば、リバプロが前段に置いてあり、特定文字列(例えばapi
)がパスの先頭に入っていたらこちらに振り分けるみたいな構成がとられていたりすると、必ずhttps://my-custom-domain.tk/api/
という始まり方でアクセスされることになる。このapi
をステージ名に設定してあげればいいのは確かにそうだが、ステージの使い方的にイケてない。(APIのversionをパスに設定することになる。)
上記の例でhoge
をパスに指定すると、APIのパス構成の中には影響せずにパスを指定することができることからここに api
をいれてあげれば、その影響を無視することができそう。
上ではリバプロとしたが、CloudFrontを置いて、S3をオリジンとする静的Webページのコンテンツとそこで利用されるAPIのドメインを揃える場合にも同一の現象が起こる。CloudFront Distributionにおいて、コンテンツのパスをルートとし、apiコールのパスを/api
のように切り分ける場合である。
(lambda@edgeとかでパスを削り落としてもいいがコストになりそう。)
複数のマッピングができるということ
一つのカスタムドメインに対して、複数のAPIのstageを紐づけてパスで切り分けることができる。