constructor()

in cdk-stacks/lib/recording/recording-stack.ts [33:228]


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

        /** CREATE AN AMAZON VPC FOR THE AMAZON ECS CLUSTER **/
        const recordingVPC = new ec2.Vpc(this, 'RecordingVPC', {
            maxAzs: 2, //default is 3
            cidr: '10.5.0.0/16',
            subnetConfiguration: [
                {
                    cidrMask: 24,
                    name: `${props.cdkAppName}-private-`,
                    subnetType: ec2.SubnetType.PRIVATE
                },
                {
                    cidrMask: 24,
                    name: `${props.cdkAppName}-public-`,
                    subnetType: ec2.SubnetType.PUBLIC
                },
            ]

        });

        /** CREATE SECURITY GROUPS **/
        const recordingECSSecurityGroup = new ec2.SecurityGroup(this, 'RecordingECSSecurityGroup', {
            securityGroupName: `${props.cdkAppName}-RecordingECSSecurityGroup`,
            vpc: recordingVPC,
            description: `ECS Security Group for ${props.cdkAppName} Recording ECS Cluster`,
            allowAllOutbound: true
        });

        /** CREATE AN ECS CLUSTER **/
        const recordingECSCluster = new ecs.Cluster(this, 'RecordingECSCluster', {
            vpc: recordingVPC,
            clusterName: `${props.cdkAppName}-Recording`,
            containerInsights: true,
        });


        /** CREATE AN AUTOSCALING GROUP FOR ECS CLUSTER INSTANCES **/
        const recordingECSAutoScalingGroup = new autoscaling.AutoScalingGroup(this, "RecordingECSAutoScalingGroup", {
            vpc: recordingVPC,
            securityGroup: recordingECSSecurityGroup,
            allowAllOutbound: true,
            associatePublicIpAddress: false,
            instanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.XLARGE2),
            machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
            vpcSubnets: {
                subnetType: ec2.SubnetType.PRIVATE
            },
            minCapacity: 1,
            maxCapacity: 5,
            signals: autoscaling.Signals.waitForMinCapacity({
                timeout: cdk.Duration.minutes(15)
            }),
            updatePolicy: autoscaling.UpdatePolicy.replacingUpdate()
        });

        const recordingECSAutoScalingGroupCFN = recordingECSAutoScalingGroup.node.defaultChild as autoscaling.CfnAutoScalingGroup;
        const recordingECSAutoScalingGroupCFNLogicalId = "RecordingECSAutoScalingGroup";
        recordingECSAutoScalingGroupCFN.overrideLogicalId(recordingECSAutoScalingGroupCFNLogicalId); //LOGICAL ID FOR CFN SIGNAL

        recordingECSAutoScalingGroup.addUserData(
            `#!/bin/bash -xe`,
            `echo ECS_CLUSTER=${recordingECSCluster.clusterName} >> /etc/ecs/ecs.config`,
            `echo ECS_IMAGE_PULL_BEHAVIOR=prefer-cached >> /etc/ecs/ecs.config`,
            `yum install -y aws-cfn-bootstrap`,
            `/opt/aws/bin/cfn-signal -e $? --stack ${this.stackName} --resource ${recordingECSAutoScalingGroupCFNLogicalId} --region ${this.region}`
        );

        const recordingECSCapacityProvider = new ecs.AsgCapacityProvider(this, 'RecordingECSCapacityProvider', {
            autoScalingGroup: recordingECSAutoScalingGroup,
            enableManagedScaling: true,
            minimumScalingStepSize: 1,
            maximumScalingStepSize: 2,
            targetCapacityPercent: 60
        });

        recordingECSCluster.addAsgCapacityProvider(recordingECSCapacityProvider);


        /* DEFINE A TASK DEFINITION FOR ECS TASKS RUNNING IN THE CLUSTER */

        const recordingTaskDefinition = new ecs.TaskDefinition(this, "RecordingTaskDefinition", {
            compatibility: ecs.Compatibility.EC2,
            volumes: [{
                name: "dbus",
                host: {
                    sourcePath: "/run/dbus/system_bus_socket:/run/dbus/system_bus_socket"
                }
            }]
        });

        const recordingTaskLogGroup = new logs.LogGroup(this, "RecordingTaskLogGroup", {
            retention: logs.RetentionDays.TWO_MONTHS,
            logGroupName: `${props.cdkAppName}-RecordingTask`,
            removalPolicy: cdk.RemovalPolicy.DESTROY
        });

        // BUILD A DOCKER IMAGE AND UPLOAD IT TO AN ECR REPOSITORY

        const recordingDockerImage = new ecr_assets.DockerImageAsset(this, "RecordingDockerImage", {
            directory: path.join(__dirname, "../../docker/recording")
        });

        recordingDockerImage.repository.grantPull(recordingTaskDefinition.obtainExecutionRole()); //add permissions to pull ecr repository

        // CREATE A TASK DEFINITION
        const recordingContainerName = `${props.cdkAppName}-RecordingContainer`;
        recordingTaskDefinition.addContainer(recordingContainerName, {
            image: ecs.EcrImage.fromRegistry(recordingDockerImage.imageUri),
            cpu: 4096,
            memoryLimitMiB: 8192,
            memoryReservationMiB: 8192,
            linuxParameters: new ecs.LinuxParameters(this, "RecordingTaskLinuxParameters", { sharedMemorySize: 2048 }),
            essential: true,
            logging: ecs.LogDriver.awsLogs({
                logGroup: recordingTaskLogGroup,
                streamPrefix: `${props.cdkAppName}`
            })
        });

        //Amazon S3 bucket to store the call recordings
        const recordingBucket = new s3.Bucket(this, "RecordingBucket", {
            bucketName: `${props.cdkAppName}-RecordingBucket-${this.account}-${this.region}`.toLowerCase(),
            encryption: s3.BucketEncryption.KMS_MANAGED
        });

        recordingBucket.grantReadWrite(recordingTaskDefinition.taskRole);


        //pre-warm task
        const autoscalingEC2InstanceLaunchRule = new events.Rule(this, 'AutoscalingEC2InstanceLaunchRule', {
            description: `Rule triggered by recordingECSAutoScalingGroup when a new EC2 instance is launched`,
            eventPattern: {
                source: ['aws.autoscaling'],
                detailType: ['EC2 Instance Launch Successful'],
                detail: {
                    'AutoScalingGroupName': [recordingECSAutoScalingGroup.autoScalingGroupName]
                }
            }
        });

        const startRecordingPreWarmTaskLambda = new nodeLambda.NodejsFunction(this, 'StartRecordingPreWarmTaskLambda', {
            functionName: `${props.cdkAppName}-StartRecordingPreWarmTaskLambda`,
            runtime: lambda.Runtime.NODEJS_12_X,
            entry: 'lambdas/handlers/RecordingAPI/startRecordingPreWarmTask.js',
            timeout: cdk.Duration.seconds(20),
            environment: {
                ECS_CLUSTER_ARN: recordingECSCluster.clusterArn,
                ECS_CONTAINER_NAME: recordingContainerName,
                ECS_TASK_DEFINITION_ARN: recordingTaskDefinition.taskDefinitionArn,
                ECS_ASG_NAME: recordingECSAutoScalingGroup.autoScalingGroupName
            }
        });

        startRecordingPreWarmTaskLambda.role?.attachInlinePolicy(new iam.Policy(this, 'ECSListContainerInstancesAccess', {
            statements: [
                new iam.PolicyStatement({
                    effect: iam.Effect.ALLOW,
                    actions: ['ecs:ListContainerInstances'],
                    resources: [recordingECSCluster.clusterArn]
                })
            ]
        }));

        startRecordingPreWarmTaskLambda.role?.attachInlinePolicy(new iam.Policy(this, 'ECSStartTaskAccess', {
            statements: [
                new iam.PolicyStatement({
                    effect: iam.Effect.ALLOW,
                    actions: ['ecs:StartTask'],
                    resources: [recordingTaskDefinition.taskDefinitionArn]
                })
            ]
        }));

        startRecordingPreWarmTaskLambda.role?.attachInlinePolicy(new iam.Policy(this, 'IAMPassRoleAccess', {
            statements: [
                new iam.PolicyStatement({
                    effect: iam.Effect.ALLOW,
                    actions: ['iam:PassRole'],
                    resources: [recordingTaskDefinition.executionRole?.roleArn!]
                })
            ]
        }));

        autoscalingEC2InstanceLaunchRule.addTarget(new events_targets.LambdaFunction(startRecordingPreWarmTaskLambda));


        //outputs
        this.recordingECSClusterARN = recordingECSCluster.clusterArn;
        this.recordingECSClusterName = recordingECSCluster.clusterName;
        this.recordingContainerName = recordingContainerName;
        this.recordingTaskDefinitionARN = recordingTaskDefinition.taskDefinitionArn;
        this.recordingBucketName = recordingBucket.bucketName;
        this.recordingTaskDefinitionExecutionRoleARN = recordingTaskDefinition.executionRole?.roleArn!;
    }