Search code examples
amazon-web-servicesamazon-elastic-beanstalkaws-cloudformationamazon-route53

How to alias a domain name to an Elastic Beanstalk Environment using CloudFormation?


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.

Solution

  • 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.