Self-registration of infrastructure with Terraform

Splitting infrastructure between multiple modules for self-registration

Table Of Contents

Today I Explained

Although Terraform uses the concept of modules as a container for multiple resources, this doesn’t mean that the entire infrastructure of an application must be contained within a single state. In fact, for practical reasons it is often necessary to split resources across multiple state files.

One of the reasons that you might have for splitting them up is that your architecture isn’t suited to the single monolithic approach. An example of this is delegating subdomains across multiple AWS Account using Route53. To enable subdomain delegation, it is necessary for the namespace records (NS) of the subdomain hosted zone to be created within the top level hosted zone.

┌────────────────┐    ┌─────────────┐
│   aeydr.dev    │  ┌─┤foo.aeydr.dev│
├────────────────┤  │ └─────────────┘
│NS foo.aeydr.dev│◄─┘
│                │    ┌─────────────┐
│NS bar.aeydr.dev│◄───┤bar.aeydr.dev│
└────────────────┘    └─────────────┘

Three boxes, with two smaller boxes containing foo.aeydr.dev and bar.aeydr.dev, with arrows pointing to a larger box named aeydr.dev. The larger box contains 2 records named foo.aeydr.dev and bar.aeydr.dev

This approach is known as the self-registration pattern. As new subdomains come online, they are responsible for registering themselves within the registry. In this case, the registry is the hosted zone, and the registration record is the namespace (NS) records. Using Terraform it is straightforward to create a subdomain hosted zone based on the top level domain:

data "aws_route53_zone" "registry" {
  provider = aws.registry

  name = var.root_domain
}

# Create the subdomain from the top level domain
resource "aws_route53_zone" "subdomain" {
  provider = aws.subdomain

  name = "${var.subdomain}.${data.aws_route53_zone.root.name}"
}

With the subdomain hosted zone created, it can then register itself with the top level domain using the extracted nameservers from the newly created hosted zone:

resource "aws_route53_record" "registration" {
  provider = aws.registry

  name    = aws_route53_zone.subdomain.name
  records = aws_route53_zone.subdomain.name_servers
  ttl     = var.ttl
  type    = "NS"
  zone_id = data.aws_route53_zone.registry.zone_id
}

A note on Pattern vs. As Is

Although the delegation for a subdomain with Route53 using this approach can be seen as self-evident, the reason to outline this approach is for it’s application in more complex patterns. Specifically with respect Transit Gateway and enabling private networking across many regions & AWS Accounts. Managing this kind of network using a single monolith can be frustrating as configuration can skew towards implementation over behaviour.

As an example, if the connections needed for networking are a complete graph, it makes sense to express that as a series of set operations or automatic registration with other nodes, rather than as a series of manually curated resources.