AWS
TypeScript
aws-cdk

AWS-CDK for TypeScriptで色んなサービスをデプロイする


はじめに

皆様はAWSのサービスをデプロイするのをどうされていますか?


  • コンソール画面からGUIで操作して…

  • AWS-CLIで利用して…

  • CloudFormationのテンプレートファイルを書いて…

  • TerraformやServerlessFrameworkなどの構成管理ツールを使って…

などの方法があると思います。

デプロイするサービスが多くなればなるほど、構成管理ツールを用いたほうが管理コストがかからなくなるので良いですよね。

どのサービスをどれだけ、どのサービスと紐づけているのかをソースコードベースで確認することができます。

また不必要なサービスを減らすことができるのも利点の一つかと思います。

今回は awslabs がOSSとして開発している AWS-CDK を紹介していこうと思います。


AWS-CDKとは?

AWS-CDK(Cloud Development Kit)はCloudForamationのテンプレートファイルを、TypeScriptやJavaScript、Javaなどで書くことができるフレームワークです。

CloudForamationのテンプレートファイルはJSONまたはYAMLで書く必要があり、馴染みのないエンジニアにとっては学習コストが高く感じることがあると思います。

AWS-CDKではtsファイルやjsファイルをビルドするとCloudFormationのYAMLファイルが生成されます。

リリースされたのは去年の夏頃で、OSSで開発が進められています。(2019/02/28時点の最新バージョンは 0.24.1)

ただまだ開発者プレビューという状態なので、「これから大きな変更があるかもしれないよ」とドキュメント内でも書かれています。

この記事ではTypeScriptでの書き方を紹介していきます。

この記事で紹介したソースコードなどは こちらのGithubリポジトリにpushしています。こちらも合わせてご確認ください。


前提条件


  • Node.js >= 8.11.x

  • TypeScript => 2.7

  • AWSのCredentailの設定(参考)(AWS-CLIの初期設定ができていたらOK)


インストール

$ npm i -g aws-cdk

$ cdk --version
0.24.1 (build 67fcf6d)


初期設定

$ mkdir hello-cdk

$ cd hello-cdk
$ cdk init app --language=typescript


コマンド

// デプロイ

$ cdk deploy

// スタックを指定してデプロイ
$ cdk deploy ${StackName}

// CloudFormationのテンプレートファイル生成
$ cdk synth

// CloudFormationのテンプレートファイル生成してファイルに書き出す
$ cdk synth --output ./output

// 差分を確認
$ cdk diff


今回デプロイしていくサービス


  • S3

  • DynamoDB

  • APIGateway

  • Lambda

  • IAM

  • Cognito


S3


Install Package

$ npm i @aws-cdk/aws-s3


SourceCode




import cdk = require("@aws-cdk/cdk");
import s3 = require("@aws-cdk/aws-s3");

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

/**
* Create S3 bucket
*/

new s3.Bucket(this, id, {
bucketName: "bucketName"
})
}
}






DynamoDB


Install Pakcage

$ npm i @aws-cdk/aws-dynamodb


SourceCode




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

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

/**
* Create DynamoDB Table
*/

new dynamodb.Table(this, id, {
tableName: "tableName",
partitionKey: {
name: "HashKey",
type: dynamodb.AttributeType.String // String or Number or Binary
},
sortKey: {
name: "SortKey",
type: dynamodb.AttributeType.String // String or Number or Binary
}
}
)
}
}






APIGateway & Lambda & IAM


Install Package

$ npm i @aws-cdk/aws-apigateway @aws-cdk/aws-lambda @aws-cdk/aws-iam


SourceCode

Webアプリケーションからのリクエストを受け取るRESTの受け口としてAPIGatewayを立てます。

また、CORSとCognitoUserPool認証を有効にしています。

LambdaはDynamoDBへの権限をいくつか付与し、APIGatewayからinvokeされるようにしています。



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

import apigateway = require("@aws-cdk/aws-apigateway");

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

/**
* Create APIGateway
*/

const api: apigateway.RestApi = new apigateway.RestApi(this, id, {
restApiName: "apiName",
description: "apiDescription"
});

/**
* Create APIGateway Authorizer
* Cognito認証の設定
*/

