AWS CDK(version 1.x)で個人的によく使う機能まとめ

## 概要

AWS CDKを業務で使いはじめて半年くらい経ったので、便利だなと思った機能を備忘録を兼ねてまとめました。以下のような方が対象です。

- TypeScriptでAWS CDKをもっと便利に使いたい方
- CloudFormationから移行した方

## CloudFormationのパラメーターを使う

CloudFormationなどでテンプレートの引数を使用したいときに使うParametersはAWS CDKでも使えます。

### TypeScript

```typescript
import * as cdk from '@aws-cdk/core';
import * as ecs from '@aws-cdk/aws-ecs';

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

    // Parameterを宣言
    const dockerImageName = new cdk.CfnParameter(this, "DockerImageName", {
      type: "String",
      default: "hello-world:latest",
    });
    const EnvironmentName = new cdk.CfnParameter(this, "EnvironmentName", {
      type: "String",
      default: "staging",
    });
      
    // 宣言したParameterを使う
    const dockerImage = ecs.ContainerImage.fromRegistry(dockerImageName.valueAsString);
  }
}
```

### コマンド

```bash
# デプロイ
cdk deploy \
  --parameters DockerImageName=nginx:latest \
  --parameters EnvironmentName=production
```

## クロススタック参照する

別スタックで宣言したリソースを参照して使いたいことが度々あります。そのときに使うのがクロススタック参照です。

`ImportValue`はCloudFormationの関数の一つですが、AWS CDKではCloudFormationの関数が使えるようになっています。`core`モジュール以下に`Fn`クラスが定義されており、その中に様々なCloudFormationの関数が定義されています。

### TypeScript

```typescript
import * as cdk from '@aws-cdk/core';
import * as sns from '@aws-cdk/aws-sns';
import { Fn } from '@aws-cdk/core';

// 参照元のスタック定義
export class SourceStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    
	const snsTopic = new sns.Topic(this, 'sns-topic', {
      topicName: 'test-sns-topic',
    });

    // Exportを使用してCloudFormation内で参照できるようにします
	this.exportValue(
      snsTopic.topicArn,
      {
      	name: 'test-sns-topic'
      }
    );
  }
}

// テスト用スタック
export class TestStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
	
    // 外部のスタックのリソースを参照する
    const snsTopicArn = Fn.importValue('test-sns-topic');
    // 取得したARNを使ってCloudWatchAlarmのアクションを定義
    errorAlarm.addAlarmAction(new cloudwatchActions.SnsAction(
      sns.Topic.fromTopicArn(
        this, 'cloudwatch-alarm', snsTopicArn
      )
    ));
  }
}
```

## 外部スタックに作成したVPCをクロススタック参照する

先程紹介したクロススタック参照ですが、AWS CDKではVPCをはじめとした一部のリソースを参照できないことがあります。(出来ないときには `cdk deploy` 実行時にエラーが発生します)

このようなときには、`Fn.ImportValue`でVpcIdを参照したうえで、一部リソースに定義されている`from~~~`系のメソッドを使って、参照先でインスタンスを定義します。

```typescript
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import { Fn } from '@aws-cdk/core';

// 参照元のスタック定義
export class SourceStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    
    // VPCを宣言します
	const vpc = new ec2.Vpc(this, 'Vpc', {
      cidr: "10.0.0.0/16",
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 0,
      defaultInstanceTenancy: ec2.DefaultInstanceTenancy.DEFAULT,
      subnetConfiguration: [
         {
           cidrMask: 24,
           name: 'test-subnet',
           subnetType: ec2.SubnetType.PUBLIC,
         },
      ]
    });

    // Exportを使用してCloudFormation内で参照できるようにします
	this.exportValue(
      vpc.vpcId,
      {
      	name: 'test-vpc'
      }
    );
  }
}

// テスト用スタック
export class TestStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
	
    // importValueを使ってVpcIDを参照して
    // さらにfromVpcAttributesを使うことで、あたかもVpcクラスのインスタンスかのように使えます
	const vpc = ec2.Vpc.fromVpcAttributes(this, 'vpc', {
      vpcId: Fn.importValue('test-vpc'),
      availabilityZones: [
        'ap-northeast-1a',
        'ap-northeast-1c',
      ],
      publicSubnetIds: [
        'test-1',
        'test-2',
      ]
    });
  }
}
```

## CDKで定義したリソースにまとめてタグをつける

リソースの管理を楽にするためにAWSにはタグ機能が用意されていますが、AWS CDKではまとめてタグをつける機能があります。

```typescript
import * as cdk from '@aws-cdk/core';
import * as s3 from '@aws-cdk/aws-s3';

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

    // S3バケットを宣言
	const s3Bucket = new s3.Bucket(this, makeRealmResourceName('s3'), {
      bucketName: makeRealmResourceName('bucket'),
    });
 
  	// このスタック内で定義したリソースすべてにタグを付けられます
  	cdk.Tags.of(this).add('Service', 'test-service');
  	cdk.Tags.of(this).add('Environment', 'staging');
  }
}

```

## おわりに

先日AWS CDKのversion2もGAとなり、さらに便利になってきましたね。引き続き便利な機能はこういう形で共有していきたいと思います。

個人的にはテストをあまり活用できていないので、そのあたりも今後使ってみたいなと思っています。