├── outputs.tf ├── README.md ├── container-definition └── container-definition.json ├── security.tf ├── variables.tf └── main.tf /outputs.tf: -------------------------------------------------------------------------------- 1 | /* 2 | Module outputs 3 | */ 4 | 5 | output "ecs_service_arn" { 6 | value = aws_ecs_service.ecs_service.id 7 | } 8 | 9 | output "ecs_task_iam_role_name" { 10 | value = aws_iam_role.ecs_task_role.name 11 | } 12 | 13 | output "aws_ecs_task_definition_arn" { 14 | value = aws_ecs_task_definition.ecs_task_definition.arn 15 | } 16 | 17 | output "target_group_arn" { 18 | value = aws_lb_target_group.target_group.arn 19 | } 20 | 21 | output "fully_qualified_domain_name" { 22 | value = var.create_route53_entry ? var.domain_name : aws_lb.ecs_alb.dns_name 23 | } 24 | 25 | output "efs_volume_dns_name" { 26 | value = aws_efs_file_system.ecs_service_storage.dns_name 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grafana on ECS Fargate 2 | 3 | This Terraform Module deploys Grafana on ECS Fargate with an ALB on top of an ECS Cluster. 4 | 5 | ## How do you use this module? 6 | 7 | * See the [root README](/README.md) for instructions on using Terraform modules. 8 | * See [variables.tf](./variables.tf) for all the variables you can set on this module. 9 | 10 | ## What is an ECS Service? 11 | 12 | To run Docker containers with ECS, you first define an [ECS 13 | Task](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_defintions.html), which is a JSON file that 14 | describes what container(s) to run, the resources (memory, CPU) those containers need, the volumes to mount, the 15 | environment variables to set, and so on. To actually run an ECS Task, you define an ECS Service, which can: 16 | 17 | 1. Deploy the requested number of Tasks across an ECS cluster based on the `desired_number_of_tasks` input variable. 18 | 1. Restart tasks if they fail. 19 | 20 | ## What is an ALB? 21 | 22 | An [Application Load Balancer](http://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html) is 23 | a "Layer 7" load balancer managed by AWS that forwards incoming requests to the ECS Tasks (Docker containers) in your ECS 24 | Service which are running in your ECS Cluster. 25 | 26 | It automatically discovers new ECS Tasks as they launch. A single ALB is shared among potentially many ECS Services. 27 | 28 | -------------------------------------------------------------------------------- /container-definition/container-definition.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "${container_name}", 4 | "image": "${image}:${version}", 5 | "logConfiguration": { 6 | "logDriver": "awslogs", 7 | "options": { 8 | "awslogs-group": "${cloudwatch_log_group_name}", 9 | "awslogs-region": "${aws_region}", 10 | "awslogs-stream-prefix": "ecs" 11 | } 12 | }, 13 | "portMappings": [ 14 | { 15 | "hostPort": ${container_port}, 16 | "protocol": "tcp", 17 | "containerPort": ${container_port} 18 | } 19 | ], 20 | "cpu": ${cpu}, 21 | "memoryReservation": ${memory}, 22 | "mountPoints": [ 23 | { 24 | "containerPath": "/var/lib/grafana", 25 | "sourceVolume": "grafana-db" 26 | } 27 | ], 28 | "secrets": [ 29 | { 30 | "valueFrom": "/grafana/GF_AUTH_GITHUB_ALLOW_SIGN_UP", 31 | "name": "GF_AUTH_GITHUB_ALLOW_SIGN_UP" 32 | }, 33 | { 34 | "valueFrom": "/grafana/GF_AUTH_GITHUB_ALLOWED_ORGANIZATIONS", 35 | "name": "GF_AUTH_GITHUB_ALLOWED_ORGANIZATIONS" 36 | }, 37 | { 38 | "valueFrom": "/grafana/GF_AUTH_GITHUB_API_URL", 39 | "name": "GF_AUTH_GITHUB_API_URL" 40 | }, 41 | { 42 | "valueFrom": "/grafana/GF_AUTH_GITHUB_AUTH_URL", 43 | "name": "GF_AUTH_GITHUB_AUTH_URL" 44 | }, 45 | { 46 | "valueFrom": "/grafana/GF_AUTH_GITHUB_CLIENT_ID", 47 | "name": "GF_AUTH_GITHUB_CLIENT_ID" 48 | }, 49 | { 50 | "valueFrom": "/grafana/GF_AUTH_GITHUB_CLIENT_SECRET", 51 | "name": "GF_AUTH_GITHUB_CLIENT_SECRET" 52 | }, 53 | { 54 | "valueFrom": "/grafana/GF_AUTH_GITHUB_ENABLED", 55 | "name": "GF_AUTH_GITHUB_ENABLED" 56 | }, 57 | { 58 | "valueFrom": "/grafana/GF_AUTH_GITHUB_SCOPES", 59 | "name": "GF_AUTH_GITHUB_SCOPES" 60 | }, 61 | { 62 | "valueFrom": "/grafana/GF_AUTH_GITHUB_TOKEN_URL", 63 | "name": "GF_AUTH_GITHUB_TOKEN_URL" 64 | }, 65 | { 66 | "valueFrom": "/grafana/GF_SERVER_ROOT_URL", 67 | "name": "GF_SERVER_ROOT_URL" 68 | }, 69 | { 70 | "valueFrom": "/grafana/GF_SERVER_ENABLE_GZIP", 71 | "name": "GF_SERVER_ENABLE_GZIP" 72 | }, 73 | { 74 | "valueFrom": "/grafana/GF_DEFAULT_INSTANCE_NAME", 75 | "name": "GF_DEFAULT_INSTANCE_NAME" 76 | } 77 | ], 78 | "volumesFrom": [], 79 | "essential": true 80 | } 81 | ] 82 | -------------------------------------------------------------------------------- /security.tf: -------------------------------------------------------------------------------- 1 | /* 2 | Here we define the security related resources for the module 3 | */ 4 | 5 | # Security group for the EFS share and mount target 6 | resource "aws_security_group" "efs_sg" { 7 | name = "${var.service_name}-efs-sg" 8 | description = "Allow traffic to the EFS storage volume" 9 | vpc_id = var.vpc_id 10 | 11 | ingress { 12 | from_port = 0 13 | to_port = 0 14 | protocol = "-1" 15 | cidr_blocks = ["0.0.0.0/0"] 16 | } 17 | 18 | egress { 19 | from_port = 0 20 | to_port = 0 21 | protocol = "-1" 22 | cidr_blocks = ["0.0.0.0/0"] 23 | } 24 | } 25 | 26 | # Security group for the ECS task 27 | resource "aws_security_group" "ecs_task_security_group" { 28 | name = "${var.service_name}-task-access" 29 | vpc_id = var.vpc_id 30 | } 31 | resource "aws_security_group_rule" "allow_outbound_all" { 32 | security_group_id = aws_security_group.ecs_task_security_group.id 33 | type = "egress" 34 | from_port = 0 35 | to_port = 0 36 | protocol = "-1" 37 | cidr_blocks = ["0.0.0.0/0"] 38 | } 39 | 40 | resource "aws_security_group_rule" "allow_inbound_on_container_port" { 41 | security_group_id = aws_security_group.ecs_task_security_group.id 42 | type = "ingress" 43 | from_port = var.container_port 44 | to_port = var.container_port 45 | protocol = "tcp" 46 | cidr_blocks = var.allow_inbound_from_cidr_blocks 47 | } 48 | 49 | # Security group for the ECS service 50 | resource "aws_security_group" "ecs_service_security_group" { 51 | name = "${var.service_name}-service-access" 52 | vpc_id = var.vpc_id 53 | } 54 | 55 | resource "aws_security_group_rule" "allow_outbound_ecs_service_all" { 56 | security_group_id = aws_security_group.ecs_service_security_group.id 57 | type = "egress" 58 | from_port = 0 59 | to_port = 0 60 | protocol = "-1" 61 | cidr_blocks = ["0.0.0.0/0"] 62 | } 63 | 64 | resource "aws_security_group_rule" "allow_inbound_ecs_service_all" { 65 | security_group_id = aws_security_group.ecs_service_security_group.id 66 | type = "ingress" 67 | from_port = 0 68 | to_port = 0 69 | protocol = "-1" 70 | cidr_blocks = ["0.0.0.0/0"] 71 | } 72 | 73 | # Security group for the ALB 74 | resource "aws_security_group" "alb_sg" { 75 | name = "${var.service_name}-alb-sg" 76 | description = "Allow traffic to the ALB created for the ${var.service_name} service" 77 | vpc_id = var.vpc_id 78 | 79 | ingress { 80 | from_port = 80 81 | to_port = 80 82 | protocol = "tcp" 83 | cidr_blocks = ["0.0.0.0/0"] 84 | } 85 | 86 | ingress { 87 | from_port = 443 88 | to_port = 443 89 | protocol = "tcp" 90 | cidr_blocks = ["0.0.0.0/0"] 91 | } 92 | 93 | egress { 94 | from_port = 0 95 | to_port = 0 96 | protocol = "-1" 97 | cidr_blocks = ["0.0.0.0/0"] 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------------------------------------------- 2 | # REQUIRED MODULE PARAMETERS 3 | # These variables must be passed in by the operator. 4 | # --------------------------------------------------------------------------------------------------------------------- 5 | 6 | variable "aws_region" { 7 | description = "The AWS region in which the ECS Service will be created." 8 | type = string 9 | } 10 | 11 | variable "service_name" { 12 | description = "The name of the ECS service (e.g. my-service-stage)" 13 | type = string 14 | } 15 | 16 | variable "platform_version" { 17 | description = "The ECS Fargate version to run Grafana on" 18 | type = string 19 | } 20 | 21 | variable "ecs_cluster" { 22 | description = "The ECS cluster to run the service on" 23 | type = string 24 | } 25 | 26 | # Docker image configuration 27 | 28 | variable "image" { 29 | description = "The Docker image to run" 30 | type = string 31 | } 32 | 33 | variable "image_version" { 34 | description = "Which version (AKA tag) of the var.image Docker image to deploy (e.g. 0.57)" 35 | type = string 36 | } 37 | 38 | variable "container_port" { 39 | description = "The port number on which this service's Docker container accepts incoming HTTP or HTTPS traffic." 40 | type = number 41 | } 42 | 43 | variable "cloudwatch_log_group_name" { 44 | description = "The name of the cloudwatch log group where the application will send logs to" 45 | type = string 46 | } 47 | 48 | # Runtime properties of this ECS Service in the ECS Cluster 49 | 50 | variable "cpu" { 51 | description = "The number of CPU units to allocate to the ECS Service." 52 | type = number 53 | } 54 | 55 | variable "memory" { 56 | description = "How much memory, in MB, to give the ECS Service." 57 | type = number 58 | } 59 | 60 | variable "desired_number_of_tasks" { 61 | description = "How many instances of the ECS Service to run across the ECS cluster" 62 | type = number 63 | } 64 | 65 | variable "allow_inbound_from_cidr_blocks" { 66 | description = "A list of IP CIDR blocks allowed to access the service" 67 | type = list 68 | } 69 | 70 | # VPC information 71 | 72 | variable "vpc_id" { 73 | description = "The VPC ID in which to deploy the resources" 74 | type = string 75 | } 76 | 77 | variable "private_subnet_ids" { 78 | description = "The list of private subnet IDs" 79 | type = list 80 | } 81 | 82 | variable "public_subnet_ids" { 83 | description = "The list of public subnet IDs" 84 | type = list 85 | } 86 | 87 | variable "ssl_cert_arn" { 88 | description = "The ARN of the SSL certificate to use on the ALB" 89 | type = string 90 | } 91 | 92 | # --------------------------------------------------------------------------------------------------------------------- 93 | # OPTIONAL MODULE PARAMETERS 94 | # These variables have defaults, but may be overridden by the operator. 95 | # --------------------------------------------------------------------------------------------------------------------- 96 | 97 | variable "health_check_grace_period_seconds" { 98 | description = "Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 1800. Only valid for services configured to use load balancers." 99 | type = number 100 | default = 15 101 | } 102 | 103 | # ALB options 104 | 105 | variable "alb_target_group_protocol" { 106 | description = "The network protocol to use for routing traffic from the ALB to the Targets. Must be one of HTTP or HTTPS. Note that if HTTPS is used, per https://goo.gl/NiOVx7, the ALB will use the security settings from ELBSecurityPolicy2015-05." 107 | type = string 108 | default = "HTTP" 109 | } 110 | 111 | variable "alb_target_group_deregistration_delay" { 112 | description = "The amount time for Elastic Load Balancing to wait before changing the state of a deregistering target from draining to unused. The range is 0-3600 seconds." 113 | type = number 114 | default = 15 115 | } 116 | 117 | # Deployment Options 118 | 119 | variable "deployment_maximum_percent" { 120 | description = "The upper limit, as a percentage of var.desired_number_of_tasks, of the number of running ECS Tasks that can be running in a service during a deployment. Setting this to more than 100 means that during deployment, ECS will deploy new instances of a Task before undeploying the old ones." 121 | type = number 122 | default = 200 123 | } 124 | 125 | variable "deployment_minimum_healthy_percent" { 126 | description = "The lower limit, as a percentage of var.desired_number_of_tasks, of the number of running ECS Tasks that must remain running and healthy in a service during a deployment. Setting this to less than 100 means that during deployment, ECS may undeploy old instances of a Task before deploying new ones." 127 | type = number 128 | default = 100 129 | } 130 | 131 | # Health check options 132 | 133 | variable "health_check_interval" { 134 | description = "The approximate amount of time, in seconds, between health checks of an individual Target. Minimum value 5 seconds, Maximum value 300 seconds." 135 | type = number 136 | default = 30 137 | } 138 | 139 | variable "health_check_protocol" { 140 | description = "The protocol the ALB uses when performing health checks on Targets. Must be one of HTTP and HTTPS." 141 | type = string 142 | default = "HTTP" 143 | } 144 | 145 | variable "health_check_timeout" { 146 | description = "The amount of time, in seconds, during which no response from a Target means a failed health check. The acceptable range is 2 to 60 seconds." 147 | type = number 148 | default = 5 149 | } 150 | 151 | variable "health_check_healthy_threshold" { 152 | description = "The number of consecutive successful health checks required before considering an unhealthy Target healthy. The acceptable range is 2 to 10." 153 | type = number 154 | default = 5 155 | } 156 | 157 | variable "health_check_unhealthy_threshold" { 158 | description = "The number of consecutive failed health checks required before considering a target unhealthy. The acceptable range is 2 to 10." 159 | type = number 160 | default = 2 161 | } 162 | 163 | variable "health_check_matcher" { 164 | description = "The HTTP codes to use when checking for a successful response from a Target. You can specify multiple values (e.g. '200,202') or a range of values (e.g. '200-299')." 165 | type = string 166 | default = "200" 167 | } 168 | 169 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | # DEPLOY A GRAFANA SERVICE ON ECS FARGATE 3 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | 5 | # --------------------------------------------------------------------------------------------------------------------- 6 | # CREATE AN ELASTIC FILE SYSTEM (NFS) TO PROVIDE PERMANENT STORAGE FOR GRAFANA 7 | # --------------------------------------------------------------------------------------------------------------------- 8 | 9 | resource "aws_efs_file_system" "ecs_service_storage" { 10 | tags = { 11 | Name = "${var.service_name}-efs" 12 | } 13 | } 14 | 15 | resource "aws_efs_mount_target" "ecs_service_storage" { 16 | count = length(var.private_subnet_ids) 17 | 18 | file_system_id = aws_efs_file_system.ecs_service_storage.id 19 | subnet_id = var.private_subnet_ids[count.index] 20 | security_groups = [aws_security_group.efs_sg.id] 21 | } 22 | 23 | # --------------------------------------------------------------------------------------------------------------------- 24 | # CONFIGURE THE CLOUDWATCH LOG GROUP FOR THIS SERVICE 25 | # --------------------------------------------------------------------------------------------------------------------- 26 | 27 | resource "aws_cloudwatch_log_group" "ecs_service" { 28 | name = var.cloudwatch_log_group_name 29 | retention_in_days = 30 30 | } 31 | 32 | # --------------------------------------------------------------------------------------------------------------------- 33 | # CREATE AN ECS TASK TO RUN THE DOCKER CONTAINER 34 | # --------------------------------------------------------------------------------------------------------------------- 35 | 36 | # Define the Assume Role IAM Policy Document for the ECS Service Scheduler IAM Role 37 | data "aws_iam_policy_document" "ecs_task" { 38 | statement { 39 | effect = "Allow" 40 | actions = ["sts:AssumeRole"] 41 | 42 | principals { 43 | type = "Service" 44 | identifiers = ["ecs-tasks.amazonaws.com"] 45 | } 46 | } 47 | } 48 | 49 | # Create the IAM roles for the ECS Task 50 | resource "aws_iam_role" "ecs_task_execution_role" { 51 | name = "${var.service_name}-task-execution-role" 52 | assume_role_policy = data.aws_iam_policy_document.ecs_task.json 53 | } 54 | resource "aws_iam_role" "ecs_task_role" { 55 | name = "${var.service_name}-task-role" 56 | assume_role_policy = data.aws_iam_policy_document.ecs_task.json 57 | } 58 | 59 | # This template_file defines the Docker containers we want to run in our ECS Task 60 | data "template_file" "ecs_task_container_definitions" { 61 | template = file("${path.module}/container-definition/container-definition.json") 62 | 63 | vars = { 64 | aws_region = var.aws_region 65 | container_name = var.service_name 66 | service_name = var.service_name 67 | image = var.image 68 | version = var.image_version 69 | cloudwatch_log_group_name = var.cloudwatch_log_group_name 70 | cpu = var.cpu 71 | memory = var.memory 72 | container_port = var.container_port 73 | } 74 | } 75 | 76 | # Create the actual task definition by passing it the container definition from above 77 | resource "aws_ecs_task_definition" "ecs_task_definition" { 78 | family = var.service_name 79 | container_definitions = data.template_file.ecs_task_container_definitions.rendered 80 | network_mode = "awsvpc" 81 | cpu = var.cpu 82 | memory = var.memory 83 | requires_compatibilities = ["FARGATE", "EC2"] 84 | task_role_arn = aws_iam_role.ecs_task_role.arn 85 | execution_role_arn = aws_iam_role.ecs_task_execution_role.arn 86 | 87 | volume { 88 | name = "grafana-db" 89 | 90 | efs_volume_configuration { 91 | file_system_id = aws_efs_file_system.ecs_service_storage.id 92 | root_directory = "/grafana" 93 | } 94 | } 95 | } 96 | 97 | # --------------------------------------------------------------------------------------------------------------------- 98 | # CONFIGURE EXTRA IAM POLICIES FOR THE ECS SERVICE AND TASK 99 | # --------------------------------------------------------------------------------------------------------------------- 100 | 101 | resource "aws_iam_policy" "ecs_task_custom_policy" { 102 | name = "${var.service_name}-ecs-task-custom-policy" 103 | path = "/" 104 | 105 | policy = <