Skip to content

Commit

Permalink
Archive a clean copy of Terraform configs (#1565)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr0grog authored Jun 13, 2023
1 parent b573558 commit 4c64ff4
Show file tree
Hide file tree
Showing 26 changed files with 1,717 additions and 1 deletion.
4 changes: 3 additions & 1 deletion terraform/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Terraform Configuration

Most everything that is part of UNIVAF runs in AWS, and the configuration for pretty much everything is stored here as [Terraform][] code. Whenever these files are changed, Terraform Cloud (a Terraform as a service offering) will pick up on it and develop a plan for what needs to actually change in AWS and, if the commit is on the `main` branch, automatically apply those changes.
The production UNIVAF service at [getmyvax.org](https://getmyvax.org) was shut down on June 15, 2023. The remaining configuration code here supports historical archives and a shutdown notice page. If you are planning to deploy your own copy of UNIVAF to AWS, you can use the former production Terraform code in the [`./deprecated`](./deprecated) folder as guide.

Whenever these files are changed, Terraform Cloud (a Terraform as a service offering) will pick up on it and develop a plan for what needs to actually change in AWS and, if the commit is on the `main` branch, automatically apply those changes.

For more on Terraform configuration files, check out the [reference docs][terraform-docs].

Expand Down
92 changes: 92 additions & 0 deletions terraform/deprecated/api-autoscaling.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Scale the API service up and down depending on usage.
#
# The target defines the resource to be scaled and its limits, while the
# policies do the actual scaling (and define how much and how often to do so),
# and the alarms ultimately define the conditions under which to scale and call
# the policies when those conditions are met.

resource "aws_appautoscaling_target" "target" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.api_service.name}"
scalable_dimension = "ecs:service:DesiredCount"
min_capacity = 2
max_capacity = 4
}

# Automatically scale capacity up by one
resource "aws_appautoscaling_policy" "up" {
name = "api_scale_up"
service_namespace = aws_appautoscaling_target.target.service_namespace
resource_id = aws_appautoscaling_target.target.resource_id
scalable_dimension = aws_appautoscaling_target.target.scalable_dimension

step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Maximum"

step_adjustment {
metric_interval_lower_bound = 0
scaling_adjustment = 1
}
}
}

# Automatically scale capacity down by one
resource "aws_appautoscaling_policy" "down" {
name = "api_scale_down"
service_namespace = aws_appautoscaling_target.target.service_namespace
resource_id = aws_appautoscaling_target.target.resource_id
scalable_dimension = aws_appautoscaling_target.target.scalable_dimension

step_scaling_policy_configuration {
adjustment_type = "ChangeInCapacity"
cooldown = 60
metric_aggregation_type = "Maximum"

step_adjustment {
metric_interval_upper_bound = 0
scaling_adjustment = -1
}
}
}

# CloudWatch alarm that triggers the autoscaling up policy
resource "aws_cloudwatch_metric_alarm" "service_cpu_high" {
alarm_name = "api_cpu_utilization_high"
comparison_operator = "GreaterThanOrEqualToThreshold"
datapoints_to_alarm = "1"
evaluation_periods = "1"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "55"

dimensions = {
ClusterName = aws_ecs_cluster.main.name
ServiceName = aws_ecs_service.api_service.name
}

alarm_actions = [aws_appautoscaling_policy.up.arn]
}

# CloudWatch alarm that triggers the autoscaling down policy
resource "aws_cloudwatch_metric_alarm" "service_cpu_low" {
alarm_name = "api_cpu_utilization_low"
comparison_operator = "LessThanOrEqualToThreshold"
datapoints_to_alarm = "2"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = "60"
statistic = "Average"
threshold = "15"

dimensions = {
ClusterName = aws_ecs_cluster.main.name
ServiceName = aws_ecs_service.api_service.name
}

alarm_actions = [aws_appautoscaling_policy.down.arn]
}
195 changes: 195 additions & 0 deletions terraform/deprecated/api-domains.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Domains and CDN/Caching Layers
#
# The DNS zone (defined by the `domain_name` variable) should be manually
# created in the AWS console, but all the records for the domain and subdomains
# are managed here in code.
#
# The domains all point to CloudFront distributions for caching and DOS
# protection. These are only turned on if there is also an SSL certificate
# (set in the `ssl_certificate_arn` variable, and which also needs to be
# created manually in the AWS console).

locals {
# The domain of the API service's load balancer (not for public use).
api_internal_subdomain = "api.internal"
api_internal_domain = (
var.domain_name != ""
? "${local.api_internal_subdomain}.${var.domain_name}"
: ""
)

# Domain at which to serve archived, historical data (stored in S3).
data_snapshots_subdomain = "archives"
data_snapshots_domain = (
var.domain_name != ""
? "${local.data_snapshots_subdomain}.${var.domain_name}"
: ""
)
}