const apiAuthorizer: apigateway.CfnAuthorizer = new apigateway.CfnAuthorizer(this, id, {
restApiId: api.restApiId,
name: "authorizerName",
identitySource: "method.request.header.Authorization",
providerArns: ["cognitoArn"],
type: "COGNITO_USER_POOLS"
});

/**
* Create Lambda Function
*/

const getHandler: lambda.Function = new lambda.Function(this, id, {
functionName: "functionName",
runtime: lambda.Runtime.NodeJS810, // Lambdaのランタイム
code: lambda.Code.directory("resources"), // Lambdaのソースコードを置いているディレクトリ
handler: index.handler, // Lambdaのハンドラー
environment: {
TZ: "Asia/Tokyo" // 環境変数
}
});

/**
* Create IAM Policy Statement
*/

const statement: iam.PolicyStatement = new iam.PolicyStatement().allow()
.addActions(
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:Query",
"dynamodb:Scan"
)
.addResource(
"DynamoDBTableARN" // 操作の対象となるDynamoDBTableのARN
);

/**
* Attach role to Lambda
*/

getHandler.addToRolePolicy(statement);

/**
* Add GET method to APIGateway
*/

const integration = new apigateway.LambdaIntegration(getHandler);
const option: apigateway.MethodOptions = {
authorizationType: apigateway.AuthorizationType.Cognito,
authorizerId: authorizer.authorizerId
}
api.root.addMethod("GET", integration, option);

/**
* Active APIGateway CORS Setting
* CORSの設定をするために OPTION メソッドを追加します
*/

const options = api.root.addMethod("OPTIONS", new apigateway.MockIntegration({
integrationResponses: [{
statusCode: "200",
responseParameters: {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
"method.response.header.Access-Control-Allow-Origin": "'*'",
"method.response.header.Access-Control-Allow-Credentials": "'false'",
"method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE'",
}
}],
passthroughBehavior: apigateway.PassthroughBehavior.Never,
requestTemplates: {
"application/json": "{\"statusCode\": 200}"
}
}));
const methodResource = (options as cdk.Construct).node.findChild("Resource") as apigateway.CfnMethod;
methodResource.propertyOverrides.methodResponses = [{
statusCode: "200",
responseModels: {
"application/json": "Empty"
},
responseParameters: {
"method.response.header.Access-Control-Allow-Headers": true,
"method.response.header.Access-Control-Allow-Origin": true,
"method.response.header.Access-Control-Allow-Credentials": true,
"method.response.header.Access-Control-Allow-Methods": true,
}
}];
}
}






Cognito


Install Package

$ npm i @aws-cdk/aws-cognito


SourceCode



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

import cognito = require("@aws-cdk/aws-cognito");

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

/**
* Create Cognito UserPool
*/

const userPool: cognito.CfnUserPool = new cognito.CfnUserPool(this, id, {
userPoolName: "UserPoolName",
// パスワードポリシー
policies: {
passwordPolicy: {
minimumLength: 8,
requireLowercase: true,
requireNumbers: true,
requireUppercase: true,
requireSymbols: false
}
},
// 必須の標準属性やカスタム属性
schema: [
{
name: "email",
attributeDataType: "String",
required: true
}
],
// 自動検証する項目
autoVerifiedAttributes: ["email"],
// Eメール検証メッセージの件名
emailVerificationSubject: "Your verification code",
// Eメール検証メッセージの内容
emailVerificationMessage: "Your verification code is {####}"
});

/**
* Create Cognito UserPool Client
*/

new cognito.CfnUserPoolClient(this, id, {
clientName:"UserPoolClientName",
userPoolId: userPool.userPoolId
});
}






まとめ

「TypeScriptからCloudFormationのYAMLが生成されるなんて素敵過ぎる!!!」と興奮気味な感じで色々触ってみましたが、実際最高でした。

YAMLファイルを書くのにあまり抵抗はないのですが、AWS-CDKで生成すると、自動で設定を保管してくれる部分もあったりするので、一からすべてを書かないといけないというわけではないです。

またドキュメントもまだまだ少なく、ドキュメント読むよりソースコード読んだほうが早い。みたいなことがあったりします。

前述したとおり、まだ開発者プレビューの段階ということなので、今後のアップデートをしっかり追って行くことが大事だと思います。

今後も他のサービスのデプロイを確認したりしていこうと思っています!

ではまた!!!