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
}
}