AWS CDK構築備忘録②(ALB+ECS+Aurora)

# 内容
下記の構成をCDKで構築します。

![ecs01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523652/a025ef11-b1d5-bee0-d970-39d86994c993.png)

* VPC
VPC、サブネットを作成します。作成した情報を他のスタックからクロススタック参照出来るようにします。

* Route53
ECSスタックの中で既存ホストゾーンにALBのAレコード(Alias)を追加しています。

* ALB,ECS
aws-ecs-patternsというモジュールがあります。こちらは一般的な構成パラメータがデフォルト値となっており、少ないパラメータでECSを構築することが可能となります。こちらをベースにコードをカスタマイズしていきます。ALBもこの中で作成されます。nginxのイメージを使用します。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/tutorial-ecs-web-server-cdk.html

上記の通り、CloudFormationで600行程度のYAMLを数行で書くことが可能です。

* Aurora,Secret Manger
VPCスタックの情報を使用してサブネットグループ、セキュリティグループを作成します。
シングル構成のAuroraを作成します。デフォルトではパスワードはSecret Managerに保存されます。なおFargate、Aurora間の接続確認は未実施です。

# 準備
```powershell
mkdir app01
cd app01
cdk init --language typescript
```
# エントリポイント
```typescript:bin/app01.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { VpcStack } from '../lib/vpc-stack';
import { EcsStack } from '../lib/ecs-stack';
import { RdsStack } from '../lib/rds-stack';


const app = new cdk.App();
const vpcStack = new VpcStack(app, 'VpcStack', {
    env: {
        account: 'AWS_ACCOUNT',
        region: 'ap-northeast-1',
    },
});

new EcsStack(app, 'EcsStack', {

    //変数を渡す
    vpc: vpcStack.vpc,
    env: {
        account: 'AWS_ACCOUNT',
        region: 'ap-northeast-1',
    },
});

new RdsStack(app, 'RdsStack', {

    //変数を渡す
    vpc: vpcStack.vpc,
    env: {
        account: 'AWS_ACCOUNT',
        region: 'ap-northeast-1',
    },
});
```

# VPCスタック
```typescript:lib/vpc-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Vpc } from 'aws-cdk-lib/aws-ec2';

export class VpcStack extends Stack {

  //別スタックから参照できるようにする
  public readonly vpc: Vpc;
  
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const vpc_name = 'cdk-vpc';
    const cidr = '10.1.0.0/16';

    this.vpc = new ec2.Vpc(this, 'VPC', {

      vpcName: vpc_name,
      cidr: cidr,
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: `${vpc_name}-public`,
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: `${vpc_name}-private`,
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ]
    })
  }
};
```

# ECSスタック
```typescript:lib/ecs-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Vpc } from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsp from 'aws-cdk-lib/aws-ecs-patterns';
import * as route53 from 'aws-cdk-lib/aws-route53';

//VpcStackの変数を扱えるようにする
interface SubStackProps extends StackProps {
  vpc: Vpc;
};

export class EcsStack extends Stack {

  //SubStackPropsを使用する
  constructor(scope: Construct, id: string, props: SubStackProps) {
    super(scope, id, props);
    
    //変数設定
    const ecs_vpc = props.vpc;
    const domain_name = 'DOMAIN_NAME'

    //Domain名設定
    const domainZone = route53.HostedZone.fromLookup(this, 'Zone', {
      domainName: 'DOMAIN_ZONE'
    });

    //Cluster設定
    const cluster = new ecs.Cluster(this, 'Cluster', {
      clusterName: 'WebApp01-Cluster',
      vpc: ecs_vpc,
    })

    //ELB+Fargate設定
    new ecsp.ApplicationLoadBalancedFargateService(this, 'WebApp01', {
      cluster,
      domainName: domain_name,
      domainZone,
      memoryLimitMiB: 512,
      desiredCount: 2,
      cpu: 256,
      assignPublicIp: true,
      loadBalancerName: 'WebApp01-lb01',
      publicLoadBalancer: true,
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry('nginx'),
      },
    });
  }
};
```

# RDSスタック
```typescript:lib/rds-stack.ts
import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Peer, Port, SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

//VpcStackの変数を扱えるようにする
interface SubStackProps extends StackProps {
  vpc: Vpc;
};

export class RdsStack extends Stack {
  constructor(scope: Construct, id: string, props: SubStackProps) {
    super(scope, id, props);

    //VPC取得　
    const vpc = props.vpc;

    //Subnet取得
    const private_subnet = vpc.selectSubnets({
      subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    }).subnets

    const public_subnet = vpc.selectSubnets({
      subnetType: ec2.SubnetType.PUBLIC,
    }).subnets

    //SubnetGroup作成
    const subnetGroup = new rds.SubnetGroup(this, 'SubnetGroup', {
      description: 'aurora subnet group for WebApp01',
      vpc: vpc,      
      vpcSubnets: {
        subnets: private_subnet,
      }
    });

    //SecurityGroup作成
    const secgroup01 = new SecurityGroup(this, 'SecGroup', {
      vpc: vpc,
    });

    //SecurityGroupにPublic SubnetからのIngressルール追加
    for (const elm of public_subnet) {
      secgroup01.addIngressRule(        
        Peer.ipv4(elm.ipv4CidrBlock),
        Port.tcp(3306),
      )};

    //RDS作成
    const cluster = new rds.DatabaseCluster(this, 'AuroraDB', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: rds.AuroraMysqlEngineVersion.VER_2_10_2
      }),
      credentials: rds.Credentials.fromGeneratedSecret('admin'),
      defaultDatabaseName: 'database1',
  
      instanceProps: {
        vpc,
        publiclyAccessible: false,
        instanceType: ec2.InstanceType.of(
          ec2.InstanceClass.BURSTABLE3,
          ec2.InstanceSize.SMALL),
          securityGroups: [secgroup01],        
        vpcSubnets: {
          subnets: private_subnet,
        },        
      },
      deletionProtection: false,
      iamAuthentication: true,
      instances: 1,
      preferredMaintenanceWindow: 'Sat:16:00-Sat:16:30',
      removalPolicy: RemovalPolicy.DESTROY,
      subnetGroup: subnetGroup,
    });
  }
}
```

# メモ
CFnテンプレートの情報を出力しない。（console.log()の確認等に使用）
```powershell
cdk synth --quiet
```