API Gatewayにカスタムドメインを設定するためのリソースを全てAWS CDKでつくってみた
最近TypeScript・AWS CDKでAWSのリソースを作るのにはまっています。
本記事では「APIGatewayのカスタムドメインのエンドポイントに対してGETリクエストを送りバックエンドのLambdaが文字列を返す」という処理を実現するためのリソースを全てAWS CDKでデプロイしてみます。
※ドメイン自体は外部サイトで取得しているため厳密には全てのリソースではありません。
- Route53 パブリックホストゾーン
- Amazon Certificate Manager(以下、ACMと記載) 上記ドメインの証明書
- API Gatewayとカスタムドメイン
- Lambda(文字列を返すだけの単純なもの)
開発環境
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.6 BuildVersion: 19G2021
$ npm -v 6.14.6
$ node -v v12.18.3
$ cdk --version 1.62.0 (build 8c2d7fc)
なお、本記事に記載しているコードは下記リポジトリに格納しています。一部本記事のコードと異なる部分があるのでご注意ください(ドメイン名を外部ファイルから取得している)。
https://github.com/urawa72/cdk_samples/tree/master/apigw-custom-domain
ドメインを取得する
今回は検証目的のため、freenomという無料でドメインを取得できるサービスを利用します。
freenomでドメインを取得する手順は弊社ブログの下記記事をご参照ください。
CDKの準備
AWS CDKのインストールやbootstapの手順については割愛します。
作業用のディレクトリを作成しcdk init
で初期リソースを作成します。
また、必要なパッケージもインストールします。
mkdir apigw-domain-sample cd apigw-domain-sample cdk init --language=typescript npm install -D @aws-cdk/aws-apigateway @aws-cdk/aws-lambda @aws-cdk/aws-certificatemanager @aws-cdk/aws-route53 @aws-cdk/aws-route53-targets
Stackを作成する
今回はRoute53、ACM、API Gateway、Lambdaの4つのStackを作成します。
Route53
freenomで取得したドメインのパブリックホストゾーンを作成します。今回はzoneNameにドメイン名を文字列として直書きしていますが、cdk.jsonや環境変数に定義して外部から受け取ることも可能です。
import * as cdk from '@aws-cdk/core'; import * as route53 from '@aws-cdk/aws-route53'; export class Route53Stack extends cdk.Stack { public readonly hostedZone: route53.HostedZone; constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.hostedZone = new route53.PublicHostedZone(this, 'HostedZone', { zoneName: 'yourdomainname.tk', // freenomで取得したドメイン comment: 'created by cdk' }); } }
ACM
Route53のStackの情報を使用してカスタムドメインとして使用するapi.yourdomainname.tk
の証明書を作成します。
import * as cdk from '@aws-cdk/core'; import * as certificatemanager from '@aws-cdk/aws-certificatemanager'; import * as route53 from '@aws-cdk/aws-route53'; interface Route53Props extends cdk.StackProps { hostedZone: route53.HostedZone } export class CertificateStack extends cdk.Stack { public readonly certificate: certificatemanager.Certificate; constructor(scope: cdk.Construct, id: string, props: Route53Props) { super(scope, id, props); this.certificate = new certificatemanager.DnsValidatedCertificate(this, 'Certificate', { domainName: `api.${props.hostedZone.zoneName}`, hostedZone: props.hostedZone, validationMethod: certificatemanager.ValidationMethod.DNS }); } }
API Gateway
Route53とACMのStackのデータを利用してリソースを作成します。本Stack内でRoute53にカスタムドメインをターゲットとするAレコードを設定します。
import * as cdk from '@aws-cdk/core'; import * as acm from '@aws-cdk/aws-certificatemanager'; import * as apigw from '@aws-cdk/aws-apigateway'; import * as route53 from '@aws-cdk/aws-route53'; import * as alias from '@aws-cdk/aws-route53-targets'; interface Route53Props extends cdk.StackProps { hostedZone: route53.HostedZone; certificate: acm.Certificate; } export class ApigwStack extends cdk.Stack { public restApi: apigw.RestApi; constructor(scope: cdk.Construct, id: string, props: Route53Props) { super(scope, id, props); this.restApi = new apigw.RestApi(this, 'SampleRestApi', { restApiName: 'sample', deployOptions: { stageName: 'dev' }, defaultCorsPreflightOptions: { allowOrigins: apigw.Cors.ALL_METHODS, allowMethods: ['GET', 'OPTIONS'], statusCode: 200, } }); const customDomain = new apigw.DomainName(this, 'CustomDomain', { domainName: `api.${props.hostedZone.zoneName}`, certificate: props.certificate, securityPolicy: apigw.SecurityPolicy.TLS_1_2, endpointType: apigw.EndpointType.REGIONAL }); new route53.ARecord(this, 'SampleARecod', { zone: props.hostedZone, recordName: `api.${props.hostedZone.zoneName}`, target: route53.RecordTarget.fromAlias(new alias.ApiGatewayDomain(customDomain)) }); customDomain.addBasePathMapping(this.restApi, { basePath: 'dev', }); } }
Lambda
samples
というリソースにGETリクエストを送れるようにします。
import * as cdk from '@aws-cdk/core'; import * as apigw from '@aws-cdk/aws-apigateway'; import * as lambda from '@aws-cdk/aws-lambda'; interface ApigwProps extends cdk.StackProps { apigw: apigw.RestApi } export class LambdaStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props: ApigwProps) { super(scope, id, props); const sampleFunction = new lambda.Function(this, 'SampleFunction', { code: lambda.Code.fromAsset('lambda'), functionName: 'sampleFunction', handler: 'sample.handler', runtime: lambda.Runtime.NODEJS_12_X, memorySize: 256 }); const sampleIntegration = new apigw.LambdaIntegration(sampleFunction); const sampleResource = props.apigw.root.addResource('samples'); sampleResource.addMethod('GET', sampleIntegration); } }
関数自体は以下のようなシンプルなものです。
export const handler = async (event: any): Promise<any> => { return { 'statusCode': 200, 'body': 'hello! sample api gateway with lambda!', 'isBase64Encoded': false }; }
Stackの依存関係の設定
依存しているStackを後続のStackのpropsで渡します。
#!/usr/bin/env node import 'source-map-support/register'; import * as cdk from '@aws-cdk/core'; import { Route53Stack } from '../lib/route53'; import { CertificateStack } from '../lib/certificate'; import { ApigwStack } from '../lib/apigw'; import { LambdaStack } from '../lib/lambda'; const app = new cdk.App(); const route53 = new Route53Stack(app, 'Route53Stack'); const acm = new CertificateStack(app, 'CertificateStack', { hostedZone: route53.hostedZone }); const apigw = new ApigwStack(app, 'ApigwStack', { hostedZone: route53.hostedZone, certificate: acm.certificate }); const new LambdaStack(app, 'LambdaStack', { apigw: apigw.restApi });
デプロイ
cdk ls
でデプロイ可能なStack一覧を確認します。エラーなく4つのStackが表示されればOKです。
$ cdk list Route53Stack CertificateStack LambdaStack ApigwStack
あとはApigwStackをデプロイすれば、他の3つのStackも自動でデプロイされます。
ただし、今回の場合はRoute53のホストゾーンを作成した後に確認できるネームサーバーをfreenomに設定しないと、ACMの証明書のDNS検証が完了しません。
そのため、まずはRoute53Stackを個別にデプロイします。
$ cdk deploy Route53Stack
続いて、freenomにてネームサーバを設定します。
freenomのトップページ > Services > My Domains > (取得したドメインの行の) Manage Domain > Management Tools > Nameservers と遷移すればネームサーバの設定画面にたどり着きます。前掲の弊社ブログ記事に画面キャプチャ付きで解説があります。
ネームサーバは4つ全て設定します。
feenom側の設定が完了したら、ApigwStackをデプロイします。ACMの証明書のDNS検証には数分かかるため、cdkによるデプロイも一時的にとまります。
$ cdk deploy ApigwStack Including dependency stacks: LambdaStack, CertificateStack, Route53Stack # IAM関連の変更内容が出力される LambdaStack: deploying...
最後までエラーなくデプロイが完了したら、以下のようにGETリクエストを送ってみます。Lambda関数自体に記載した文字列が返ってくれば成功です。
$ curl -X GET https://api.yourdomainname.tk/dev/samples 'hello! sample api gateway with lambda!'
まとめ
Route53とACMの間で手作業が必要になるため完全自動化はできていませんが、それでも簡単にAPI Gatewayをカスタムドメインで試せるので便利です。
今後もAWS CDKのマイコードスニペットを増やしていき、検証などでリソースを作成する際に活用していきたいと思います。