예제 #1
0
        private void ConfigureService(Configuration settings)
        {
            if (EcsCluster == null)
            {
                throw new InvalidOperationException($"{nameof(EcsCluster)} has not been set. The {nameof(ConfigureCluster)} method should be called before {nameof(ConfigureService)}");
            }
            if (AppTaskDefinition == null)
            {
                throw new InvalidOperationException($"{nameof(AppTaskDefinition)} has not been set. The {nameof(ConfigureTaskDefinition)} method should be called before {nameof(ConfigureService)}");
            }

            var fargateServiceProps = new FargateServiceProps
            {
                Cluster        = EcsCluster,
                TaskDefinition = AppTaskDefinition,
                AssignPublicIp = settings.Vpc.IsDefault,
                DesiredCount   = settings.DesiredCount
            };


            if (!string.IsNullOrEmpty(settings.ECSServiceSecurityGroups))
            {
                var ecsServiceSecurityGroups = new List <ISecurityGroup>();
                var count = 1;
                foreach (var securityGroupId in settings.ECSServiceSecurityGroups.Split(','))
                {
                    ecsServiceSecurityGroups.Add(SecurityGroup.FromSecurityGroupId(this, $"AdditionalGroup-{count++}", securityGroupId.Trim(), new SecurityGroupImportOptions
                    {
                        Mutable = false
                    }));
                }

                fargateServiceProps.SecurityGroups = ecsServiceSecurityGroups.ToArray();
            }

            AppFargateService = new FargateService(this, nameof(AppFargateService), InvokeCustomizeCDKPropsEvent(nameof(AppFargateService), this, fargateServiceProps));
        }
