AWS CDKで独自ドメインホスティング環境構築を自動化してみた

![img](https://day-journal.com/memo/images/try-085_00.png)

### AWS CDKで独自ドメインホスティング環境構築を自動化してみました :tada:  

<br>

以前の記事内容をAWS CDKで実現する方法をためしてみました！

https://qiita.com/dayjournal/items/945613577b3056ec4f71

https://qiita.com/dayjournal/items/314f3de3805f6962f0e9

<br>

今回の作成したテンプレートをGitHubで公開したのでぜひご利用ください！

https://github.com/dayjournal/aws-cdk-templates-showcase

#### /lib/unique-domain-hosting-stack.ts

```typescript
import { Stack, StackProps, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as targets from 'aws-cdk-lib/aws-route53-targets';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';

export class UniqueDomainHostingStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    // ドメイン名取得
    const domainName = this.node.tryGetContext('domainName');
    // 擬似パラメータ取得
    const {
      accountId,
      region,
    } = new cdk.ScopedAws(this);
    // ホストゾーンID取得
    const hostedZoneId = route53.HostedZone.fromLookup(this, 'HostedZoneId', {
      domainName: domainName
    });
    // Certificate Manager設定
    const certificateManagerCertificate = new acm.DnsValidatedCertificate(this, 'CertificateManagerCertificate', {
      // ドメイン指定
      domainName: domainName,
      // ホストゾーンID指定
      hostedZone: hostedZoneId,
      // リージョン指定 (CloudFront固定)
      region: 'us-east-1',
      // 検証方法指定
      validation: acm.CertificateValidation.fromDns(),
    });
    // S3バケット作成
    const s3Bucket = new s3.Bucket(this, 'S3Bucket', {
      // バケット名指定
      bucketName: `${domainName}-${region}-${accountId}`,
      // ブロックパブリックアクセスをすべてON
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      // リソース削除時にバケット削除
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });
    // CloudFrontOAI設定
    const cloudFrontOAI = new cloudfront.OriginAccessIdentity(this, 'CloudFrontOriginAccessIdentityy', {
      comment: 'Unique Domain Hosting Environment',
    })
    // CloudFront設定
    const cloudFrontDistribution = new cloudfront.Distribution(this, 'CloudFrontDistribution', {
      // ドメイン指定
      domainNames: [domainName],
      // キャッシュビヘイビア設定
      defaultBehavior: {
        // オリジンID指定
        origin: new origins.S3Origin(s3Bucket, {
          originAccessIdentity: cloudFrontOAI
        }),
        // 自動圧縮有無指定
        compress: true,
        // HTTPメソッド指定
        allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
        // ビューアプロトコルポリシー指定
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        // キャッシュポリシー指定 (CachingOptimized)
        cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
      },
      // SPA対応設定
      errorResponses: [
        {
          httpStatus: 403,
          responsePagePath: '/index.html',
          responseHttpStatus: 200,
          ttl: cdk.Duration.minutes(0),
        },
        {
          httpStatus: 404,
          responsePagePath: '/index.html',
          responseHttpStatus: 200,
          ttl: cdk.Duration.minutes(0),
        },
      ],
      // 料金クラス指定
      priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
      // ディストリビューション有効無効指定
      enabled: true,
      // SSL証明書設定
      certificate: certificateManagerCertificate,
      // セキュリティポリシー指定
      minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
      // HTTPバージョン指定
      httpVersion: cloudfront.HttpVersion.HTTP2,
      // ルートURL指定
      defaultRootObject: 'index.html',
      // IPv6有無指定
      enableIpv6: true,
    })
    // Route53レコード設定
    new route53.ARecord(this, 'Route53RecordSet', {
      // ドメイン指定
      recordName: domainName,
      // ホストゾーンID指定
      zone: hostedZoneId,
      // エイリアスターゲット設定
      target: route53.RecordTarget.fromAlias(
        new targets.CloudFrontTarget(cloudFrontDistribution)
      ),
    });
    // S3バケットにデプロイ
    new s3deploy.BucketDeployment(this, 'S3BucketDeploy', {
      // 対象ディレクトリ指定
      sources: [s3deploy.Source.asset('./dist')],
      // S3バケット指定
      destinationBucket: s3Bucket,
      // CloudFront指定
      distribution: cloudFrontDistribution,
      distributionPaths: ['/*'],
    });
    // デプロイ先URL表示
    new CfnOutput(this, 'DeployURL', {
      value: `https://${domainName}`,
    })
  }
}
```

#### /bin/unique-domain-hosting.ts

```typescript
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { UniqueDomainHostingStack } from '../lib/unique-domain-hosting-stack';

