継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

プログラマブルにAWS リソース構成に役立つ「 AWS CDK 」

タダです。

昨年、「AWS Cloud Development Kit」(以下、AWS CDK) が開発者プレビューですが、発表されました。

aws.amazon.com

github.com

AWS CDK」の特徴は、プラグラマブルに CloudFormation のプロビジョングができるので、アプリケーションの開発からインフラまで一貫した開発を行えるのがメリットになります。

普段は、 CloudFormaiton を使ってプロビジョニングを行なっているので、 CloudFormation との比較を考慮しつつ触っていきます!

TL;DR

  • CDK のワークショップを使って、 CDK を実践した
    • サンプルプロジェクトとサーバーレス構成を作った
  • CDK を使ってみて CloudFormation との比較をした

AWS CDK の概要

AWS CDK」 はコードでクラウドインフラストラクチャを定義し、CloudFormation でプロビジョニングするためのオープンソースのソフトウェア開発フレームワークです。

サポートされている言語は以下の通りです。今後は Python もサポート予定とのことです。

AWS CDK の実践

早速、CDK Workshopを参考に 「AWS CDK」を実践をしていきます。

準備作業

環境は、AWS Cloud(以下、Cloud9) を使います。「AWS CDK」には以下の要件が必要です。

  • AWS CLI
  • Node.js (バージョンが8.11以上)

Cloud9 には AWS CLI がインストールされているのでインストール不要です。

また、Cloud9 のデフォルトの Node.js ではバージョンが満たしてないため、バージョンをあげます。

ワークショップでは、バージョンを「v8.12.0」にする必要があるため左記バージョンまでアップデートします。

$ node -v
v6.16.0
$ nvm install v8.12.0
Downloading https://nodejs.org/dist/v8.12.0/node-v8.12.0-linux-x64.tar.xz...
####################################################################################################################################################################### 100.0%
Now using node v8.12.0 (npm v6.4.1)

$ node -v
v8.12.0

次に、「AWS CDK」も導入します。ワークショップでは、バージョンを意図的に「0.22」を指定しています。

$ npm install -g aws-cdk@0.22.0
/home/ec2-user/.nvm/versions/node/v8.12.0/bin/cdk -> /home/ec2-user/.nvm/versions/node/v8.12.0/lib/node_modules/aws-cdk/bin/cdk
+ aws-cdk@0.22.0
added 276 packages from 255 contributors in 18.188s

$ cdk --version
0.22.0 (build 644ebf5)

東京リージョンにリソースを作りたいので、「AWS_DEFAULT_REGION」の変数をセットします。

AWS CDK」のコマンドでcdk doctor コマンドを打つと現状の設定を確認できて便利なので、上記の変数をセットした後確認します。

$ export AWS_DEFAULT_REGION=ap-northeast-1
$ cdk doctor
ℹ️ CDK Version: 0.22.0 (build 644ebf5)
ℹ️ AWS environment variables:
  - AWS_DEFAULT_REGION = ap-northeast-1
  - AWS_CLOUDWATCH_HOME = /opt/aws/apitools/mon
  - AWS_PATH = /opt/aws
  - AWS_AUTO_SCALING_HOME = /opt/aws/apitools/as
  - AWS_ELB_HOME = /opt/aws/apitools/elb
ℹ️ No CDK environment variables

AWS CDK プロジェクトの作成

次に TypeScript のサンプルプロジェクトを作成していきます。

$ mkdir cdk-workshop && cd cdk-workshop
$ cdk init sample-app --language typescript
Applying project template sample-app for typescript
Initializing a new git repository...
Executing npm install...
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN cdk-workshop@0.1.0 No repository field.
npm WARN cdk-workshop@0.1.0 No license field.

# Useful commands

* `npm run build`   compile typescript to js
* `npm run watch`   watch for changes and compile
* `cdk deploy`      deploy this stack to your default AWS account/region
* `cdk diff`        compare deployed stack with current state
* `cdk synth`       emits the synthesized CloudFormation template

プロジェクト関連ファイルについて解説があったためまとめます。

  • lib/cdk-workshop-stackts.tsはCDKアプリケーションのメインスタックが定義される
  • bin/cdk-workshop.tsはCDKアプリケーションのエントリポイントになる
  • lib/cdk-workshop-stack.tsで定義されているスタックをロードされる
  • package.json はnpmモジュールのマニフェストファイルである
  • cdk.jsonはツールキットにアプリの実行方法を指示する
    • 今回はnode bin/cdk-workshop.jsになる
  • tsconfig.jsonはTypeScriptの設定が定義される
  • node_modulesディレクトリにはnpmによって管理され、プロジェクトのすべての依存関係を含まれる

f:id:sadayoshi_tada:20190216174502p:plain

サンプルプロジェクトでは、以下のリソースを作ります。

  • SQS キュー
  • SNS トピック
  • SQS キューを SNS トピックでサブスクライブ
import sns = require('@aws-cdk/aws-sns');
import sqs = require('@aws-cdk/aws-sqs');
import cdk = require('@aws-cdk/cdk');

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

    const queue = new sqs.Queue(this, 'CdkWorkshopQueue', {
      visibilityTimeoutSec: 300
    });

    const topic = new sns.Topic(this, 'CdkWorkshopTopic');

    topic.subscribeQueue(queue);
  }
}

アプリケーションコードから CloudFormation テンプレートの確認とデプロイ

cdk synthを実行すると CDK で作る、 CloudFormation のテンプレートを確認できます

$ cdk synth
Resources:
  CdkWorkshopQueue50D9D426:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
    Metadata:
      aws:cdk:path: CdkWorkshopStack/CdkWorkshopQueue/Resource
  CdkWorkshopQueuePolicyAF2494A5:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sqs:SendMessage
            Condition:
              ArnEquals:
                aws:SourceArn:
                  Ref: CdkWorkshopTopicD368A42F
            Effect: Allow
            Principal:
              Service: sns.amazonaws.com
            Resource:
              Fn::GetAtt:
                - CdkWorkshopQueue50D9D426
                - Arn
        Version: "2012-10-17"
      Queues:
        - Ref: CdkWorkshopQueue50D9D426
    Metadata:
      aws:cdk:path: CdkWorkshopStack/CdkWorkshopQueue/Policy/Resource
  CdkWorkshopTopicD368A42F:
    Type: AWS::SNS::Topic
    Metadata:
      aws:cdk:path: CdkWorkshopStack/CdkWorkshopTopic/Resource
  CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7:
    Type: AWS::SNS::Subscription
    Properties:
      Endpoint:
        Fn::GetAtt:
          - CdkWorkshopQueue50D9D426
          - Arn
      Protocol: sqs
      TopicArn:
        Ref: CdkWorkshopTopicD368A42F
    Metadata:
      aws:cdk:path: CdkWorkshopStack/CdkWorkshopTopic/CdkWorkshopQueueSubscription/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=0.22.0,@aws-cdk/aws-cloudwatch=0.22.0,@aws-cdk/aws-iam=0.22.0,@aws-cdk/aws-kms=0.22.0,@aws-cdk/aws-s3-notifications=0.22.0,@aws-cdk/aws-sns=0.22.0,@aws-cdk/aws-sqs=0.22.0,@aws-cdk/cdk=0.22.0,@aws-cdk/cx-api=0.22.0,jsii-runtime=node.js/v8.12.0