예제 #2
0
        internal CreditoWebApiStack(Construct scope, string id, CustomStackProps props = null)
            : base(scope, id, props)
        {
            var vpc = props.Vpc;

            var creditoWebApiTargetGroup = new ApplicationTargetGroup(this, "CreditoWebApiTargetGroup",
                                                                      new ApplicationTargetGroupProps
            {
                Protocol            = ApplicationProtocol.HTTP,
                Port                = 80,
                Vpc                 = vpc,
                TargetType          = TargetType.IP,
                DeregistrationDelay = Duration.Seconds(60),
                HealthCheck         =
                    new Amazon.CDK.AWS.ElasticLoadBalancingV2.HealthCheck
                {
                    Enabled  = true,
                    Path     = "/api/credito/_monitor/shallow",
                    Protocol = Amazon.CDK.AWS.ElasticLoadBalancingV2.Protocol.HTTP,
                    Port     = "traffic-port",
                    UnhealthyThresholdCount = 2,
                    Interval = Duration.Seconds(60),
                    HealthyThresholdCount = 5,
                    Timeout          = Duration.Seconds(5),
                    HealthyHttpCodes = "200"
                }
            });

            var webApiServiceSecurityGroup = SecurityGroup.FromSecurityGroupId(this,
                                                                               "WebApiServiceSecurityGroup",
                                                                               Fn.ImportValue(Globals.GetDeployEnvironment(this).PutEnvNamePrefixWithDash("WebApiServiceSecurityGroupId")));

            var appListener = ApplicationListener.FromApplicationListenerAttributes(this, "AppListener",
                                                                                    new ApplicationListenerAttributes
            {
                ListenerArn   = Fn.ImportValue(Globals.GetDeployEnvironment(this).PutEnvNamePrefixWithDash("AppListenerArn")),
                SecurityGroup = webApiServiceSecurityGroup
            });

            appListener.AddTargetGroups("CreditoWebApiTargetGroup",
                                        new AddApplicationTargetGroupsProps
            {
                Conditions =
                    new ListenerCondition[]
                {
                    ListenerCondition.PathPatterns(new string[] { "/api/credito*" })
                },
                Priority     = 100,
                TargetGroups = new ApplicationTargetGroup[] { creditoWebApiTargetGroup }
            });

            var creditoWebApiLogGroup = new LogGroup(this, "CreditoWebApiContainerLogGroup",
                                                     new LogGroupProps
            {
                LogGroupName  = $"/ecs/{Globals.GetDeployEnvironment(this).EnvName}/credito/web-api",
                Retention     = RetentionDays.FIVE_DAYS,
                RemovalPolicy = RemovalPolicy.SNAPSHOT
            });

            var creditoWebApiTaskDefinition = new FargateTaskDefinition(this, "CreditoWebApiTaskDefinition",
                                                                        new FargateTaskDefinitionProps
            {
                MemoryLimitMiB = 512,
                Cpu            = 256
            });

            var creditoWebApiLogging = new AwsLogDriver(
                new AwsLogDriverProps
            {
                StreamPrefix = "ecs",
                LogGroup     = creditoWebApiLogGroup
            });

            var creditoWebApiContainer = creditoWebApiTaskDefinition.AddContainer("CreditoWebApiContainer",
                                                                                  new ContainerDefinitionOptions
            {
                Image = ContainerImage.FromAsset(
                    Directory.GetCurrentDirectory(),
                    new AssetImageProps
                {
                    File = "src/Credito.WebApi/Dockerfile"
                }),
                Logging     = creditoWebApiLogging,
                Environment =
                    new Dictionary <string, string>()
                {
                    ["CreditoDatabase__ConnectionString"] =
                        StringParameter.ValueFromLookup(
                            this,
                            $"/{Globals.GetDeployEnvironment(this).EnvName}/credito/web-api/db/connection-string"),
                    ["CreditoDatabase__DatabaseName"] =
                        StringParameter.ValueFromLookup(
                            this,
                            $"/{Globals.GetDeployEnvironment(this).EnvName}/credito/web-api/db/database-name")
                }
            });

            creditoWebApiContainer.AddPortMappings(
                new PortMapping
            {
                ContainerPort = 80,
                HostPort      = 80,
                Protocol      = Amazon.CDK.AWS.ECS.Protocol.TCP
            });

            var cluster = Cluster.FromClusterAttributes(this, "Cluster",
                                                        new ClusterAttributes
            {
                ClusterName    = Fn.ImportValue(Globals.GetDeployEnvironment(this).PutEnvNamePrefixWithDash("ClusterName")),
                Vpc            = vpc,
                SecurityGroups = new SecurityGroup[] { }
            });

            var creditoWebApiService = new FargateService(this, "CreditoWebApiService",
                                                          new FargateServiceProps
            {
                Cluster        = cluster,
                TaskDefinition = creditoWebApiTaskDefinition,
                DesiredCount   = 1,
                CircuitBreaker = new DeploymentCircuitBreaker {
                    Rollback = true
                },
                AssignPublicIp         = false,
                HealthCheckGracePeriod = Duration.Seconds(60),
                SecurityGroups         = new ISecurityGroup[] { webApiServiceSecurityGroup },
                VpcSubnets             = new SubnetSelection {
                    SubnetType = SubnetType.PRIVATE
                }
            });

            creditoWebApiService.AttachToApplicationTargetGroup(creditoWebApiTargetGroup);
        }
