AWSTemplateFormatVersion: '2010-09-09' Description: >- This template creates test bed for Elastic Load Balancer Lambda POC. Creates following resources: - One VPC - One IGW - One NGW - One public route table, nh=igw - One private route table, nh-ngw - One public subnet: - PublicSubnet1 in az1 - Two private subnet: - PrivateSubnet1 in az1 - PrivateSubnet2 in az2 - bastionsg with inbound rules to allow: - ssh, http and icmp from anywhere - all traffic from VPC CIDR - Three Amazon Linux 2 instances - Server1 in PrivateSubnet1 - Server2 in PrivateSubnet2 - Client1 in PublicSubnet1 - Internet facing Elastic Load Balancer (TCP:80) - Target group with type IP (TCP:80) - 3 Lambdas: AmazonLinux2Ami, S3ObjectLambda and ElbHostnameTarget updates target group with appropriate IPs - One Event Rule with ElbHostnameTarget Lambda as target - One Lambda Permission to invoke Event Rule Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Network Configuration Parameters: - VpcCidr - PublicAZ1 - PrivateAZ1 - PrivateAZ2 - PublicSubnet1Cidr - PrivateSubnet1Cidr - PrivateSubnet2Cidr - Label: default: EC2 Key Pair Parameters: - KeyPairName - Label: default: Server Configuration Parameters: - EC2InstanceType - EC2AmiId - EC2DiskSize - SSHLocation - Label: default: Elastic Load Balancer Configuration Parameters: - ElbName - ElbScheme - ElbType - Label: default: Target Group Configuration Parameters: - TargetGroupName - ListenerPort - ListenerProtocol - Label: default: S3 Configuration Parameters: - DstS3BucketName - CreateDstS3BucketCondition - Label: default: User Configurable Lambda Environment Variables Parameters: - TargetFQDN - DnsServers - MaxLookupPerInvocation - InvocationBeforeRegistration - ReportIpCountCwMetric - RemoveUntrackedTgIp - Label: default: CloudWatch Alarm Configuation Parameters: - CreateAlarmCondition - CompositeAlarmSnsEmail ParameterLabels: VpcCidr: default: Enter VPC Network CIDR PublicAZ1: default: Availability Zone For Public Subnet PublicSubnet1 PrivateAZ1: default: Availability Zone For Private Subnet PrivateSubnet1 PrivateAZ2: default: Availability Zone For Private Subnet PrivateSubnet2 PublicSubnet1Cidr: default: Network CIDR for PublicSubnet1 PrivateSubnet1Cidr: default: Network CIDR for PrivateSubnet1 PrivateSubnet2Cidr: default: Network CIDR for PrivateSubnet2 KeyPairName: default: EC2 KeyPair EC2 instances SSHLocation: default: Network CIDR to access servers EC2InstanceType: default: EC2 instance type for servers EC2AmiId: default: Latest AMI ID for client and server (ec2 instance) EC2DiskSize: default: EC2 instance size in GB for servers ElbName: default: Elastic Load Balancer Name ElbScheme: default: Elastic Load Balancer Scheme ElbType: default: Elastic Load Balancer Type TargetGroupName: default: Target Group Name ListenerPort: default: Listener Port ListenerProtocol: default: Listener Protocol DstS3BucketName: default: Destination S3 Bucket Name CreateDstS3BucketCondition: default: Create S3 bucket condition TargetFQDN: default: Fully Qualified Domain Name (FQDN) used for managing your application cluster DnsServers: default: Domain Name Server IP address to query MaxLookupPerInvocation: default: The max times of DNS look per invocation InvocationBeforeRegistration: default: The number of required Invocations before a IP is deregistered ReportIpCountCwMetric: default: Enable/Disable Hostname IP count CloudWatch metric RemoveUntrackedTgIp: default: Remove IPs that were not added by the fucntion CompositeAlarmSnsEmail: default: Email for SNS Topic for Composite Alarm CreateAlarmCondition: default: Create CloudWatch Alarm condition Parameters: VpcCidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.0.0.0/16 Description: CIDR block for the VPC Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PublicAZ1: Description: Availability Zone to use for PublicSubnet1 in the VPC. Type: AWS::EC2::AvailabilityZone::Name ConstraintDescription: Must be a valid availability zone PrivateAZ1: Description: Availability Zone to use for PrivateSubnet1 in the VPC. Type: AWS::EC2::AvailabilityZone::Name ConstraintDescription: Must be a valid availability zone PrivateAZ2: Description: Availability Zone to use for PrivateSubnet2 in the VPC. Type: AWS::EC2::AvailabilityZone::Name ConstraintDescription: Must be a valid availability zone PublicSubnet1Cidr: Description: >- CIDR block for the PublicSubnet1 located in PublicAZ1. Allocate from VPC CIDR, else CloudFormation will fail. AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.0.1.0/24 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PrivateSubnet1Cidr: Description: >- CIDR block for the PrivateSubnet1 located in PrivateAZ1. Allocate from VPC CIDR, else CloudFormation will fail. AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.0.11.0/24 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 PrivateSubnet2Cidr: Description: >- CIDR block for the PrivateSubnet2 located in PrivateAZ2. Allocate from VPC CIDR, else CloudFormation will fail. AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.0.12.0/24 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 KeyPairName: Description: EC2 KeyPair required for accessing EC2 instance Type: AWS::EC2::KeyPair::KeyName ConstraintDescription: Must be the name of an existing EC2 KeyPair. SSHLocation: Description: >- Enter desired network CIDR to access servers. Default is 0.0.0.0/0. 0.0.0.0/0 is not recommended, change it to CIDR of your choice. AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" MinLength: '9' MaxLength: '18' Default: 0.0.0.0/0 Type: String ConstraintDescription: Must be a valid Network CIDR of the form x.x.x.x/y EC2InstanceType: Description: >- Select EC2 instance type for server instances. Default is c5n.9xlarge Default: t2.micro AllowedValues: [t2.micro] Type: String EC2AmiId: Type: AWS::SSM::Parameter::Value Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' EC2DiskSize: Description: >- Enter desired disk size in GB for server instances. Default is 8GB Default: 8 AllowedValues: [8] Type: Number ConstraintDescription: Should be a valid instance size in GB ElbName: Description: >- Elastic Load balancer name. This name must be unique within your AWS account and can have a maximum of 32 alphanumeric characters and hyphens. A name cannot begin or end with a hyphen. Type: String Default: "Nlb1" ConstraintDescription: Must be a valid ELB Name ElbScheme: Description: >- Specify Elastic Load Balancer scheme. Valid values are internet-facing and internal. Default is internet-facing Default: internet-facing AllowedValues: ['internet-facing', 'internal'] Type: String ConstraintDescription: Must be a valid ELB scheme ElbType: Description: 'Specify Elastic Load Balancer Type: application | network' Default: network AllowedValues: ['application', 'network'] Type: String ConstraintDescription: Must be a valid Elastic Load Balancer Type TargetGroupName: Description: Target Group Name Type: String Default: 'tg1' ConstraintDescription: Must be a valid target group name ListenerPort: Description: The port on which the load balancer is listening. Default: 80 Type: Number ConstraintDescription: Must be a valid port number ListenerProtocol: Description: >- The protocol for connections from clients to the load balancer. For Application Load Balancers, the supported protocols are HTTP and HTTPS. For Network Load Balancers, the supported protocols are TCP, TLS, UDP, and TCP_UDP. AllowedValues: ['HTTP', 'HTTPS', 'TCP', 'TCP_UDP', 'TLS', 'UDP'] Default: TCP Type: String ConstraintDescription: Must be a valid protocol DstS3BucketName: Description: >- Destination S3 bucket name. Required for this stack. If using existing bucket, set CreateDstS3BucketCondition parameter to No, else set it to Yes. Type: String ConstraintDescription: Must be globally unique S3 bucket name. CreateDstS3BucketCondition: Description: >- Do you want to create new S3 bucket or use an existing one? If using an existing bucket, verify it is in the same region as the region from where you are launching this stack (launch stack region). Default: "No" AllowedValues: ["Yes", "No"] Type: String ConstraintDescription: Must be a valid Yes or No option TargetFQDN: Description: Full Qualified Domain Name (FQDN) used for managing your application cluster Default: test.example.com Type: String ConstraintDescription: Must be a valid FQDN DnsServers: Description: >- The IP address of domain name server that receives the query. You can provide IP address of custom DNS server(s). If you intend to use Amazon provided DNS as the domain name server, Lambda function should be connected to VPC. Specify mulitple servers separted by ',': '10.10.10.10, 10.10.10.11' Type: String MaxLookupPerInvocation: Description: >- The max times of DNS look per invocation Type: Number Default: 10 ConstraintDescription: Must be a valid integer value InvocationBeforeRegistration: Description: >- The number of required Invocations before a IP is deregistered Type: Number Default: 3 ConstraintDescription: 'Must be a valid integer value' ReportIpCountCwMetric: Description: >- Enable/Disable Hostname IP count CloudWatch metric Type: String Default: true AllowedValues: [true, false] ConstraintDescription: Must be True or False RemoveUntrackedTgIp: Description: >- Remove IPs that were not added by the fucntion Type: String Default: false AllowedValues: [true, false] ConstraintDescription: Must be True or False CreateAlarmCondition: Description: >- Do you want to create CloudWatch Alarm for Lambda? Default: "Yes" AllowedValues: ["Yes", "No"] Type: String ConstraintDescription: Must be a valid Yes or No option CompositeAlarmSnsEmail: Description: Email for SNS Topic for Composite Alarm Type: String ConstraintDescription: Must be a valid email. Conditions: CreateDstS3Bucket: !Equals - !Ref CreateDstS3BucketCondition - "Yes" CreateAlarms: !Equals - !Ref CreateAlarmCondition - "Yes" Resources: S3BucketForLambda: Condition: CreateDstS3Bucket Type: AWS::S3::Bucket Properties: BucketName: !Ref DstS3BucketName Tags: - Key: Name Value: ElbLambdaSol-S3Bucket VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: 'default' Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-vpc' InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-igw' AttachInternetGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway Eip1: Type: AWS::EC2::EIP Properties: Domain: VPC NatGateway1: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt - Eip1 - AllocationId SubnetId: !Ref PublicSubnet1 Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-ngw1' PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref PublicAZ1 CidrBlock: !Ref PublicSubnet1Cidr VpcId: !Ref VPC MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-pubsub1' PrivateSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref PrivateAZ1 CidrBlock: !Ref PrivateSubnet1Cidr VpcId: !Ref VPC MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-prisub1' PrivateSubnet2: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref PrivateAZ2 CidrBlock: !Ref PrivateSubnet2Cidr VpcId: !Ref VPC MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-prisub2' PublicRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-pubrtb1' PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-prirtb1' PublicRoute1: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway RouteTableId: !Ref PublicRouteTable1 PrivateRoute1: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway1 RouteTableId: !Ref PrivateRouteTable1 PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable1 PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable1 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable1 BastionSg: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VPC GroupName: !Join - '' - - !Ref 'AWS::StackName' - '-bastionsg' GroupDescription: 'Bastion Security Group' SecurityGroupIngress: - CidrIp: !Ref SSHLocation IpProtocol: tcp FromPort: 22 ToPort: 22 - CidrIp: !Ref SSHLocation IpProtocol: tcp FromPort: 80 ToPort: 80 - CidrIp: !Ref SSHLocation IpProtocol: ICMP FromPort: -1 ToPort: -1 - CidrIp: !Ref VpcCidr IpProtocol: '-1' FromPort: -1 ToPort: -1 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: '-1' FromPort: -1 ToPort: -1 Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-bastonsg' AppSg: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VPC GroupName: !Join - '' - - !Ref 'AWS::StackName' - '-appsg' GroupDescription: 'Application Securit Group' SecurityGroupIngress: - SourceSecurityGroupId: !Ref BastionSg IpProtocol: tcp FromPort: 22 ToPort: 22 - SourceSecurityGroupId: !Ref BastionSg IpProtocol: tcp FromPort: 80 ToPort: 80 - SourceSecurityGroupId: !Ref BastionSg IpProtocol: ICMP FromPort: -1 ToPort: -1 - CidrIp: !Ref VpcCidr IpProtocol: '-1' FromPort: -1 ToPort: -1 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: '-1' FromPort: -1 ToPort: -1 Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-appsg' LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: root PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:DeleteObject - s3:CreateBucket - s3:DeleteBucket Resource: !Join - "" - - 'arn:aws:s3:::' - !Ref DstS3BucketName - '/*' - Effect: Allow Action: - s3:ListAllMyBuckets - cloudwatch:PutMetricData - elasticloadbalancing:RegisterTargets - elasticloadbalancing:DeregisterTargets - elasticloadbalancing:DescribeTargetHealth - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface Resource: "*" S3ObjectLambda: Type: AWS::Lambda::Function Properties: Handler: 'index.handler' Role: !GetAtt - LambdaExecutionRole - Arn Code: ZipFile: | import json import logging import os import sys import urllib.request from urllib.parse import urlparse import boto3 import cfnresponse from botocore.exceptions import ClientError try: s3_resource = boto3.resource('s3') s3_client = boto3.client('s3') except ClientError as e: logger.error(f"ERROR: failed to connect to S3 resource or client: {e}") sys.exit(1) def obj_info(url): url_path = urlparse(url).path obj_name = os.path.basename(url_path) obj_path = f'/tmp/{obj_name}' return obj_name, obj_path def upload_to_s3(url, dst_bucket, obj_path, obj_name): urllib.request.urlretrieve(url, obj_path) s3_resource.Bucket(dst_bucket).upload_file(Filename=obj_path, Key=obj_name) def handler(event, context): logger = logging.getLogger() logger.setLevel(logging.INFO) logger.info("INFO: Received event: {}".format(json.dumps(event))) responseData = {} responseStatus = cfnresponse.FAILED print(event["ResourceProperties"]) try: src_url = event["ResourceProperties"]["SourceUrl"] dst_s3 = event["ResourceProperties"]["DstS3Bucket"] aws_region = event["ResourceProperties"]["AwsRegion"] except Exception as e: logger.error(f"parameter retival failure: {e}") sys.exit(1) if isinstance(dst_s3, list): dst_s3 = dst_s3[0] obj_name = obj_info(src_url)[0] obj_path = obj_info(src_url)[1] try: if event["RequestType"] == "Delete": s3_resource.Object(dst_s3, obj_name).delete() responseStatus = cfnresponse.SUCCESS cfnresponse.send(event, context, responseStatus, responseData) except Exception: logger.exception("Signaling failure to CloudFormation.") cfnresponse.send(event, context, cfnresponse.FAILED, {}) if event["RequestType"] == "Create": logger.info(f"INFO: Copying {obj_name} to {dst_s3}") upload_to_s3(src_url, dst_s3, obj_path, obj_name) responseStatus = cfnresponse.SUCCESS cfnresponse.send(event, context, responseStatus, responseData) Runtime: python3.7 Timeout: 45 S3EditObject: Type: Custom::S3EditObject Properties: ServiceToken: !GetAtt S3ObjectLambda.Arn SourceUrl: https://github.com/aws-samples/hostname-as-target-for-elastic-load-balancer/blob/main/source/ElbHostnameAsTarget.zip?raw=true DstS3Bucket: !If [CreateDstS3Bucket, !Ref S3BucketForLambda, !Ref DstS3BucketName] AwsRegion: !Ref AWS::Region Server1: DependsOn: ['NatGateway1'] Type: AWS::EC2::Instance Metadata: Comment1: "Configure Simple Webserver" Comment2: "Save website content to /var/www/html/index.html" AWS::CloudFormation::Init: configSets: Install: - "Install" Install: files: /var/www/html/index.html: content: !Sub | AWS Elb Demo