続いて、CloudFormation のデプロイを行なっていきます。

AWS CDK」アプリを初めてデプロイするときに「bootstrap stack」をインストールする必要があります。

「bootstrap stack」では、CloudFormation テンプレートを格納するS3が用意されます。

$ cdk bootstrap
   Bootstrapping environment xxxxxxxxxxx/ap-northeast-1...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 04:30:07 | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket 
 0/2 | 04:30:09 | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket Resource creation Initiated
 1/2 | 04:30:30 | CREATE_COMPLETE      | AWS::S3::Bucket | StagingBucket 
 2/2 | 04:30:32 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CDKToolkit 
   Environment xxxxxxxxxxx/ap-northeast-1 bootstrapped.

コマンドが成功すると、 CloudFormation スタックが作成されS3バケットが作成されます。 f:id:sadayoshi_tada:20190216174416p:plain

$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬─────────────────────────┬────────┬─────────────────┬───────────────────────────┬──────────────────────────────────────────┐
│   │ Resource                │ Effect │ Action          │ Principal                 │ Condition                                │
├───┼─────────────────────────┼────────┼─────────────────┼───────────────────────────┼──────────────────────────────────────────┤
│ + │ ${CdkWorkshopQueue.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                           │
│   │                         │        │                 │                           │   "aws:SourceArn": "${CdkWorkshopTopic}" │
│   │                         │        │                 │                           │ }                                        │
└───┴─────────────────────────┴────────┴─────────────────┴───────────────────────────┴──────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Do you wish to deploy these changes (y/n)? y
CdkWorkshopStack: deploying...
CdkWorkshopStack: creating CloudFormation changeset...
 0/6 | 04:38:15 | CREATE_IN_PROGRESS   | AWS::SNS::Topic        | CdkWorkshopTopic (CdkWorkshopTopicD368A42F) 
 0/6 | 04:38:16 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata     | CDKMetadata 
 0/6 | 04:38:16 | CREATE_IN_PROGRESS   | AWS::SQS::Queue        | CdkWorkshopQueue (CdkWorkshopQueue50D9D426) 
 0/6 | 04:38:16 | CREATE_IN_PROGRESS   | AWS::SNS::Topic        | CdkWorkshopTopic (CdkWorkshopTopicD368A42F) Resource creation Initiated
 0/6 | 04:38:16 | CREATE_IN_PROGRESS   | AWS::SQS::Queue        | CdkWorkshopQueue (CdkWorkshopQueue50D9D426) Resource creation Initiated
 1/6 | 04:38:17 | CREATE_COMPLETE      | AWS::SQS::Queue        | CdkWorkshopQueue (CdkWorkshopQueue50D9D426) 
 1/6 | 04:38:18 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata     | CDKMetadata Resource creation Initiated
 2/6 | 04:38:18 | CREATE_COMPLETE      | AWS::CDK::Metadata     | CDKMetadata 
 3/6 | 04:38:27 | CREATE_COMPLETE      | AWS::SNS::Topic        | CdkWorkshopTopic (CdkWorkshopTopicD368A42F) 
 3/6 | 04:38:29 | CREATE_IN_PROGRESS   | AWS::SNS::Subscription | CdkWorkshopTopic/CdkWorkshopQueueSubscription (CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7) 
 3/6 | 04:38:30 | CREATE_IN_PROGRESS   | AWS::SQS::QueuePolicy  | CdkWorkshopQueue/Policy (CdkWorkshopQueuePolicyAF2494A5) 
 3/6 | 04:38:31 | CREATE_IN_PROGRESS   | AWS::SNS::Subscription | CdkWorkshopTopic/CdkWorkshopQueueSubscription (CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7) Resource creation Initiated
 3/6 | 04:38:31 | CREATE_IN_PROGRESS   | AWS::SQS::QueuePolicy  | CdkWorkshopQueue/Policy (CdkWorkshopQueuePolicyAF2494A5) Resource creation Initiated
 4/6 | 04:38:31 | CREATE_COMPLETE      | AWS::SNS::Subscription | CdkWorkshopTopic/CdkWorkshopQueueSubscription (CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7) 
 5/6 | 04:38:31 | CREATE_COMPLETE      | AWS::SQS::QueuePolicy  | CdkWorkshopQueue/Policy (CdkWorkshopQueuePolicyAF2494A5) 
 6/6 | 04:38:33 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CdkWorkshopStack 

   CdkWorkshopStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxx:stack/CdkWorkshopStack/a5bd1260-31a4-11e9-87b8-0ec110ab8a1e

CloudFormation のコンソールを確認するとスタックが出来上がっているのを確認できました。

f:id:sadayoshi_tada:20190216174343p:plain

次の作業のためにサンプルで作成したリソースを削除します。

lib/cdk-workshop-stack.tsを以下のように編集します。

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

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

  }
}

変更後のリソースの変化の確認の仕方は、cdk diffで確認可能です。

$ cdk diff
IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬─────────────────┬───────────────────────────┬──────────────────────────────────────────────────┐
│   │ Resource                        │ Effect │ Action          │ Principal                 │ Condition                                        │
├───┼─────────────────────────────────┼────────┼─────────────────┼───────────────────────────┼──────────────────────────────────────────────────┤
│ - │ ${CdkWorkshopQueue50D9D426.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                                   │
│   │                                 │        │                 │                           │   "aws:SourceArn": "${CdkWorkshopTopicD368A42F}" │
│   │                                 │        │                 │                           │ }                                                │
└───┴─────────────────────────────────┴────────┴─────────────────┴───────────────────────────┴──────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Resources
[-] AWS::SQS::Queue CdkWorkshopQueue50D9D426 destroy
[-] AWS::SQS::QueuePolicy CdkWorkshopQueuePolicyAF2494A5 destroy
[-] AWS::SNS::Topic CdkWorkshopTopicD368A42F destroy
[-] AWS::SNS::Subscription CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7 destroy

変更内容が問題なければ、再度cdk deployを実行します。

$ cdk deploy
CdkWorkshopStack: deploying...
CdkWorkshopStack: creating CloudFormation changeset...
 0/6 | 06:00:36 | UPDATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata 
 1/6 | 06:00:38 | UPDATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata 
 1/6 | 06:00:40 | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | CdkWorkshopStack 
 1/6 | 06:00:42 | DELETE_IN_PROGRESS   | AWS::SNS::Subscription | CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7 
 1/6 | 06:00:42 | DELETE_IN_PROGRESS   | AWS::SQS::QueuePolicy | CdkWorkshopQueuePolicyAF2494A5 
 2/6 | 06:00:43 | DELETE_COMPLETE      | AWS::SNS::Subscription | CdkWorkshopTopicCdkWorkshopQueueSubscription88D211C7 
 3/6 | 06:00:44 | DELETE_COMPLETE      | AWS::SQS::QueuePolicy | CdkWorkshopQueuePolicyAF2494A5 
 3/6 | 06:00:45 | DELETE_IN_PROGRESS   | AWS::SNS::Topic    | CdkWorkshopTopicD368A42F 
 3/6 | 06:00:46 | DELETE_IN_PROGRESS   | AWS::SQS::Queue    | CdkWorkshopQueue50D9D426 
 4/6 | 06:00:46 | DELETE_COMPLETE      | AWS::SNS::Topic    | CdkWorkshopTopicD368A42F 
