15
votes

What is the correct Route 53 CloudFormation configuration to alias sub-domain names to an Elastic Beanstalk Environment ELBs?

I have copied the HostedZoneIds from the Amazon Route 53 Hosted Zone ID table to mappings:

"Beanstalk2Route53HostedZoneId" : {
  "us-east-1"      : { "HostedZoneId": "Z117KPS5GTRQ2G" },
  "us-west-1"      : { "HostedZoneId": "Z1LQECGX5PH1X" },
  "us-west-2"      : { "HostedZoneId": "Z38NKT9BP95V3O" },
  "eu-west-1"      : { "HostedZoneId": "Z2NYPWQ7DFZAZH" },
  "eu-central-1"   : { "HostedZoneId": "Z1FRNW7UH4DEZJ" },
  "ap-northeast-1" : { "HostedZoneId": "Z1R25G3KIG2GBW" },
  "ap-northeast-2" : { "HostedZoneId": "Z3JE5OI70TWKCP" },
  "ap-southeast-1" : { "HostedZoneId": "Z16FZ9L249IFLT" },
  "ap-southeast-2" : { "HostedZoneId": "Z2PCDNR3VC2G1N" },
  "sa-east-1"      : { "HostedZoneId": "Z10X7K2B4QSOFV" }
}

My resources have two Beanstalk Environments:

"MyBeanstalkConfig": {
  "Type": "AWS::ElasticBeanstalk::ConfigurationTemplate",
  "Properties": {
    "OptionSettings": {
      { "Namespace": "aws:elb:listener:80", "OptionName": "ListenerEnabled", "Value" : "false" },
      { "Namespace": "aws:elb:listener:443", "OptionName": "ListenerEnabled", "Value" : "true" },
      { "Namespace": "aws:elb:listener:443", "OptionName": "InstancePort", "Value" : "8081" },
      { "Namespace": "aws:elb:listener:443", "OptionName": "ListenerProtocol", "Value" : "HTTPS" },
      { "Namespace": "aws:elb:listener:443", "OptionName": "SSLCertificateId", "Value" : "arn:aws:iam::[accountNbr]:server-certificate/example-cert-name" },
      [...]
    }
  }
},

"MyStageBeanstalkEnv": {
  "Type": "AWS::ElasticBeanstalk::Environment",
  "Properties": {
    "Description": "Stage Environment",
    "TemplateName": { "Ref": "MyBeanstalkConfig" },
    [...]
  }
},

"MyProdBeanstalkEnv": {
  "Type": "AWS::ElasticBeanstalk::Environment",
  "Properties": {
    "Description": "Production Environment",
    "TemplateName": { "Ref": "MyBeanstalkConfig" },
    [...]
  }
},

Outputs:

"StageEndpoint" : {
  "Description" : "endpoint of the stage environment",
  "Value" : { "Fn::GetAtt" : [ "MyStageBeanstalkEnv", "EndpointURL" ] }
},
"ProdEndpoint" : {
  "Description" : "endpoint of the production environment",
  "Value" : { "Fn::GetAtt" : [ "MyProdBeanstalkEnv", "EndpointURL" ] }
}

Both the stage and the prod Beanstalk Environments are working, i.e. they respond to calls to MyStageBeanstalkEnv.eu-west-1.elasticbeanstalk.com as well as the endpoints returned by { "Fn::GetAtt" : [ "MyStageBeanstalkEnv", "EndpointURL" ] } (which look like awseb-[abc-123-xyz].eu-west-1.elb.amazonaws.com). Unsurprisingly, the cert is not valid since it expects the domain name to be either stage.example.com or prod.example.com.


Now I attempt to add Route 53 configuration:

"ExampleDomainHostedZone": {
  "Type" : "AWS::Route53::HostedZone",
  "Properties" : {
    "Name" : "example.com"
  }
},

"ExampleDomainRecordSetGroup" : {
  "Type" : "AWS::Route53::RecordSetGroup",
  "Properties" : {
    "HostedZoneId" : { "Ref": "ExampleDomainHostedZone" },
    "RecordSets" : [{
      "AliasTarget" : {
        "DNSName" : { "Fn::GetAtt" : ["MyStageBeanstalkEnv", "EndpointURL"] },
        "EvaluateTargetHealth" : false,
        "HostedZoneId" : { "Fn::FindInMap" : [ "Beanstalk2Route53HostedZoneId", {"Ref" : "AWS::Region"}, "HostedZoneId" ]}
      },
      "Name" : "stage.example.com",
      "Type": "A"
    },
    {
      "AliasTarget" : {
        "DNSName" : { "Fn::GetAtt" : ["MyProdBeanstalkEnv", "EndpointURL"] },
        "EvaluateTargetHealth" : false,
        "HostedZoneId" : { "Fn::FindInMap" : [ "Beanstalk2Route53HostedZoneId", {"Ref" : "AWS::Region"}, "HostedZoneId" ]}
      },
      "Name" : "prod.example.com",
      "Type": "A"
    }]
  }
},