Welcome to Elastic Load Balancer Hostname as a Target Demo

This is a simple webserver running in ${PrivateAZ1}

mode: "000755" owner: "ec2-user" group: "ec2-user" Properties: ImageId: !Ref EC2AmiId KeyName: !Ref KeyPairName InstanceType: !Ref EC2InstanceType SecurityGroupIds: - !Ref BastionSg SubnetId: !Ref PrivateSubnet1 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref EC2DiskSize Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-server1' UserData: Fn::Base64: !Sub | #!/bin/bash -xe yum update -y; yum install -y aws-cfn-bootstrap yum install httpd -y systemctl start httpd hostnamectl set-hostname server1; echo 'ClientAliveInterval 60' | sudo tee --append /etc/ssh/sshd_config; service sshd restart; touch /home/ec2-user/.vimrc; echo "set background=dark" >> /home/ec2-user/.vimrc; # Install the files from the metadata /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --configsets Install --resource Server1 --region ${AWS::Region} Server2: DependsOn: ['NatGateway1'] Type: AWS::EC2::Instance Metadata: Comment1: "Configure Simple Webserver" Comment2: "Save website content to /var/www/html/index.html" AWS::CloudFormation::Init: configSets: Install: - "Install" Install: files: /var/www/html/index.html: content: !Sub | AWS Elb Demo