4/6 Currently in progress: CdkWorkshopStack, CdkWorkshopQueue50D9D426

   CdkWorkshopStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxx:stack/CdkWorkshopStack/a5bd1260-31a4-11e9-87b8-0ec110ab8a1e

Lambda と API Gateway の構成方法

次に、AWS Lambda(以下、Lambda)、Amazon API Gateway(以下、APIGW)のサーバーレス構成を CDK で行います。

まず最初に、Lambda のコードを作成します。

binやlibと同じ階層に lambda/hello.js を作成し、以下のコードを記載します。

APIGW の URI にアクセスすると、「Hello, CDK! You've hit /(アクセスしたパス)」と返すコードになります。

exports.handler = async function(event) {
  console.log('request:', JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'text/plain' },
    body: `Hello, CDK! You've hit ${event.path}\n`
  };
};

加えて、 Constructs ライブラリを追加します。このライブラリは AWS サービスごとにあり、AWSのベストプラクティスに従った設定を内包しています。

今回でいうと、 Lambda を定義したり、 APIGW を定義するには上記のライブラリをインストールする必要があります。

$ npm install @aws-cdk/aws-lambda@0.22.0
npm WARN cdk-workshop@0.1.0 No repository field.
npm WARN cdk-workshop@0.1.0 No license field.

+ @aws-cdk/aws-lambda@0.22.0
updated 1 package and audited 1901 packages in 3.194s
found 0 vulnerabilities
$ npm install @aws-cdk/aws-apigateway@0.22.0
npm WARN cdk-workshop@0.1.0 No repository field.
npm WARN cdk-workshop@0.1.0 No license field.

+ @aws-cdk/aws-apigateway@0.22.0
added 1 package from 1 contributor and audited 2476 packages in 6.122s
found 0 vulnerabilities

APIGW と Lambda を統合するコードを lib/cdk-workshop-stack.tsに追記します。

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

export class CdkWorkshopStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    
    const hello = new lambda.Function(this, 'HelloHandler', {
      runtime: lambda.Runtime.NodeJS810,
      code: lambda.Code.asset('lambda'),
      handler: 'hello.handler'
    });
    
    new apigw.LambdaRestApi(this, 'Endpoint', {
      handler:hello
    });
  }
}

cdk diff で変更箇所の確認してみます。

$ cdk diff
The CdkWorkshopStack stack uses assets, which are currently not accounted for in the diff output! See https://github.com/awslabs/aws-cdk/issues/395
IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬───────────────────────┬──────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                        │ Effect │ Action                │ Principal                        │ Condition                                                                                                                                               │
├───┼─────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Endpoint/CloudWatchRole.Arn}  │ Allow  │ sts:AssumeRole        │ Service:apigateway.amazonaws.com │                                                                                                                                                         │
├───┼─────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler}                 │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com │ "ArnLike": {                                                                                                                                            │
│   │                                 │        │                       │                                  │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${EndpointEEF1FD8F}/${Endpoint/DeploymentStage.prod}/*/"         │
│   │                                 │        │                       │                                  │ }                                                                                                                                                       │
├───┼─────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler}                 │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com │ "ArnLike": {                                                                                                                                            │
│   │                                 │        │                       │                                  │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${EndpointEEF1FD8F}/test-invoke-stage/*/"                        │
│   │                                 │        │                       │                                  │ }                                                                                                                                                       │
├───┼─────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler}                 │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com │ "ArnLike": {                                                                                                                                            │
│   │                                 │        │                       │                                  │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${EndpointEEF1FD8F}/${Endpoint/DeploymentStage.prod}/*/{proxy+}" │
│   │                                 │        │                       │                                  │ }                                                                                                                                                       │
├───┼─────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler}                 │ Allow  │ lambda:InvokeFunction │ Service:apigateway.amazonaws.com │ "ArnLike": {                                                                                                                                            │
│   │                                 │        │                       │                                  │   "AWS:SourceArn": "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${EndpointEEF1FD8F}/test-invoke-stage/*/{proxy+}"                │
│   │                                 │        │                       │                                  │ }                                                                                                                                                       │
├───┼─────────────────────────────────┼────────┼───────────────────────┼──────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler/ServiceRole.Arn} │ Allow  │ sts:AssumeRole        │ Service:lambda.amazonaws.com     │                                                                                                                                                         │
└───┴─────────────────────────────────┴────────┴───────────────────────┴──────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────┘
IAM Policy Changes
┌───┬─────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                    │ Managed Policy ARN                                                                      │
├───┼─────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${Endpoint/CloudWatchRole}  │ arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs │
├───┼─────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${HelloHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole          │
└───┴─────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See http://bit.ly/cdk-2EhF7Np)

Parameters
[+] Parameter HelloHandler/Code/S3Bucket HelloHandlerCodeS3Bucket4359A483: {"Type":"String","Description":"S3 bucket for asset \"CdkWorkshopStack/HelloHandler/Code\""}
[+] Parameter HelloHandler/Code/S3VersionKey HelloHandlerCodeS3VersionKey07D12610: {"Type":"String","Description":"S3 key for asset version \"CdkWorkshopStack/HelloHandler/Code\""}

Resources
[+] AWS::IAM::Role HelloHandler/ServiceRole HelloHandlerServiceRole11EF7C63 
[+] AWS::Lambda::Function HelloHandler HelloHandler2E4FBA4D 
[+] AWS::Lambda::Permission HelloHandler/ApiPermission.ANY.. HelloHandlerApiPermissionANYAC4E141E 
[+] AWS::Lambda::Permission HelloHandler/ApiPermission.Test.ANY.. HelloHandlerApiPermissionTestANYDDD56D72 
[+] AWS::Lambda::Permission HelloHandler/ApiPermission.ANY..{proxy+} HelloHandlerApiPermissionANYproxy90E90CD6 
[+] AWS::Lambda::Permission HelloHandler/ApiPermission.Test.ANY..{proxy+} HelloHandlerApiPermissionTestANYproxy9803526C 
[+] AWS::ApiGateway::RestApi Endpoint EndpointEEF1FD8F 
[+] AWS::ApiGateway::Deployment Endpoint/Deployment EndpointDeployment318525DA37c0e38727e25b4317827bf43e918fbf 
[+] AWS::ApiGateway::Stage Endpoint/DeploymentStage.prod EndpointDeploymentStageprodB78BEEA0 
[+] AWS::IAM::Role Endpoint/CloudWatchRole EndpointCloudWatchRoleC3C64E0F 
[+] AWS::ApiGateway::Account Endpoint/Account EndpointAccountB8304247 
[+] AWS::ApiGateway::Resource Endpoint/{proxy+} Endpointproxy39E2174E 
[+] AWS::ApiGateway::Method Endpoint/{proxy+}/ANY EndpointproxyANYC09721C5 
[+] AWS::ApiGateway::Method Endpoint/ANY EndpointANY485C938B 

Outputs
[+] Output Endpoint/Endpoint Endpoint8024A810: {"Value":{"Fn::Join":["",["https://",{"Ref":"EndpointEEF1FD8F"},".execute-api.",{"Ref":"AWS::Region"},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"EndpointDeploymentStageprodB78BEEA0"},"/"]]},"Export":{"Name":"CdkWorkshopStack:Endpoint8024A810"}}

