Skip to content

Instantly share code, notes, and snippets.

@broglep-work
Last active July 9, 2019 07:46
Show Gist options
  • Save broglep-work/0ceed52c509f75a6c26afc43b807a0eb to your computer and use it in GitHub Desktop.
Save broglep-work/0ceed52c509f75a6c26afc43b807a0eb to your computer and use it in GitHub Desktop.
AWSTemplateFormatVersion: 2010-09-09
Description: 'Registers ecs tasks to a second target group'
Parameters:
ClusterArn:
Description: 'ANR of cluster the service resides in'
Type: String
ServiceName:
Description: 'Name of the service to register'
Type: String
ContainerName:
Description: 'Name of the container to register'
Type: String
VpcId:
Description: 'VPC ID'
Type: String
Subnets:
Description: 'Subnets for ALB (comma separated)'
Type: String
Outputs:
LambdaFunction:
Value: !GetAtt LambdaFunction.Arn
Resources:
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: 'internet-facing'
SecurityGroups:
- !Ref ALBPublicSecurityGroup
Subnets: !Split [ ",", !Ref Subnets ]
Type: 'application'
ALBPublicSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Public security group"
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: "80"
ToPort: "80"
CidrIp: "0.0.0.0/0"
ALBSslListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: 'forward'
TargetGroupArn: !Ref ALBTargetGroup
LoadBalancerArn: !Ref ALB
Port: '80'
Protocol: 'HTTP'
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: '5'
HealthCheckPath: '/is-alive'
HealthCheckProtocol: 'HTTP'
HealthCheckTimeoutSeconds: '3'
HealthyThresholdCount: '2'
Port: '80'
Protocol: 'HTTP'
TargetGroupAttributes:
- Key: 'deregistration_delay.timeout_seconds'
Value: '60'
UnhealthyThresholdCount: '3'
VpcId: !Ref VpcId
ECSTaskStateChangeEventRule:
Type: AWS::Events::Rule
Properties:
Description: !Sub "Invoke Lambda Function for ECS Task Stage Changes of ${ServiceName}"
State: ENABLED
EventPattern:
source:
- "aws.ecs"
detail-type:
- "ECS Task State Change"
detail:
clusterArn:
- !Ref ClusterArn
group:
- !Sub "service:${ServiceName}"
Targets:
-
Arn: !GetAtt LambdaFunction.Arn
Id: "lambda"
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Sub ${LambdaFunction.Arn}
Principal: 'events.amazonaws.com'
SourceArn: !Sub ${ECSTaskStateChangeEventRule.Arn}
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Description: !Sub 'Lambda function for registering and de-registering target of ${ServiceName}'
Environment:
Variables:
SERVICE: !Ref ServiceName
CONTAINER: !Ref ContainerName
TARGET_GROUP: !Ref ALBTargetGroup
Handler: 'index.lambda_handler'
MemorySize: 128
Role: !GetAtt LambdaExecutionRole.Arn
Runtime: python3.6
Timeout: 10
Code:
ZipFile: |
import os
import json
import boto3
SERVICE = os.environ['SERVICE']
CONTAINER = os.environ['CONTAINER']
TARGET_GROUP = os.environ['TARGET_GROUP']
CONTAINER_PORT = None
elbv2 = boto3.client('elbv2')
ecs = boto3.client('ecs')
def lambda_handler(event, context):
if event["source"] != "aws.ecs":
raise ValueError("Function only supports input from events with a source type of: aws.ecs")
if event["detail-type"] != "ECS Task State Change":
raise ValueError("Function only supports input from events with a detail-type of: ECS Task State Change")
lastStatus = event['detail']['lastStatus']
desiredStatus = event['detail']['desiredStatus']
print("Last Status: %s" % lastStatus)
print("Desired Status: %s" % desiredStatus)
res = ecs.describe_container_instances(cluster=event['detail']['clusterArn'],containerInstances=[event['detail']['containerInstanceArn']])
ec2InstanceId = res['containerInstances'][0]['ec2InstanceId']
# Optimize: Cache this lookup
res = ecs.describe_services(cluster=event['detail']['clusterArn'],services=[SERVICE])
service = next(service for service in res['services'] if service['serviceName'] == SERVICE)
loadBalancer = next(loadBalancer for loadBalancer in service['loadBalancers'] if loadBalancer['containerName'] == CONTAINER)
CONTAINER_PORT = loadBalancer['containerPort']
if lastStatus == 'PENDING':
# In Pending state we do not know host port yet
return
container = next(container for container in event['detail']['containers'] if container['name'] == CONTAINER)
binding = next(binding for binding in container['networkBindings'] if binding['containerPort'] == CONTAINER_PORT)
hostPort = binding['hostPort']
target = {'Id': ec2InstanceId,'Port': hostPort}
print("Target: %s" % target)
if lastStatus == 'RUNNING' and desiredStatus == 'RUNNING':
print('Registering Target')
elbv2.register_targets(TargetGroupArn=TARGET_GROUP, Targets=[target])
if lastStatus == 'RUNNING' and desiredStatus == 'STOPPED':
print('Deregistering Target')
elbv2.deregister_targets(TargetGroupArn=TARGET_GROUP,Targets=[target])
LambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Policies:
-
PolicyName: "DescribeECSAndElasticLoadBalancing"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "ecs:DescribeServices"
- "ecs:DescribeContainerInstances"
Resource: "*"
-
PolicyName: "UpdateTargetGroup"
PolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Action:
- "elasticloadbalancing:RegisterTargets"
- "elasticloadbalancing:DeregisterTargets"
Resource: !Ref ALBTargetGroup
Path: "/service-role/"
@ozbillwang
Copy link

ozbillwang commented Jan 11, 2019

@broglep-koubachi

I understood this lambda function need three environment variables.

SERVICE is the ECS service name in that ECS cluster
TARGET_GROUP is the new created target group in this template

But what CONTAINER name I should provide? Any sample for me?

### Update
I knew what need be used.

By the way, if you need test it with a sample events, check here:

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_cwe_events.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment