【AWS CDK】CDK標準の3種類のConstructを使って、AWSリソースをデプロイしてみた

はじめに

CX事業本部@札幌の佐藤です。この記事では、AWS CDKのConstructについて説明していきます。

この記事に出てくるサンプルコードは以下のリポジトリにありますので、参考にしてみてください。

https://github.com/briete/aws-cdk-construct-library-sample

前提

AWS CDK Workshopを試した程度の知識を前提にしています。まだやっていないという方、とてもわかりやすいWorkshopなのでぜひやってみてください。

概要

AWS CDKは、基本概念として以下の3つの要素によって構成されています。

  • App
    • AWS CDKの最上位層。複数スタックの依存関係などを定義します。
  • Stack
    • CloudFormationスタックと1:1で対応します。AWSへのデプロイはこのスタック単位で行います。
  • Construct
    • デプロイするAWSリソース。上記のStack層に、Construct Library を使用してAWSリソースを定義していきます。

今回は、この中のConstructについて焦点を当てていきます。

3種類のConstruct

AWS CDKでは、Construct Libraryというライブラリを使ってAWSリソースを作成していきます。このConstruct Libraryには以下の3つの種類があります。

  • High Level Construct
    • Function クラスのように、AWS CDKがCloudFormationの各リソースを抽象化してくれているライブラリです。クラスには、addEventSource(source)のような直感的にリソースを定義できる便利なメソッドがあり、CloudFormationを使うよりも簡単に各リソースを定義することができます。
    • ほとんどの場合は、こちらを使った方が便利です。
    • ただ、すべてのリソースがHigh Level Constructに対応しているわけではなく、developer previewとなっているリソースなどはまだまだ対応されていない状況です(aws-iotaws-greengrassなど)。ただ、AWS CDKは頻繁にアップデートされているため、High Level Constructが提供されるのも時間の問題かと思います。
    • developer previewのリソースに関しては、下記のLow Level Constractを使って定義することになります。
  • Low Level Construct
    • CfnFunction クラスのように、High Lebel Constructsより低レベルなライブラリで、CloudFormationの各リソースと1:1の関係になっています。クラスにCfnプレフィックスがついているものがこれに該当します。
    • CloudFormationリソースと1:1で対応しているため、もし既存のCloudFormationテンプレートをどうしてもCDKに移行したいなどの要件がある場合は、Low Level Constructを使って、CloudFormationのYAML/JSONを手動でTypeScriptやPythonに変換することもできます。
    • 作成しようとしているAWSリソースに、High Level Constructがあるならばそちらを使った方が簡単にリソースを定義できるためオススメです。より細かいパラメーターの設定をしたいなどの要件がある場合にこちらを使えば良いと思います。
  • Patterns
    • aws-ecs-patterns のように、複数のリソースを含む一般的なパターンを定義しているライブラリです。このクラスのコンストラクタに、必要なリソースのインスタンスを渡すだけで、一般的なパターンのインフラ環境を簡単に作成することができます。

High Level Construct、Low Level Construct、Patternでそれぞれデプロイしてみた

実際に、上記の3つのライブラリを使用して、AWSリソースを作成してみたいとおもいます。

環境

項目 バージョン
macOS Mojave 10.14.5
node v10.16.0
npm 6.9.0
aws-cdk 1.11.0 (build 4ed4d96)

CDKプロジェクトと必要なライブラリのインストール

$ mkdir aws-cdk-construct-library-sample
$ cd aws-cdk-construct-library-sample
$ cdk init app --language=typescript
$ npm install --save @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-iam @aws-cdk/aws-ecs-patterns @aws-cdk/aws-ecs

High Level Constructで実装

まずは、High Level Constructで実装します。Lambda + API Gatewayのリソースを作成します。

サンプル用のLambdaのコード

export async function handler(event: any) {
  return {
    statusCode: 200,
    body: 'Sample Lambda Response'
  }
}

CDKでインフラを定義

