Skip to content
Open

Nlb #44

Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 169 additions & 91 deletions cloudlift/deployment/service_template_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
NetworkConfiguration, PlacementStrategy,
PortMapping, Service, TaskDefinition)
from troposphere.elasticloadbalancingv2 import Action, Certificate, Listener
from troposphere.elasticloadbalancingv2 import LoadBalancer as ALBLoadBalancer
from troposphere.elasticloadbalancingv2 import LoadBalancer as ELBv2
from troposphere.elasticloadbalancingv2 import (Matcher, RedirectConfig,
TargetGroup,
TargetGroupAttribute)
Expand Down Expand Up @@ -169,11 +169,12 @@ def _add_service(self, service_name, config):
"Cpu": 0
}

if 'http_interface' in config:
if 'http_interface' in config or 'udp_interface' in config:
container_definition_arguments['PortMappings'] = [
PortMapping(
ContainerPort=int(
config['http_interface']['container_port']
config['http_interface']['container_port'] if 'http_interface' in config else
config['udp_interface']['container_port']
)
)
]
Expand Down Expand Up @@ -220,7 +221,70 @@ def _add_service(self, service_name, config):
MinimumHealthyPercent=100,
MaximumPercent=200
)
if 'http_interface' in config:
if 'udp_interface' in config:
nlb, lb, service_listener, nlb_sg = self._add_alb(cd, service_name, config, launch_type)

if launch_type == self.LAUNCH_TYPE_FARGATE:
# if launch type is ec2, then services inherit the ec2 instance security group
# otherwise, we need to specify a security group for the service
service_security_group = SecurityGroup(
pascalcase("FargateService" + self.env + service_name),
GroupName=pascalcase("FargateService" + self.env + service_name),
SecurityGroupIngress=[{
'IpProtocol': 'UDP',
'SourceSecurityGroupId': Ref(nlb_sg),
'ToPort': int(config['udp_interface']['container_port']),
'FromPort': int(config['udp_interface']['container_port']),
}],
VpcId=Ref(self.vpc),
GroupDescription=pascalcase("FargateService" + self.env + service_name)
)
self.template.add_resource(service_security_group)

launch_type_svc = {
'NetworkConfiguration': NetworkConfiguration(
AwsvpcConfiguration=AwsvpcConfiguration(
Subnets=[
Ref(self.private_subnet1),
Ref(self.private_subnet2)
],
SecurityGroups=[
Ref(service_security_group)
]
)
)
}
else:
launch_type_svc = {
'Role': Ref(self.ecs_service_role),
'PlacementStrategies': self.PLACEMENT_STRATEGIES
}
svc = Service(
service_name,
LoadBalancers=[lb],
Cluster=self.cluster_name,
TaskDefinition=Ref(td),
DesiredCount=desired_count,
DependsOn=service_listener.title,
LaunchType=launch_type,
**launch_type_svc,
)
self.template.add_output(
Output(
service_name + 'EcsServiceName',
Description='The ECS name which needs to be entered',
Value=GetAtt(svc, 'Name')
)
)
self.template.add_output(
Output(
service_name + "URL",
Description="The URL at which the service is accessible",
Value=Sub("udp://${" + nlb.name + ".DNSName}")
)
)
self.template.add_resource(svc)
elif 'http_interface' in config:
alb, lb, service_listener, alb_sg = self._add_alb(cd, service_name, config, launch_type)

if launch_type == self.LAUNCH_TYPE_FARGATE:
Expand Down Expand Up @@ -344,71 +408,63 @@ def _gen_log_config(self, service_name):

