using Amazon.CDK; using Amazon.CDK.AWS.EC2; using Amazon.CDK.AWS.ECR; using Amazon.CDK.AWS.ECS; using Amazon.CDK.AWS.ElasticLoadBalancingV2; using Amazon.CDK.AWS.IAM; using System.Collections.Generic; namespace Infrastructure { public class InfrastructureStack : Stack { #region Declerations // Networking Vpc _vpc; // ELB ApplicationLoadBalancer _alb; ApplicationListener _albListener; // ECS Fargate Cluster _ecsFargateCluster; Role _ecsTaskRole; Role _ecsTaskExecutionRole; SecurityGroup _ecsFargateIngressSecurityGroup; FargateTaskDefinition _modernizedService_A__TaskDefinition; FargateService _modernizedService_A__ecsServiceDefinition; FargateTaskDefinition _modernizedService_B__TaskDefinition; FargateService _modernizedService_B__ecsServiceDefinition; #endregion internal InfrastructureStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) { // VPC, Subnets, NATs, IGW, etc. supporting the Legacy and the Modernized resources. CreateNetworkLandingZone(); // ALB that will front the ECS Fargate containers. CreateApplicationLoadBalancer(); OrchestrateEcsFargateResources(); } #region Networking Resources /// /// Creates network resources like VPC, Subnets, Route Tables, NAT, Internet Gateways. /// private void CreateNetworkLandingZone() { /* FYI: Propotional to the MaxAzs value (e.g. 2 here), the following AWS resources get yielded: * 1) Total four subnets; pubilc and private subnet pairs in each AZ * 2) Two NAT Gateways for high availability * 3) One Internet Gateway (IGW) * 4) Five Route Tables (RT) with traffic routes configured as following * a) One as the Main RT * b) Two RT directing traffic from within the Public subnets to the IGW * c) Two RT directing traffic from within the Private subnets to the NAT */ _vpc = new Vpc(this, InfrastructureConfigs.vpc_id, new VpcProps { MaxAzs = 2, Cidr = InfrastructureConfigs.vpc_cidr, SubnetConfiguration = new[] { new SubnetConfiguration() { Name = InfrastructureConfigs.vpc_public_subnet_name, CidrMask = 24, SubnetType = SubnetType.PUBLIC }, new SubnetConfiguration() { Name = InfrastructureConfigs.vpc_private_subnet_name, CidrMask = 24, SubnetType = SubnetType.PRIVATE } } }); } #endregion #region ELB Resources /// /// Create and configure the Application Load Balancer (ALB) and its listener. This will front the ECS Fargate services. /// note: The targets will be added when the ECS Fargate service(s) are created. /// private void CreateApplicationLoadBalancer() { // Controls the internet traffic coming into the ALB var alb_ingress_securitygroup_name = new SecurityGroup(this, InfrastructureConfigs.alb_ingress_securitygroup_name, new SecurityGroupProps { Vpc = _vpc, AllowAllOutbound = true }); alb_ingress_securitygroup_name.Connections.AllowToAnyIpv4(Port.Tcp(80)); // Create and Configure the ALB _alb = new ApplicationLoadBalancer(this, InfrastructureConfigs.alb_name, new ApplicationLoadBalancerProps { LoadBalancerName = InfrastructureConfigs.alb_name, Vpc = _vpc, InternetFacing = true, SecurityGroup = alb_ingress_securitygroup_name }); // Create listener _albListener = _alb.AddListener(InfrastructureConfigs.alb_listener_name, new ApplicationListenerProps { Protocol = ApplicationProtocol.HTTP, Port = 80, DefaultAction = ListenerAction.FixedResponse(503) }); } #endregion #region ECS Fargate Resources /// /// This orchestrator will spin up following ECS fargate resources /// 0. create Application Load Balancer directing internet traffic to the appropriate Workload_* targets; ECS Fargate containers /// 1. create ECS Fargate Cluster /// 2. create Task Definition /// a. Task roles /// b. Register ECR image /// c. specify Container specs like memory, vCPU, port etc. /// build Service blueprint /// Register Task definition /// Specify task 'desired state'; number of instances to run /// Register VPC /// Register Subnet /// Disable public IP /// Register ELB /// Register service discovery /// private void OrchestrateEcsFargateResources() { CreateEcsTaskRole(); CreateEcsTaskExecutionRole(); CreateEcsFargateIngressSG(); CreateEcsFargateCluster(); // Service_A CreateModernizedService_A__EcsFargateTaskDef(); CreateModernizedService_A__EcsFargateServiceDef(); // Service_B CreateModernizedService_B__EcsFargateTaskDef(); CreateModernizedService_B__EcsFargateServiceDef(); } /// /// Create IAM role that tasks can use to make API requests to authorized AWS services. /// private void CreateEcsTaskRole() { _ecsTaskRole = new Role(this, InfrastructureConfigs.ecsfargate_task_def_role_name, new RoleProps { AssumedBy = new ServicePrincipal(InfrastructureConfigs.aws_service_principal_name), // An IAM principal that represents an AWS service (e.g, sqs.amazonaws.com). ManagedPolicies = new List() { ManagedPolicy.FromAwsManagedPolicyName("CloudWatchLogsFullAccess"), ManagedPolicy.FromAwsManagedPolicyName("AWSXRayDaemonWriteAccess") }.ToArray() }); } /// /// Create role required by tasks to pull container images and publish container logs to Amazon CloudWatch on our behalf. /// private void CreateEcsTaskExecutionRole() { _ecsTaskExecutionRole = new Role(this, InfrastructureConfigs.ecsfargate_task_def_execution_role_name, new RoleProps { AssumedBy = new ServicePrincipal(InfrastructureConfigs.aws_service_principal_name), ManagedPolicies = new List() { ManagedPolicy.FromAwsManagedPolicyName("AmazonEC2ContainerRegistryReadOnly"), ManagedPolicy.FromAwsManagedPolicyName("CloudWatchLogsFullAccess") }.ToArray() }); } /// /// Rules governing the traffic that would come into the app behing hosted within the Task/Container instance. /// private void CreateEcsFargateIngressSG() { _ecsFargateIngressSecurityGroup = new SecurityGroup(this, InfrastructureConfigs.ecsFargate_task_ingress_sg, new SecurityGroupProps { Vpc = _vpc, SecurityGroupName = InfrastructureConfigs.ecsFargate_task_ingress_sg, Description = "Firewall for ECS Task ingress.", AllowAllOutbound = true }); // Note: Same ports applies to both of the Modernized Services. So using one of them sufficies the needs. _ecsFargateIngressSecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(InfrastructureConfigs.modernizedService_A__app_container_listener_port), "ALB will fwd. the incoming internet traffic on this port"); _ecsFargateIngressSecurityGroup.AddIngressRule(Peer.AnyIpv4(), Port.Tcp(InfrastructureConfigs.modernizedService_A__app_container_listener_port), "Allow ALB target to perform health checks on the vGW Envoy Proxy"); } /// /// Creates the ECS Farget cluster /// private void CreateEcsFargateCluster() { _ecsFargateCluster = new Cluster(this, InfrastructureConfigs.ecsfargate_cluster_name, new ClusterProps { ClusterName = InfrastructureConfigs.ecsfargate_cluster_name, Vpc = _vpc, ContainerInsights = true }); } #region Service_A /// /// Creates & configures the ECS Fargate Task definition: /// 1. Task roles /// 2. Register ECR image /// 3. Specify Container specs like memory, vCPU, port etc. /// 4. Add ability to read to read senstative information securely; from the AWS secret manager /// private void CreateModernizedService_A__EcsFargateTaskDef() { // Modernized Service_A: create the task definition _modernizedService_A__TaskDefinition = new FargateTaskDefinition(this, InfrastructureConfigs.modernizedService_A__ecsfargate_task_def_name, new FargateTaskDefinitionProps { TaskRole = _ecsTaskRole, ExecutionRole = _ecsTaskExecutionRole, MemoryLimitMiB = InfrastructureConfigs.modernizedService_A__ecsfargate_task_def_memoryInMB, Cpu = InfrastructureConfigs.modernizedService_A__ecsfargate_task_def_cpuUnits }); // Modernized Service_A: register the container definition // Note: ensure this AWS ECR repository exists and it has the correct container image in it. var workload_A_container = _modernizedService_A__TaskDefinition.AddContainer(InfrastructureConfigs.modernizedService_A__container_name, new ContainerDefinitionProps() { Image = ContainerImage.FromEcrRepository(Repository.FromRepositoryName(this, InfrastructureConfigs.modernizedService_A__img_repo_name, InfrastructureConfigs.modernizedService_A__img_repo_name)) }); // Modernized Service_A: register the container ports workload_A_container.AddPortMappings( new PortMapping() { HostPort = InfrastructureConfigs.modernizedService_A__app_container_listener_port, ContainerPort = InfrastructureConfigs.modernizedService_A__app_container_listener_port, Protocol = Amazon.CDK.AWS.ECS.Protocol.TCP } ); // AWS XRay: register the container definition var xRayContainerRegistration = _modernizedService_A__TaskDefinition.AddContainer(InfrastructureConfigs.aws_xray__img_repo_name, new ContainerDefinitionProps() { Image = ContainerImage.FromRegistry(InfrastructureConfigs.aws_xray__img_repo_name), Essential = true, User = InfrastructureConfigs.container_nonroot_user_id }); // AWS XRay: register the container ports xRayContainerRegistration.AddPortMappings( new PortMapping { Protocol = Amazon.CDK.AWS.ECS.Protocol.UDP, ContainerPort = InfrastructureConfigs.aws_xray__container_port, HostPort = InfrastructureConfigs.aws_xray__container_port } ); } /// /// Creates & configures the ECS Fargate Service /// private void CreateModernizedService_A__EcsFargateServiceDef() { // Create and confgirue Fargate service _modernizedService_A__ecsServiceDefinition = new FargateService(this, InfrastructureConfigs.modernizedService_A__ecsfargate_service_name, new FargateServiceProps { ServiceName = InfrastructureConfigs.modernizedService_A__ecsfargate_service_name, TaskDefinition = _modernizedService_A__TaskDefinition, Cluster = _ecsFargateCluster, DesiredCount = InfrastructureConfigs.modernizedService_A__ecsfargate_service_task_desired_count, SecurityGroups = new [] { _ecsFargateIngressSecurityGroup }, AssignPublicIp = false, VpcSubnets = new SubnetSelection() { OnePerAz = true, SubnetType = SubnetType.PRIVATE } }); // Register with the ALB targets // Helpful source: https://docs.aws.amazon.com/cdk/api/latest/dotnet/api/Amazon.CDK.AWS.ElasticLoadBalancingV2.html _albListener.AddTargets(InfrastructureConfigs.modernizedService_A__alb_listener_targetgroup_name, new AddApplicationTargetsProps { Priority = 2, Conditions = new[] { ListenerCondition.PathPatterns(new[] { InfrastructureConfigs.modernizedService_A__alb_listener_path_pattern }) }, TargetGroupName = InfrastructureConfigs.modernizedService_A__alb_listener_targetgroup_name, Port = InfrastructureConfigs.modernizedService_A__app_container_listener_port, Targets = new List { _modernizedService_A__ecsServiceDefinition }.ToArray(), HealthCheck = new Amazon.CDK.AWS.ElasticLoadBalancingV2.HealthCheck() { Path = InfrastructureConfigs.modernizedService_A__alb_listener_targetgroup_healthcheck_path, Port = InfrastructureConfigs.modernizedService_A__app_container_listener_port.ToString(), Protocol = Amazon.CDK.AWS.ElasticLoadBalancingV2.Protocol.HTTP } }); } #endregion #region Service_B /// /// Creates & configures the ECS Fargate Task definition: /// 1. Task roles /// 2. Register ECR image /// 3. Specify Container specs like memory, vCPU, port etc. /// 4. Add ability to read to read senstative information securely; from the AWS secret manager /// private void CreateModernizedService_B__EcsFargateTaskDef() { // Modernized Service_A: create the task definition _modernizedService_B__TaskDefinition = new FargateTaskDefinition(this, InfrastructureConfigs.modernizedService_B__ecsfargate_task_def_name, new FargateTaskDefinitionProps { TaskRole = _ecsTaskRole, ExecutionRole = _ecsTaskExecutionRole, MemoryLimitMiB = InfrastructureConfigs.modernizedService_B__ecsfargate_task_def_memoryInMB, Cpu = InfrastructureConfigs.modernizedService_B__ecsfargate_task_def_cpuUnits }); // Modernized Service_A: register the container definition // Note: ensure this AWS ECR repository exists and it has the correct container image in it. var workload_A_container = _modernizedService_B__TaskDefinition.AddContainer(InfrastructureConfigs.modernizedService_B__container_name, new ContainerDefinitionProps() { Image = ContainerImage.FromEcrRepository(Repository.FromRepositoryName(this, InfrastructureConfigs.modernizedService_B__img_repo_name, InfrastructureConfigs.modernizedService_B__img_repo_name)) }); // Modernized Service_A: register the container ports workload_A_container.AddPortMappings( new PortMapping() { HostPort = InfrastructureConfigs.modernizedService_B__app_container_listener_port, ContainerPort = InfrastructureConfigs.modernizedService_B__app_container_listener_port, Protocol = Amazon.CDK.AWS.ECS.Protocol.TCP } ); // AWS XRay: register the container definition var xRayContainerRegistration = _modernizedService_B__TaskDefinition.AddContainer(InfrastructureConfigs.aws_xray__img_repo_name, new ContainerDefinitionProps() { Image = ContainerImage.FromRegistry(InfrastructureConfigs.aws_xray__img_repo_name), Essential = true, User = InfrastructureConfigs.container_nonroot_user_id }); // AWS XRay: register the container ports xRayContainerRegistration.AddPortMappings( new PortMapping { Protocol = Amazon.CDK.AWS.ECS.Protocol.UDP, ContainerPort = InfrastructureConfigs.aws_xray__container_port, HostPort = InfrastructureConfigs.aws_xray__container_port } ); } /// /// Creates & configures the ECS Fargate Service /// private void CreateModernizedService_B__EcsFargateServiceDef() { // Create and confgirue Fargate service _modernizedService_B__ecsServiceDefinition = new FargateService(this, InfrastructureConfigs.modernizedService_B__ecsfargate_service_name, new FargateServiceProps { ServiceName = InfrastructureConfigs.modernizedService_B__ecsfargate_service_name, TaskDefinition = _modernizedService_B__TaskDefinition, Cluster = _ecsFargateCluster, DesiredCount = InfrastructureConfigs.modernizedService_B__ecsfargate_service_task_desired_count, SecurityGroups = new[] { _ecsFargateIngressSecurityGroup }, AssignPublicIp = false, VpcSubnets = new SubnetSelection() { OnePerAz = true, SubnetType = SubnetType.PRIVATE } }); // Register with the ALB targets // Helpful source: https://docs.aws.amazon.com/cdk/api/latest/dotnet/api/Amazon.CDK.AWS.ElasticLoadBalancingV2.html _albListener.AddTargets(InfrastructureConfigs.modernizedService_B__alb_listener_targetgroup_name, new AddApplicationTargetsProps { Priority = 3, Conditions = new[] { ListenerCondition.PathPatterns(new[] { InfrastructureConfigs.modernizedService_B__alb_listener_path_pattern }) }, TargetGroupName = InfrastructureConfigs.modernizedService_B__alb_listener_targetgroup_name, Port = InfrastructureConfigs.modernizedService_B__app_container_listener_port, Targets = new List { _modernizedService_B__ecsServiceDefinition }.ToArray(), HealthCheck = new Amazon.CDK.AWS.ElasticLoadBalancingV2.HealthCheck() { Path = InfrastructureConfigs.modernizedService_B__alb_listener_targetgroup_healthcheck_path, Port = InfrastructureConfigs.modernizedService_B__app_container_listener_port.ToString(), Protocol = Amazon.CDK.AWS.ElasticLoadBalancingV2.Protocol.HTTP } }); } #endregion #endregion } }