Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# infrastructure
Terraform infrastructure as code

## Instructions
## Dependencies
The only things that will need to be set up before deploying for the first time are an AWS account, and a domain name with a Route53 zone created for it.
You'll also need a user created and the credentials available in your shell.

[AWS Docs: Set up the AWS CLI](https://docs.aws.amazon.com/polly/latest/dg/setup-aws-cli.html)
[AWS Docs: Register a domain with Route53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-register.html)

## Instructions
To init and apply the terraform configs, simply run the makefile and specify the environment. The default environment is `staging`
```
make ENV=<environment>
Expand All @@ -21,14 +28,14 @@ The most expensive component will be the EKS cluster as well as the instances th
- Costs will vary depending on the region selected but based on `us-west-2` the following items will contribute to the most of the cost of the infrastructure
- EKS Cluster: $0.1 USD / hr
- NAT Gateway: $0.045 USD / hr
- RDS (db.t3.small): $0.034 USD / hr
- RDS (db.t3.small): $0.034 USD / hr
- EC2 (t2.small): $0.023 USD / hr
- Expected total monthly cost: $ 0.202 USD / hr or ~$150USD / month
- Expected total monthly cost: $ 0.202 USD / hr or ~$150USD / month

EC2 instance sizing can be configured in [terraform/environments/staging/main.tf](terraform/environments/staging/main.tf)


## AWS Setting the Kubernetes context
## AWS Setting the Kubernetes context
```
aws eks update-kubeconfig --name <cluster-name> --region us-west-2
aws eks update-kubeconfig --name <cluster-name> --region us-west-2 --role-arn <role-arn>
Expand Down Expand Up @@ -59,7 +66,7 @@ aws ecr delete-repository --repository-name <ecr-repo-name> --region <aws-region
```
Describing the ECR repositories will also give you a list of the fully resolved repository URI.

If you need your AWS account ID, you can use:
If you need your AWS account ID, you can use:
```
aws sts get-caller-identity --query Account --output text
```
Expand Down Expand Up @@ -106,14 +113,14 @@ kubectl exec -it <bash-pod-id> -- /bin/bash
In the container shell
```
Apt-get update -y
Apt-get install pgcli
Apt-get install pgcli
pgcli -h <rds-url> -U master_user -d postgres
CREATE DATABASE <database>;
create USER <db-user> with password '<db-password>';
GRANT ALL PRIVILEGES ON DATABASE <database> to <db-user>;
```

### Accessing Database in VPC:
### Accessing Database in VPC:
```
kubectl run --restart=Never --image=alpine/socat db-gateway -- -d -d tcp-listen:5432,fork,reuseaddr tcp-connect:<RDS_ADDRESS>:5432
kubectl port-forward db-gateway 5432:5432
Expand Down
14 changes: 7 additions & 7 deletions commit0.module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ author: ''
icon: ''
thumbnail: ''

prompts:
prompts:
- field: region
label: Select AWS Region
options:
options:
- "us-west-1"
- "us-west-2"
- "us-east-1"
Expand All @@ -17,12 +17,12 @@ prompts:
- "eu-west-1"
- "ap-east-1"
- "ap-south-1"
- field: accountId
- field: accountId
label: AWS Account ID
- field: stagingHost
label: Staging Host Name
- field: productionHost
label: Production Host Name
- field: productionHost
label: Production Root Host Name (e.g. mydomain.com)
- field: stagingHost
label: Staging Root Host Name (e.g. mydomain-staging.com)

template:
strictMode: true
Expand Down
4 changes: 2 additions & 2 deletions terraform/environments/production/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ module "production" {

# Hosting configuration
s3_hosting_buckets = [
"<% index .Params `productionHost` %>"
"assets.<% index .Params `productionHost` %>"
]
s3_hosting_cert_domain = "<% index .Params `productionHost` %>"
domain_name = "<% index .Params `productionHost` %>"

# DB configuration
db_instance_class = "db.t3.small"
Expand Down
4 changes: 2 additions & 2 deletions terraform/environments/staging/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ module "staging" {

# Hosting configuration
s3_hosting_buckets = [
"<% index .Params `stagingHost` %>",
"assets.<% index .Params `stagingHost` %>",
]
s3_hosting_cert_domain = "<% index .Params `stagingHost` %>"
domain_name = "<% index .Params `stagingHost` %>"

# DB configuration
db_instance_class = "db.t3.small"
Expand Down
48 changes: 48 additions & 0 deletions terraform/modules/certificate/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

# Create a route53 zone
# resource "aws_route53_zone" "public" {
# name = var.domain_name
# }

# Reference an existing route53 zone
data "aws_route53_zone" "public" {
name = var.zone_name
}



# To use an ACM cert with CF it has to exist in us-east-1
provider "aws" {
region = var.region
alias = "custom"
}

# Create an ACM cert for this domain
resource "aws_acm_certificate" "cert" {
count = length(var.domain_names)
provider = aws.custom

domain_name = var.domain_names[count.index]
validation_method = "DNS"
}

# Route53 record to validate the certificate
resource "aws_route53_record" "cert_validation_record" {
count = length(aws_acm_certificate.cert)
provider = aws.custom

name = aws_acm_certificate.cert[count.index].domain_validation_options[0]["resource_record_name"]
records = [aws_acm_certificate.cert[count.index].domain_validation_options[0]["resource_record_value"]]
type = "CNAME"
allow_overwrite = true
zone_id = data.aws_route53_zone.public.zone_id
ttl = 300
}

resource "aws_acm_certificate_validation" "cert" {
count = length(aws_acm_certificate.cert)
provider = aws.custom

certificate_arn = aws_acm_certificate.cert[count.index].arn
validation_record_fqdns = aws_route53_record.cert_validation_record.*.fqdn
}
14 changes: 14 additions & 0 deletions terraform/modules/certificate/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
output "route53_zone_id" {
description = "Identifier of the Route53 Zone"
value = data.aws_route53_zone.public.zone_id
}

output "certificate_arns" {
description = "The ARNs of the created certificates, keyed by domain name"
value = zipmap(aws_acm_certificate.cert[*].domain_name, aws_acm_certificate.cert[*].arn)
}

output "certificate_validations" {
description = "The ids of the certificate validations. Provided as a dependency so dependents can wait until the cert is actually valid"
value = aws_acm_certificate_validation.cert[*].id
}
13 changes: 13 additions & 0 deletions terraform/modules/certificate/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
variable "region" {
description = "The AWS region"
}

variable "zone_name" {
description = "Domains of the Route53 hosted zone"
type = string
}

variable "domain_names" {
description = "Domains to create an ACM Cert for"
type = list(string)
}
4 changes: 4 additions & 0 deletions terraform/modules/certificate/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

terraform {
required_version = ">= 0.12"
}
25 changes: 22 additions & 3 deletions terraform/modules/environment/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,31 @@ data "aws_iam_user" "ci_user" {
user_name = "ci-user" # Should have been created in the bootstrap process
}

module "wildcard_domain" {
source = "../../modules/certificate"

region = var.region
zone_name = var.domain_name
domain_names = ["*.${var.domain_name}"]
}

module "assets_domains" {
source = "../../modules/certificate"

region = "us-east-1" # For CF, the cert must be in us-east-1
zone_name = var.domain_name
domain_names = var.s3_hosting_buckets
}

module "s3_hosting" {
source = "../../modules/s3_hosting"

buckets = var.s3_hosting_buckets
cert_domain = var.s3_hosting_cert_domain
project = var.project
buckets = var.s3_hosting_buckets
project = var.project
environment = var.environment
certificate_arns = module.assets_domains.certificate_arns
route53_zone_id = module.assets_domains.route53_zone_id
certificate_validations = module.assets_domains.certificate_validations
}

module "db" {
Expand Down
4 changes: 2 additions & 2 deletions terraform/modules/environment/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ variable "s3_hosting_buckets" {
type = set(string)
}

variable "s3_hosting_cert_domain" {
description = "Domain of the ACM certificate to lookup for Cloudfront to use"
variable "domain_name" {
description = "Domain to create a R53 Zone and ACM Cert for"
type = string
}

Expand Down
21 changes: 4 additions & 17 deletions terraform/modules/s3_hosting/main.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
locals {
assets_access_identity = "${var.project}-client-assets"
assets_access_identity = "${var.project}-${var.environment}-client-assets"
}

resource "aws_s3_bucket" "client_assets" {
Expand Down Expand Up @@ -65,13 +65,6 @@ provider "aws" {
alias = "east1"
}

# Find an already created ACM cert for this domain
data "aws_acm_certificate" "wildcard_cert" {
provider = aws.east1
domain = var.cert_domain
most_recent = "true"
}

# Create the cloudfront distribution
resource "aws_cloudfront_distribution" "client_assets_distribution" {
for_each = var.buckets
Expand All @@ -91,7 +84,7 @@ resource "aws_cloudfront_distribution" "client_assets_distribution" {
response_code = 200
error_caching_min_ttl = 0
response_page_path = "/index.html"
}
}

enabled = true
is_ipv6_enabled = true
Expand Down Expand Up @@ -128,24 +121,18 @@ resource "aws_cloudfront_distribution" "client_assets_distribution" {

# Use our cert
viewer_certificate {
acm_certificate_arn = data.aws_acm_certificate.wildcard_cert.arn
acm_certificate_arn = var.certificate_arns[each.value]
minimum_protocol_version = "TLSv1"
ssl_support_method = "sni-only"
}

}

# Find the route53 zone
data "aws_route53_zone" "public" {
name = "${var.cert_domain}."
private_zone = false
}

# Subdomain to point at CF
resource "aws_route53_record" "client_assets" {
for_each = var.buckets

zone_id = data.aws_route53_zone.public.zone_id
zone_id = var.route53_zone_id
name = each.value
type = "CNAME"
ttl = "120"
Expand Down
17 changes: 15 additions & 2 deletions terraform/modules/s3_hosting/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,25 @@ variable "project" {
description = "The name of the project, mostly for tagging"
}

variable "environment" {
description = "The environment (dev/staging/prod)"
}

variable "buckets" {
description = "S3 hosting buckets"
type = set(string)
}

variable "cert_domain" {
description = "Domain of the ACM certificate to lookup for Cloudfront to use"
variable "certificate_arns" {
description = "ARN of the certificate we created for the assets domain, keyed by domain"
type = map
}

variable "certificate_validations" {
description = "Certificate validations, provided as a dependency so we can wait on the certs to be valid"
}

variable "route53_zone_id" {
description = "ID of the Route53 zone to create a record in"
type = string
}