예제 #3
0
        internal CdkStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
        {
            var stackProps                = ReportStackProps.ParseOrDefault(props);
            var dframeWorkerLogGroup      = "MagicOnionBenchWorkerLogGroup";
            var dframeMasterLogGroup      = "MagicOnionBenchMasterLogGroup";
            var benchNetwork              = stackProps.GetBenchNetwork();
            var recreateMagicOnionTrigger = stackProps.GetBenchmarkServerBinariesHash();

            // s3
            var s3 = new Bucket(this, "Bucket", new BucketProps
            {
                AutoDeleteObjects = true,
                RemovalPolicy     = RemovalPolicy.DESTROY,
                AccessControl     = BucketAccessControl.PRIVATE,
            });
            var lifecycleRule = new LifecycleRule
            {
                Enabled    = true,
                Prefix     = "reports/",
                Expiration = Duration.Days(stackProps.DaysKeepReports),
                AbortIncompleteMultipartUploadAfter = Duration.Days(1),
            };

            s3.AddLifecycleRule(lifecycleRule);
            s3.AddToResourcePolicy(new PolicyStatement(new PolicyStatementProps
            {
                Sid        = "AllowPublicRead",
                Effect     = Effect.ALLOW,
                Principals = new[] { new AnyPrincipal() },
                Actions    = new[] { "s3:GetObject*" },
                Resources  = new[] { $"{s3.BucketArn}/html/*" },
            }));
            s3.AddToResourcePolicy(new PolicyStatement(new PolicyStatementProps
            {
                Sid        = "AllowAwsAccountAccess",
                Effect     = Effect.ALLOW,
                Principals = new[] { new AccountRootPrincipal() },
                Actions    = new[] { "s3:*" },
                Resources  = new[] { $"{s3.BucketArn}/*" },
            }));

            // s3 deploy
            var masterDllDeployment = new BucketDeployment(this, "DeployMasterDll", new BucketDeploymentProps
            {
                DestinationBucket    = s3,
                Sources              = new[] { Source.Asset(Path.Combine(Directory.GetCurrentDirectory(), $"out/linux/server")) },
                DestinationKeyPrefix = $"assembly/linux/server"
            });
            var userdataDeployment = new BucketDeployment(this, "UserData", new BucketDeploymentProps
            {
                DestinationBucket    = s3,
                Sources              = new[] { Source.Asset(Path.Combine(Directory.GetCurrentDirectory(), "userdata/")) },
                DestinationKeyPrefix = "userdata/"
            });

            // docker deploy
            var dockerImage = new DockerImageAsset(this, "dframeWorkerImage", new DockerImageAssetProps
            {
                Directory = Path.Combine(Directory.GetCurrentDirectory(), "app"),
                File      = "ConsoleAppEcs/Dockerfile.Ecs",
            });
            var dframeImage = ContainerImage.FromDockerImageAsset(dockerImage);

            // network
            var vpc = new Vpc(this, "Vpc", new VpcProps
            {
                MaxAzs              = 2,
                NatGateways         = 0,
                SubnetConfiguration = new[] { new SubnetConfiguration {
                                                  Name = "public", SubnetType = SubnetType.PUBLIC
                                              } },
            });
            var allsubnets = new SubnetSelection {
                Subnets = vpc.PublicSubnets
            };
            var singleSubnets = new SubnetSelection {
                Subnets = new[] { vpc.PublicSubnets.First() }
            };
            var sg = new SecurityGroup(this, "MasterSg", new SecurityGroupProps
            {
                AllowAllOutbound = true,
                Vpc = vpc,
            });

            foreach (var subnet in vpc.PublicSubnets)
            {
                sg.AddIngressRule(Peer.Ipv4(vpc.VpcCidrBlock), Port.AllTcp(), "VPC", true);
            }

            // service discovery
            var serviceDiscoveryDomain = "local";
            var serverMapName          = "server";
            var dframeMapName          = "dframe-master";
            var ns = new PrivateDnsNamespace(this, "Namespace", new PrivateDnsNamespaceProps
            {
                Vpc  = vpc,
                Name = serviceDiscoveryDomain,
            });
            var serviceDiscoveryServer = ns.CreateService("server", new DnsServiceProps
            {
                Name          = serverMapName,
                DnsRecordType = DnsRecordType.A,
                RoutingPolicy = RoutingPolicy.MULTIVALUE,
            });

            // alb
            var albDnsName = "benchmark-alb";
            var benchToMagicOnionDnsName = benchNetwork.RequireAlb
                ? $"{benchNetwork.EndpointScheme}://{albDnsName}.{stackProps.AlbDomain.domain}"
                : $"{benchNetwork.EndpointScheme}://{serverMapName}.{serviceDiscoveryDomain}";
            IApplicationTargetGroup grpcTargetGroup  = null;
            IApplicationTargetGroup httpsTargetGroup = null;

            if (benchNetwork.RequireAlb)
            {
                // route53
                var hostedZone = HostedZone.FromHostedZoneAttributes(this, "HostedZone", new HostedZoneAttributes
                {
                    HostedZoneId = stackProps.AlbDomain.zoneId,
                    ZoneName     = stackProps.AlbDomain.domain,
                });

                // acm
                var certificate = new DnsValidatedCertificate(this, "certificate", new DnsValidatedCertificateProps
                {
                    DomainName = $"{albDnsName}.{hostedZone.ZoneName}",
                    HostedZone = hostedZone,
                });
                // alb
                var lb = new ApplicationLoadBalancer(this, "LB", new ApplicationLoadBalancerProps
                {
                    Vpc           = vpc,
                    VpcSubnets    = allsubnets,
                    SecurityGroup = new SecurityGroup(this, "AlbSg", new SecurityGroupProps
                    {
                        AllowAllOutbound = true,
                        Vpc = vpc,
                    }),
                    InternetFacing = false,
                    Http2Enabled   = true,
                });
                grpcTargetGroup  = AddGrpcTargetGroup(benchNetwork, vpc, certificate, lb);
                httpsTargetGroup = AddHttpsTargetGroup(benchNetwork, vpc, certificate, lb);

                // Dns Record
                _ = new CnameRecord(this, "alb-alias-record", new CnameRecordProps
                {
                    RecordName = $"{albDnsName}.{stackProps.AlbDomain.domain}",
                    Ttl        = Duration.Seconds(60),
                    Zone       = hostedZone,
                    DomainName = lb.LoadBalancerDnsName,
                });
            }

            // iam
            var iamEc2MagicOnionRole  = GetIamEc2MagicOnionRole(s3, serviceDiscoveryServer);
            var iamEcsTaskExecuteRole = GetIamEcsTaskExecuteRole(new[] { dframeWorkerLogGroup, dframeMasterLogGroup });
            var iamDFrameTaskDefRole  = GetIamEcsDframeTaskDefRole(s3);
            var iamWorkerTaskDefRole  = GetIamEcsWorkerTaskDefRole(s3);

            // secrets
            var ddToken = stackProps.UseEc2DatadogAgentProfiler || stackProps.UseFargateDatadogAgentProfiler
                ? Amazon.CDK.AWS.SecretsManager.Secret.FromSecretNameV2(this, "dd-token", "magiconion-benchmark-datadog-token")
                : null;

            // MagicOnion
            var asg = new AutoScalingGroup(this, "MagicOnionAsg", new AutoScalingGroupProps
            {
                // Monitoring is default DETAILED.
                SpotPrice                = "1.0", // 0.0096 for spot price average for m3.medium
                Vpc                      = vpc,
                SecurityGroup            = sg,
                VpcSubnets               = singleSubnets,
                InstanceType             = stackProps.MagicOnionInstanceType,
                DesiredCapacity          = 1,
                MaxCapacity              = 1,
                MinCapacity              = 0,
                AssociatePublicIpAddress = true,
                MachineImage             = new AmazonLinuxImage(new AmazonLinuxImageProps
                {
                    CpuType        = AmazonLinuxCpuType.X86_64,
                    Generation     = AmazonLinuxGeneration.AMAZON_LINUX_2,
                    Storage        = AmazonLinuxStorage.GENERAL_PURPOSE,
                    Virtualization = AmazonLinuxVirt.HVM,
                }),
                AllowAllOutbound = true,
                GroupMetrics     = new[] { GroupMetrics.All() },
                Role             = iamEc2MagicOnionRole,
                UpdatePolicy     = UpdatePolicy.ReplacingUpdate(),
                Signals          = Signals.WaitForCount(1, new SignalsOptions
                {
                    Timeout = Duration.Minutes(10),
                }),
            });

            asg.AddSecretsReadGrant(ddToken, () => stackProps.UseEc2DatadogAgentProfiler);
            var userdata = GetUserData(recreateMagicOnionTrigger, s3.BucketName, stackProps.BenchmarkBinaryNames, serviceDiscoveryServer.ServiceId, stackProps.UseEc2CloudWatchAgentProfiler, stackProps.UseEc2DatadogAgentProfiler);

            asg.AddUserData(userdata);
            asg.UserData.AddSignalOnExitCommand(asg);
            asg.Node.AddDependency(masterDllDeployment);
            asg.Node.AddDependency(userdataDeployment);
            if (stackProps.EnableMagicOnionScaleInCron)
            {
                asg.ScaleOnSchedule("ScheduleOut", new BasicScheduledActionProps
                {
                    DesiredCapacity = 1,
                    MaxCapacity     = 1,
                    // AM9:00 (JST+9) on Monday to Wednesday
                    Schedule = Schedule.Expression("0 0 * 1-3 *"),
                });
                asg.ScaleOnSchedule("ScheduleIn", new BasicScheduledActionProps
                {
                    DesiredCapacity = 0,
                    MaxCapacity     = 0,
                    // PM9:00 (JST+9) on Everyday
                    Schedule = Schedule.Expression("0 12 * 1-7 *"),
                });
            }
            if (benchNetwork.RequireAlb)
            {
                asg.AttachToApplicationTargetGroup(grpcTargetGroup);
                asg.AttachToApplicationTargetGroup(httpsTargetGroup);
            }

            // ECS
            var cluster = new Cluster(this, "WorkerCluster", new ClusterProps
            {
                Vpc = vpc,
            });

            cluster.Node.AddDependency(asg); // wait until asg is up

            // dframe-worker
            var dframeWorkerContainerName = "worker";
            var dframeWorkerTaskDef       = new FargateTaskDefinition(this, "DFrameWorkerTaskDef", new FargateTaskDefinitionProps
            {
                ExecutionRole  = iamEcsTaskExecuteRole,
                TaskRole       = iamWorkerTaskDefRole,
                Cpu            = stackProps.WorkerFargate.CpuSize,
                MemoryLimitMiB = stackProps.WorkerFargate.MemorySize,
            });

            dframeWorkerTaskDef.AddContainer(dframeWorkerContainerName, new ContainerDefinitionOptions
            {
                Image       = dframeImage,
                Command     = new[] { "--worker-flag" },
                Environment = new Dictionary <string, string>
                {
                    { "DFRAME_MASTER_CONNECT_TO_HOST", $"{dframeMapName}.{serviceDiscoveryDomain}" },
                    { "DFRAME_MASTER_CONNECT_TO_PORT", "12345" },
                    { "BENCH_SERVER_HOST", benchToMagicOnionDnsName },
                    { "BENCH_REPORTID", stackProps.ReportId },
                    { "BENCH_S3BUCKET", s3.BucketName },
                },
                Logging = LogDriver.AwsLogs(new AwsLogDriverProps
                {
                    LogGroup = new LogGroup(this, "WorkerLogGroup", new LogGroupProps
                    {
                        LogGroupName  = dframeWorkerLogGroup,
                        RemovalPolicy = RemovalPolicy.DESTROY,
                        Retention     = RetentionDays.TWO_WEEKS,
                    }),
                    StreamPrefix = dframeWorkerLogGroup,
                }),
            });
            dframeWorkerTaskDef.AddDatadogContainer($"{dframeWorkerContainerName}-datadog", ddToken, () => stackProps.UseFargateDatadogAgentProfiler);
            var dframeWorkerService = new FargateService(this, "DFrameWorkerService", new FargateServiceProps
            {
                ServiceName       = "DFrameWorkerService",
                DesiredCount      = 0,
                Cluster           = cluster,
                TaskDefinition    = dframeWorkerTaskDef,
                VpcSubnets        = singleSubnets,
                SecurityGroups    = new[] { sg },
                PlatformVersion   = FargatePlatformVersion.VERSION1_4,
                MinHealthyPercent = 0,
                AssignPublicIp    = true,
            });

            // dframe-master
            var dframeMasterTaskDef = new FargateTaskDefinition(this, "DFrameMasterTaskDef", new FargateTaskDefinitionProps
            {
                ExecutionRole  = iamEcsTaskExecuteRole,
                TaskRole       = iamDFrameTaskDefRole,
                Cpu            = stackProps.MasterFargate.CpuSize,
                MemoryLimitMiB = stackProps.MasterFargate.MemorySize,
            });

            dframeMasterTaskDef.AddContainer("dframe", new ContainerDefinitionOptions
            {
                Image       = dframeImage,
                Environment = new Dictionary <string, string>
                {
                    { "DFRAME_CLUSTER_NAME", cluster.ClusterName },
                    { "DFRAME_MASTER_SERVICE_NAME", "DFrameMasterService" },
                    { "DFRAME_WORKER_CONTAINER_NAME", dframeWorkerContainerName },
                    { "DFRAME_WORKER_SERVICE_NAME", dframeWorkerService.ServiceName },
                    { "DFRAME_WORKER_TASK_NAME", Fn.Select(1, Fn.Split("/", dframeWorkerTaskDef.TaskDefinitionArn)) },
                    { "DFRAME_WORKER_IMAGE", dockerImage.ImageUri },
                    { "BENCH_REPORTID", stackProps.ReportId },
                    { "BENCH_S3BUCKET", s3.BucketName },
                },
                Logging = LogDriver.AwsLogs(new AwsLogDriverProps
                {
                    LogGroup = new LogGroup(this, "MasterLogGroup", new LogGroupProps
                    {
                        LogGroupName  = dframeMasterLogGroup,
                        RemovalPolicy = RemovalPolicy.DESTROY,
                        Retention     = RetentionDays.TWO_WEEKS,
                    }),
                    StreamPrefix = dframeMasterLogGroup,
                }),
            });
            dframeMasterTaskDef.AddDatadogContainer($"dframe-datadog", ddToken, () => stackProps.UseFargateDatadogAgentProfiler);
            var dframeMasterService = new FargateService(this, "DFrameMasterService", new FargateServiceProps
            {
                ServiceName       = "DFrameMasterService",
                DesiredCount      = 1,
                Cluster           = cluster,
                TaskDefinition    = dframeMasterTaskDef,
                VpcSubnets        = singleSubnets,
                SecurityGroups    = new[] { sg },
                PlatformVersion   = FargatePlatformVersion.VERSION1_4,
                MinHealthyPercent = 0,
                AssignPublicIp    = true,
            });

            dframeMasterService.EnableCloudMap(new CloudMapOptions
            {
                CloudMapNamespace = ns,
                Name          = dframeMapName,
                DnsRecordType = DnsRecordType.A,
                DnsTtl        = Duration.Seconds(300),
            });

            // output
            new CfnOutput(this, "ReportUrl", new CfnOutputProps {
                Value = $"https://{s3.BucketRegionalDomainName}/html/{stackProps.ReportId}/index.html"
            });
            new CfnOutput(this, "EndPointStyle", new CfnOutputProps {
                Value = stackProps.BenchmarkEndpoint.ToString()
            });
            new CfnOutput(this, "AsgName", new CfnOutputProps {
                Value = asg.AutoScalingGroupName
            });
            new CfnOutput(this, "EcsClusterName", new CfnOutputProps {
                Value = cluster.ClusterName
            });
            new CfnOutput(this, "DFrameWorkerEcsTaskdefImage", new CfnOutputProps {
                Value = dockerImage.ImageUri
            });
        }