When I attempt to update the CloudFormation stack I get the following error in the AWS console:

16:12:00 UTC+0200 CREATE_FAILED AWS::Route53::RecordSetGroup ExampleDomainRecordSetGroup Tried to create an alias that targets awseb-[abc-123-xyz].eu-west-1.elb.amazonaws.com., type A in zone Z2NYPWQ7DFZAZH, but the alias target name does not lie within the target zone

In this context, awseb-[abc-123-xyz].eu-west-1.elb.amazonaws.com is the same URL as provided by the Beanstalk ELB.


Comments:

  • I have successfully managed to setup Route 53 alias resource record to the same Beanstalk Environments in the AWS console following the description To add an alias resource record set in Amazon Route 53, so it is "just" a question about transferring these configuration steps to the CloudFormation template.
  • The stack is deployed in eu-west-1.
  • Instead of using the AWS::Route53::RecordSetGroup resource I have also tried to create two separate AWS::Route53::RecordSet resources, but the stack update failed with the same error.
3

3 Answers

10
votes

Some Googling hinted that (1, 2) the Amazon Route 53 Hosted Zone ID cannot be used when configuring alias. It is stated that eu-west-1 has the Hosted Zone ID Z2NYPWQ7DFZAZH for the elasticbeanstalk.eu-west-1.amazonaws.com endpoint. However, when double-checking the ELB configuration of what Beanstalk actually generated by using the AWS CLI I found that:

$ aws elb describe-load-balancers --region eu-west-1
{
    "LoadBalancerDescriptions": [
        {
            [...]
            "CanonicalHostedZoneNameID": "Z3NF1Z3NOM5OY2",
            "CanonicalHostedZoneName": "awseb-[abc-123-xyz].eu-west-1.elb.amazonaws.com",
        }
    ]
}

In other words, the hosted zone name id is different. Moreover, the CanonicalHostedZoneName is equal to the DNSName in the AliasTarget, i.e. awseb-[abc-123-xyz].eu-west-1.elb.amazonaws.com, which is not the same endpoint as elasticbeanstalk.eu-west-1.amazonaws.com used in Amazon Route 53 Hosted Zone ID. Consequently, I changed the mappings to include the CanonicalHostedZoneNameID provided from the CLI output:

"Beanstalk2Route53HostedZoneId" : {
  "eu-west-1" : { "HostedZoneId": "Z3NF1Z3NOM5OY2" }
}

Now the stack could be updated successfully. Regrettably only for eu-west-1, but the procedure can be updated if / when I deploy the stack to other regions.

After the stack was updated, there was still no response from the DNS names (stable.example.com and unstable.example.com). Updating Your Registrar's Name Servers solved that problem.

7
votes

I had same problem with Route53 Alias for CloudFront. I build HostedZOneId mapping but it never worked for me, by searching AWS documentation I find this:

The hosted zone ID.

For load balancers, use the canonical hosted zone ID of the load balancer.

For Amazon S3, use the hosted zone ID for your bucket's website endpoint.

For CloudFront, use Z2FDTNDATAQYW2.

For a list of hosted zone IDs of other services, see the relevant service in the AWS Regions and Endpoints.

So I just hard-coded it:

"AliasTarget": {
    "HostedZoneId": "Z2FDTNDATAQYW2",
    "DNSName": {
        "Fn::GetAtt": ["MyCloudFrontDistribution", "DomainName"]
    }
}

http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html

0
votes

There are 2 ways to connect to ElasticBeanstalk from Route53 via Alias:

  1. using the ElasticBeanstalk endpoint, eg foobar.u8k2abcm4.eu-central-1.elasticbeanstalk.com/
  2. using the endpoint of the LoadBalancer from the ElasticBeanstalk environment, eg awseb-[abc-123-xyz].eu-west-1.elb.amazonaws.com

The value which is returned from "Fn::GetAtt" : ["BeanstalkEnvironment", "EndpointURL"] is the LoadBalancer Endpoint (docs), therefor the LoadBalancer Mapping Table must be used, see here (and not the one for ElasticBeanstalk).

Not sure if/how it works in cases where the ElasticBeanstalk Env is not load balanced.