2
votes

I have created a Elastic Beanstalk and CloudWatch Alarm in CloudFormation with the following code snippet:

        "ElasticBeanstalkEnvironment": {
        "Type": "AWS::ElasticBeanstalk::Environment",
        "Properties": {
            "ApplicationName": "my-app",
            "EnvironmentName": "my-eb",
            "SolutionStackName": "64bit Amazon Linux 2018.03 v3.0.1 running Tomcat 8 Java 8",
            "OptionSettings": [
                {
                    "Namespace": "aws:elb:loadbalancer",
                    "OptionName": "CrossZone",
                    "Value": "true"
                },
                {
                    "Namespace": "aws:elb:listener:80",
                    "OptionName": "ListenerProtocol",
                    "Value": "HTTP"
                },
                {
                    "Namespace": "aws:elb:listener:80",
                    "OptionName": "InstancePort",
                    "Value": "80"
                },
                etc...
            ]
        },
        "CloudWatchBacken500XXAlarm": {
            "Type": "AWS::CloudWatch::Alarm",
            "Properties" : {
                "AlarmActions": ["arn:aws:sns:us-east-1:12345678:mysnstopic"],
                "Namespace": "AWS/ELB",
                "Dimensions": [{
                    "Name": "LoadBalancerName",
                    "Value" : {
                        "Fn::GetAtt": [
                            "ElasticBeanstalkEnvironment",
                            "EndpointURL"  
                        ]
                    }
                  }],
                "MetricName": "HTTPCode_Backend_5XX",
                "Statistic": "Sum",
                "Period": "60",
                "EvaluationPeriods": "1",
                "ComparisonOperator": "GreaterThanOrEqualToThreshold",
                "Threshold": "1"
                }
        }

You can see that the CloudWatch Alarm is configured to alert if the Elastic Beanstalk's load balancer receives 5XX Errors. However I am not able to get the load balancer Name attribute which would look something like this:

awseb-e-a-AWSEBLoa-AY8LC6V30OAW

Instead the Fn::GetAtt("EndpointURL") attribute returns the load balancer's DNSName which looks something like this:

awseb-e-a-AWSEBLoa-AY8LC6V30OAW-175133046.us-east-1.elb.amazonaws.com

Which will fail to create the CloudWatch alarm correctly as it expects to get the load balancer Name not DNSName.

What's the best way to get the Load Balancer's Name? I don't want to have to create the Load Balancer as an external resource like "AWS::ElasticLoadBalancing::LoadBalancer" or try to use some substring method to extract the Name string from the DNSName string.

4

4 Answers

1
votes

OK I found a solution, its quite the hack, I have been able to extract the LoadBalancerName from the endpoint using several Split/Select/Join calls on the DNSName string returned from - Fn::GetAtt("EndpointURL"). Here is the code:

"CloudWatchBackend500XXAlarm": {
"Type": "AWS::CloudWatch::Alarm",
"Properties" : {
    "AlarmDescription": "Elastic Beanstalk Has Received 5XX Backend Connection Errors",
    "AlarmActions": ["arn:aws:sns:us-east-1:1234567890:mysnstopic"],
    "Namespace": "AWS/ELB",
    "Dimensions": [
        {
            "Name": "LoadBalancerName",
            "Value": {
                "Fn::Join": ["", [{
                    "Fn::Select": [
                        "0",
                        {
                            "Fn::Split": ["AWSEBLoa-",
                                {
                                    "Fn::GetAtt": [
                                        "ElasticBeanstalkEnvironment",
                                        "EndpointURL"
                                    ]
                                }
                            ]
                        }
                    ]
                },
                "AWSEBLoa-",
                {
                    "Fn::Select": [
                        "0",
                        {
                            "Fn::Split": ["-",
                                {
                                    "Fn::Select": [
                                        "1",
                                        {
                                            "Fn::Split": ["AWSEBLoa-",
                                                {
                                                    "Fn::GetAtt": [
                                                        "ElasticBeanstalkEnvironment",
                                                        "EndpointURL"
                                                    ]
                                                }
                                            ]
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]]
        }
        }
    ],                  
    "MetricName": "HTTPCode_Backend_5XX",
    "Statistic": "Sum",
    "Period": "60",
    "EvaluationPeriods": "1",
    "ComparisonOperator": "GreaterThanOrEqualToThreshold",
    "Threshold": "1"
}

}

ie the code above is able to extract 'awseb-k-3-AWSEBLoa-11B26NY4PQB9A' from 'awseb-k-3-AWSEBLoa-11B26NY4PQB9A-739614614.us-east-1.elb.amazonaws.com' and the CloudWatch alarm works perfectly. I think I would have to adjust the code to work with internal load balancers, ie formatted with 'internal-' at the start of the DNSName but I am fine for now.

I've submitted a request to AWS asking them to add a CloudFormation method to return the LoadBalancer Name and not just the DNSName.

1
votes

Using ebextensions would be much easier to create an alarm - as fetching the ELB name is as easy as Value: { "Ref" : "AWSEBLoadBalancer" }

Create a file with .config extension (say - BackendErrors.config) and place it in a folder with name ".ebextensions". Complete ebextension will be as follows:

Resources:
  CloudWatchBacken500XXAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmDescription: "Elastic Beanstalk Has Received 5XX Backend Connection Errors"
      AlarmName: { "Fn::Join" : ["", [{ "Ref" : "AWSEBEnvironmentName" }, "-Backend-5XX-Alarm." ]]}
      AlarmActions:
        - "arn:aws:sns:us-east-1:123456789012:mysnstopic"
      Namespace: AWS/ELB
      Dimensions:
        - Name: LoadBalancerName
          Value: { "Ref" : "AWSEBLoadBalancer" }
      MetricName: HTTPCode_Backend_5XX
      Statistic: Sum
      Period: 60
      EvaluationPeriods: 1
      Threshold: 1
      ComparisonOperator: GreaterThanOrEqualToThreshold

The .ebextensions folder should be created at the top level of your application source bundle:

~/workspace/my-application/
|-- .ebextensions
|   |-- BackendErrors.config
0
votes

If you have access to the environment name, you can perform a DescribeEnvironmentResources API call. The response would include information about the loadbalancer of your environment.

0
votes

Coming here a few years later, it looks like Amazon have added the attribute now :

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-loadbalancer.html

To quote:

LoadBalancerFullName

The full name of the load balancer. For example, app/my-load-balancer/50dc6c495c0c9188.

So you can now do :

  Dimensions:
    - Name: LoadBalancer
      Value: !GetAtt YourALBRef.LoadBalancerFullName