Welcome to AWS Elb Demo

This is a simple webserver running in ${PrivateAZ2}

mode: "000755" owner: "ec2-user" group: "ec2-user" Properties: ImageId: !Ref EC2AmiId KeyName: !Ref KeyPairName InstanceType: !Ref EC2InstanceType SecurityGroupIds: - !Ref BastionSg SubnetId: !Ref PrivateSubnet2 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref EC2DiskSize Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-server2' UserData: Fn::Base64: !Sub | #!/bin/bash -xe yum update -y; yum install -y aws-cfn-bootstrap yum install httpd -y systemctl start httpd hostnamectl set-hostname server2; echo 'ClientAliveInterval 60' | sudo tee --append /etc/ssh/sshd_config; service sshd restart; touch /home/ec2-user/.vimrc; echo "set background=dark" >> /home/ec2-user/.vimrc; # Install the files from the metadata /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --configsets Install --resource Server2 --region ${AWS::Region} Client1: Type: AWS::EC2::Instance Properties: ImageId: !Ref EC2AmiId KeyName: !Ref KeyPairName InstanceType: !Ref EC2InstanceType SecurityGroupIds: - !Ref BastionSg SubnetId: !Ref PublicSubnet1 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref EC2DiskSize Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-client1' UserData: Fn::Base64: | #!/bin/bash -xe yum update -y; yum install -y aws-cfn-bootstrap hostnamectl set-hostname client1; echo 'ClientAliveInterval 60' | sudo tee --append /etc/ssh/sshd_config; service sshd restart; touch /home/ec2-user/.vimrc; echo "set background=dark" >> /home/ec2-user/.vimrc; Dns: Type: AWS::Route53::HostedZone Properties: HostedZoneConfig: Comment: My hosted zone for example.com Name: example.com VPCs: - VPCId: !Ref VPC VPCRegion: !Ref AWS::Region DnsRecord1: Type: AWS::Route53::RecordSet Properties: HostedZoneId: !Ref Dns Name: test.example.com. Type: A TTL: '900' ResourceRecords: - !GetAtt Server1.PrivateIp - !GetAtt Server2.PrivateIp ElbHostnameTarget: Type: AWS::Lambda::Function Properties: Handler: "elb_hostname_as_target.lambda_handler" Role: !GetAtt - LambdaExecutionRole - Arn Code: S3Bucket: !If [CreateDstS3Bucket, !Ref S3BucketForLambda, !Ref DstS3BucketName] S3Key: ElbHostnameAsTarget.zip Environment: Variables: TARGET_FQDN: !Ref TargetFQDN ELB_TG_ARN: !Ref TargetGroup1 S3_BUCKET: !If [CreateDstS3Bucket, !Ref S3BucketForLambda, !Ref DstS3BucketName] DNS_SERVER: !Ref DnsServers BUCKET_REGION: !Ref AWS::Region MAX_LOOKUP_PER_INVOCATION: !Ref MaxLookupPerInvocation INVOCATIONS_BEFORE_DEREGISTRATION: !Ref InvocationBeforeRegistration REPORT_IP_COUNT_CW_METRIC: !Ref ReportIpCountCwMetric REMOVE_UNTRACKED_TG_IP: !Ref RemoveUntrackedTgIp Runtime: python3.7 Timeout: 45 VpcConfig: SecurityGroupIds: - !Ref AppSg SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 LambdaEventRule: Type: AWS::Events::Rule Properties: Description: ScheduledRule Name: ElbHostnameAsTargetEventbridgeTrigger ScheduleExpression: rate(5 minutes) State: ENABLED Targets: - Arn: !GetAtt ElbHostnameTarget.Arn Id: ElbHostnameTargetV1 PermissionForEventsToInvokeLambda: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref ElbHostnameTarget Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt LambdaEventRule.Arn Elb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Ref ElbName Scheme: !Ref ElbScheme Type: !Ref ElbType Subnets: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-Elb1' TargetGroup1: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Ref TargetGroupName Port: !Ref ListenerPort Protocol: !Ref ListenerProtocol TargetType: ip TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 20 VpcId: !Ref VPC Tags: - Key: Name Value: !Join - '' - - !Ref 'AWS::StackName' - '-tg1' Listener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: Port: !Ref ListenerPort Protocol: !Ref ListenerProtocol DefaultActions: - Type: forward TargetGroupArn: !Ref TargetGroup1 LoadBalancerArn: !Ref Elb SnsTopic: Condition: CreateAlarms Type: AWS::SNS::Topic Properties: Subscription: - Endpoint: !Ref CompositeAlarmSnsEmail Protocol: email TopicName: !Join - '' - - !Ref ElbHostnameTarget - '-sns-topic' LambdaInvocationsAlarm: Condition: CreateAlarms Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: Lambda invocations. Alarms when function invocation reports an error AlarmName: !Join - '' - - !Ref ElbHostnameTarget - '-invocations' MetricName: Invocations Namespace: AWS/Lambda Dimensions: - Name: FunctionName Value: !Ref ElbHostnameTarget Period: 60 EvaluationPeriods: 1 DatapointsToAlarm: 1 Threshold: 1 ComparisonOperator: LessThanThreshold Statistic: Sum TreatMissingData: breaching LambdaErrorsAlarm: Condition: CreateAlarms Type: AWS::CloudWatch::Alarm Properties: AlarmDescription: Lambda errors. Alarm when function reports an error AlarmName: !Join - '' - - !Ref ElbHostnameTarget - '-errors' MetricName: Errors Namespace: AWS/Lambda Dimensions: - Name: FunctionName Value: !Ref ElbHostnameTarget Period: 60 EvaluationPeriods: 1 DatapointsToAlarm: 1 Threshold: 0 ComparisonOperator: GreaterThanThreshold Statistic: Sum TreatMissingData: breaching LambdaCompositeAlarm: Condition: CreateAlarms Type: AWS::CloudWatch::CompositeAlarm DependsOn: - LambdaInvocationsAlarm - LambdaErrorsAlarm Properties: AlarmName: !Join - '' - - !Ref ElbHostnameTarget - '-monitor' AlarmRule: !Sub "(ALARM(${LambdaErrorsAlarm}) OR ALARM(${LambdaInvocationsAlarm}))" AlarmActions: - !Ref SnsTopic AlarmDescription: Lambda composite. Alarm triggers when either of the metric alarms trigger Outputs: Server1PrivateIp: Description: Server1 private ip Value: Fn::GetAtt: - Server1 - PrivateIp Server2PrivateIp: Description: Server2 private ip Value: !GetAtt Server2.PrivateIp Client1PublicIp: Description: Client1 public ip Value: !GetAtt Client1.PublicIp Client1PrivateIp: Description: Client1 private ip Value: !GetAtt Client1.PrivateIp Client1DNS: Description: Client1 public DNS Value: !GetAtt Client1.PublicDnsName ElbArn: Description: Elastic Load Balancer ARN Value: !Ref Elb Export: Name: !Sub "${AWS::StackName}-ElbArn" TargetGroupArn: Description: 'TargetGroup ARN' Value: !Ref TargetGroup1 Export: Name: !Sub "${AWS::StackName}-TargetGroupArn" TestPhz: Description: Test Private Hosted Zone Value: !Ref Dns S3ObjectLambdaArn: Description: S3Object Lambda ARN Value: !GetAtt S3ObjectLambda.Arn ElbHostnameTargetArn: Description: ElbHostnameTarget Lambda ARN Value: !GetAtt ElbHostnameTarget.Arn ElbLambdaInvocationsAlarm: Condition: CreateAlarms Description: 'ELB Lambda Invocations Alarm' Value: !GetAtt LambdaInvocationsAlarm.Arn ElbLambdaErrorsAlarm: Condition: CreateAlarms Description: 'ELB Lambda Errors Alarm' Value: !GetAtt LambdaErrorsAlarm.Arn ElbLambdaCompositeAlarm: Condition: CreateAlarms Description: 'ELB Lambda Composite Alarm' Value: !GetAtt LambdaCompositeAlarm.Arn