Source & Binary

Provision the demo service in staging

This section is a work in-progress and will be expanded.

We’re going to pick up where the getting started guide left your staging environment and provision a real service there.

Because AWS accounts are notoriously tedious to close, feel free to use a domain, environment, and quality that suit your organization instead of the placeholders used here.

Ensure the environment and quality you wish to use is a valid pair by re-running substrate-bootstrap-network-account. If you wish to follow the tutorial to the letter, allow beta-quality infrastructure in the staging environment.

Run substrate-create-account -domain="demo" -environment="staging" -quality="beta" to (re)create the AWS account and generate the structure of the Terraform code.

For this tutorial we’re going to use the DNS to route traffic but this could just as easily be done via your favorite edge load balancing and routing software.

Buy a domain name to use in staging or delegate a zone from a domain you already own. If you’re buying a domain name, buy it using Route 53 Domains in your demo staging alpha account.

In modules/demo/global/main.tf, add the following code (substituting the domain names and regions you’re using):

locals {
  domain_name = module.substrate.tags.environment == "production" ? "src-bin.co" : "src-bin.org"
}

resource "aws_acm_certificate" "demo" {
  domain_name       = "demo.${local.domain_name}"
  validation_method = "DNS"
}

resource "aws_acm_certificate_validation" "demo" {
  certificate_arn         = aws_acm_certificate.demo.arn
  validation_record_fqdns = [for record in aws_route53_record.domain-validation : record.fqdn]
}

resource "aws_route53_record" "demo" {
  for_each = toset(["us-east-2", "us-west-2"])

  latency_routing_policy {
    region = each.key
  }
  name           = "global"
  records        = ["${each.key}.${aws_route53_zone.demo.name}"]
  set_identifier = each.key
  ttl            = 1
  type           = "CNAME"
  zone_id        = aws_route53_zone.demo.zone_id
}

resource "aws_route53_record" "domain-validation" {
  for_each = {
    for dvo in aws_acm_certificate.demo.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  name    = each.value.name
  records = [each.value.record]
  ttl     = 60
  type    = each.value.type
  zone_id = data.aws_route53_zone.demo.zone_id
}

resource "aws_route53_zone" "demo" {
  name = "${module.substrate.tags.quality}.demo.${local.domain_name}"
}

In modules/demo/regional/main.tf, add the following code:

data "aws_region" "current" {}

data "aws_route53_zone" "demo" {
  name = "${module.substrate.tags.quality}.demo.${local.domain_name}"
}

locals {
  body        = <<EOF
<!doctype HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Substrate Demo</title>
</head>
<body>
<h1>Substrate Demo</h1>
<dl>
<dt>Domain</dt><dd>${module.substrate.tags.domain}</dd>
<dt>Environment</dt><dd>${module.substrate.tags.environment}</dd>
<dt>Quality</dt><dd>${module.substrate.tags.quality}</dd>
<dt>Region</dt><dd>${data.aws_region.current.name}</dd>
</dl>
</body>
</html>
EOF
  domain_name = module.substrate.tags.environment == "production" ? "src-bin.co" : "src-bin.org"
}

resource "aws_acm_certificate" "demo" {
  domain_name       = "demo.${local.domain_name}"
  validation_method = "DNS"
}

resource "aws_acm_certificate_validation" "demo" {
  certificate_arn         = aws_acm_certificate.demo.arn
  validation_record_fqdns = [for record in aws_route53_record.demo : record.fqdn]
}

resource "aws_lb" "demo" {
  internal           = false
  load_balancer_type = "application"
  name               = "demo"
  security_groups    = [aws_security_group.demo-alb.id]
  subnets            = module.substrate.private_subnet_ids
}

resource "aws_lb_listener" "http" {
  default_action {
    fixed_response {
      content_type = "text/html"
      message_body = local.body
      status_code  = 200
    }
    type = "fixed-response"
  }
  load_balancer_arn = aws_lb.demo.arn
  port              = 80
  protocol          = "HTTP"
}


resource "aws_lb_listener" "https" {
  certificate_arn = aws_acm_certificate_validation.demo.certificate_arn
  default_action {
    fixed_response {
      content_type = "text/html"
      message_body = local.body
      status_code  = 200
    }
    type = "fixed-response"
  }
  load_balancer_arn = aws_lb.demo.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-FS-1-2-Res-2020-10"
}

resource "aws_route53_record" "demo" {
  alias {
    evaluate_target_health = false
    name                   = aws_lb.demo.dns_name
    zone_id                = aws_lb.demo.zone_id
  }
  name    = data.aws_region.current.name
  type    = "A"
  zone_id = data.aws_route53_zone.demo.zone_id
}

resource "aws_security_group" "demo-alb" {
  ingress {
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP"
    from_port   = 80
    protocol    = "tcp"
    to_port     = 80
  }
  ingress {
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS"
    from_port   = 443
    protocol    = "tcp"
    to_port     = 443
  }
  name   = "demo-alb"
  vpc_id = module.substrate.vpc_id
}

In root-modules/demo/staging/alpha/main.tf, add the following code:

data "aws_route53_zone" "staging" {
  name = "${local.domain_name}."
}

locals {
  domain_name    = "src-bin.org"
}

resource "aws_route53_record" "CNAME-demo-alpha" {
  name           = "demo"
  records        = ["global.alpha.demo.${data.aws_route53_zone.staging.name}"]
  set_identifier = "alpha"
  ttl            = 1
  type           = "CNAME"
  weighted_routing_policy {
    weight = 100
  }
  zone_id = data.aws_route53_zone.staging.zone_id
}

Once more, run substrate-create-account -domain="demo" -environment="staging" -quality="beta", this time to run all your Terraform code to create the DNS records, certificate, and ALB we just described.

Visit https://demo.src-bin.org (substituting your domain name (or not)) to see the fruits of your labor.

Iterate on this to your heart’s content. Add EC2 auto-scaling groups, other compute resources, databases, or whatever else you need to support your service.

When you’re happy with what you’ve built, move on to provision the demo service in production.