それでは、デプロイしてみます。

$ cdk deploy
Do you wish to deploy these changes (y/n)? y
CdkWorkshopStack: deploying...
Updated: lambda (zip)
CdkWorkshopStack: creating CloudFormation changeset...
  0/16 | 06:53:20 | CREATE_IN_PROGRESS   | AWS::IAM::Role              | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63) 
  0/16 | 06:53:20 | CREATE_IN_PROGRESS   | AWS::IAM::Role              | Endpoint/CloudWatchRole (EndpointCloudWatchRoleC3C64E0F) 
  0/16 | 06:53:20 | CREATE_IN_PROGRESS   | AWS::ApiGateway::RestApi    | Endpoint (EndpointEEF1FD8F) 
  0/16 | 06:53:21 | CREATE_IN_PROGRESS   | AWS::IAM::Role              | Endpoint/CloudWatchRole (EndpointCloudWatchRoleC3C64E0F) Resource creation Initiated
  0/16 | 06:53:21 | CREATE_IN_PROGRESS   | AWS::IAM::Role              | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63) Resource creation Initiated
  0/16 | 06:53:21 | CREATE_IN_PROGRESS   | AWS::ApiGateway::RestApi    | Endpoint (EndpointEEF1FD8F) Resource creation Initiated
  1/16 | 06:53:21 | CREATE_COMPLETE      | AWS::ApiGateway::RestApi    | Endpoint (EndpointEEF1FD8F) 
  1/16 | 06:53:21 | UPDATE_IN_PROGRESS   | AWS::CDK::Metadata          | CDKMetadata 
  2/16 | 06:53:24 | UPDATE_COMPLETE      | AWS::CDK::Metadata          | CDKMetadata 
  2/16 | 06:53:25 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Resource   | Endpoint/{proxy+} (Endpointproxy39E2174E) 
  2/16 | 06:53:26 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Resource   | Endpoint/{proxy+} (Endpointproxy39E2174E) Resource creation Initiated
  3/16 | 06:53:26 | CREATE_COMPLETE      | AWS::ApiGateway::Resource   | Endpoint/{proxy+} (Endpointproxy39E2174E) 
  4/16 | 06:53:39 | CREATE_COMPLETE      | AWS::IAM::Role              | Endpoint/CloudWatchRole (EndpointCloudWatchRoleC3C64E0F) 
  5/16 | 06:53:39 | CREATE_COMPLETE      | AWS::IAM::Role              | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63) 
  5/16 | 06:53:42 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Account    | Endpoint/Account (EndpointAccountB8304247) 
  5/16 | 06:53:43 | CREATE_IN_PROGRESS   | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) 
  5/16 | 06:53:43 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Account    | Endpoint/Account (EndpointAccountB8304247) Resource creation Initiated
  6/16 | 06:53:43 | CREATE_COMPLETE      | AWS::ApiGateway::Account    | Endpoint/Account (EndpointAccountB8304247) 
  6/16 | 06:53:44 | CREATE_IN_PROGRESS   | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) Resource creation Initiated
  7/16 | 06:53:44 | CREATE_COMPLETE      | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) 
  7/16 | 06:53:47 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.Test.ANY.. (HelloHandlerApiPermissionTestANYDDD56D72) 
  7/16 | 06:53:47 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.Test.ANY..{proxy+} (HelloHandlerApiPermissionTestANYproxy9803526C) 
  7/16 | 06:53:48 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Method     | Endpoint/{proxy+}/ANY (EndpointproxyANYC09721C5) 
  7/16 | 06:53:48 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Method     | Endpoint/ANY (EndpointANY485C938B) 
  7/16 | 06:53:48 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.Test.ANY.. (HelloHandlerApiPermissionTestANYDDD56D72) Resource creation Initiated
  7/16 | 06:53:48 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.Test.ANY..{proxy+} (HelloHandlerApiPermissionTestANYproxy9803526C) Resource creation Initiated
  7/16 | 06:53:48 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Method     | Endpoint/{proxy+}/ANY (EndpointproxyANYC09721C5) Resource creation Initiated
  7/16 | 06:53:48 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Method     | Endpoint/ANY (EndpointANY485C938B) Resource creation Initiated
  8/16 | 06:53:49 | CREATE_COMPLETE      | AWS::ApiGateway::Method     | Endpoint/{proxy+}/ANY (EndpointproxyANYC09721C5) 
  9/16 | 06:53:49 | CREATE_COMPLETE      | AWS::ApiGateway::Method     | Endpoint/ANY (EndpointANY485C938B) 
  9/16 | 06:53:52 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Deployment | Endpoint/Deployment (EndpointDeployment318525DA37c0e38727e25b4317827bf43e918fbf) 
  9/16 | 06:53:53 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Deployment | Endpoint/Deployment (EndpointDeployment318525DA37c0e38727e25b4317827bf43e918fbf) Resource creation Initiated
 10/16 | 06:53:53 | CREATE_COMPLETE      | AWS::ApiGateway::Deployment | Endpoint/Deployment (EndpointDeployment318525DA37c0e38727e25b4317827bf43e918fbf) 
 10/16 | 06:53:56 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Stage      | Endpoint/DeploymentStage.prod (EndpointDeploymentStageprodB78BEEA0) 
 10/16 | 06:53:57 | CREATE_IN_PROGRESS   | AWS::ApiGateway::Stage      | Endpoint/DeploymentStage.prod (EndpointDeploymentStageprodB78BEEA0) Resource creation Initiated
 11/16 | 06:53:58 | CREATE_COMPLETE      | AWS::ApiGateway::Stage      | Endpoint/DeploymentStage.prod (EndpointDeploymentStageprodB78BEEA0) 
 12/16 | 06:53:58 | CREATE_COMPLETE      | AWS::Lambda::Permission     | HelloHandler/ApiPermission.Test.ANY.. (HelloHandlerApiPermissionTestANYDDD56D72) 
 13/16 | 06:53:58 | CREATE_COMPLETE      | AWS::Lambda::Permission     | HelloHandler/ApiPermission.Test.ANY..{proxy+} (HelloHandlerApiPermissionTestANYproxy9803526C) 
 13/16 | 06:54:02 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.ANY..{proxy+} (HelloHandlerApiPermissionANYproxy90E90CD6) 
 13/16 | 06:54:02 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.ANY.. (HelloHandlerApiPermissionANYAC4E141E) 
 13/16 | 06:54:02 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.ANY.. (HelloHandlerApiPermissionANYAC4E141E) Resource creation Initiated
 13/16 | 06:54:03 | CREATE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHandler/ApiPermission.ANY..{proxy+} (HelloHandlerApiPermissionANYproxy90E90CD6) Resource creation Initiated
 14/16 | 06:54:13 | CREATE_COMPLETE      | AWS::Lambda::Permission     | HelloHandler/ApiPermission.ANY.. (HelloHandlerApiPermissionANYAC4E141E) 
 15/16 | 06:54:13 | CREATE_COMPLETE      | AWS::Lambda::Permission     | HelloHandler/ApiPermission.ANY..{proxy+} (HelloHandlerApiPermissionANYproxy90E90CD6) 
 15/16 | 06:54:16 | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack  | CdkWorkshopStack 
 16/16 | 06:54:17 | UPDATE_COMPLETE      | AWS::CloudFormation::Stack  | CdkWorkshopStack 

   CdkWorkshopStack

Outputs:
CdkWorkshopStack.Endpoint8024A810 = https://vam8k4pk2e.execute-api.ap-northeast-1.amazonaws.com/prod/

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/CdkWorkshopStack/a5bd1260-31a4-11e9-87b8-0ec110ab8a1e

デプロイされたエンドポイントに対して、アクセスしてみたら想定通りの結果が帰ってきました。

$ curl https://vam8k4pk2e.execute-api.ap-northeast-1.amazonaws.com/prod/
Hello, CDK! You've hit /

DynamoDB リソースの追加

次に、Amazon DynamoDB(以下、DynamoDB)のリソースを追加します。

アクセスした URI のパスを DynamoDB のテーブルに記録する処理を定義するものを定義します。

まずは、DynamoDB の Constructs ライブラリをインストールします。

$ npm install @aws-cdk/aws-dynamodb@0.22.0
npm WARN cdk-workshop@0.1.0 No repository field.
npm WARN cdk-workshop@0.1.0 No license field.

+ @aws-cdk/aws-dynamodb@0.22.0
added 3 packages from 1 contributor and audited 2564 packages in 10.132s
found 0 vulnerabilities

lambda/hitcounter.js に以下のコードを追加します。

const { DynamoDB, Lambda } = require('aws-sdk');

exports.handler = async function(event) {
  console.log("request:", JSON.stringify(event, undefined, 2));

  // create AWS SDK clients
  const dynamo = new DynamoDB();
  const lambda = new Lambda();

  // update dynamo entry for "path" with hits++
  await dynamo.updateItem({
    TableName: process.env.HITS_TABLE_NAME, # DynamoDBテーブルの名前
    Key: { path: { S: event.path } },
    UpdateExpression: 'ADD hits :incr',
    ExpressionAttributeValues: { ':incr': { N: '1' } }
  }).promise();

  // call downstream function and capture response
  const resp = await lambda.invoke({
    FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME, #Lambda関数名
    Payload: JSON.stringify(event)
  }).promise();

  console.log('downstream response:', JSON.stringify(resp, undefined, 2));

  // return response back to upstream caller
  return JSON.parse(resp.Payload);
};

lib/hitcounter.tsは次のように編集します。

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

export interface HitCounterProps {
  /** the function for which we want to count url hits **/
  downstream: lambda.Function;
}

export class HitCounter extends cdk.Construct {
  /** allows accessing the counter function */
  public readonly handler: lambda.Function;

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

    const table = new dynamodb.Table(this, 'Hits');
    table.addPartitionKey({ name: 'path', type: dynamodb.AttributeType.String });

    this.handler = new lambda.Function(this, 'HitCounterHandler', {
      runtime: lambda.Runtime.NodeJS810,
      handler: 'hitcounter.handler',
      code: lambda.Code.asset('lambda'),
      environment: {
        DOWNSTREAM_FUNCTION_NAME: props.downstream.functionName,
        HITS_TABLE_NAME: table.tableName
      }
    });

    // grant the lambda role read/write permissions to our table
    table.grantReadWriteData(this.handler.role);

    // grant the lambda role invoke permissions to the downstream function
    props.downstream.grantInvoke(this.handler.role);
  }
}

lib/cdk-workshop-stack.ts は次のように編集します。

import cdk = require('@aws-cdk/cdk');
import lambda = require('@aws-cdk/aws-lambda');
import apigw = require('@aws-cdk/aws-apigateway');
import { HitCounter } from './hitcounter';

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

    const hello = new lambda.Function(this, 'HelloHandler', {
      runtime: lambda.Runtime.NodeJS810,
      code: lambda.Code.asset('lambda'),
      handler: 'hello.handler'
    });

    const helloWithCounter = new HitCounter(this, 'HelloHitCounter', {
      downstream: hello
    });

    // defines an API Gateway REST API resource backed by our "hello" function.
    new apigw.LambdaRestApi(this, 'Endpoint', {
      handler: helloWithCounter.handler
    });
  }
}

それではデプロイします。

$ cdk deploy
CdkWorkshopStack: deploying...
Updated: lambda (zip)
Updated: lambda (zip)
CdkWorkshopStack: creating CloudFormation changeset...
 0/6 | 07:43:28 | UPDATE_IN_PROGRESS   | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) 
 1/6 | 07:43:29 | UPDATE_COMPLETE      | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) 
 1/6 | 07:43:36 | UPDATE_IN_PROGRESS   | AWS::Lambda::Function       | HelloHitCounter/HitCounterHandler (HelloHitCounterHitCounterHandlerDAEA7B37) 
 2/6 | 07:43:37 | UPDATE_COMPLETE      | AWS::Lambda::Function       | HelloHitCounter/HitCounterHandler (HelloHitCounterHitCounterHandlerDAEA7B37) 

   CdkWorkshopStack

Outputs:
CdkWorkshopStack.Endpoint8024A810 = https://vam8k4pk2e.execute-api.ap-northeast-1.amazonaws.com/prod/

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/CdkWorkshopStack/a5bd1260-31a4-11e9-87b8-0ec110ab8a1e

URI にアクセスしてみます。

 $ curl -i https://vam8k4pk2e.execute-api.ap-northeast-1.amazonaws.com/prod/
Hello, CDK! You've hit /
 $ curl -i https://vam8k4pk2e.execute-api.ap-northeast-1.amazonaws.com/prod/hello
Hello, CDK! You've hit /hello
 $ curl -i https://vam8k4pk2e.execute-api.ap-northeast-1.amazonaws.com/prod/test
Hello, CDK! You've hit /test

3回アクセスしたので、 DynamoDB のテーブルに3つレコードが登録されていることを確認できました。 f:id:sadayoshi_tada:20190216175856p:plain

最後は、作ったリソースを削除しますが、cdk destroy を実行します。