import cdk = require('@aws-cdk/core');
import lambda = require('@aws-cdk/aws-lambda');
import apigateway = require('@aws-cdk/aws-apigateway');

export class AwsCdkHighLevelConstructLibrarySampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const sampleLambda = new lambda.Function(this, 'sample-lambda', {
      code: lambda.Code.asset('src/handler'),
      handler: 'app.handler',
      runtime: lambda.Runtime.NODEJS_10_X,
    });

    const api = new apigateway.RestApi(this, 'api', {
      restApiName: 'SampleApi'
    });

    const integration = new apigateway.LambdaIntegration(sampleLambda, {
      proxy: true
    });

    const resource = api.root.addResource('sample');
    resource.addMethod('POST', integration);
  }
}

Lambda + API Gatewayの構成ならば、High Level Constructを使えば30行ほどで書けてしまいます。

デプロイ

$ cdk deploy AwsCdkHighLevelConstructLibrarySampleStack

動作確認

$ curl -sS -X POST https://XXXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/sample
Sample Lambda Response

うまく動いているようです。

Low Level Constructで実装

次に、Low Level ConstructでLambda + APi Gatewayを実装します。こちらの場合は、上記に比べて抽象化がなされていないためCloudFormationと同じように、一つ一つ定義していくことになります。

CDKでインフラを定義

import cdk = require('@aws-cdk/core');
import lambda = require('@aws-cdk/aws-lambda');
import apigateway = require('@aws-cdk/aws-apigateway');
import iam = require('@aws-cdk/aws-iam');

