Amazon Elastic Container Service (Amazon ECS) と AWS CDK で起動テンプレートを利用した Capacity Providers を設定する

小ネタです。

CDK の `AutoScalingGroup` は、[起動テンプレートに対応しておらず](https://github.com/aws/aws-cdk/issues/6734)、現時点 (2021/12)　では起動設定を利用します。

ECS の Capacity Providers を CDK で設定する場合、以下のように `AutoScalingGroup` を利用するので、普通にやると起動設定が利用されるわけですね。

```typescript
  private addCapacity(capacity: number) {
    
    // 起動設定で ASG が作成される
    const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'GeneralCapacityASG', {
      vpc: this.cluster.vpc,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
      machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
      minCapacity: 0,
      maxCapacity: capacity,
    });
    
    const capacityProvider = new ecs.AsgCapacityProvider(this, 'GeneralCapacityProvider', {
      autoScalingGroup,
    });
    this.cluster.addAsgCapacityProvider(capacityProvider);
  }
```

ここで、せっかくなので起動テンプレートを利用して、一つの Capacity Provider で オンデマンドとスポットインスタンスを混ぜたり、複数のインスタンスタイプを混ぜたりしてみたいと思います。

元ネタは、以下のソースの合わせ技です。

https://github.com/aws/aws-cdk/issues/6734
https://github.com/aws-containers/ecsdemo-platform/blob/master/cdk/app.py

```typescript
  private addMixedCapacity(capacity: number) {
    const instanceTypes = [
      ec2.InstanceType.of(ec2.InstanceClass.M6I, ec2.InstanceSize.LARGE),
      ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
      ec2.InstanceType.of(ec2.InstanceClass.M5A, ec2.InstanceSize.LARGE),
      ec2.InstanceType.of(ec2.InstanceClass.M4, ec2.InstanceSize.LARGE),
    ];

    // ECS のコンテナインスタンスに必要異なロール、 UserData の設定
    const launchTemplateRole = new iam.Role(this, 'LaunchTemplateRole', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonEC2ContainerServiceforEC2Role'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
      ]
    });
    const launchTemplateUserData = ec2.UserData.forLinux();
    launchTemplateUserData.addCommands(`echo ECS_CLUSTER=${this.node.tryGetContext('clusterName')} >> /etc/ecs/ecs.config`);

    // 起動テンプレートの作成
    const launchTemplate = new ec2.LaunchTemplate(this, 'LaunchTemplate', {
      machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
      role: launchTemplateRole,
      userData: launchTemplateUserData,
    });

    // AutoScaling Group　の作成
    const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'MixedCapacityASG', {
      vpc: this.cluster.vpc,
      instanceType: instanceTypes[0],
      machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
      minCapacity: 0,
      maxCapacity: capacity,
    });

    // 下位レイヤーの CloudFormation 設定を取得し、起動設定を起動テンプレートに置き換える
    const cfnAsg = autoScalingGroup.node.defaultChild as autoscaling.CfnAutoScalingGroup;
    autoScalingGroup.node.tryRemoveChild('LaunchConfig');
    cfnAsg.launchConfigurationName = undefined;
    
    const cfnLaunchTemplate = launchTemplate.node.defaultChild as ec2.CfnLaunchTemplate;
    cfnAsg.mixedInstancesPolicy = {
      instancesDistribution: {
        onDemandBaseCapacity: 1,
        onDemandPercentageAboveBaseCapacity: 0,
        spotAllocationStrategy: 'capacity-optimized',
      },
      launchTemplate: {
        launchTemplateSpecification: {
          launchTemplateId: cfnLaunchTemplate.ref,
          version: cfnLaunchTemplate.attrLatestVersionNumber,
        },
        overrides: instanceTypes.map(instanceType => {
          return {
            instanceType: instanceType.toString()
          }
        }),
      },
    };

    const capacityProvider = new ecs.AsgCapacityProvider(this, 'MixedCapacityProvider', {
      autoScalingGroup
    });
    this.cluster.addAsgCapacityProvider(capacityProvider);
  }
```

以上、ちょっとした小ネタでした〜