$ cdk destroy
Are you sure you want to delete: CdkWorkshopStack (y/n)? y
CdkWorkshopStack: destroying...
   0 | 07:55:23 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack  | CdkWorkshopStack User Initiated
   0 | 07:55:25 | DELETE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.Test.ANY.. (HelloHitCounterHitCounterHandlerApiPermissionTestANY1DD26B5F) 
   0 | 07:55:25 | DELETE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.Test.ANY..{proxy+} (HelloHitCounterHitCounterHandlerApiPermissionTestANYproxyDBD04C8D) 
   0 | 07:55:25 | DELETE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.ANY.. (HelloHitCounterHitCounterHandlerApiPermissionANYBC1D138C) 
   0 | 07:55:25 | DELETE_IN_PROGRESS   | AWS::CDK::Metadata          | CDKMetadata 
   0 | 07:55:25 | DELETE_IN_PROGRESS   | AWS::ApiGateway::Account    | Endpoint/Account (EndpointAccountB8304247) 
   0 | 07:55:26 | DELETE_IN_PROGRESS   | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.ANY..{proxy+} (HelloHitCounterHitCounterHandlerApiPermissionANYproxy4F7D3D9D) 
   1 | 07:55:26 | DELETE_COMPLETE      | AWS::ApiGateway::Account    | Endpoint/Account (EndpointAccountB8304247) 
   1 | 07:55:27 | DELETE_IN_PROGRESS   | AWS::IAM::Role              | Endpoint/CloudWatchRole (EndpointCloudWatchRoleC3C64E0F) 
   2 | 07:55:28 | DELETE_COMPLETE      | AWS::CDK::Metadata          | CDKMetadata 
   3 | 07:55:28 | DELETE_COMPLETE      | AWS::IAM::Role              | Endpoint/CloudWatchRole (EndpointCloudWatchRoleC3C64E0F) 
   4 | 07:55:36 | DELETE_COMPLETE      | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.Test.ANY..{proxy+} (HelloHitCounterHitCounterHandlerApiPermissionTestANYproxyDBD04C8D) 
   5 | 07:55:36 | DELETE_COMPLETE      | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.ANY.. (HelloHitCounterHitCounterHandlerApiPermissionANYBC1D138C) 
   6 | 07:55:36 | DELETE_COMPLETE      | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.ANY..{proxy+} (HelloHitCounterHitCounterHandlerApiPermissionANYproxy4F7D3D9D) 
   7 | 07:55:36 | DELETE_COMPLETE      | AWS::Lambda::Permission     | HelloHitCounter/HitCounterHandler/ApiPermission.Test.ANY.. (HelloHitCounterHitCounterHandlerApiPermissionTestANY1DD26B5F) 
   7 | 07:55:37 | DELETE_IN_PROGRESS   | AWS::ApiGateway::Stage      | Endpoint/DeploymentStage.prod (EndpointDeploymentStageprodB78BEEA0) 
   8 | 07:55:39 | DELETE_COMPLETE      | AWS::ApiGateway::Stage      | Endpoint/DeploymentStage.prod (EndpointDeploymentStageprodB78BEEA0) 
   8 | 07:55:40 | DELETE_IN_PROGRESS   | AWS::ApiGateway::Deployment | Endpoint/Deployment (EndpointDeployment318525DA741e521b7179af57a3425e580c915cc2) 
   9 | 07:55:41 | DELETE_COMPLETE      | AWS::ApiGateway::Deployment | Endpoint/Deployment (EndpointDeployment318525DA741e521b7179af57a3425e580c915cc2) 
   9 | 07:55:42 | DELETE_IN_PROGRESS   | AWS::ApiGateway::Method     | Endpoint/{proxy+}/ANY (EndpointproxyANYC09721C5) 
   9 | 07:55:42 | DELETE_IN_PROGRESS   | AWS::ApiGateway::Method     | Endpoint/ANY (EndpointANY485C938B) 
  10 | 07:55:42 | DELETE_COMPLETE      | AWS::ApiGateway::Method     | Endpoint/{proxy+}/ANY (EndpointproxyANYC09721C5) 
  11 | 07:55:42 | DELETE_COMPLETE      | AWS::ApiGateway::Method     | Endpoint/ANY (EndpointANY485C938B) 
  11 | 07:55:43 | DELETE_IN_PROGRESS   | AWS::ApiGateway::Resource   | Endpoint/{proxy+} (Endpointproxy39E2174E) 
  11 | 07:55:44 | DELETE_IN_PROGRESS   | AWS::Lambda::Function       | HelloHitCounter/HitCounterHandler (HelloHitCounterHitCounterHandlerDAEA7B37) 
  12 | 07:55:44 | DELETE_COMPLETE      | AWS::ApiGateway::Resource   | Endpoint/{proxy+} (Endpointproxy39E2174E) 
  13 | 07:55:44 | DELETE_COMPLETE      | AWS::Lambda::Function       | HelloHitCounter/HitCounterHandler (HelloHitCounterHitCounterHandlerDAEA7B37) 
  13 | 07:55:45 | DELETE_IN_PROGRESS   | AWS::ApiGateway::RestApi    | Endpoint (EndpointEEF1FD8F) 
  13 | 07:55:45 | DELETE_IN_PROGRESS   | AWS::IAM::Policy            | HelloHitCounter/HitCounterHandler/ServiceRole/DefaultPolicy (HelloHitCounterHitCounterHandlerServiceRoleDefaultPolicy1487A60A) 
  14 | 07:55:46 | DELETE_COMPLETE      | AWS::ApiGateway::RestApi    | Endpoint (EndpointEEF1FD8F) 
  15 | 07:55:47 | DELETE_COMPLETE      | AWS::IAM::Policy            | HelloHitCounter/HitCounterHandler/ServiceRole/DefaultPolicy (HelloHitCounterHitCounterHandlerServiceRoleDefaultPolicy1487A60A) 
  15 | 07:55:48 | DELETE_IN_PROGRESS   | AWS::DynamoDB::Table        | HelloHitCounter/Hits (HelloHitCounterHits7AAEBF80) 
  15 | 07:55:48 | DELETE_IN_PROGRESS   | AWS::IAM::Role              | HelloHitCounter/HitCounterHandler/ServiceRole (HelloHitCounterHitCounterHandlerServiceRoleD45002B8) 
  15 | 07:55:48 | DELETE_IN_PROGRESS   | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) 
  16 | 07:55:49 | DELETE_COMPLETE      | AWS::Lambda::Function       | HelloHandler (HelloHandler2E4FBA4D) 
  17 | 07:55:49 | DELETE_COMPLETE      | AWS::IAM::Role              | HelloHitCounter/HitCounterHandler/ServiceRole (HelloHitCounterHitCounterHandlerServiceRoleD45002B8) 
  17 | 07:55:50 | DELETE_IN_PROGRESS   | AWS::IAM::Role              | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63) 
  18 | 07:55:51 | DELETE_COMPLETE      | AWS::IAM::Role              | HelloHandler/ServiceRole (HelloHandlerServiceRole11EF7C63) 

   CdkWorkshopStack: destroyed

AWS CDK と CloudFormation の比較

AWS CDK」を使ってみて CloudFormation と使い分けする場合の比較をしてみようと思います。

AWS CDK と CloudFormation を比較してみてのメリット

  • 普段書き慣れている言語であれば、YAMLJSON の CloudFormation よりは書きやすい
  • 感覚的にリソースのデプロイが CloudFormation よりは簡易
    • !Ref のような関数を使わなくても関連リソースの定義ができるのはよかった
    • Lambda の IAM ロール設定が table.grantReadWriteData(this.handler.role); だけなので、CloudFormation のように ARN を指定しなくていいのでシンプルだと感じた
  • IDEで書いているため、コードの中でのライブラリ定義の参照元などをすぐに調べやすい

