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

Initial commit #1

Merged
merged 4 commits into from
Aug 22, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.swp
.terraform
*.tfstate*
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,83 @@
# pb_deploy
`pb_deploy` is a [Terraform](https://www.terraform.io) configuration for a
production deployment of [PolicyBrain], as run by the
[Open Source Policy Center], on Amazon Web Services infrastructure.

## Why Terraform?
Terraform is a declarative "infrastructure as code" open-source application,
meaning that it can deploy a new set of infrastructure components with ease
given account information, and it can keep it up-to-date with a programmatically
specified configuration, performing changes only as needed.

This is possible because Terraform keeps track of the [state][Terraform state].
In the case of OSPC, we use [AWS S3 and DynamoDB][Terraform S3 backend] to share
and lock state information among different OSPC collaborators.

## Infrastructure description
The main component of this infrastructure is [Amazon ECS Fargate], which is a
technology for deploying Docker containers without having to manage the
underlying servers. ECS is used to run relatively few Internet-facing Flask
servers, as well as relatively many Celery workers. Rather than being run on
ECS, Redis is managed through [Amazon ElastiCache], and stores the task queue.
[Amazon Route 53] and [Amazon ELB] are used to provide a permanent hostname,
such as `staging.ospcapi.org`, that will always point to the correct ECS task
IP(s). (Currently, the alternative, Route53 Service Discovery for ECS, only
supports private IPs.)

The networking is handled using a [Virtual Public Cloud][Amazon VPC] and a
single public subnet, and all instances are given public IPs. This is to avoid
the expense of a NAT gateway required with private subnets, but AWS security
groups are used to define permissible Internet access as restrictively as
possible.

## Usage
Because of Terraform's declarative nature, the steps for initial setup and for
modification of the infrastructure should be the same. You should specify
[AWS authentication credentials][Terraform AWS authentication] either through
environment variables or through the `~/.aws/credentials` file. The
configuration also assumes that the following resources have already been
created in the region given in the `terraform.tfvars` file:

- An [Amazon ECS task execution role]
- An Amazon Route 53 zone with the name given in `terraform.tfvars`; this can
be any domain or subdomain for which you need to configure the nameservers
- An S3 bucket and a DynamoDB table with the names given in `main.tf` in order
to store Terraform state

This configuration makes extensive use of [Terraform workspaces] in order to
separate state and resources for different deployment environments. The
`default` workspace can be used if this distinction is not needed, but should
**not** be used for OSPC deployments, which will make use of the `production`
and `staging` environments.

An example deployment workflow would be:

```shell
cd ~/pb_deploy
terraform init
terraform workspace select production
terraform plan -out=production.tfplan
terraform apply production.tfplan
```

A deployment environment could be torn down as follows:

```shell
cd ~/pb_deploy
terraform init
terraform workspace select staging
terraform destroy
```

[PolicyBrain]: https://github.com/OpenSourcePolicyCenter/PolicyBrain
[Open Source Policy Center]: https://github.com/OpenSourcePolicyCenter/PolicyBrain
[Terraform state]: https://www.terraform.io/docs/state/index.html
[Terraform S3 backend]: https://www.terraform.io/docs/backends/types/s3.html
[Amazon ECS Fargate]: https://aws.amazon.com/fargate/
[Amazon ElastiCache]: https://aws.amazon.com/elasticache/
[Amazon Route 53]: https://aws.amazon.com/route53/
[Amazon ELB]: https://aws.amazon.com/elasticloadbalancing/
[Amazon VPC]: https://aws.amazon.com/vpc/
[Terraform AWS authentication]: https://www.terraform.io/docs/providers/aws/index.html#authentication
[Amazon ECS task execution role]: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html
[Terraform workspaces]: https://www.terraform.io/docs/state/workspaces.html
28 changes: 28 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
provider "aws" {
region = "${var.region}"
}

terraform {
backend "s3" {
bucket = "ospc-terraform-state-storage-s3"
key = "terraform.tfstate"
dynamodb_table = "ospc-terraform-state-lock-table"
region = "us-east-2"
encrypt = true
}
}

module "networking" {
source = "./modules/networking"
environment = "${terraform.workspace}"
region = "${var.region}"
}

module "worker" {
source = "./modules/worker"
environment = "${terraform.workspace}"
region = "${var.region}"
vpc_id = "${module.networking.vpc_id}"
subnet_ids = ["${module.networking.public_subnet_ids}"]
api_hostname = "${var.api_hostname}"
}
62 changes: 62 additions & 0 deletions modules/networking/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* Fetch the available AZs in the configured region */
data "aws_availability_zones" "available" {}

/* The VPC */
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true

tags {
Name = "${var.environment}-vpc"
Environment = "${var.environment}"
}
}

/* Subnets */
/* Internet gateway for the public subnet */
resource "aws_internet_gateway" "ig" {
vpc_id = "${aws_vpc.vpc.id}"

tags {
Name = "${var.environment}-igw"
Environment = "${var.environment}"
}
}

/* Public subnet */
resource "aws_subnet" "public" {
vpc_id = "${aws_vpc.vpc.id}"
cidr_block = "${cidrsubnet(aws_vpc.vpc.cidr_block, 8, count.index+1)}"
count = 2
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = true

tags {
Name = "${var.environment}-${data.aws_availability_zones.available.names[count.index]}-public-subnet"
Environment = "${var.environment}"
}
}

/* Routing table for public subnet */
resource "aws_route_table" "public" {
vpc_id = "${aws_vpc.vpc.id}"

tags {
Name = "${var.environment}-public-route-table"
Environment = "${var.environment}"
}
}

resource "aws_route" "public_internet_gateway" {
route_table_id = "${aws_route_table.public.id}"
destination_cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.ig.id}"
}

/* Route table associations */
resource "aws_route_table_association" "public" {
count = "${aws_subnet.public.count}"
subnet_id = "${aws_subnet.public.*.id[count.index]}"
route_table_id = "${aws_route_table.public.id}"
}
7 changes: 7 additions & 0 deletions modules/networking/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
output "vpc_id" {
value = "${aws_vpc.vpc.id}"
}

output "public_subnet_ids" {
value = ["${aws_subnet.public.*.id}"]
}
2 changes: 2 additions & 0 deletions modules/networking/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
variable "environment" {}
variable "region" {}
Loading