Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure CloudFront proxy for Tilegarden #629

Merged
merged 7 commits into from
Dec 19, 2018
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
1 change: 1 addition & 0 deletions common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ services:
- PFB_AWS_BATCH_TILEMAKER_JOB_QUEUE_NAME
- PFB_AWS_BATCH_TILEMAKER_JOB_DEFINITION_NAME_REVISION
- PFB_AWS_BATCH_TILEMAKER_JOB_DEFINITION_NAME
- PFB_TILEGARDEN_ROOT=http://localhost:9400
volumes:
- $HOME/.aws:/root/.aws:ro

Expand Down
112 changes: 112 additions & 0 deletions deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Amazon Web Services deployment is driven by [Terraform](https://terraform.io/) a
* [AWS Credentials](#aws-credentials)
* [Terraform](#terraform)
* [AWS Batch](#aws-batch)
* [Tilegarden](#tilegarden)

## AWS Credentials

Expand Down Expand Up @@ -82,3 +83,114 @@ Click 'Create queue' again and follow the same steps to create a second queue na
Once the unmanaged compute environment has a 'VALID' status, navigate to [EC2 Container Service](https://console.aws.amazon.com/ecs/home?region=us-east-1) and copy the full name of the newly created ECS Cluster into the `batch_ecs_cluster_name` tfvar for the appropriate environment.

Congratulations, the necessary resources for your environment are ready. The ECS instance configuration and autoscaling group attached to the compute environment are managed by Terraform.

## Tilegarden

We serve map tiles using [Tilegarden](https://github.com/azavea/tilegarden), a
serverless tile generator that runs on AWS Lambda. Portions of Tilegarden
are deployed separately from the Terraform stack because Tilegarden
is tightly coupled with [Claudia](https://claudiajs.com/), an opinionated
deployment tool for publishing Node code as Lambda functions. The main
components of Tilegarden that are deployed this way include a Lambda function
and an API Gateway.

CI will deploy updates to existing Tilegarden instances automatically, but there are
a few extra steps you'll need to take in order to stand up an entirely new
instance of Tilegarden.

### 1. Create a remote state bucket for Claudia

Remote state for Claudia should exist in the same S3 bucket as Terraform remote
state. For staging, this bucket should be something like `staging-pfb-config-us-east-1`.
In that bucket, create a new folder (that is, an object prefix) for
Tilegarden remote state called `tilegarden/`.

### 2. Configure environment variables for your function

Tilegarden uses an `.env` file in order to pass environment variables into
the Lambda function. Copy over the example file to create a new one:

```
$ cp ./src/tilegarden/.env.example ./src/tilegarden/.env
```

The three required variables are `AWS_PROFILE`, `PROJECT_NAME`, and
`LAMBDA_REGION`. Other optional variables can be uncommented and edited to
reflect your configuration. If you need Tilegarden to access a database, for
example, you'll likely want to set `LAMBDA_SUBNETS` and `LAMBDA_SECURITY_GROUPS` to point
to the relevant resources in your VPC.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this interact with "it's best to finish these steps before you deploy a new stack with Terraform"? Seems like there's a bit of circularity here that would require bootstrapping, i.e. the process for creating a new stack would need to be "batch, terraform, tilegarden, terraform"? (Or would it be "batch, tilegarden, terraform, tilegarden"? Or is there actually a linear way to do it?)

If you need Tilegarden to access a database

Tilegarden isn't good for much without access to a database, at least not on this project. I might rephrase this to "For example, to give Tilegarden access to the database, set..."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent point. It's possible you could get away with doing it linearly (Batch -> Tilegarden -> Terraform) if you used an existing VPC, but we're not doing that here, so realistically this is implying that you'll have to do Batch -> Terraform -> Tilegarden -> Terraform.

I think we could make the setup easier if we were at least explicit about this workflow, and gave the user some guidance in doing it. My expectation is that if you were to set the tilegarden_api_gateway_domain_name variable to some bogus value on the first Terraform run, the resources will build properly, but the CDN won't return tiles properly; then if you adjust the variable again after setting up Tilegarden things should wire up correctly on the second terraform apply.

I don't love this workflow, but it's the best I can think of given the constraints we're under, where the Lambda function and the CDN are interdependent but each has to be configured and managed from a separate source of truth. If you can think of a way around it, let me know!


If you have any additional environment variables (like database connection
strings) that you need access to in your function, add them to `./src/tilegarden/.env`
and they will be passed in during deployment.

### 3. Deploy a new Tilegarden instance with Claudia

Before deploying your instance, `touch` a file that Claudia can use to save
deployment metadata:

```
$ touch ./src/tilegarden/claudia.json
```

Next, use the Tilegarden Node scripts in the VM to deploy a new Tilegarden instance:

```
vagrant@pfb-network-connectivity:/vagrant$ docker-compose \
-f docker-compose.yml \
-f docker-compose.test.yml \
run --rm --entrypoint yarn \
tilegarden deploy-new
```

### 4. Update remote state for Claudia and Terraform

Once the deployment has completed, upload your `.env` file and Claudia metadata
file to the remote state bucket so that CI can update Tilegarden automatically:

```
$ aws s3 cp ./src/tilegarden/.env s3://<remote-state-bucket>/tilegarden/.env
$ aws s3 cp ./src/tilegarden/claudia.json s3://<remote-state-bucket>/tilegarden/claudia.json
```

In addition, edit the remote Terraform variables in `terraform.tfvars` for your
deployment to point to the domain name for the new API Gateway that Claudia has created.
The variable for the API Gateway should look something like this:

```hcl
# Tilegarden
tilegarden_api_gateway_domain_name = "<api.id>.execute-api.<lambda.region>.amazonaws.com"
hectcastro marked this conversation as resolved.
Show resolved Hide resolved
```

You can locate this domain name in the Lambda console, or you can construct it
by using the `api.id` and `lambda.region` values stored in the `claudia.json` file
that Claudia created during deployment to format the example string in the
code block above.

### A note about Terraform resources

Certain Terraform and Tilegarden resources are interdependent, in that they expect
IDs of resources created by the other deployment tool as input variables. In particular,
the CloudFront CDN managed by Terraform relies on the `tilegarden_api_gateway_domain_name`
variable in order to point the CDN to the Tilegarden API Gateway endpoint, and the
Tilegarden Claudia deployment tool relies on the `LAMBDA_SUBNETS` and
`LAMBDA_SECURITY_GROUPS` variables in order to give the Tilegarden Lambda
function access to database resources in the VPC.

In order to manage this interdependence, we recommend that you first deploy
Terraform resources using a dummy value for `tilegarden_api_gateway_endpoint`;
this way, all of the Terraform infrastructure should build correctly, but the CDN will
not serve tiles properly. Then, stand up a Tilegarden instance using the relevant
input values from your Terraform-managed resources, and once you're done
deploying Tilegarden you can update `tilegarden_api_gateway_endpoint` to point
to the API Gateway that Claudia has created.

In brief, the anticipated order of resource creation when deploying a new
stack should be:

1. Create Batch resources
2. Create Terraform resources with dummy `tilegarden_api_gateway_domain_name`
variable
3. Create Tilegarden resources
4. Update Terraform resources with correct `tilegarden_api_gateway_domain_name`
variable
3 changes: 3 additions & 0 deletions deployment/terraform/app.tf
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ data "template_file" "pfb_app_https_ecs_task" {
batch_analysis_job_definition_name_revision = "${var.batch_analysis_job_definition_name_revision}"
batch_tilemaker_job_queue_name = "${var.batch_tilemaker_job_queue_name}"
batch_tilemaker_job_definition_name_revision = "${var.batch_tilemaker_job_definition_name_revision}"
tilegarden_root = "https://${aws_route53_record.tilegarden.fqdn}"
}
}

Expand Down Expand Up @@ -197,6 +198,7 @@ data "template_file" "pfb_app_async_queue_ecs_task" {
batch_analysis_job_definition_name_revision = "${var.batch_analysis_job_definition_name_revision}"
batch_tilemaker_job_queue_name = "${var.batch_tilemaker_job_queue_name}"
batch_tilemaker_job_definition_name_revision = "${var.batch_tilemaker_job_definition_name_revision}"
tilegarden_root = "https://${aws_route53_record.tilegarden.fqdn}"
}
}

Expand Down Expand Up @@ -235,6 +237,7 @@ data "template_file" "pfb_app_management_ecs_task" {
batch_analysis_job_definition_name_revision = "${var.batch_analysis_job_definition_name_revision}"
batch_tilemaker_job_queue_name = "${var.batch_tilemaker_job_queue_name}"
batch_tilemaker_job_definition_name_revision = "${var.batch_tilemaker_job_definition_name_revision}"
tilegarden_root = "https://${aws_route53_record.tilegarden.fqdn}"
}
}

Expand Down
56 changes: 56 additions & 0 deletions deployment/terraform/cdn.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
resource "aws_cloudfront_distribution" "tilegarden" {
origin {
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2", "TLSv1.1", "TLSv1"]
}

domain_name = "${var.tilegarden_api_gateway_domain_name}"
origin_path = "/latest"
origin_id = "tilegardenOriginEastId"

custom_header {
name = "Accept"
value = "image/png"
}
}

aliases = ["tiles.${var.r53_public_hosted_zone}"]
price_class = "${var.cloudfront_price_class}"
enabled = true
is_ipv6_enabled = true
comment = "${var.project} (${var.environment})"

default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "tilegardenOriginEastId"

forwarded_values {
query_string = true

cookies {
forward = "none"
}
}

viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = "300" # Five minutes
max_ttl = "86400" # One day
}

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

viewer_certificate {
acm_certificate_arn = "${var.ssl_certificate_arn}"
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1"
}
}
12 changes: 12 additions & 0 deletions deployment/terraform/dns.tf
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@ resource "aws_route53_record" "pfb_app" {
evaluate_target_health = true
}
}

resource "aws_route53_record" "tilegarden" {
zone_id = "${aws_route53_zone.external.zone_id}"
name = "tiles.${var.r53_public_hosted_zone}"
type = "A"

alias {
name = "${aws_cloudfront_distribution.tilegarden.domain_name}"
zone_id = "${aws_cloudfront_distribution.tilegarden.hosted_zone_id}"
evaluate_target_health = true
}
}
4 changes: 4 additions & 0 deletions deployment/terraform/task-definitions/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
{
"name": "PFB_AWS_BATCH_TILEMAKER_JOB_DEFINITION_NAME_REVISION",
"value": "${batch_tilemaker_job_definition_name_revision}"
},
{
"name": "PFB_TILEGARDEN_ROOT",
"value": "${tilegarden_root}"
}
],
"logConfiguration": {
Expand Down
4 changes: 4 additions & 0 deletions deployment/terraform/task-definitions/django-q.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
{
"name": "PFB_AWS_BATCH_TILEMAKER_JOB_DEFINITION_NAME_REVISION",
"value": "${batch_tilemaker_job_definition_name_revision}"
},
{
"name": "PFB_TILEGARDEN_ROOT",
"value": "${tilegarden_root}"
}
],
"logConfiguration": {
Expand Down
4 changes: 4 additions & 0 deletions deployment/terraform/task-definitions/management.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
{
"name": "PFB_AWS_BATCH_TILEMAKER_JOB_DEFINITION_NAME_REVISION",
"value": "${batch_tilemaker_job_definition_name_revision}"
},
{
"name": "PFB_TILEGARDEN_ROOT",
"value": "${tilegarden_root}"
}
],
"logConfiguration": {
Expand Down
6 changes: 6 additions & 0 deletions deployment/terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,9 @@ variable "aws_cloudwatch_logs_policy_arn" {
variable "pfb_app_alb_ingress_cidr_block" {
type = "list"
}

# CloudFront distribution
variable "tilegarden_api_gateway_domain_name" {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Claudia creates the API Gateway resource for Tilegarden separately from Terraform, we need to specify this value as an input variable.

variable "cloudfront_price_class" {
default = "PriceClass_100"
}
6 changes: 3 additions & 3 deletions src/django/pfb_network_connectivity/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,6 @@
PFB_ANALYSIS_PRESIGNED_URL_EXPIRES = 3600

# Root URL for tile server.
# TODO (probably with issue #595): this is the development answer. For staging/production
# we'll have to supply the URL of the actual deployed CloudFront distribution.
TILEGARDEN_ROOT = 'http://localhost:9400'
TILEGARDEN_ROOT = os.getenv('PFB_TILEGARDEN_ROOT')
if not TILEGARDEN_ROOT:
raise ImproperlyConfigured('env.PFB_TILEGARDEN_ROOT is required')
8 changes: 7 additions & 1 deletion src/tilegarden/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ LAMBDA_REGION=
#LAMBDA_ROLE=role-name
# Amount of time in seconds your lambdas will wait before timing out
# Increase this value if your tile requests are timing out
#LAMBDA_TIMEOUT=15
LAMBDA_TIMEOUT=60
# Memory in MB allocated to your lambda functions
# Increase this value if you plan on rendering vector tiles
#LAMBDA_MEMORY=128
Expand All @@ -26,3 +26,9 @@ LAMBDA_REGION=
#LAMBDA_SUBNETS=subnet1,subnet2,subnet...N
# VPC Security Groups that your lambdas should use (comma separated list)
#LAMBDA_SECURITY_GROUPS=group1,group2,group...N

# PFB-specific variables
PFB_DB_DATABASE=
PFB_DB_PASSWORD=
PFB_DB_PORT=
PFB_DB_USER=