AWS CDK と CloudFormation を比較してみてのデメリット

  • サポートされてる言語の習得が必須であること
  • 自由度が高く CloudFormation テンプレートを書けるのでプロジェクト内での運用ルールを考える必要はありそう
  • CloudFormation のサポートしている JSONYAMLの方が開発者が馴染むのなら CloudFormation の方が良い

まとめ

AWS CDK」を使った AWS リソース構成の実践と、 CloudFormation との使い分けを自分なりに考察してみました。

現状サポートされている言語が少ないものの、個人的には今後の Python サポートが楽しみです。

プログマブルに条件分岐や繰り返しの処理などを組み合わせて、 CloudFormation だけではできなかった運用ができそうです。

なお、 Constructs ライブラリのレファレンスはこちらです。 awslabs.github.io

本リリースまでに CloudFormation で作ったソースを置き換えたりしてみて更に使い込んでみたいです。

CloudFront でのアカウント跨ぎでアクセスログとオリジンアクセスアイデンティティを設定する

タダです。

Amazon CloudFront(以下、CloudFront)から Amazon S3(以下、S3)にアカウント跨ぎでアクセスさせる必要があり、次の2つの設定検証を行いました。

今回は上記の検証結果をまとめていきます。

CloudFront のアクセスログをアカウント跨ぎで S3 に格納

CloudFront のアクセスログは S3 に格納することができます。

同一アカウントであれば S3 バケットの情報がリストされて、アクセスログの出力先を指定できます。 f:id:sadayoshi_tada:20190209140300p:plain

別アカウントの S3 バケットアクセスログを格納したい場合は、個別にバケット情報を入力し、S3 バケットのアクセスコントロール設定を追加する必要があります。

1. S3 側のアクセスコントロール設定

まずは、S3 のアクセスコントロール設定を行います。

Access for other AWS accounts」にて「CloudFrontのアカウントのルートユーザーの ID」の情報を追加し、「FULL_CONTROL」を足します。

上記のルートユーザーの ID は、 CloudFront のアカウントで S3 バケットを作成し、アクセスコントロールから「Your AWS account (owner) Canonical ID」で確認可能です。

2つ目の ID は、後述する「2. CloudFront 側のアクセスログ設定」を行うと自動的に反映されます。 f:id:sadayoshi_tada:20190209153412p:plain

参考

docs.aws.amazon.com

2. CloudFront 側のアクセスログ設定

CloudFrontの「Distribution Settings」においてまず、「Logging」を有効化します。

次に、「Bucket for Logs」および「Log Prefix」を設定します。

f:id:sadayoshi_tada:20190209152522p:plain

S3 バケットのアクセスコントロール設定が問題なければ、正常に設定完了します。

エラーが表示された場合、S3 バケットのアクセスコントロール設定を再度見直してみると良いと思います。

CloudFront のオリジンアクセスアイデンティティの設定をアカウント跨ぎで設定

CloudFront のオリジンアクセスアイデンティティは、 S3 バケットからのコンテンツ配信する際に設定します。

この設定もアカウントまたぎを行うには、アクセスログ設定同様に個別のバケット情報を入力する必要があります。

1. CloudFront 側のオリジンアクセスアイデンティティ設定

CloudFront 側の設定で、まずはオリジンアクセスアイデンティティを発行します。

発行した後に、設定編集画面から「Amazon S3 Canonical User Id」の情報をコピーしておきます( S3 バケットポリシー設定で使います) f:id:sadayoshi_tada:20190209144209p:plain

次に、オリジンの設定を行なっていきます。

オリジンの設定では、以下の設定を行います。

  • Origin Domain Name」に別アカウントの 「S3 バケット名 +.s3.amazonaws.com」を設定
  • Restrict Backet Access」の設定を「Yes」を設定
  • Origin Access Identity」の設定を「Use an Existing Identity」を選択し、上記で設定したオリジンアクセスアイデンティティ名を設定
  • Grant Read Permission on Bucket」の設定を「No, I Will Update Permissions」を設定 f:id:sadayoshi_tada:20190209144215p:plain

2. S3側のバケットポリシー設定

S3のバケットポリシーを以下のように設定します。

CanonicalUser」の箇所に、「 1. CloudFront 側の設定」でコピーしておいた「Amazon S3 Canonical User Id」を反映します。