const app = new cdk.App();
new UniqueDomainHostingStack(app, 'UniqueDomainHostingStack', {
  // アカウント情報設定
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION
  },
});
```

#### cdk.json

```json
{
  "app": "npx ts-node --prefer-ts-exts bin/unique-domain-hosting.ts",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
    "@aws-cdk/core:stackRelativeExports": true,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
    "@aws-cdk/aws-lambda:recognizeVersionProps": true,
    "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
    "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
    "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
    "@aws-cdk/core:target-partitions": [
      "aws",
      "aws-cn"
    ],
    "domainName": "ドメイン名設定"
  }
}
```

#### package.json

```json
{
  "name": "unique-domain-hosting",
  "version": "0.1.0",
  "bin": {
    "unique-domain-hosting": "bin/unique-domain-hosting.js"
  },
 "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
  "keywords": [],
  "author": "Yasunori Kirimoto",
  "license": "ISC",
  "devDependencies": {
    "@types/jest": "^26.0.10",
    "@types/node": "10.17.27",
    "jest": "^26.4.2",
    "ts-jest": "^26.2.0",
    "aws-cdk": "2.15.0",
    "ts-node": "^9.0.0",
    "typescript": "~3.9.7"
  },
  "dependencies": {
    "aws-cdk-lib": "2.15.0",
    "constructs": "^10.0.0",
    "source-map-support": "^0.5.16"
  }
}
```

<br>

## 事前準備

- [AWS CLI](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html)のインストールと設定
- [AWS CDK #001 - TypeScript環境構築](https://day-journal.com/memo/aws-cdk-001)
- Amazon Route 53による独自ドメインの取得

![img](https://day-journal.com/memo/images/try-085_07.png)

<br>

実行環境

- node v16.10.0
- npm v7.24.0

<br>

## 構築の流れ

1. [各ファイル配置と設定ファイル更新](https://qiita.com/dayjournal/items/056dca3d33fdf2675f81#%E5%90%84%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E9%85%8D%E7%BD%AE%E3%81%A8%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%9B%B4%E6%96%B0)
2. [独自ドメインホスティング環境を自動デプロイ](https://qiita.com/dayjournal/items/056dca3d33fdf2675f81#%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9B%E3%82%B9%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E7%92%B0%E5%A2%83%E3%82%92%E8%87%AA%E5%8B%95%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4)

<br>

## 各ファイル配置と設定ファイル更新

はじめに、各ファイル配置と設定ファイルを更新します。

「[aws-cdk-templates-showcase](https://github.com/dayjournal/aws-cdk-templates-showcase)」リポジトリ内の「unique-domain-hosting」ディレクトリをそのまま利用するか任意の場所にコピーします。
![img](https://day-journal.com/memo/images/try-085_08.png)

「cdk.json」にAmazon Route 53で取得した独自ドメイン名を設定します。

```json
"domainName": "ドメイン名設定"
```

「dist」ディレクトリにデプロイしたいアプリケーションファイル一式をコピーします。
![img](https://day-journal.com/memo/images/try-085_09.png)

<br>

## 独自ドメインホスティング環境を自動デプロイ

最後に、独自ドメインホスティング環境を自動デプロイします。  

パッケージをインストールします。

```sh
npm install
```

デプロイ前に初回のみ下記コマンドを実行します。リージョンを変更した時にも実行します。

```sh
cdk bootstrap
```

![img](https://day-journal.com/memo/images/try-085_01.png)

プロジェクトをデプロイします。

```sh
cdk deploy
```

![img](https://day-journal.com/memo/images/try-085_02.png)

スタックが作成されたのを確認。各サービスも自動作成されているのを確認できます。
![img](https://day-journal.com/memo/images/try-085_03.png)

独自ドメインにアクセスするとデプロイしたWebSiteが表示されます。
![img](https://day-journal.com/memo/images/try-085_04.png)

<br>

AWS CDKを利用することで、独自ドメインホスティング等のさまざまなリソース構築を自動化することが可能です。今回は、ACMを固定リージョンで作成やCloudFront用のS3バケットポリシー自動生成やデプロイしたいアプリケーションをコード内で定義する等がコード量も少なく実装可能でした。各機能定義は、AWS CloudFormationと同様にドキュメントで学習する必要はありますが、すごく将来性を感じるツールで構築するのが楽しいです :bulb: 

今後も色々と自動化していければと思います！

<br>

AWS CDKについて、他にも記事を書いています。よろしければぜひ :bow:
<a href="https://day-journal.com/memo/tags/AWS-CDK" target="_blank">tags - AWS CDK</a>

やってみたシリーズ :grinning:
<a href="https://day-journal.com/memo/tags/Try" target="_blank">tags - Try</a>

https://qiita.com/dayjournal/items/314f3de3805f6962f0e9

https://qiita.com/dayjournal/items/57cb0a8232d11c6c99af

https://qiita.com/dayjournal/items/945613577b3056ec4f71

<br>
<br>

<a href="https://dayjournal.booth.pm" target="_blank"><img src="https://day-journal.com/memo/images/etc/book.png" alt="book"></a>