예제 #4
0
        private void ConfigureECSClusterAndService(IRecipeProps <Configuration> recipeConfiguration)
        {
            if (AppVpc == null)
            {
                throw new InvalidOperationException($"{nameof(AppVpc)} has not been set. The {nameof(ConfigureVpc)} method should be called before {nameof(ConfigureECSClusterAndService)}");
            }

            var settings = recipeConfiguration.Settings;

            if (settings.ECSCluster.CreateNew)
            {
                EcsCluster = new Cluster(this, nameof(EcsCluster), InvokeCustomizeCDKPropsEvent(nameof(EcsCluster), this, new ClusterProps
                {
                    Vpc         = AppVpc,
                    ClusterName = settings.ECSCluster.NewClusterName
                }));
            }
            else
            {
                EcsCluster = Cluster.FromClusterAttributes(this, nameof(EcsCluster), InvokeCustomizeCDKPropsEvent(nameof(EcsCluster), this, new ClusterAttributes
                {
                    ClusterArn     = settings.ECSCluster.ClusterArn,
                    ClusterName    = ECSFargateUtilities.GetClusterNameFromArn(settings.ECSCluster.ClusterArn),
                    SecurityGroups = new ISecurityGroup[0],
                    Vpc            = AppVpc
                }));
            }

            AppTaskDefinition = new FargateTaskDefinition(this, nameof(AppTaskDefinition), InvokeCustomizeCDKPropsEvent(nameof(AppTaskDefinition), this, new FargateTaskDefinitionProps
            {
                TaskRole       = AppIAMTaskRole,
                Cpu            = settings.TaskCpu,
                MemoryLimitMiB = settings.TaskMemory
            }));

            AppLogging = new AwsLogDriver(InvokeCustomizeCDKPropsEvent(nameof(AppLogging), this, new AwsLogDriverProps
            {
                StreamPrefix = recipeConfiguration.StackName
            }));

            if (string.IsNullOrEmpty(recipeConfiguration.ECRRepositoryName))
            {
                throw new InvalidOrMissingConfigurationException("The provided ECR Repository Name is null or empty.");
            }

            EcrRepository          = Repository.FromRepositoryName(this, nameof(EcrRepository), recipeConfiguration.ECRRepositoryName);
            AppContainerDefinition = AppTaskDefinition.AddContainer(nameof(AppContainerDefinition), InvokeCustomizeCDKPropsEvent(nameof(AppContainerDefinition), this, new ContainerDefinitionOptions
            {
                Image   = ContainerImage.FromEcrRepository(EcrRepository, recipeConfiguration.ECRImageTag),
                Logging = AppLogging
            }));

            AppContainerDefinition.AddPortMappings(new PortMapping
            {
                ContainerPort = 80,
                Protocol      = Amazon.CDK.AWS.ECS.Protocol.TCP
            });

            WebAccessSecurityGroup = new SecurityGroup(this, nameof(WebAccessSecurityGroup), InvokeCustomizeCDKPropsEvent(nameof(WebAccessSecurityGroup), this, new SecurityGroupProps
            {
                Vpc = AppVpc,
                SecurityGroupName = $"{recipeConfiguration.StackName}-ECSService"
            }));

            EcsServiceSecurityGroups = new List <ISecurityGroup>();
            EcsServiceSecurityGroups.Add(WebAccessSecurityGroup);

            if (!string.IsNullOrEmpty(settings.AdditionalECSServiceSecurityGroups))
            {
                var count = 1;
                foreach (var securityGroupId in settings.AdditionalECSServiceSecurityGroups.Split(','))
                {
                    EcsServiceSecurityGroups.Add(SecurityGroup.FromSecurityGroupId(this, $"AdditionalGroup-{count++}", securityGroupId.Trim(), new SecurityGroupImportOptions
                    {
                        Mutable = false
                    }));
                }
            }

            AppFargateService = new FargateService(this, nameof(AppFargateService), InvokeCustomizeCDKPropsEvent(nameof(AppFargateService), this, new FargateServiceProps
            {
                Cluster        = EcsCluster,
                TaskDefinition = AppTaskDefinition,
                DesiredCount   = settings.DesiredCount,
                ServiceName    = settings.ECSServiceName,
                AssignPublicIp = settings.Vpc.IsDefault,
                SecurityGroups = EcsServiceSecurityGroups.ToArray()
            }));
        }
        internal UsersServiceFargateStack(Construct scope, string id, IUsersServiceFargateStackProps props) : base(scope, id, props)
        {
            var repository = Repository.FromRepositoryName(this, "UsersRepository", "users-service");


            var taskDefinition = new FargateTaskDefinition(this, "UsersServiceTaskDef", new FargateTaskDefinitionProps
            {
                Cpu            = 256,
                MemoryLimitMiB = 512
            });

            var container = taskDefinition.AddContainer("WebApi", new ContainerDefinitionOptions
            {
                Image   = ContainerImage.FromEcrRepository(repository),
                Logging = new AwsLogDriver(new AwsLogDriverProps
                {
                    LogRetention = Amazon.CDK.AWS.Logs.RetentionDays.TWO_WEEKS,
                    StreamPrefix = "UsersService-"
                })
            });

            container.AddPortMappings(new PortMapping
            {
                ContainerPort = 80
            });

            var service = new FargateService(this, "UsersService", new FargateServiceProps
            {
                Cluster        = props.Cluster,
                TaskDefinition = taskDefinition,
                DesiredCount   = 2,
                VpcSubnets     = new SubnetSelection
                {
                    SubnetType = SubnetType.PUBLIC
                },
                AssignPublicIp = true
            });

            service.AutoScaleTaskCount(new EnableScalingProps
            {
                MinCapacity = 2,
                MaxCapacity = 4
            }).ScaleOnCpuUtilization("CPU", new CpuUtilizationScalingProps
            {
                TargetUtilizationPercent = 70
            });

            props.Listener.AddTargets("UsersServiceTarget", new AddApplicationTargetsProps
            {
                HealthCheck = new HealthCheck
                {
                    HealthyHttpCodes        = "200-299",
                    Path                    = "/api/users/health",
                    Protocol                = Amazon.CDK.AWS.ElasticLoadBalancingV2.Protocol.HTTP,
                    HealthyThresholdCount   = 2,
                    UnhealthyThresholdCount = 5,
                    Timeout                 = Duration.Seconds(10),
                    Interval                = Duration.Seconds(70),
                    Port                    = "80"
                },
                Protocol    = ApplicationProtocol.HTTP,
                Port        = 80,
                Targets     = new IApplicationLoadBalancerTarget[] { service },
                PathPattern = "/api/users*",
                Priority    = 2
            });
        }