# Domain DNS Recods -----------------------------------------------------------

data "aws_route53_zone" "domain_zone" {
count = var.domain_name != "" ? 1 : 0
name = var.domain_name
}

# DNS record for the domain specified in the `domain_name` variable.
resource "aws_route53_record" "api_domain_record" {
count = var.domain_name != "" ? 1 : 0

zone_id = data.aws_route53_zone.domain_zone[0].zone_id
name = var.domain_name
type = "A"

alias {
name = aws_cloudfront_distribution.univaf_api_ecs[0].domain_name
zone_id = aws_cloudfront_distribution.univaf_api_ecs[0].hosted_zone_id
evaluate_target_health = false
}
}

# The `www.` subdomain. It is an alias for the primary domain name.
resource "aws_route53_record" "api_www_domain_record" {
count = var.domain_name != "" ? 1 : 0
zone_id = data.aws_route53_zone.domain_zone[0].zone_id
name = "www"
type = "CNAME"
records = [var.domain_name]
ttl = 300
}

# The `api.internal` subdomain. Used for the API service's load balancer so it
# can be secured with HTTPS.
resource "aws_route53_record" "api_load_balancer_domain_record" {
count = var.domain_name != "" ? 1 : 0

zone_id = data.aws_route53_zone.domain_zone[0].zone_id
name = local.api_internal_subdomain
type = "A"

alias {
name = aws_alb.main.dns_name
zone_id = aws_alb.main.zone_id
evaluate_target_health = false
}
}

# The `ecs.` subdomain.
# This specifically points to the deployment on ECS (as opposed to a possible
# external host).
resource "aws_route53_record" "api_ecs_domain_record" {
count = var.domain_name != "" ? 1 : 0

zone_id = data.aws_route53_zone.domain_zone[0].zone_id
name = "ecs"
type = "A"

alias {
name = aws_cloudfront_distribution.univaf_api_ecs[0].domain_name
zone_id = aws_cloudfront_distribution.univaf_api_ecs[0].hosted_zone_id
evaluate_target_health = false
}
}


# CloudFront ------------------------------------------------------------------

# Use CloudFront as a caching layer in front of the API server that's running
# in ECS. Enabled only if var.domain and var.ssl_certificate_arn are provided.
resource "aws_cloudfront_distribution" "univaf_api_ecs" {
count = (
var.domain_name != ""
&& var.ssl_certificate_arn != "" ? 1 : 0
)
enabled = true
price_class = "PriceClass_100" # North America
aliases = [
var.domain_name,
"www.${var.domain_name}",
"ecs.${var.domain_name}"
]
http_version = "http2and3"

origin {
origin_id = "ecs.${var.domain_name}"
domain_name = local.api_internal_domain

custom_header {
name = var.api_cloudfront_secret_header_name
value = var.api_cloudfront_secret
}

custom_origin_config {
http_port = 80
https_port = 443
origin_ssl_protocols = ["SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"]
origin_protocol_policy = "https-only"
}
}

default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "ecs.${var.domain_name}"
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
max_ttl = 3600

forwarded_values {
headers = ["Host", "Origin", "Authorization", "x-api-key"]
query_string = true

cookies {
forward = "none"
}
}
}

viewer_certificate {
acm_certificate_arn = var.ssl_certificate_arn
ssl_support_method = "sni-only"
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}
}

# Provide a protective caching layer and a nice domain name for the S3 bucket
# with historical data. (Allowing direct public access can get expensive.)
# Docs: https://github.com/cloudposse/terraform-aws-cloudfront-s3-cdn
module "univaf_data_snaphsots_cdn" {
count = (
var.domain_name != ""
&& var.ssl_certificate_arn != "" ? 1 : 0
)
source = "cloudposse/cloudfront-s3-cdn/aws"
version = "0.90.0"

origin_bucket = aws_s3_bucket.data_snapshots.bucket
dns_alias_enabled = true
aliases = [local.data_snapshots_domain]
parent_zone_id = data.aws_route53_zone.domain_zone[0].zone_id
acm_certificate_arn = var.ssl_certificate_arn
cloudfront_access_logging_enabled = false

default_ttl = 60 * 60 * 24 * 7 # 1 Week
http_version = "http2and3"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
# By default, CORS headers are forwarded, but we don't really care about them
# since the bucket is not operating in "website" mode.
forward_header_values = []

# HACK: this module creates bad values if you don't explicitly set one or
# more of namespace, environment, stage, name, or attributes.
# Basically, Cloud Posse modules generate an internal ID from the above,
# and that ID is used for lots of things. Bad stuff happens if it is empty.
# This issue is marked as closed, but is not actually solved:
# https://github.com/cloudposse/terraform-aws-cloudfront-s3-cdn/issues/151
namespace = "cp"
name = "univaf_data_snaphsots_cdn"
}
Loading

0 comments on commit 4c64ff4

Please sign in to comment.