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

feat: add terraform infra as code for AWS fargate #1987

Merged
merged 16 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 15 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ success-*.png
desktop.ini

twitch.json
terraform/terraform.tfstate
terraform/terraform.tfstate.backup
32 changes: 32 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Terraform for AWS Fargate

Here's some configurable terraform to get you up and running with the streetmerchant docker image in AWS ECS Fargate.

Running on cloud infrastructure, your mileage may vary, and you'll need to integrate with one of the chat notifications rather than having your local browser navigate to a url for you.

The author's findings were that it worked ok; running the container from within EU-West-2 region was suficient to get a timely alert for PS5 stock on amazon, and follow the link to a successful basket add.

Dependencies:
- Terraform 14

##Getting started

There's an example tfvars file to start you off; rename this with your own preferences. Anything you can set in the `dotenv` file you'll need to set in terraform.tfvars to get the env vars into your fargate container.

Authenticate yourself with your own AWS account as with any aws commandline tool.

If you wish, add a specific section to your aws credentials file and set that profile name in `terraform.tfvars`.

Then you can:
```shell
cd ./terraform
terraform init

terraform plan
terraform apply
```

## What's included

- container, running streetmerchant, with your chosen config
- cloud metrics and a dashboard tracking 'out of stock' and 'error' responses from your configured stores
40 changes: 40 additions & 0 deletions terraform/dashboard.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"widgets": [
{
"type": "metric",
"x": 0,
"y": 0,
"width": 18,
"height": 12,
"properties": {
"metrics": ${out_of_stock},
"view": "timeSeries",
"stacked": false,
"region": "${region}",
"start": "-PT1H",
"end": "P0D",
"stat": "Sum",
"period": 300,
"title": "out of stock"
}
},
{
"type": "metric",
"x": 0,
"y": 0,
"width": 18,
"height": 12,
"properties": {
"metrics": ${error},
"view": "timeSeries",
"stacked": false,
"region": "${region}",
"start": "-PT1H",
"end": "P0D",
"stat": "Sum",
"period": 300,
"title": "error"
}
}
]
}
14 changes: 14 additions & 0 deletions terraform/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}

provider "aws" {
region = var.region
shared_credentials_file = var.credential_file
profile = var.credential_profile
}
42 changes: 42 additions & 0 deletions terraform/resource-ecs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
resource "aws_ecs_cluster" "main" {
name = "${var.app_name}-ecs-cluster"
}

resource "aws_ecs_service" "main" {
name = "${var.app_name}-ecs-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.main.id
desired_count = 1
network_configuration {
subnets = [
aws_subnet.aws-subnet.id
]
assign_public_ip = true
}
launch_type = "FARGATE"
}

data "aws_iam_role" "ecs_task_execution_role" {
name = "ecsTaskExecutionRole"
}

locals {
container_env = [for k, v in var.streetmerchant_env : { name: k, value: v}]
}

resource "aws_ecs_task_definition" "main" {
container_definitions = templatefile("taskdef.json.template", {
"name": var.app_name
"awslogs-group": aws_cloudwatch_log_group.main.name
"region": var.region
"cpu": var.cpu
"memory": parseint(var.memory,10)
"environment": jsonencode(local.container_env)
})
family = var.app_name
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = var.cpu
memory = var.memory
execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn
}
51 changes: 51 additions & 0 deletions terraform/resource-logging.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
resource "aws_cloudwatch_log_group" "main" {
name = var.app_name
retention_in_days = 3
}

locals {
stores = split(",",var.streetmerchant_env["STORES"])
metrics = {
out_of_stock = [for store in local.stores : ["${var.app_name}-out-of-stock", store]]
error = [for store in local.stores : ["${var.app_name}-error", store]]
}
}

resource "aws_cloudwatch_log_metric_filter" "out_of_stock" {
for_each = toset(local.stores)

log_group_name = aws_cloudwatch_log_group.main.name
name = "${each.key}-out-of-stock"

pattern = "${each.key} \"OUT OF STOCK\""
metric_transformation {
name = each.key
namespace = "${var.app_name}-out-of-stock"
value = 1
default_value = 0
}
}

resource "aws_cloudwatch_log_metric_filter" "error" {
for_each = toset(local.stores)

log_group_name = aws_cloudwatch_log_group.main.name
name = "${each.key}-error"

pattern = "${each.key} \"ERROR\""
metric_transformation {
name = each.key
namespace = "${var.app_name}-error"
value = 1
default_value = 0
}
}

resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "${var.app_name}-dashboard"
dashboard_body = templatefile("dashboard.json.template", {
out_of_stock = jsonencode(local.metrics.out_of_stock)
error = jsonencode(local.metrics.error)
region = var.region
})
}
40 changes: 40 additions & 0 deletions terraform/resource-vpc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
resource "aws_vpc" "main" {
enable_dns_support = true
cidr_block = "10.0.0.0/16"
tags = {
app = "ps5"
}
}

resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
app = "ps5"
}
}

resource "aws_subnet" "aws-subnet" {
vpc_id = aws_vpc.main.id
cidr_block = aws_vpc.main.cidr_block
map_public_ip_on_launch = true
tags = {
app = "ps5"
}
}

resource "aws_route_table" "main" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
app = "ps5"
}
}

resource "aws_main_route_table_association" "main" {
route_table_id = aws_route_table.main.id
vpc_id = aws_vpc.main.id
}

18 changes: 18 additions & 0 deletions terraform/taskdef.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"name": "${name}-task",
"image": "ghcr.io/jef/streetmerchant:latest",
"cpu": ${cpu},
"memory": ${memory},
"essential": true,
"environment": ${environment},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${awslogs-group}",
"awslogs-region": "${region}",
"awslogs-stream-prefix": "ecs"
}
}
}
]
8 changes: 8 additions & 0 deletions terraform/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
credential_profile = "ps5"

streetmerchant_env = {
"STORES" = "amazon-uk,game,argos,box,currys,johnlewis,shopto,smythstoys,very,amazon-it,amazon-nl"
"SHOW_ONLY_SERIES" = "sonyps5c,sonyps5de"
"SLACK_TOKEN" = "your slack api token"
"SLACK_CHANNEL" = "your slack channel name"
}
40 changes: 40 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
variable "credential_file" {
type = string
description = "your aws credentials file"
default = "~/.aws/credentials"
}

variable "credential_profile" {
type = string
description = "the section in ~/.aws/credentials with your desired aws_access_key_id and aws_secret_access_key values"
default = "default"
}

variable "region" {
type = string
description = "aws region"
default = "eu-west-2"
}

variable "app_name" {
type = string
default = "streetmerchant"
}

variable "memory" {
type = string
default = "2048"
description = "ecs task memory"
}

variable "cpu" {
type = number
default = 1024
description = "ecs task cpu"
}

variable "streetmerchant_env" {
type = map
description = "name/value pairs for .env values"
default = {}
}