def _add_alb(self, cd, service_name, config, launch_type):
sg_name = 'SG' + self.env + service_name
protocol = 'http' if 'http_interface' in config else 'udp'
elb_config = config[f'{protocol}_interface']
svc_alb_sg = SecurityGroup(
re.sub(r'\W+', '', sg_name),
GroupName=self.env + '-' + service_name,
SecurityGroupIngress=self._generate_alb_security_group_ingress(
config
),
elb_config, protocol),
VpcId=Ref(self.vpc),
GroupDescription=Sub(service_name + "-alb-sg")
)
self.template.add_resource(svc_alb_sg)
alb_name = service_name + pascalcase(self.env)
if config['http_interface']['internal']:
if elb_config['internal']:
alb_subnets = [
Ref(self.private_subnet1),
Ref(self.private_subnet2)
]
scheme = "internal"
alb_name += 'Internal'
alb_name = alb_name[:32]
alb = ALBLoadBalancer(
'ALB' + service_name,
Subnets=alb_subnets,
SecurityGroups=[
self.alb_security_group,
Ref(svc_alb_sg)
],
Name=alb_name,
Tags=[
{'Value': alb_name, 'Key': 'Name'}
],
Scheme=scheme
)
else:
scheme = 'internet-facing'
alb_subnets = [
Ref(self.public_subnet1),
Ref(self.public_subnet2)
]
alb_name = alb_name[:32]
alb = ALBLoadBalancer(
'ALB' + service_name,
Subnets=alb_subnets,
SecurityGroups=[
self.alb_security_group,
Ref(svc_alb_sg)
],
Name=alb_name,
Tags=[
{'Value': alb_name, 'Key': 'Name'}
]
)
alb_name = alb_name[:32]
elb = ELBv2(
'ALB' + service_name,
Subnets=alb_subnets,
SecurityGroups=[
self.alb_security_group,
Ref(svc_alb_sg)
] if protocol == 'http' else [],
Name=alb_name,
Tags=[
{'Value': alb_name, 'Key': 'Name'}
],
Scheme=scheme,
Type='application' if 'http_interface' in config else 'network'
)

self.template.add_resource(alb)
self.template.add_resource(elb)

target_group_name = "TargetGroup" + service_name
health_check_path = config['http_interface']['health_check_path'] if 'health_check_path' in config['http_interface'] else "/elb-check"
if config['http_interface']['internal']:
health_check_path = elb_config['health_check_path'] if 'health_check_path' in elb_config else "/elb-check"
if elb_config['internal']:
target_group_name = target_group_name + 'Internal'

target_group_config = {}
if launch_type == self.LAUNCH_TYPE_FARGATE:
target_group_config['TargetType'] = 'ip'
if protocol == 'http':
target_group_config['Matcher'] = Matcher(HttpCode="200-399")
target_group_config['HealthCheckPath'] = health_check_path

service_target_group = TargetGroup(
target_group_name,
HealthCheckPath=health_check_path,
HealthyThresholdCount=2,
HealthCheckIntervalSeconds=30,
TargetGroupAttributes=[
Expand All @@ -418,11 +474,10 @@ def _add_alb(self, cd, service_name, config, launch_type):
)
],
VpcId=Ref(self.vpc),
Protocol="HTTP",
Matcher=Matcher(HttpCode="200-399"),
Port=int(config['http_interface']['container_port']),
Protocol=protocol.upper(),
Port=int(elb_config['container_port']),
HealthCheckTimeoutSeconds=10,
UnhealthyThresholdCount=3,
UnhealthyThresholdCount=2,
**target_group_config
)

Expand All @@ -433,20 +488,33 @@ def _add_alb(self, cd, service_name, config, launch_type):
lb = LoadBalancer(
ContainerName=cd.Name,
TargetGroupArn=Ref(service_target_group),
ContainerPort=int(config['http_interface']['container_port'])
ContainerPort=int(elb_config['container_port'])
)
target_group_action = Action(
TargetGroupArn=Ref(target_group_name),
Type="forward"
)
service_listener = self._add_service_listener(
service_name,
target_group_action,
alb,
config['http_interface']['internal']
)
self._add_alb_alarms(service_name, alb)
return alb, lb, service_listener, svc_alb_sg

if protocol == 'http':
service_listener = self._add_service_listener(
service_name,
target_group_action,
elb,
elb_config['internal'],
protocol
)
elif protocol == 'udp':
service_listener = Listener(
"LoadBalancerListener" + service_name,
Protocol="UDP",
DefaultActions=[target_group_action],
LoadBalancerArn=Ref(elb),
Port=int(config['udp_interface']['container_port']),
)
self.template.add_resource(service_listener)

self._add_elb_alarms(service_name, elb)
return elb, lb, service_listener, svc_alb_sg