export class AwsCdkLowLevelConstructLibrarySampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const region: string = cdk.Stack.of(this).region;
    const accountId: string = cdk.Stack.of(this).account;

    const lambdaServiceRole = new iam.CfnRole(this, 'sampleLambdaServiceRole', {
      assumeRolePolicyDocument: {
        "Statement": [
          {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Principal": {
              "Service": "lambda.amazonaws.com"
            }
          }
        ],
        "Version": "2012-10-17"
      },
      managedPolicyArns: [
        "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      ]
    });

    const lambdaFunction = new lambda.CfnFunction(this, 'SampleLambdaFunction', {
      code: {
        s3Bucket: "s3bucket",
        s3Key: "s3key"
      },
      handler: "app.handler",
      role: lambdaServiceRole.attrArn,
      runtime: "nodejs10.x" 
    });

    lambdaFunction.addDependsOn(lambdaServiceRole);

    const api = new apigateway.CfnRestApi(this, 'SampleRestApi', {
      name: "SampleApi"
    });

    const cloudwatchRole = new iam.CfnRole(this, 'CloudWatchRole', {
      assumeRolePolicyDocument: {
        "Statement": [
          {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Principal": {
              "Service": "apigateway.amazonaws.com"
            }
          }
        ],
        "Version": "2012-10-17"
      },
      managedPolicyArns: [
        "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
      ]
    });

    const apiAccount = new apigateway.CfnAccount(this, 'SampleApiAccount', {
      cloudWatchRoleArn: cloudwatchRole.attrArn
    });
    apiAccount.addDependsOn(api);

    const apiResource = new apigateway.CfnResource(this, 'SampleApiResource', {
      parentId: api.attrRootResourceId,
      pathPart: "sample",
      restApiId: api.ref
    });

    const apiMethod = new apigateway.CfnMethod(this, 'SampleApiMethod', {
      httpMethod: "POST",
      resourceId: apiResource.ref,
      restApiId: api.ref,
      authorizationType: "NONE",
      integration: {
        integrationHttpMethod: "POST",
        type: "AWS_PROXY",
        uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${lambdaFunction.attrArn}/invocations`
      }
    });

    const apiDeployment = new apigateway.CfnDeployment(this, 'Deployment', {
      restApiId: api.ref
    });
    apiDeployment.addDependsOn(apiMethod);
    apiDeployment.addDependsOn(apiResource);

    const apiStage = new apigateway.CfnStage(this, 'SampleStage', {
      restApiId: api.ref,
      deploymentId: apiDeployment.ref,
      stageName: "prod"
    });

    new lambda.CfnPermission(this, 'SampleLambdaPermission', {
      action: "lambda:InvokeFunction",
      functionName: lambdaFunction.attrArn,
      principal: "apigateway.amazonaws.com",
      sourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.ref}/${apiStage.ref}/POST/sample`
    });

    new lambda.CfnPermission(this, 'TestSamleLambdaPermission', {
      action: "lambda:InvokeFunction",
      functionName: lambdaFunction.attrArn,
      principal: "apigateway.amazonaws.com",
      sourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.ref}/test-invoke-stage/POST/sample`
    });
  }
}

Low Level Constractでは120行ぐらい書かなければいけませんでした。High Level Constructと比べて、コード量は4倍近くになっています。CloudFormationと1:1なので、リソースのARNを直接指定しなければならない場面や、IAMのポリシーを自分でガリガリ書いていく必要があるので、High Level Constractに比べて冗長な記述になってしまいます。

デプロイ

$ cdk deploy AwsCdkLowLevelConstructLibrarySampleStack

動作確認

$ curl -sS -X POST https://XXXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/sample
Sample Lambda Response

先ほどと同じく動作しています。

High Level ConstructとLow Level Constructの比較

実際に同じAWSリソースを実装してみて、High Level ConstructとLow Level Constructで、かなり行数が違っていることがわかると思います。High Level Constructでは、AWS CDKが抽象化してくれているので、内部で色々なリソースを作ってくれています。そのため行数を少なく記述できます。リソースごとに細かいパラメータの設定が必要でない限りは、High Level Constructを使用するのがオススメです。

Patterns で実装

High Level Constructの他にも、Patternsというさらに一般的なパターンとして抽象化されているライブラリがあります。現状、AWS CDK公式で提供しているPatternsは以下の2つがあります。

  • aws-ecs-patterns
  • aws-route53-patterns

今回は、aws-ecs-patternsライブラリを使用して、ECS、Fargate、Application Load Balancerのインフラ環境を作ってみます。

CDKでインフラを定義

import cdk = require('@aws-cdk/core');
import ecs = require('@aws-cdk/aws-ecs');
import ecsPatterns = require('@aws-cdk/aws-ecs-patterns');

export class AwsCdkPatternsConstructLibrarySampleStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const cluster = new ecs.Cluster(this, 'SampleCluster', {
      clusterName: 'SampleCluster',
    });

    const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition');
    const container = taskDefinition.addContainer('SampleContainer', {
      image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
    });
    container.addPortMappings({
      containerPort: 80
    });

    const appLoadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'SampleService', {
      cluster: cluster,
      memoryLimitMiB: 1024,
      cpu: 512,
      desiredCount: 1,
      taskDefinition: taskDefinition
    });
  }
}

cdk synthコマンドで、生成されたCloudFormationテンプレートを確認してみます。

$ cdk synth AwsCdkPatternsConstructLibrarySampleStack
Resources:
  SampleClusterB4B72990:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: SampleCluster
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Resource
  SampleClusterVpcD1C6ABD9:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/Resource
  SampleClusterVpcPublicSubnet1SubnetE377A512:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/18
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1
        - Key: aws-cdk:subnet-name
          Value: Public
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/Subnet
  SampleClusterVpcPublicSubnet1RouteTable7114D244:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/RouteTable
  SampleClusterVpcPublicSubnet1RouteTableAssociation0B5402E3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPublicSubnet1RouteTable7114D244
      SubnetId:
        Ref: SampleClusterVpcPublicSubnet1SubnetE377A512
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/RouteTableAssociation
  SampleClusterVpcPublicSubnet1DefaultRoute28A82BC4:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPublicSubnet1RouteTable7114D244
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: SampleClusterVpcIGW21649D5C
    DependsOn:
      - SampleClusterVpcVPCGW39AFB859
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/DefaultRoute
  SampleClusterVpcPublicSubnet1EIPD2C3FC83:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/EIP
  SampleClusterVpcPublicSubnet1NATGateway715FE613:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - SampleClusterVpcPublicSubnet1EIPD2C3FC83
          - AllocationId
      SubnetId:
        Ref: SampleClusterVpcPublicSubnet1SubnetE377A512
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/NATGateway
  SampleClusterVpcPublicSubnet2SubnetB88D2B08:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.64.0/18
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2
        - Key: aws-cdk:subnet-name
          Value: Public
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/Subnet
  SampleClusterVpcPublicSubnet2RouteTable8A11EEAD:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/RouteTable
  SampleClusterVpcPublicSubnet2RouteTableAssociation857BF408:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPublicSubnet2RouteTable8A11EEAD
      SubnetId:
        Ref: SampleClusterVpcPublicSubnet2SubnetB88D2B08
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/RouteTableAssociation
  SampleClusterVpcPublicSubnet2DefaultRouteFD4087CF:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPublicSubnet2RouteTable8A11EEAD
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: SampleClusterVpcIGW21649D5C
    DependsOn:
      - SampleClusterVpcVPCGW39AFB859
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/DefaultRoute
  SampleClusterVpcPublicSubnet2EIPCB2281EA:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/EIP
  SampleClusterVpcPublicSubnet2NATGatewayB385D543:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - SampleClusterVpcPublicSubnet2EIPCB2281EA
          - AllocationId
      SubnetId:
        Ref: SampleClusterVpcPublicSubnet2SubnetB88D2B08
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/NATGateway
  SampleClusterVpcPrivateSubnet1Subnet24256A44:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.128.0/18
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1
        - Key: aws-cdk:subnet-name
          Value: Private
        - Key: aws-cdk:subnet-type
          Value: Private
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/Subnet
  SampleClusterVpcPrivateSubnet1RouteTable55080EB4:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/RouteTable
  SampleClusterVpcPrivateSubnet1RouteTableAssociationBC171CD8:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPrivateSubnet1RouteTable55080EB4
      SubnetId:
        Ref: SampleClusterVpcPrivateSubnet1Subnet24256A44
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/RouteTableAssociation
  SampleClusterVpcPrivateSubnet1DefaultRouteB1C5B147:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPrivateSubnet1RouteTable55080EB4
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: SampleClusterVpcPublicSubnet1NATGateway715FE613
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/DefaultRoute
  SampleClusterVpcPrivateSubnet2Subnet25DCB36D:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.192.0/18
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2
        - Key: aws-cdk:subnet-name
          Value: Private
        - Key: aws-cdk:subnet-type
          Value: Private
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/Subnet
  SampleClusterVpcPrivateSubnet2RouteTable35B9289E:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/RouteTable
  SampleClusterVpcPrivateSubnet2RouteTableAssociationC174EB56:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPrivateSubnet2RouteTable35B9289E
      SubnetId:
        Ref: SampleClusterVpcPrivateSubnet2Subnet25DCB36D
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/RouteTableAssociation
  SampleClusterVpcPrivateSubnet2DefaultRoute74AE4D72:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: SampleClusterVpcPrivateSubnet2RouteTable35B9289E
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: SampleClusterVpcPublicSubnet2NATGatewayB385D543
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/DefaultRoute
  SampleClusterVpcIGW21649D5C:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/IGW
  SampleClusterVpcVPCGW39AFB859:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
      InternetGatewayId:
        Ref: SampleClusterVpcIGW21649D5C
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/VPCGW
  TaskDefinitionTaskRoleFD40A61D:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
        Version: "2012-10-17"
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/TaskDefinition/TaskRole/Resource
  TaskDefinitionB36D86D9:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Essential: true
          Image: amazon/amazon-ecs-sample
          Name: SampleContainer
          PortMappings:
            - ContainerPort: 80
              Protocol: tcp
      Cpu: "256"
      Family: AwsCdkPatternsConstructLibrarySampleStackTaskDefinitionDFEE4CCD
      Memory: "512"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      TaskRoleArn:
        Fn::GetAtt:
          - TaskDefinitionTaskRoleFD40A61D
          - Arn
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/TaskDefinition/Resource
  SampleServiceLBF4CAFE0D:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      SecurityGroups:
        - Fn::GetAtt:
            - SampleServiceLBSecurityGroup3D8CF0A2
            - GroupId
      Subnets:
        - Ref: SampleClusterVpcPublicSubnet1SubnetE377A512
        - Ref: SampleClusterVpcPublicSubnet2SubnetB88D2B08
      Type: application
    DependsOn:
      - SampleClusterVpcPublicSubnet1DefaultRoute28A82BC4
      - SampleClusterVpcPublicSubnet2DefaultRouteFD4087CF
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/Resource
  SampleServiceLBSecurityGroup3D8CF0A2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Automatically created Security Group for ELB AwsCdkPatternsConstructLibrarySampleStackSampleServiceLB60DF5B18
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: Allow from anyone on port 80
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/SecurityGroup/Resource
  SampleServiceLBSecurityGrouptoAwsCdkPatternsConstructLibrarySampleStackSampleServiceSecurityGroup3FF4258B809AF497E9:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId:
        Fn::GetAtt:
          - SampleServiceLBSecurityGroup3D8CF0A2
          - GroupId
      IpProtocol: tcp
      Description: Load balancer to target
      DestinationSecurityGroupId:
        Fn::GetAtt:
          - SampleServiceSecurityGroupF3490F3B
          - GroupId
      FromPort: 80
      ToPort: 80
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/SecurityGroup/to AwsCdkPatternsConstructLibrarySampleStackSampleServiceSecurityGroup3FF4258B:80
  SampleServiceLBPublicListenerA227ACD3:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn:
            Ref: SampleServiceLBPublicListenerECSGroup1AB7CB78
          Type: forward
      LoadBalancerArn:
        Ref: SampleServiceLBF4CAFE0D
      Port: 80
      Protocol: HTTP
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/PublicListener/Resource
  SampleServiceLBPublicListenerECSGroup1AB7CB78:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Port: 80
      Protocol: HTTP
      TargetType: ip
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/PublicListener/ECSGroup/Resource
  SampleService263FFCD0:
    Type: AWS::ECS::Service
    Properties:
      TaskDefinition:
        Ref: TaskDefinitionB36D86D9
      Cluster:
        Ref: SampleClusterB4B72990
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 50
      DesiredCount: 1
      EnableECSManagedTags: false
      HealthCheckGracePeriodSeconds: 60
      LaunchType: FARGATE
      LoadBalancers:
        - ContainerName: SampleContainer
          ContainerPort: 80
          TargetGroupArn:
            Ref: SampleServiceLBPublicListenerECSGroup1AB7CB78
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - Fn::GetAtt:
                - SampleServiceSecurityGroupF3490F3B
                - GroupId
          Subnets:
            - Ref: SampleClusterVpcPrivateSubnet1Subnet24256A44
            - Ref: SampleClusterVpcPrivateSubnet2Subnet25DCB36D
    DependsOn:
      - SampleServiceLBPublicListenerECSGroup1AB7CB78
      - SampleServiceLBPublicListenerA227ACD3
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/Service
  SampleServiceSecurityGroupF3490F3B:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/SecurityGroup
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      VpcId:
        Ref: SampleClusterVpcD1C6ABD9
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/SecurityGroup/Resource
  SampleServiceSecurityGroupfromAwsCdkPatternsConstructLibrarySampleStackSampleServiceLBSecurityGroup3BC91F0D80BE55E0A3:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      Description: Load balancer to target
      FromPort: 80
      GroupId:
        Fn::GetAtt:
          - SampleServiceSecurityGroupF3490F3B
          - GroupId
      SourceSecurityGroupId:
        Fn::GetAtt:
          - SampleServiceLBSecurityGroup3D8CF0A2
          - GroupId
      ToPort: 80
    Metadata:
      aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/SecurityGroup/from AwsCdkPatternsConstructLibrarySampleStackSampleServiceLBSecurityGroup3BC91F0D:80
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=1.11.0,@aws-cdk/assets=1.11.0,@aws-cdk/aws-apigateway=1.11.0,@aws-cdk/aws-applicationautoscaling=1.11.0,@aws-cdk/aws-autoscaling=1.11.0,@aws-cdk/aws-autoscaling-common=1.11.0,@aws-cdk/aws-autoscaling-hooktargets=1.11.0,@aws-cdk/aws-certificatemanager=1.11.0,@aws-cdk/aws-cloudformation=1.11.0,@aws-cdk/aws-cloudwatch=1.11.0,@aws-cdk/aws-ec2=1.11.0,@aws-cdk/aws-ecr=1.11.0,@aws-cdk/aws-ecr-assets=1.11.0,@aws-cdk/aws-ecs=1.11.0,@aws-cdk/aws-ecs-patterns=1.11.0,@aws-cdk/aws-elasticloadbalancingv2=1.11.0,@aws-cdk/aws-events=1.11.0,@aws-cdk/aws-events-targets=1.11.0,@aws-cdk/aws-iam=1.11.0,@aws-cdk/aws-kms=1.11.0,@aws-cdk/aws-lambda=1.11.0,@aws-cdk/aws-logs=1.11.0,@aws-cdk/aws-route53=1.11.0,@aws-cdk/aws-route53-targets=1.11.0,@aws-cdk/aws-s3=1.11.0,@aws-cdk/aws-s3-assets=1.11.0,@aws-cdk/aws-servicediscovery=1.11.0,@aws-cdk/aws-sns=1.11.0,@aws-cdk/aws-sns-subscriptions=1.11.0,@aws-cdk/aws-sqs=1.11.0,@aws-cdk/aws-ssm=1.11.0,@aws-cdk/core=1.11.0,@aws-cdk/cx-api=1.11.0,@aws-cdk/region-info=1.11.0,jsii-runtime=node.js/v10.16.0
    Condition: CDKMetadataAvailable
Outputs:
  SampleServiceLoadBalancerDNS54524680:
    Value:
      Fn::GetAtt:
        - SampleServiceLBF4CAFE0D
        - DNSName
  SampleServiceServiceURLA7529434:
    Value:
      Fn::Join:
        - ""
        - - http://
          - Fn::GetAtt:
              - SampleServiceLBF4CAFE0D
              - DNSName
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2

たった30行ほどのコードですが、500行越えのCloudFormationテンプレートが出力されました。以下のリソースが作成されているのが確認できます。

  • VPC
  • Public Subnet
  • Private Subnet
  • NAT Gateway
  • Route Table
  • Security Group
  • ELB
  • ECS Cluster
  • ECS Service
  • ECS Task Definition

Patternライブラリが、いかにCloudFormationテンプレートを抽象化してくれているかがわかるかと思います。20 ~ 30行ほどのコードであらかじめ定義された一般的なパターンのインフラ環境をデプロイできるというのは、とても強力な機能です。標準のpatternライブラリ自体は2種類とまだ少ないですが、今後のアップデートに期待です。

デプロイ

$ cdk deploy AwsCdkPatternsConstructLibrarySampleStack

動作確認

デプロイできたらCloudFormationの出力タブにELBのURLが出力されているため、それをクリックしてみます。以下のように、ECS、Fargate上で動いているPHPのサンプルアプリケーションが表示されれば成功です。

終わりに

以上、AWS CDKのConstructs Libraryを理解するための記事でした。先日行われていたAWS DevDayでもCDKのセッションがあったりと、CDK界隈が盛り上がってきているなと感じています。本当に便利なのでぜひみなさんも触ってみてください!