{
    "Version": "2012-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": " Grant a CloudFront Origin Identity access to support private content",
            "Effect": "Allow",
            "Principal": {
                "CanonicalUser":"CloudFront Origin Identity Canonical User ID"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::[S3バケット名]/*"
        }
    ]
}

参考

docs.aws.amazon.com

3. Route53 に CloudFront のレコードを追加

最後に Route53 に CloudFront のレコード( xxxx.cloudfront.net )を設定します。

A レコードの Alias もしくは CNAME レコードで登録し、各エッジロケーションの情報が伝搬されればコンテンツにアクセス可能です。

まとめ

CloudFrontのアカウントまたぎでのS3バケット周りの設定方法を紹介しました。

最近のAWSの利用形態が、1つのアカウントでの運用から用途に応じてマルチアカウントかつ、マルチリージョンでの利用が増えてきた印象です。

AWSの権限制御の仕組みを理解することが意図した設定を実現するために必要だと改めて感じました。

今後もアカウントまたぎでの設定を行なった場合はブログにアウトプットしていきます。

Cousera で「Machine Learning Course」の 第2週を受講した

タダです。

Cousera の 「Machine Learning Course」の2週目の講座を受けたので個人メモをまとめていきます。 www.coursera.org

第2週の講座は、次の4つのセクションになります。

  1. Multivariate Linear Regression
  2. Computing Parameters Analytically
  3. Submitting Programming Assignments
  4. Octave/Matlab Tutorial

なお、3.については課題の提出方法のレクチャーとなるため、まとめから割愛します。

Multivariate Linear Regression

  • N=1の特徴で最急降下法を使う場合、勾配降下方程式自体は一般に同じ形式で、n個の機能について以下のように繰り返すだけになる f:id:sadayoshi_tada:20190201200839p:plain

  • フィーチャースケーリングを使えば、特徴量の範囲を調整することで最急降下法を最も早く収束可能である

    • フィーチャースケーリングは、入力値を入力変数の範囲(すなわち、最大値から最小値を引いたもの)で割ることを含み、その結果、ちょうど1の新しい範囲が得られる
    • 値のレンジは小さすぎたりり、大きすぎたりしてはいけない
  • ノーマライゼーション(正規化)
    • 正規化では、入力変数の値から入力変数の平均値を減算することで、入力変数の新しい平均値がゼロになる
    • 範囲で割ったり、標準偏差で割ったりすると、結果が異なることに注意が必要である

参考: osozaki-engineer-tips.com

  • 多項式回帰
    • 仮説関数を二次関数、3次関数、平方根関数(またはその他の形式)にすることで、仮説関数の動作や曲線を変更できる

Computing Parameters Analytically

  • 正規方程式は最小二乗法の計算で役立つ方法として紹介されたが、いまいち理解しきれていないため理解でき次第また追記する

参考: omedstu.jimdo.com

  • 最急降下法と正規方程式のメリットデメリッ
    • 最急降下法
      • メリット:特徴量がたくさんあっても機能する
      • デメリット:学習率を選ぶ必要がある、たくさんのイテレーションが必要になり、処理が遅くなる
    • 正規方程式
      • メリット:学習率のアルファを選ぶ必要はなく実装はシンプル、イテレーションが不要
      • デメリット:X転置Xの逆行列を計算する必要がある、特徴量がたくさんあると遅くなる
  • 正規方程式の非可逆とは何か?
    • 2つの特徴が非常に密接に関連している(すなわち、それらは線形に依存している)冗長な特徴
    • 特徴が多すぎる場合は、いくつかの機能を削除するか、「正則化」を使用する
      • 上記の問題を解決するには、他の機能と線形的に依存している機能を削除するか、機能が多すぎる場合は1つ以上の機能を削除する

Octave/Matlab Tutorial

本項では、Octaveを使った文法の解説とベクトル化の実装が解説された

そもそもOctaveは高レベルプログラミング言語で、Andrew先生曰く初めて触れるプログラミングに触れる学生にも馴染むということだ

GNU Octave は、主に数値解析を目的とした高レベルプログラミング言語である。Octaveは線形ならびに非線形問題を数値的に解くためのコマンドライン·インタフェースを提供する。また、 MATLABとほぼ互換性のある、数値実験を行うためのプログラミング言語として使用することができる。 Octaveは、GNUプロジェクトの一つでGNU General Public Licenseの条件の下のフリーソフトウェアである。 GNU OctaveScilabは、MATLABオープンソース代替品の一つである。 ただし、Octaveは、ScilabよりもMATLABとの互換性維持に重点を置いている

ja.wikipedia.org

Octaveで特徴的だったのは、行列とベクトルの書き方である

# 行列の表現方法
>>A = [1 2; 3 4 ; 5 6]

#出力
# A =
# 
#   1   2   3
#    4   5   6
#    7   8   9

# ベクトルの表現方法
>>B = [ 1 ; 2 ; 3 ]

# 出力
#B =
#
#   1
#   2
#  3

行列のデータ参照は以下のように行う

# 行列の参照
>>A = [1 2; 3 4 ; 5 6]
>>A(2, 3)

# 出力(2行目3列目のデータを参照)
#ans =  6

>>A([1, 3], [2, 3])

#出力(1, 3行目で2, 3列目のデータを参照)
#ans =
#
#  2   3
#   8   9

その他、学んだ文法をまとめる

# for文
>>for i=1:5
i
endfor

#出力
#i =  1
#i =  2
#i =  3
#i =  4
#i =  5

#if文
>> a=1; b=2; x=0;
>> if ( a==1 && b==2)
x=1;
elseif( a==1 && b==2)
x=2;
else
x=3;
end
>> x

#出力
#x =  1

ベクトル化の実装では行列の内積の計算をOctaveで行う方法が紹介された

>> A = [1;2;3]

#出力
#A =
#
#   1
#   2
#   3

>> B = [4;5;6]

#出力
#B =
#
#   4
#   5
#   6

>> Answer = a` * b

#出力
#Answer =  2

>> display(ans)

#出力
#   2   3
#   8   9

まとめ

第2週のプログラムで以下のことを学びました。

  • フィーチャースケーリングは最急降下法で処理する場合、最も早く処理を完結できる
  • 最急降下法と正規方程式の使い分け
  • Octaveの文法や実装例

第3週のプログラムも引き続き受講していきます。

補完教材として、以下の本を読んでいます。こちらの書評も書きたい...!

関連記事

sadayoshi-tada.hatenablog.com sadayoshi-tada.hatenablog.com

『機械学習を始める前の「学習」』を発表した

タダです。

社内勉強会で、『機械学習を始める前の「学習」』と題して機械学習を始めるにあたっての用語やツール、手法などの発表をしてきました。

モチベーション

去年からAIの分野に興味をもって機械学習ディープラーニングを0から勉強し始めました。

最近機械学習の実践としてJupyter Notebookを使ってscikit-learnやnumpy、matplotlibなどのライブラリを活用したデータの解析やモデル生成の勉強がようやくわかってきました。

機械学習に興味があってもいきなり実践をやってしまうといろんな専門用語、ツール等がわからず、勉強してて辛かった過去の経験から実践に入る前段の情報を伝えたいと思って資料を作りました。

資料

資料はこちらです。

www.slideshare.net

個人的にやったほうがいいと思った学習パス

個人的に、機械学習の勉強やモデルのプログラミングを実際にやってみたりして感じたのは、以下の順番で理解が必要なのかと思っています。

  1. 機械学習の体系的な理解
  2. 実データを使ったモデルの生成

そのためには、東大松尾教授の公開コンテンツや、Coursera のコンテンツで知識をインプットし、 KaggleSIGNATE で扱っているような既にデータがあるサービスを利用して実践力を高めるのが個人的にオススメです。

関連記事

私は機械学習の体系的な理解を行うためCoursera の 「Machine Learning Course」で主に勉強中です。

sadayoshi-tada.hatenablog.com

まとめ

今回は、機械学習の導入の話をさせてもらったのですが、次は実践のプログラミングを行いながら、機械学習を体感してもらえる内容にしていきたいです。

参考にさせてもらった関連記事

カックさんのプレゼン時に意識していることと Keynote のベストプラクティス参考に資料を作りました! ありがとうございました!

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

Cousera で「Machine Learning Course」の 第1週を受講した

タダです。

Cousera の 「Machine Learning Course」を受講してます。 www.coursera.org

講座の Introduction 以降を受講したので個人的なメモをまとめていきます。

第1週の講座は、次の3つのセクションになります。

  1. Model and Cost Function
  2. Parameter Learning
  3. Linear Algebra Review

Model and Cost Function

回帰に関する事例として住宅価格の例が紹介された。

講座では学習の訓練セットとして、以下のモノを使った。

  • 小文字のm :訓練のサンプル数
  • 小文字のx :入力変数、特徴
  • y:出力変数、目標変数

訓練セットによって定義されるものは、教師あり学習アルゴリズムになる。

住宅価格の例のように、予測しようとしている目標変数が連続的である場合、回帰問題と呼ぶ。

少数の離散値(数値が連続していない)しか取ることができない時(例えば、癌腫瘍を見てどれが悪性でどれが良性かを判別する場合など)、分類問題と呼ぶ。

Parameter Learning

最急降下法は、適当に初期点を選び、関数の値の最小値あるいは局所的最小値に到達するように反復する。

最小値を求める目的関数(コスト関数)と、偏微分項(勾配)が最急降下法を使うのに必要になる。

なお、ディープラーニングの文脈では、最急降下法をベースにした確率的勾配降下法が使われることがある。

Linear Algebra Review

本章では、行列とベクトルの解説に加えて、行列同士の計算、ベクトル同士の計算、行列とベクトルの計算が解説された。

また、行列のインバース(逆行列)は手で計算せず、プログラミングでやることが多く、 Octave が紹介された。

ja.wikipedia.org

まとめ

第1週のプログラムで以下のことを学びました。

第2週のプログラムも引き続き受講していきます。

関連記事

sadayoshi-tada.hatenablog.com