def _add_service_listener(self, service_name, target_group_action,
alb, internal):
Expand Down Expand Up @@ -492,14 +560,14 @@ def _add_service_listener(self, service_name, target_group_action,
self.template.add_resource(http_redirection_listener)
return service_listener

def _add_alb_alarms(self, service_name, alb):
def _add_elb_alarms(self, service_name, elb_v2):
unhealthy_alarm = Alarm(
'ElbUnhealthyHostAlarm' + service_name,
EvaluationPeriods=1,
Dimensions=[
MetricDimension(
Name='LoadBalancer',
Value=GetAtt(alb, 'LoadBalancerFullName')
Value=GetAtt(elb_v2, 'LoadBalancerFullName')
)
],
AlarmActions=[Ref(self.notification_sns_arn)],
Expand All @@ -520,7 +588,7 @@ def _add_alb_alarms(self, service_name, alb):
Dimensions=[
MetricDimension(
Name='LoadBalancer',
Value=GetAtt(alb, 'LoadBalancerFullName')
Value=GetAtt(elb_v2, 'LoadBalancerFullName')
)
],
AlarmActions=[Ref(self.notification_sns_arn)],
Expand All @@ -536,47 +604,57 @@ def _add_alb_alarms(self, service_name, alb):
MetricName='RejectedConnectionCount',
TreatMissingData='notBreaching'
)
self.template.add_resource(rejected_connections_alarm)
http_code_elb5xx_alarm = Alarm(
'ElbHTTPCodeELB5xxAlarm' + service_name,
EvaluationPeriods=1,
Dimensions=[
MetricDimension(
Name='LoadBalancer',
Value=GetAtt(alb, 'LoadBalancerFullName')
)
],
AlarmActions=[Ref(self.notification_sns_arn)],
OKActions=[Ref(self.notification_sns_arn)],
AlarmDescription='Triggers if 5xx response originated \
from load balancer',
Namespace='AWS/ApplicationELB',
Period=60,
ComparisonOperator='GreaterThanOrEqualToThreshold',
Statistic='Sum',
Threshold='3',
MetricName='HTTPCode_ELB_5XX_Count',
TreatMissingData='notBreaching'
)
self.template.add_resource(http_code_elb5xx_alarm)
if elb_v2.Type == "network":
self.template.add_resource(rejected_connections_alarm)
http_code_elb5xx_alarm = Alarm(
'ElbHTTPCodeELB5xxAlarm' + service_name,
EvaluationPeriods=1,
Dimensions=[
MetricDimension(
Name='LoadBalancer',
Value=GetAtt(elb_v2, 'LoadBalancerFullName')
)
],
AlarmActions=[Ref(self.notification_sns_arn)],
OKActions=[Ref(self.notification_sns_arn)],
AlarmDescription='Triggers if 5xx response originated \
from load balancer',
Namespace='AWS/ApplicationELB',
Period=60,
ComparisonOperator='GreaterThanOrEqualToThreshold',
Statistic='Sum',
Threshold='3',
MetricName='HTTPCode_ELB_5XX_Count',
TreatMissingData='notBreaching'
)
self.template.add_resource(http_code_elb5xx_alarm)

def _generate_alb_security_group_ingress(self, config):
def _generate_alb_security_group_ingress(self, config, protocol='http'):
ingress_rules = []
for access_ip in config['http_interface']['restrict_access_to']:
for access_ip in config['restrict_access_to']:
if access_ip.find('/') == -1:
access_ip = access_ip + '/32'
ingress_rules.append({
'ToPort': 80,
'IpProtocol': 'TCP',
'FromPort': 80,
'CidrIp': access_ip
})
ingress_rules.append({
'ToPort': 443,
'IpProtocol': 'TCP',
'FromPort': 443,
'CidrIp': access_ip
})
if protocol == 'http':
ingress_rules.append({
'ToPort': 80,
'IpProtocol': 'TCP',
'FromPort': 80,
'CidrIp': access_ip
})
ingress_rules.append({
'ToPort': 443,
'IpProtocol': 'TCP',
'FromPort': 443,
'CidrIp': access_ip
})
else:
port = config['container_port']
ingress_rules.append({
'ToPort': int(port),
'IpProtocol': 'UDP',
'FromPort': int(port),
'CidrIp': access_ip
})
return ingress_rules

def _add_ecs_service_iam_role(self):
Expand Down