constructor()

in lib/fis-example-stack.ts [10:247]


  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = new ec2.Vpc(this, 'vpc', {
      cidr: '10.0.0.0/16'
    });

    const ssmRole = new iam.Role(this, 'ssm-instance-role', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')]
    });
    const instances = [];

    for(let i=0; i<6; i++) {
      // didnt find a way to automatically balance between AZs
      const spreadSubnet = vpc.privateSubnets[i % 2 == 0 ? 0:1];
      const instance = new ec2.Instance(this, `instance-${i}`, {
        vpc: vpc,
        instanceType: ec2.InstanceType.of( ec2.InstanceClass.BURSTABLE3_AMD, ec2.InstanceSize.NANO),
        machineImage: ec2.MachineImage.latestAmazonLinux({generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2} ),
        vpcSubnets: { subnets: [spreadSubnet] },
        role: ssmRole
      });
      cdk.Tags.of(instance).add('FIS-Target', 'true');
      instances.push(instance);
    }

    const targetInstance = instances[0];
    const targetInstanceId = targetInstance.instanceId;

    const alarm = new cw.Alarm(this, 'cw-alarm', {
      alarmName: 'NetworkInAbnormal',
      metric: new cw.Metric({
        metricName: 'NetworkIn',
        namespace: 'AWS/EC2',
      }).with( {
        period: cdk.Duration.seconds(60)
      }),
      threshold: 10,
      evaluationPeriods: 1,
      treatMissingData: cw.TreatMissingData.NOT_BREACHING,
      comparisonOperator: cw.ComparisonOperator.LESS_THAN_THRESHOLD,
      datapointsToAlarm: 1,

    })

    const role = new iam.Role(this, 'fis-role', {
      managedPolicies: [
          // TODO restrict to ec2:StartInstances and ec2:StopInstances
          iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2FullAccess'),
      ],
      assumedBy: new iam.ServicePrincipal('fis.amazonaws.com'),
    });

    role.addToPolicy( new iam.PolicyStatement({
      resources: ['*'],
      actions: ['ssm:SendCommand', 'ssm:ListCommands', 'ssm:CancelCommands'],
    }))

    const action = {
      actionId: 'aws:ec2:stop-instances',
      parameters: { startInstancesAfterDuration: 'PT1M' },
      targets: { Instances: 'instanceTargets'}
    }

    const cpuStressAction = {
      actionId: 'aws:ssm:send-command',
      description: 'burn cpu vis SSM',
      parameters: {
        documentArn: `arn:aws:ssm:${this.region}::document/AWSFIS-Run-CPU-Stress`,
        documentParameters: JSON.stringify({ DurationSeconds: '120'} ),
        duration: 'PT2M'
      },
      targets: { Instances: 'instanceTargets' }
    }

    const target: fis.CfnExperimentTemplate.ExperimentTemplateTargetProperty = {
      resourceType: 'aws:ec2:instance',
      selectionMode: 'ALL',
      resourceArns:
         [
            `arn:aws:ec2:${this.region}:${this.account}:instance/${targetInstanceId}`
        ]
    }

    const template = new fis.CfnExperimentTemplate(this,'fis-template-demo-stop-instance', {
      description: 'Demo for Stopping and Starting a single instance via instance id',
      roleArn: role.roleArn,
      stopConditions: [
        { source: 'none' }
      ],
      tags: { Name: 'StopStartInstance'},
      actions: {
        'instanceActions' : action
      },
      targets: {
        'instanceTargets': target
      }
    });

    const templateWithAlarm = new fis.CfnExperimentTemplate(this,'fis-template-demo-stop-instance-with-alarm', {
      description: 'Demo for Stopping and Starting a single instance via instance id and abort by alarm',
      roleArn: role.roleArn,
      stopConditions: [{
        source: 'aws:cloudwatch:alarm',
        value: alarm.alarmArn
      }],
      tags: { Name: 'AbortExperimentByAlarm'},
      actions: {
        'instanceActions' : {
          ...action,
          parameters: {startInstancesAfterDuration: 'PT10M'},
        }
      },
      targets: {
        'instanceTargets': target
      }
    });

    const templateWithAzFilter = new fis.CfnExperimentTemplate(this,'fis-template-demo-stop-instances-in-az', {
      description: 'Demo for Stopping and Starting all instances in an AZ',
      roleArn: role.roleArn,
      stopConditions: [
        { source: 'none' }
      ],
      tags: { Name: 'StopInstancesInAz'},
      actions: {
        'instanceActions' : action
      },
      targets: {
        'instanceTargets': {
          resourceType: 'aws:ec2:instance',
          selectionMode: 'ALL',
          resourceTags: {
            'FIS-Target': 'true'
          },
          filters:  [
              {
                path:'Placement.AvailabilityZone',
                values: [ targetInstance.instanceAvailabilityZone ]
              },
              {
                path:'State.Name',
                values: [ 'running' ]
              }
            ]

        }
      }
    });

    const templateCpuStress = new fis.CfnExperimentTemplate(this,'fis-template-demo-cpu-stress', {
      description: 'Demo for injecting CPU stress via SSM',
      roleArn: role.roleArn,
      stopConditions: [
        { source: 'none' }
      ],
      tags: { Name: 'BurnCPUViaSSM'},
      actions: {
        'instanceActions' : cpuStressAction
      },
      targets: {
        'instanceTargets': target
      }
    });

    const templateStopEksWorker = new fis.CfnExperimentTemplate(this,'fis-template-demo-stop-eks-node', {
      description: 'Demo for terminating an eks worker',
      roleArn: role.roleArn,
      stopConditions: [
        { source: 'none' }
      ],
      tags: { Name: 'Terminate EKS Worker'},
      actions: {
        'instanceActions' : {
          actionId: 'aws:eks:terminate-nodegroup-instances',
          description: 'Terminate EKS NodeGroup Instance',
          parameters: {
            instanceTerminationPercentage: "50"
          },
          targets: { Nodegroups: 'nodeGroupTarget' }
        }
      },
      targets: {
        'nodeGroupTarget': {
          resourceType: 'aws:eks:nodegroup',
          selectionMode: 'ALL',
          // TODO make this configurable via either CDK's context or CfnParameter
          resourceTags: {
            'eksctl.cluster.k8s.io/v1alpha1/cluster-name': 'eks-demo'
          }
        }
      }
    });

    const killContainerScript = 'sudo docker kill $(sudo docker ps -f name=k8s_ecsdemo-frontend -q)';
    const killContainerAction = {
      actionId: 'aws:ssm:send-command',
      description: 'Kill the frontend container',
      parameters: {
        documentArn: `arn:aws:ssm:${this.region}::document/AWS-RunShellScript`,
        documentParameters: JSON.stringify(
            { commands: killContainerScript} ),
        duration: 'PT1M'
      },
      targets: { Instances: 'workerNodesTarget' }
    }

    const workerNodesTarget: fis.CfnExperimentTemplate.ExperimentTemplateTargetProperty = {
      resourceType: 'aws:ec2:instance',
      resourceTags: {
        // TODO make this configurable via CDK's context or CfnParameter
        Name: 'eks-demo-nodegroup-Node'
      },
      selectionMode: 'ALL',
      filters: [
        {
          path:'State.Name',
          values: [ 'running' ]
        }
      ]
    }

    const templateKillContainer = new fis.CfnExperimentTemplate(this,'fis-template-demo-kill-container', {
      description: 'Demo for killing a docker container on an EKS worker',
      roleArn: role.roleArn,
      stopConditions: [
        { source: 'none' }
      ],
      tags: { Name: 'Kill Container on EKS Worker'},
      actions: {
        'instanceActions' : killContainerAction
      },
      targets: {
        'workerNodesTarget': workerNodesTarget
      }
    });
  }