【備忘録・まとめ】AWS Cloud Development Kit (AWS CDK)

## 出典

https://docs.aws.amazon.com/ja_jp/cdk/latest/guide/home.html

## まとめ

* [CFn](https://qiita.com/yhsmt/items/7d8660b133339d884e41)テンプレートを、各種プログラミング言語のコードから生成するフレームワーク
* TypeScript, JavaScript, Python, Java, C#から好きな言語を選択できる（2019-07-26現在）
* リソースの構造をコードで管理できるようになる
* 繰り返しや参照、構造の再利用やパラメータの注入など、CFnテンプレートでは表現が難しかった記述が柔軟にできるようになる
* 下のレイヤーに潜っているCFnの存在を意識できないと、使うのが辛そう

## 要点

### Constructs

constructは、SDKアプリにおいて基本的な構成単位になる。

* あらゆる構成物を包括するクラウドコンポーネントを意味する
* コンポーネントの中身は1つのリソースでもOK
* 複数のリソースからなる高レベルなコンポーネントであってもOK

#### AWS Construct Library

* CDKには[Construct Library](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html)が含まれていて、各種AWSリソースを表現するconstructsが含まれている
* このライブラリには、AWSで利用可能な全てのリソースが含まれている
* これらのリソースにはいくつかのレベル（constructの粒度）がある
    * 最も低レベルなもの: _CFN Resources_
        * `Cfn`という接頭辞が名前についている
        * 例: `s3.CfnBucket`
        * CFN Resourcesを使うときには、リソースのプロパティを明示的に設定しなければならない
        * そのリソースについて完全な理解が要求される
    * 次点で低レベルのもの: _AWS Resources_
        * intent-based API
        * CFN constructsから要求されるglue logicやboilerplate等
        * リソースを素朴に使い祭に便利な各種メソッド
        * リソースについて全て知らなくても便利に使える各種デフォルトの設定・・・などを有する
        * 例: `s3.Bucket`
    * 更に高レベルなもの: _patterns_
        * いくつかのリソースを含むconstructs
        * 例
            * aws-ecs-patterns.LoadBalancedFargateService: ELB + Fargate
            * apigateway.LambdaRestApi: Api Gateway + Lambda

#### Composition

* 高レベルの抽象的なconstructsを構成する要となるのが_Composition_と呼ばれるパターン
* この入れ子の階層構造は_construct tree_と呼ばれる
* AWS CDKにおいて、この木構造のルートは[AWS CDK app](https://docs.aws.amazon.com/cdk/latest/guide/apps_and_stacks.html#apps)になる
* このappの中に、1つ以上の[stack](https://docs.aws.amazon.com/cdk/latest/guide/apps_and_stacks.html#stacks)を作ることになる
    * このstackが、デプロイの1単位になる
    * CFｎのスタックに類するもの
* _Composition_パターンを踏襲することにより、再利用可能なcomponentsを定義できるようになる

#### Initialization

* Constructsは[Construct](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Construct.html)基底クラスを拡張する形で実装する
* Constructは初期化時に3つの引数を取る
    * scope
        * 初期化されるConstructを内包する、親のConstruct
    * id
        * このConstructのスコープにおいて固有の[識別子](https://docs.aws.amazon.com/cdk/latest/guide/identifiers.html)
        * このConstructの名前空間として使われ、リソース名やCFnの論理ID等を付与するのに用いられる
        * Constructを再利用する際に、スコープ内で識別子がユニークであることを確実にする
    * props
        * プロパティのキーバリューセット
        * 言語依存ではあるが、全く無い場合には無視することもできる

#### Interacting with Constructs

* AWS Construct Libraryに含まれるConstructsは、一定のガイドラインに沿ったパターンの実装・APIになっている
    * [grant](https://docs.aws.amazon.com/cdk/latest/guide/permissions.html#permissions_grants)メソッド
        * IAMのREADないしWRITE系ポリシーをアタッチするメソッド
    * ARNやname、URLのgetter
* 詳しくは[このへん](https://docs.aws.amazon.com/cdk/latest/guide/resources.html)参照

### Apps

#### The App Construct

* Constructは、必ず直接ないし間接的にどこかのStack内で定義されなければならない
* 更に、StackはApp constructの中に定義される必要がある

```typescript
const app = new App();
new MyFirstStack(app, 'hello-cdk');
app.synth();
```

* App constructは拡張することもできる

```typescript
class MyApp extends App {
  constructor() {
    new MyFirstStack(this, 'hello-cdk');
  }
}

new MyApp().synth();
```

#### App Lifecycle

![App Lifecycle](https://docs.aws.amazon.com/cdk/latest/guide/images/Lifecycle.png)

1. Construction
    * CDKのコードが実行されて定義されたConstructのインスタンスが生成され、各Constructがリンクされる。
2. Preparation
    * prepareメソッドが実装されたConstructの全てが追加される。ユーザー的にあまり意識する必要はなさそう。
3. Validation
    * validateメソッドが実装されたConstructのバリデーションが実行される。
    * このフックに通知とか追加しておける。
    * なるべくバリデーションは入れたほうが良いらしい。
4. Synthesis
    * CDK app実行の最終段階。`app.synth()`の呼び出しによりトリガーされる。
    * construct treeをなぞって、全てのconstructのsynthesizeメソッドが呼び出される。
    * CFnテンプレート、Lambda bundle、Docker image assets等のデプロイに必要な生成物をここで作る。
5. Deployment
    * Synthesisフェーズで生成したものをここでAWSにデプロイする。
    * CFnのスタックもここでデプロイされる。

### Stacks

* AWS CDKにおいてデプロイのひとかたまりになるもので、CFnのスタックとほぼ同義
* `cdk synth`が実行されると、cloud assemblyはスタックのインスタンス毎に（同じクラスのインスタンスであっても）templateを分けて生成する
* このCFnテンプレートにおけるCFnパラメータは、AWS CDK内で定義され、デプロイ時にのみ利用可能になる
* 下記の例では、合計６つのCFnスタックがデプロイされる

```typescript
class ControlPlane extends Stack { ... }
class DataPlane extends Stack { ... }
class Monitoring extends Stack { ... }

class MyService extends Construct {
  constructor(...) {
    new ControlPlane(this, ...);
    new DataPlane(this, ...);
    new Monitoring(this, ...);
  }
}

const app = new App();
new MyService(app, 'beta');
new MyService(app, 'prod', { prod: true });
app.run();
```

* Stackの名前は明示的に決めることもできるが、デフォルトではStackオブジェクトのconstruct IDから決定される

```typescript
new MyStack(this, 'not:a:stack:name', { stackName: 'this-is-stack-name' });
```

他にも[色々API](https://docs.aws.amazon.com/ja_jp/cdk/latest/guide/stacks.html)がある。

### Environments

* 各々のスタックは、明示的ないし暗黙的にenvironmentと関連付けられる
* environmentが未定義の場合、デプロイ時（`cdk deploy`実行時）の環境に準じて属性が決められる
    * stack.account
    * stack.region
    * stack.availablityZones
    * など
* productionで使うときは、それぞれのstackについてenvironmentを明示的にコーディングするのが推奨される
* 複数の環境にスタックをデプロイするときは、正しくクレデンシャルを設定して`cdk deploy STACK`すること

```typescript
const envEU  = { account: '2383838383', region: 'eu-west-1' };
const envUSA = { account: '8373873873', region: 'us-west-2' };

new MyFirstStack(this, 'first-stack-us', { env: envUSA, encryption: false });
new MyFirstStack(this, 'first-stack-eu', { env: envEU, encryption: true  });
```

### Resources

#### Referencing Resources

* CDKのコード上では、各種CDK resourceのインターフェースを備えたオブジェクトを、それらを引数やパラメータとしてとるメソッドやオブジェクトに渡すことができる
    * 例
        * ECS serviceにclusterを渡す
        * CloudFront distributionにsourceBucketを渡す　など

```typescript
const cluster = new ecs.Cluster(this, 'Cluster', { /* ... */ });

const service = new ecs.Service(this, 'Service', {
  cluster: cluster,
  /* ... */
});
```

#### Accessing Resources in a Different Stack

* 同一アカウント、同一リージョン内であれば、他のスタックにあるリソースもCDK内では参照可能

```typescript
const prod = { account: '123456789012', region: 'us-east-1' };

const stack1 = new StackThatProvidesABucket(app, 'Stack1' , { env: prod });

// stack2 will take a property { bucket: IBucket }
const stack2 = new StackThatExpectsABucket(app, 'Stack2', {
  bucket: stack1.bucket, 
  env: prod
});
```

#### Physical Names

* CDKを通して作成されたスタックのリソースは、物理名がCFnの論理名とは異なる
* 論理名の後ろに、ランダムな接尾辞がつく
    * 例
        * 論理名: Stack2MyBucket4DD88B4F
        * 物理名: tack2mybucket4dd88b4f-iuv1rbv9z3to
* プロパティ等で物理名を指定することも可
    * ただし、冪等性のないCDKのコードになる可能性がある
        * 作成後に変更不可なプロパティを変更してリソースが再作成される場合など

```typescript
const bucket = new s3.Bucket(this, 'MyBucket', {
  bucketName: 'my-bucket-name',
});
```

#### Passing Unique Identifiers

* 基本的にresourceへの参照はオブジェクトとして渡されるべきだが、やむなく必要な場合にはidentifierを渡すこともできる

```typescript
const bucket = new s3.Bucket(this, 'Bucket');

new lambda.Function(this, 'MyLambda', {
  /* ... */,
  environment: {
    BUCKET_NAME: bucket.bucketName,
  },
});
```

#### Importing Existing External Resources

* 既に作成済みのリソースを、そのARNを用いて生成したCDKオブジェクトとしてスタックに加えることができる

```typescript
// Construct a resource (bucket) just by its name (must be same account)
Bucket.fromName(this, 'Bucket', 'my-bucket-name');

// Construct a resource (bucket) by its full ARN (can be cross account)
Bucket.fromArn(this, 'Bucket', 'arn:aws:s3:::my-bucket-name');

// Construct a resource by giving more than one attribute (complex resources)
Resource.fromAttributes(this, 'MyResource', {
  resourceName: 'booh',
  vpc: vpc
});
```

#### Permission Grants

* 殆どのAWS constructsは扱いやすいgrantメソッドを実装している
* 例: 以下のコードでは、lambda関数にs3バケットへの読み書きを許可し、それがKMSによって暗号化されていれば、その復号もlambdaに許可する

```typescript
bucket.grantReadWrite(lambda);
```

* grantメソッドは`iam.Grant`オブジェクトを返す
    * このオブジェクトのsuccess属性が返す値によって、許可が適切に適用されたかどうか確認できる
* 特定の権限についてgrantメソッドが使えない場合には、より一般的なgrantメソッドを使って設定できる

```typescript
table.grant(lambda, 'dynamodb:CreateBackup');
```

* resourceを作成する際のプロパティとして、このresourceにアタッチするroleを指定できる
* 指定しなければ、CDKのコード上ではこのresouceのオブジェクトに直接grantメソッドを使えばよい
* また、roleに直接policyをアタッチすることもできる（`addToRolePolicy`メソッド）

#### Metrics and Alarms

* resouceはCloudWatchでモニタリングできるメトリクスを生成する
* 以下の例ではSQSの`ApproximateNumberOfMessagesNotVisible`を監視するalermを設定している

```typescript
import cw = require('@aws-cdk/aws-cloudwatch');
import sqs = require('@aws-cdk/aws-sqs');
import { Duration } from '@aws-cdk/core';

const queue = new sqs.Queue(this, 'MyQueue');

const metric = queue.metricApproximateNumberOfMessagesNotVisible({
  label: 'Messages Visible (Approx)',
  period: Duration.minutes(5),
  // ...
});
metric.createAlarm(this, 'TooManyMessagesAlarm', {
  comparisonOperator: cw.ComparisonOperator.GreaterThan,
  threshold: 100,
  // ...
});
```

#### Network Traffic

ACLの設定とか。

#### Amazon CloudWatch Events

* イベントソースのresouceで`addXxxNotification`を使ってイベントリスナーを登録すると、イベントを発行できる

```typescript
Import targets = require('@aws-cdk/aws-events-targets');
const handler = new lambda.Function(this, 'Handler', { /*…*/ });
const bucket = new s3.Bucket(this, 'Bucket');
bucket.addObjectCreatedNotification(new targets.LambdaFunction(handler));
```

### Identifiers

* CDKでは様々なidentifiersを用いており、それらの種類を知っておく必要がある
* identifierはそれらが存在するスコープ内で一意であればよい
    * グローバルスコープで一意である必要はない
    * 然るべきスコープ内で重複すると、CDKはエラーを出す

#### Construct IDs

* CDKにおいて最も一般的なidentifier
* constructをインスタンス化する時の２番目の引数
    * この最初の引数になるconstructのスコープ内でユニークであればOK
* 以下の例では、`Stack1`と`Stack2`の２つのスタック内でそれぞれ`MyBucket`が作成されている

```typescript
import { App, Construct, Stack, StackProps } from '@aws-cdk/cdk';
import s3 = require('@aws-cdk/aws-s3');

class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    new s3.Bucket(this, 'MyBucket');
  }
}

const app = new App();
new MyStack(app, 'Stack1');
new MyStack(app, 'Stack2');
```

#### Paths

* construct treeにおける、リソースのpath
* `/`で区切られた文字列で表現される
    * 例
        * `Stack1/MyBucket`
        * `Stack2/MyBucket`
* CDKのプログラムにおいては、下記のようにpathを取得できる
    * CDKアプリにおいて、pathは常にユニークになる

```typescript
const path: string = myConstruct.node.path;
```

#### Unique IDs

* pathを連結し、8-digit hashを加えた文字列
* 下記の要領で、全てのconstructのunique idを取得できる
* CDKによって生成されるCFnテンプレートにおける論理名になる


```typescript
const uid: string = myConstruct.node.uniqueId;
```

#### Logical IDs

* 前述のUnique IDが、CFnの_logical identifiers_として使われている
* CFnが管理しているリソースはこのLogical IDで識別している
    * そのため、CDKによって生成されたIDが同一アカウント・リージョン内で重複すると、他のデプロイのリソースを破壊する可能性が生じる
    * 故に、Logical IDを変更することは極力避けなければならない

### Tokens

* CDKのコード上では扱われるものの、CFnスタックのdeploy時まで未定となる各種値のプレースホルダの文字列を_tokens_と呼ぶ
* 下記の要領で確認できる

```typescript
${TOKEN[Bucket.Name.1234]}
```

* 例えば、下記のコードにおいて`bucket.bucketName`はdeploy時まで確定できない
* そのため、内部的には_tokens_を使っている

```typescript
const bucket = new s3.Bucket(this, 'MyBucket');

const fn = new lambda.Function(stack, 'MyLambda', {
  // ...
  environment: {
    BUCKET_NAME: bucket.bucketName,
  }
});
```
* メタプログラミング感が出てくるので、これを直接使って凝ったことはしない方がよさそう

### Tagging

* `Tag`クラスが２つのメソッドを持っていて、コレで付与と削除を行う
    * `Tag.add()`: constructとその子供の全てにタグを付与する
    * `Tag.remove()`: constructとその子供全てに付与されている指定のタグを全て除去する
* Taggingは[Aspect](https://docs.aws.amazon.com/cdk/latest/guide/aspects.html)と呼ばれる特定のスコープに対する操作の方法を用いて実現されている

```typescript
Tag.add(myConstruct, 'key', 'value');
Tag.remove(myConstruct, 'key');
```

#### Tag.add()

```typescript
Tag.add(myConstruct, 'tagname', 'value', {
  applyToLaunchedInstances: false,
  includeResourceTypes: ['AWS::Xxx::Yyy'],
  excludeResourceTypes: ['AWS::Xxx::Zzz'],
  priority: 100,
});
```
上記の例におけるその他オプション

* applyToLaunchedInstances
    * オートスケーリング機能等によって自動的に作成されたリソースにも同様のタグを振るかどうか
    * デフォルトは`true`なので、これを`false`にすると、前述の挙動が止まる
* includeResourceTypes/excludeResourceTypes
    * 特定タイプのリソースにのみ、タグ付与・削除を行う場合に使う
* priority
    * priorityが同じタグ付が指定された場合、construct treeの葉に近い方から優先される

`Tag.remove()`もほぼ同様。

### Assets

* _Assets_とは、CDK appにバンドルするファイル、ディレクトリ、Dockerイメージ等のこと
    * 例えばLambdaのハンドラ関数もこれに含まれる
    * CDKのコードにおいては、この_Assets_をconstructsから参照できる
* CDKは各assetsについてsource hashを生成していて、変更があったか否かを確認して、必要があればデプロイする
* CDK CLIによってデプロイ時に指定されたパラメータ等もassetsとして扱われる

#### Amazon S3 Assets

* [aws-s3-assets](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-s3-assets-readme.html)モジュールを用いて、ローカルにあるファイルやディレクトリをCDKがS3にアップロードできる

```typescript
import { Asset } from '@aws-cdk/aws-s3-assets';

// Archived and uploaded to Amazon S3 as a .zip file
const directoryAsset = new Asset(this, "SampleZippedDirAsset", {
  path: path.join(__dirname, "sample-asset-directory")
});

// Uploaded to Amazon S3 as-is
const fileAsset = new Asset(this, 'SampleSingleFileAsset', {
  path: path.join(__dirname, 'file-asset.txt')
});
```

* Lambdaのハンドラ関数もS3 Assetsとして扱われる
* S3 assetsはデプロイ時にURLや実際のオブジェクト名を解決してCDKコード内で参照できる

```typescript
const imageAsset = new Asset(this, "SampleAsset", {
  path: path.join(__dirname, "images/my-image.png")
});

new lambda.Function(this, "myLambdaFunction", {
  code: lambda.Code.asset(path.join(__dirname, "handler")),
  runtime: lambda.Runtime.PYTHON_3_6,
  handler: "index.lambda_handler",
  environment: {
    'S3_BUCKET_NAME': imageAsset.s3BucketName,
    'S3_OBJECT_KEY': imageAsset.s3ObjectKey,
    'S3_URL': imageAsset.s3Url
  }
});
```

* grantメソッドでassetsに対するIAMパーミッションを設定することもできる

```typescript
const asset = new Asset(this, 'MyFile', {
  path: path.join(__dirname, 'my-image.png')
});

const group = new iam.Group(this, 'MyUserGroup');
asset.grantRead(group);
```

#### Docker Image Assets

* CDKは[aws-ecr-assets](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ecr-assets-readme.html)プラグインを使うことで、Dockerイメージをassetsとして扱うことができる
* 下記のコードで、ローカルビルド（Dockerファイル等を配置しておく）したイメージをECRにアップロードする
    * `my-image`ディレクトリにDockerfileを配置する

```typescript
import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets';

const asset = new DockerImageAsset(this, 'MyBuildImage', {
  directory: path.join(__dirname, 'my-image')
});
```

* 下記の要領で、ローカルビルドしたECRリポジトリのイメージをFargateで使う

```typescript
import ecs = require('@aws-cdk/aws-ecs');
import path = require('path');
import { DockerImageAsset } from '@aws-cdk/aws-ecr-assets';

const asset = new DockerImageAsset(this, 'my-image', {
  directory: path.join(__dirname, "..", "demo-image")
});

const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDef", {
  memoryLimitMiB: 1024,
  cpu: 512
});

taskDefinition.addContainer("my-other-container", {
  // 下記のコードも可
  // image: ecs.ContainerImage.fromAsset(path.join(__dirname, "..", "demo-image"))
  image: ecs.ContainerImage.fromEcrRepository(asset.repository, asset.imageUri)
});
```

### Permissions

* AWS Construct Libraryでよく用いられるイディオムがある
* IAMモジュールにそれらが実装されている

#### Grants

* 他からのアクセスを受け付けるタイプのリソースには、アクセスを許可するgrantメソッドが用意されている
    * S3 bucketとか DynamoDB tableとか
    * 名前も大概決まってる
        * grantRead
        * grantReadWrite とか
* grantを受けるオブジェクトは[IGrantable](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.IGrantable.html)を実装している必要がある
    * IAM RoleとかUserとかGroupなんかが該当
    * 他にもgrantできるオブジェクトもある（後述）

#### Roles

* `Role`オブジェクトには`assumedBy`プロパティでService Principalを設定する

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

const role = new iam.Role(this, 'Role', {
  assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),   // required
});
```

* `addToPolicy`メソッドでポリシーを追加する
* `PolicyStatement`のインスタンスを渡す

```typescript
role.addToPolicy(new iam.PolicyStatement({
  effect: iam.Effect.DENY,
  resources: [bucket.bucketArn, otherRole.roleArn],
  actions: ['ec2:SomeAction', 's3:AnotherAction'],
  conditions: {StringEquals: {
    'ec2:AuthorizedService': 'codebuild.amazonaws.com',
}}}));
```

* IAM Roleを設定しないでインスタンスを作ると、自動的にRoleを生成して使ってくれるサービスもある

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

// imagine roleOrUndefined is a function that might return a Role object
// under some conditions, and undefined under other conditions
const someRole: iam.IRole | undefined = roleOrUndefined();

const project = new codebuild.Project(this, 'Project', {
  // if someRole is undefined, the Project creates a new default role, 
  // trusting the codebuild.amazonaws.com service principal
  role: someRole,
});
```

* ただし、明示的にRoleを設定しない場合には、外部からimportされた場合、属性値としてRoleを参照された時に何も返さないので注意する
* なるべく明示的に指定する

```typescript
// project is imported into the CDK application
const project = codebuild.Project.fromProjectName(this, 'Project', 'ProjectName');

// project is imported, so project.role is undefined, and this call has no effect
project.addToRolePolicy(new iam.PolicyStatement({
  effect: iam.Effect.ALLOW,   // ... and so on defining the policy
}));
```

#### Resource Policies

* Resource Policyとして、[Policy Statement](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.PolicyStatement.html)を設定する

```typescript
bucket.addToResourcePolicy(new iam.PolicyStatement({
  effect: iam.Effect.ALLOW,
  actions: ['s3:SomeAction'],
  resources: [bucket.bucketArn],
  principals: [role]
}));
```

### Context

#### Construct Context

* CDKは、AMIやAZ等、CDKを前回デプロイした時の各種パラメータを`cdk.context.json`に保持している
* それらのアップデートがあった後のCDKの実行時に意図しない変更、アップデートが入らないようにしている
* CDKユーザーは、主に3つの方法でContextを指定できる
    * CDKが自動で確認、収集する
    * CDK CLIを`--context`オプション付で実行する
    * CDKのコード中で`construct.node.setContext`メソッドを使う
* CDKのコード中では`construct.node.tryGetContext`で値を取得できる

#### Viewing and Managing Context

* `cdk context`コマンドで、コンテキストの内容を確認・管理できる
* `cdk context --reset`で、コンテキストを削除できる
    * `cdk context --clear` で、全てのコンテキストを削除
* これらの内容を最新にしたければ、`cdk synth`コマンドを使う

### Aspects

* _Aspects_は、オペレーションの範囲を設定したスコープ内に限定する方法
* 色々範囲は設定できる
    * constructs
    * tag
    * constructsの状態 など
* 以下の要領で指定する

```typescript
myConstruct.node.applyAspect(new SomeAspect(...));
```

* これを実行すると、construct内部のリストにaspectが追加され、任意の順番で実行される。
* エラーハンドリングなんかに使うと便利そう

```typescript
class BucketVersioningChecker implements IAspect {
  public visit(node: IConstruct): void {
    // See that we're dealing with a CfnBucket
    if (node instanceof s3.CfnBucket) {

      // Check for versioning property, exclude the case where the property
      // can be a token (IResolvable).
      if (!node.versioningConfiguration 
        || (!Tokenization.isResolvable(node.versioningConfiguration)
            && node.versioningConfiguration.status !== 'Enabled')) {
        
        node.node.addError('Bucket versioning is not enabled');
      }
    }
  }
}

// Apply to the stack
stack.node.applyAspect(new BucketVersioningChecker());
```

### Escape Hatches

[逃げ手についての補足](https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html)。機能が足りない、痒いところに手が届かなかった時は・・・

* CFn Constructsを直接使う
* AWS Constructs内のCFn Resourceを直接修正する
* CFｎテンプレートと実際のリソースに不整合が生じた時等に、それらを無視する形でとりあえず同期・削除等操作できる
* 足りないリソースをカスタムリソースとして作る

あるいは、今後この辺りの仕様も充実してくるのかもしれない。

## ポイントとかやってみて思うこと

あとで書く。