DDG - Develop a Digital Garden

develop a digital garden

AWS CDKを使ってほぼ一撃で静的サイトを構築する

AWS CDKとは?

AWS において Infrastructure as Code (以下 IaC) を実現するためのツールです。 CDK 登場以前も CloudFormation を利用して JSONYAML での IaC は実現可能でしたが、

  • 複数のスタックに共通する構成を定義しづらい
  • ほぼ同一構成なリソースを複数作成するのが面倒

という課題がありました。 CDK は プログラミング言語を使って定義できるので、 IF文によるパラメータ切り替えや、ループ処理による複数リソース定義が可能です!

利用できるプログラミング言語は、

です。

今回は、サンプルコードが充実している TypeScript を使用して、CloudFront + S3 の静的サイトを構築してみます。

前提

AWS CLIが利用できる状態。 ※AWS CDKは内部的に AWS-CLI と CloudFormation を利用して、リソースを操作しています。

構築手順

CDKのインストール

$ yarn global add aws-cdk

## バージョン確認
$ cdk --version
1.18.0 (build bc924bc)

CDKを書いていく

CDKプロジェクトを作成していきましょう。

$ cdk init --app cdk-static-site --language=typescript

必要なライブラリをインストールします。

$ yarn add @aws-cdk/aws-iam @aws-cdk/aws-s3 @aws-cdk/aws-cloudfront

CloudFront と S3 を定義します。

import cdk = require('@aws-cdk/core');
import s3 = require("@aws-cdk/aws-s3");
import cloudfront = require("@aws-cdk/aws-cloudfront");
import iam = require("@aws-cdk/aws-iam");

type Stage = 'prod' | 'stg' | 'test';

export class CdkStaticSiteStack extends cdk.Stack {

  readonly AppDomain: string = 'example.com';
  readonly AppName: string = 'example';
  readonly Stage: Stage = 'prod';

  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 独自ドメインを設定する場合に使用する(証明書ARNチェック)
    // const acmArn: string = this.node.tryGetContext('acmArn');
    // if (!acmArn) {
    //   throw new Error('CloudFrontに設定する証明書のARNをContextに設定してください。');
    // }

    // CloudFront オリジン用のS3バケットを作成する
    const originBucket = new s3.Bucket(this, this.prefix() + '-bucket', {
      // バケット名
      bucketName: this.prefix() + '-' + this.account,
      // バージョニング(無効)
      versioned: false,
      // CDKスタック削除時の挙動(スタック削除時にバケットも削除する)
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // CloudFront で設定する オリジンアクセスアイデンティティ を作成する
    const oai = new cloudfront.CfnCloudFrontOriginAccessIdentity(this, this.prefix() + '-oai', {
      cloudFrontOriginAccessIdentityConfig: {
        comment: 's3 access.',
      }
    });

    // S3バケットポリシーで、CloudFrontのオリジンアクセスアイデンティティを許可する
    const policy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['s3:GetObject'],
      principals: [new iam.CanonicalUserPrincipal(oai.attrS3CanonicalUserId)],
      resources: [
        originBucket.bucketArn + '/*'
      ]
    });
    originBucket.addToResourcePolicy(policy);

    // CloudFrontディストリビューションを作成する
    const distribution = new cloudfront.CloudFrontWebDistribution(this, this.prefix() + '-cloudfront', {
      defaultRootObject: '/index.html',
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      httpVersion: cloudfront.HttpVersion.HTTP2,
      priceClass: cloudfront.PriceClass.PRICE_CLASS_200,
      originConfigs: [
        {
          s3OriginSource: {
            s3BucketSource: originBucket,
            originAccessIdentityId: oai.ref
          },
          behaviors: [
            {
              isDefaultBehavior: true,
              compress: true,
              minTtl: cdk.Duration.seconds(0),
              maxTtl: cdk.Duration.days(365),
              defaultTtl: cdk.Duration.days(1),
            }
          ]
        }
      ],
      // 独自ドメインを設定する場合に使用する
      // viewerCertificate: {
      //   aliases: [this.AppDomain],
      //   props: {
      //     acmCertificateArn: acmArn,
      //     minimumProtocolVersion: "TLSv1.2_2018",
      //     sslSupportMethod: "sni-only",
      //   }
      // },
      errorConfigurations: [
        {
          errorCode: 403,
          errorCachingMinTtl: 300,
          responseCode: 200,
          responsePagePath: '/index.html'
        }
      ]
    });

    cdk.Tag.add(this, 'App', this.AppName);
    cdk.Tag.add(this, 'Stage', this.Stage);
  }

  private prefix(): string {
    return this.AppName.toLowerCase() + '-' + this.Stage.toLowerCase();
  }
}

デプロイしてみます。

# TypeScriptをコンパイルする
$ yarn build

# CloudFomationテンプレートを生成する
$ cdk synth

# デプロイする
# しばらく待ちます。CloudFrontディストリビューションの作成に10分程度かかります。
$ cdk deploy

$ cdk deploy が終わり次第、S3にindex.htmlをアップロードし、 CloudFrontエンドポイントにアクセスするとindex.htmlの内容が表示されるはずです!

まとめ

このように、100行未満のコードを書くだけで、静的サイトが構築できます。 プログラミング言語を利用するので、柔軟に書けて、パラメータ切り替えや構成の再利用が行いやすいです。 今回は単純な構成例ですが、実務での冗長化構成や監視定義など、大規模になればなるほど、 プログラミング言語で定義できるというのが活きてくるかと思います。

まだまだ発展途上で、AWSJのSAの方にも「まだ手を出すのは早い」と言われたこともありますが、 どんどんCDKで構築していきたいと思います!

補足: 独自ドメインを設定する場合

上記コードの// 独自ドメインを設定する場合に使用するとコメントしている処理を利用してください。 CDKを実行する前に、ACMで証明書を発行しておき、その証明書のARNをcdk.context.jsonに追記してください。

{
  "@aws-cdk/core:enableStackNameDuplicates": "true",
  "acmArn": ""
}