Fargate&ECS Execのテスト環境をCDKで作成

# 記事の背景
AWS ECS/Fargate上で稼働させているSpringBootアプリケーションのJVMの情報を監視するために、ECS　Execを使いたいと思っています。

手頃なECS環境がなかったため、試験用の環境を構築することにしました。

マネジメントコンソールやTerraformで作るのは面倒なので、CDKを使用しました。

# 参考
以下のサイトを参考にしています。

https://aws.amazon.com/jp/blogs/news/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/

https://qiita.com/cohey0727/items/c18ea73ee788752a1873


# ポイント

* ECS Execを使うためにTaskRoleにSSMの権限を付与しています。
* CFnのテンプレートを直接操作しています。（これは、ecsPatterns.ApplicationLoadBalancedFargateServiceで、EnableExecuteCommandができないためです。いつか、設定のメソッドやコンストラクターパラメータに追加されるでしょう）


```typescript:cdk-fargate-stack.ts
import * as cdk from "@aws-cdk/core";
import * as ec2 from "@aws-cdk/aws-ec2";
import * as ecs from "@aws-cdk/aws-ecs";
import * as iam from "@aws-cdk/aws-iam";
import * as ecsPatterns from "@aws-cdk/aws-ecs-patterns";
import { Duration } from "@aws-cdk/core";

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

    const vpc = new ec2.Vpc(this, "MyCdkVpc", {
      cidr: "10.x.x.x/16",
    });

    const cluster = new ecs.Cluster(this, "MyCdkCluster", {
      vpc,
    });

    // ECS Execを使うためにTaskRoleにSSMの権限を付与する
    const taskRole = new iam.Role(this, "MyCdkTaskRole", {
      assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
    });
    taskRole.addToPrincipalPolicy(
      new iam.PolicyStatement({
        actions: [
          "ssmmessages:CreateControlChannel",
          "ssmmessages:CreateDataChannel",
          "ssmmessages:OpenControlChannel",
          "ssmmessages:OpenDataChannel",
        ],
        resources: ["*"],
      })
    );

    const loadBalancedFargateService =
      new ecsPatterns.ApplicationLoadBalancedFargateService(
        this,
        "MyCdkService",
        {
          cluster: cluster, // Required
          memoryLimitMiB: 1024,
          cpu: 512,
          desiredCount: 1, // Optional(Default=3!)
          taskImageOptions: {
            image: ecs.ContainerImage.fromRegistry(
              "xxxx/spring-boot-docker"
            ),
            taskRole: taskRole,
          },
          healthCheckGracePeriod: Duration.seconds(240),
        }
      );

    loadBalancedFargateService.targetGroup.configureHealthCheck({
      path: "/custom-health-path",
      healthyThresholdCount: 2, // Optional
      interval: Duration.seconds(15), // Optional
    });

    this.enableExecuteCommand(loadBalancedFargateService);
  }

  // CFnのテンプレートを直接操作する
  enableExecuteCommand = (service: ecsPatterns.ApplicationLoadBalancedFargateService) => {
    service.node.children.filter(this.isFargateService).forEach((fargateService) => {
      fargateService.node.children.filter(this.isCfnService).forEach((cfnService) => {
        cfnService.addOverride("Properties.EnableExecuteCommand", true);
      });
    });
  };

  isFargateService = (cdkChild: any): cdkChild is ecs.FargateService => {
   return cdkChild instanceof ecs.FargateService;
  }

  isCfnService = (cdkChild:any): cdkChild is ecs.CfnService => {
    return cdkChild instanceof ecs.CfnService;
  }
}
```

# 所感

CDKはAPIドキュメントが充実しているので、それを読めばおおよそのことができるようになるとは思いますが、まだまだ自分自身のノウハウがたまっていないので、色々と試してみようと思いました。