概要
CloudFrontで公開したページのドメインを自分で取得したドメインのサブドメインに変更したい。
今回は、freenomで取得したhoge.tk
ドメインを使ってwww.fuga.hoge.tk
ドメインを設定する。
FreenomとRoute53のネームサーバの紐づけは手動で行い、
Route53とCloudFrontの紐づけおよびSSL証明書の発行をCDKで行う。
2023/05/20 追記 deprecatedのクラスを使っている。修正版はこちら
作業方針
CDKでCloudFront+S3な静的Webサイトを作る方法[AWS CDK入門]ではCloudFrontWebDistribution
クラスを使った例が紹介されているので、今回はそれを参考にDistribution
クラスを使って設定する。(※どちらも利用できるがDistributionのほうが後発で機能豊富)
また、今回Route53のhostedZoneは手動で作成する。Route53をcdkで作る場合はAPI Gatewayにカスタムドメインを設定するためのリソースを全てAWS CDKでつくってみたが参考となる。
事前準備
ドメインの取得
freenom で新規ドメインを取得。
今回はhoge.tk
が取得できたこととする。
ネームサーバの作成
freenomで取得したドメインのネームサーバーをRoute53で作成する。
ホストゾーンをfreenomで取得したドメインhoge.tk
で作成する。
作成後、ネームサーバが4つ作成されるので控えておく
ネームサーバとドメインの紐づけ
FreenomのServices > My Domains > Manage Domain を選択。
ManagementTools > Nameservers を選択。
Use custom nameservers を選択し、ネームサーバをRoute53で作成した4つ分入力する。
Change Nameserversを押して設定完了。
設定
利用パッケージ
aws cdk 1.121.0
-
dotenv 10.0.0
- .envファイルで環境変数を設定している。
package.json
{
"name": "cdk",
"version": "0.1.0",
"bin": {
"cdk": "bin/cdk.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"bundle": "cdk synth > cdk.out/template.yml",
"deploy": "cdk deploy --profile produser --require-approval never --all"
},
"devDependencies": {
"@aws-cdk/assert": "1.121.0",
"@types/aws-lambda": "^8.10.83",
"@types/node": "16.7.10",
"aws-cdk": "1.121.0",
"dotenv": "^10.0.0",
"esbuild": "^0.12.25",
"npm-check-updates": "^11.8.5",
"ts-node": "^10.2.1",
"typescript": "~4.4.2"
},
"dependencies": {
"@aws-cdk/aws-certificatemanager": "^1.121.0",
"@aws-cdk/aws-cloudfront": "^1.121.0",
"@aws-cdk/aws-cloudfront-origins": "^1.121.0",
"@aws-cdk/aws-iam": "^1.121.0",
"@aws-cdk/aws-route53": "^1.121.0",
"@aws-cdk/aws-route53-targets": "^1.121.0",
"@aws-cdk/aws-s3": "^1.121.0",
"@aws-cdk/aws-s3-deployment": "^1.121.0",
"@aws-cdk/core": "1.121.0",
"source-map-support": "^0.5.19"
}
}
{
"app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
"versionReporting": false
}
実行ファイル
- 環境変数を設定
bin/cdk.ts
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from '@aws-cdk/core'
import { AWSHogeClientStack } from '../lib/cdk-stack'
import * as dotenv from 'dotenv'
// .envファイルから環境変数読込
dotenv.config()
// 環境変数の型をstring | undefined から stringにするためのチェック
if (!process.env.PROJECT_ID) throw new Error(`please add PROJECT_ID to .env`)
if (!process.env.ROOT_DOMAIN) throw new Error(`please add ROOT_DOMAIN to .env`)
if (!process.env.DEPLOY_DOMAIN)
throw new Error(`please add DEPLOY_DOMAIN to .env`)
if (!process.env.TAG_PROJECT_NAME)
throw new Error(`please add TAG_PROJECT_NAME to .env`)
if (!process.env.CF_COMMENT)
throw new Error(`please add CF_COMMENT to .env`)
const app = new cdk.App()
const env = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
}
const projectId = process.env.PROJECT_ID
new AWSLosTRPGClientStack(app, `${projectId}-stack`, {
bucketName: `${projectId}-s3-bucket`,
identityName: `${projectId}-origin-access-identity-to-s3-bucket`,
defaultCachePolicyName: `${projectId}-cache-policy-default`,
distributionName: `${projectId}-distribution-cloudfront`,
rootDomain: process.env.ROOT_DOMAIN,
deployDomain: process.env.DEPLOY_DOMAIN,
projectNameTag: process.env.TAG_PROJECT_NAME,
aRecordName: `${projectId}-a-record`,
aaaaRecordName: `${projectId}-aaaa-record`,
cfComment: process.env.CF_COMMENT
env,
})
PROJECT_ID=hoge-proj-01
ROOT_DOMAIN=hoge.tk
DEPLOY_DOMAIN=www.fuga.hoge.tk
TAG_PROJECT_NAME=hogehoge
CF_COMMENT=CloudFront の Distributeion の説明
Stack
constructor(scope: core.Construct, id: string, props: Props) {
super(scope, id, props)
const bucket = this.createS3(props.bucketName) // CloudFront オリジン用のS3バケットを作成
const identity = this.createIdentity(bucket, props.identityName) // CloudFront で設定する オリジンアクセスアイデンティティ を作成
this.createPolicy(bucket, identity) // S3バケットポリシーで、CloudFrontのオリジンアクセスアイデンティティを許可
+ const zone = this.findRoute53HostedZone(props.rootDomain) // 利用するホストゾーンをドメイン名で取得
+ const cert = this.createTLSCertificate(props.deployDomain, zone) // ホストゾーンにSSL証明書の発行
// CloudFrontディストリビューションを作成
const distribution = this.createCloudFront(
bucket,
identity,
+ cert,
props
)
// 指定したディレクトリをデプロイ
this.deployS3(bucket, distribution, '../client/build', props.bucketName)
+ this.addRoute53Records(zone, distribution, props) // route53 の CloudFrontに紐づくレコード作成
core.Tags.of(this).add('Project', props.projectNameTag)
}
lib/cdk-stack.ts
import * as core from '@aws-cdk/core'
import * as s3 from '@aws-cdk/aws-s3'
import * as cf from '@aws-cdk/aws-cloudfront'
import * as iam from '@aws-cdk/aws-iam'
import * as s3deploy from '@aws-cdk/aws-s3-deployment'
import * as lambda from '@aws-cdk/aws-lambda'
import * as origins from '@aws-cdk/aws-cloudfront-origins'
import * as route53 from '@aws-cdk/aws-route53'
import * as route53Targets from '@aws-cdk/aws-route53-targets'
import * as certManager from '@aws-cdk/aws-certificatemanager'
interface Props extends core.StackProps {
bucketName: string
identityName: string
defaultCachePolicyName: string
distributionName: string
rootDomain: string
deployDomain: string
aRecordName: string
aaaaRecordName: string
cfComment: string
}
export class AWSHogeClientStack extends core.Stack {
constructor(scope: core.Construct, id: string, props: Props) {
super(scope, id, props)
// CloudFront オリジン用のS3バケットを作成
const bucket = this.createS3(props.bucketName)
// CloudFront で設定する オリジンアクセスアイデンティティ を作成
const identity = this.createIdentity(bucket, props.identityName)
// S3バケットポリシーで、CloudFrontのオリジンアクセスアイデンティティを許可
this.createPolicy(bucket, identity)
const zone = this.findRoute53HostedZone(props.rootDomain)
const cert = this.createTLSCertificate(props.deployDomain, zone)
// CloudFrontディストリビューションを作成
const distribution = this.createCloudFront(
bucket,
identity,
cert,
props
)
// 指定したディレクトリをデプロイ
this.deployS3(bucket, distribution, '../client/build', props.bucketName)
// route53 の CloudFrontに紐づくレコード作成
this.addRoute53Records(zone, distribution, props)
core.Tags.of(this).add('Project', props.projectNameTag)
}
private createS3(bucketName: string) {
const bucket = new s3.Bucket(this, bucketName, {
bucketName,
accessControl: s3.BucketAccessControl.PRIVATE,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: core.RemovalPolicy.DESTROY,
cors: [
{
allowedMethods: [s3.HttpMethods.GET],
allowedOrigins: ['*'],
allowedHeaders: ['*'],
},
],
})
return bucket
}
private createIdentity(bucket: s3.Bucket, identityName: string) {
const identity = new cf.OriginAccessIdentity(this, identityName, {
comment: `${bucket.bucketName} access identity`,
})
return identity
}
private createPolicy(bucket: s3.Bucket, identity: cf.OriginAccessIdentity) {
const myBucketPolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:GetObject', 's3:ListBucket'],
principals: [
new iam.CanonicalUserPrincipal(
identity.cloudFrontOriginAccessIdentityS3CanonicalUserId,
),
],
resources: [bucket.bucketArn + '/*', bucket.bucketArn],
})
bucket.addToResourcePolicy(myBucketPolicy)
}
private createCloudFront(
bucket: s3.Bucket,
identity: cf.OriginAccessIdentity,
cert: certManager.DnsValidatedCertificate,
props: Props
) {
const defaultPolicyOption = {
cachePolicyName: props.defaultCachePolicyName,
comment: 'A default policy',
defaultTtl: core.Duration.days(2),
minTtl: core.Duration.seconds(0),
maxTtl: core.Duration.days(10),
cookieBehavior: cf.CacheCookieBehavior.all(),
headerBehavior: cf.CacheHeaderBehavior.none(),
queryStringBehavior: cf.CacheQueryStringBehavior.none(),
enableAcceptEncodingGzip: true,
enableAcceptEncodingBrotli: true,
}
const myCachePolicy = new cf.CachePolicy(
this,
props.defaultCachePolicyName,
defaultPolicyOption,
)
const origin = new origins.S3Origin(bucket, {
originAccessIdentity: identity,
})
const d = new cf.Distribution(this, props.distributionName, {
comment: props.cfComment,
defaultRootObject: '/index.html',
priceClass: cf.PriceClass.PRICE_CLASS_200,
defaultBehavior: {
origin,
allowedMethods: cf.AllowedMethods.ALLOW_GET_HEAD,
cachedMethods: cf.CachedMethods.CACHE_GET_HEAD,
cachePolicy: myCachePolicy,
viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
// 2021.09.05 GUIコンソール上の推奨とCDKのデフォルト値がずれていたので明示
minimumProtocolVersion: cf.SecurityPolicyProtocol.TLS_V1_2_2021,
// Route53と連携するためのカスタムドメイン
certificate: cert,
domainNames: [props.deployDomain],
})
core.Tags.of(d).add('Service', 'Cloud Front')
return d
}
private deployS3(
siteBucket: s3.Bucket,
distribution: cf.Distribution,
sourcePath: string,
bucketName: string,
) {
// Deploy site contents to S3 bucket
new s3deploy.BucketDeployment(
this,
`${bucketName}-deploy-with-invalidation`,
{
sources: [s3deploy.Source.asset(sourcePath)],
destinationBucket: siteBucket,
distribution,
distributionPaths: ['/*'],
},
)
}
private findRoute53HostedZone(rootDomain: string) {
return route53.HostedZone.fromLookup(this, `${rootDomain}-hosted-zone`, {
domainName: rootDomain,
})
}
private createTLSCertificate(
deployDomain: string,
hostedZone: route53.IHostedZone,
) {
return new certManager.DnsValidatedCertificate(
this,
`${deployDomain}-certificate`,
{
domainName: deployDomain,
hostedZone, // DNS 認証に Route 53 のホストゾーンを使う
region: 'us-east-1', // 必ず us-east-1 リージョン
validation: certManager.CertificateValidation.fromDns(),
},
)
}
private addRoute53Records(
zone: route53.IHostedZone,
cf: cf.Distribution,
props: Props
) {
const propsForRoute53Records = {
zone,
recordName: props.deployDomain,
target: route53.RecordTarget.fromAlias(
new route53Targets.CloudFrontTarget(cf),
),
}
new route53.ARecord(this, props.aRecordName, propsForRoute53Records)
new route53.AaaaRecord(this, props.aaaaRecordName, propsForRoute53Records)
}
}
参考
Freenom
aws - route53 - ドメイン名を使用したトラフィックの Amazon CloudFront ウェブディストリビューションへのルーティング
aws - cloudfront - 代替ドメイン名 (CNAME) を追加することによるカスタム URL の使用
CDKでCloudFront+S3な静的Webサイトを作る方法[AWS CDK入門]
API Gatewayにカスタムドメインを設定するためのリソースを全てAWS CDKでつくってみた