-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add terraform config to support locality aware routing within ECS
- Loading branch information
1 parent
59c7f1c
commit 89bacb2
Showing
26 changed files
with
1,002 additions
and
2 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# Locality based routing between ECS tasks | ||
|
||
This example demonstrates how Consul routes traffic based on the locality where the ECS tasks are deployed. As of 1.17, Consul only supports locality aware routing within a single partition. Support for multiple partitions and multiple cluster peers will soon be added in the upcoming releases. | ||
|
||
**Note**: This feature only works for a Consul enterprise installation setup. | ||
|
||
![Example architecture](https://github.com/hashicorp/terraform-aws-consul-ecs/blob/main/_docs/locality-aware-routing-arch.png?raw=true) | ||
|
||
This terraform example deploys a client application and two server application tasks spread across available within the same AWS region. The consul server is also deployed as an ECS task along with an ECS controller. | ||
|
||
## Requirements | ||
|
||
* `jq` | ||
* `curl` | ||
* Terraform >= 1.2.2 | ||
* Authentication credentials for the [Terraform AWS provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication) | ||
|
||
## Usage | ||
|
||
### Setup | ||
|
||
Clone this repository: | ||
|
||
```console | ||
$ git clone https://github.com/hashicorp/terraform-aws-consul-ecs.git | ||
$ git checkout tags/<latest-version> | ||
$ cd terraform-aws-consul-ecs/examples/locality-aware-routing | ||
``` | ||
|
||
This module contains everything needed to spin up the example. The only | ||
requirements are: | ||
- You need to pass in the `name` variable. This value will be used as a unique identifier | ||
for all resources created by the example. The examples below use the name `ecs`. | ||
- You need to pass in the IP address of your workstation via the `lb_ingress_ip` | ||
variable. This is used for the security groups on the elastic load balancers to ensure | ||
only you have access to them. | ||
- Consul Enterprise license that needs to be passed to the `consul_license` variable. This license will be used to run the enterprise version of Consul in this example. One way to pass these licenses would be add them to a `.tfvars` file and pass it as an argument to the `terraform apply` command. | ||
|
||
Determine your public IP. You can use a site like https://ifconfig.me/: | ||
|
||
```console | ||
$ curl ifconfig.me | ||
123.456.789.1% | ||
``` | ||
|
||
Example `input.tfvars` | ||
|
||
``` | ||
lb_ingress_ip = "123.456.789.1" | ||
consul_license = "<license>" | ||
``` | ||
|
||
Initialize Terraform: | ||
|
||
```console | ||
$ terraform init | ||
``` | ||
|
||
### Terraform Apply | ||
|
||
Then apply the Terraform passing in a name and your IP: | ||
|
||
```console | ||
$ terraform apply \ | ||
-var name=ecs \ | ||
-var-file=input.tfvars | ||
``` | ||
|
||
The plan should look similar to: | ||
|
||
```shell | ||
Plan: 81 to add, 0 to change, 0 to destroy. | ||
|
||
Changes to Outputs: | ||
+ client_lb_address = (known after apply) | ||
+ consul_server_bootstrap_token = (sensitive value) | ||
+ consul_server_url = (known after apply) | ||
``` | ||
|
||
Type `yes` to apply the changes. | ||
|
||
~> **Warning:** These resources will cost money. Be sure to run `terraform destroy` | ||
when you've finished testing. | ||
|
||
The apply should take 7-10 minutes. When complete, the URLs of the two load | ||
balancers should be in the output, along with the bootstrap token for the Consul server: | ||
|
||
```shell | ||
Apply complete! Resources: 81 added, 0 changed, 0 destroyed. | ||
|
||
Outputs: | ||
|
||
client_lb_address = "http://example-client-app-1959503271.us-west-2.elb.amazonaws.com:9090/ui" | ||
consul_server_bootstrap_token = <sensitive> | ||
consul_server_url = "http://ecs-dc1-consul-server-713584774.us-west-2.elb.amazonaws.com:8500" | ||
``` | ||
|
||
### Explore | ||
|
||
Get the bootstrap token for the Consul server from the Terraform output: | ||
|
||
```console | ||
$ terraform output -json | jq -r .consul_server_bootstrap_token.value | ||
e2cb39e2-b9fd-18af-025f-86f6da6889a7 | ||
``` | ||
|
||
If you click on the URL of the `consul_server_url`, you should be able | ||
to view the Consul UI and log in using the `consul_server_bootstrap_token` above: | ||
|
||
![Consul dc1 UI](https://github.com/hashicorp/terraform-aws-consul-ecs/blob/main/_docs/locality-aware-dc1-ui.png?raw=true) | ||
|
||
If you browse to the URL of the `client_lb_address`, the example application UI should be displayed: | ||
|
||
![Example App UI](https://github.com/hashicorp/terraform-aws-consul-ecs/blob/main/_docs/locality-aware-dc1-ui.png?raw=true) | ||
|
||
Notice the IP of the upstream server application's task. Because of the locality parameters added during the service registration, Consul takes care of routing traffic from the client application to the server application task within the same availability zone. | ||
|
||
#### Testing failover | ||
|
||
Terminate the server app's task that resides in the same availability zone as that of the client app's task. This can be done by manually stopping the desired task from the ECS UI or with the following CLI command | ||
|
||
``` | ||
aws ecs stop-task --region ${AWS_REGION} --cluster ${CLUSTER_ARN} --task ${TASK_ARN} --reason "Testing failover" | ||
``` | ||
|
||
Once the task gets successfully stopped, try making calls to the server application from `client_lb_address`. The first few calls should fail but once the failure breaches a particular threshold calls will automatically be failed over to the server app's task present in another availability zone within the same AWS region. | ||
|
||
![Example App UI](https://github.com/hashicorp/terraform-aws-consul-ecs/blob/main/_docs/locality-aware-dc1-failover-ui.png?raw=true) | ||
|
||
## Cleanup | ||
|
||
Once you've finished testing, be sure to clean up the resources you've created: | ||
|
||
```console | ||
$ terraform destroy \ | ||
-var name=ecs \ | ||
-var-file=input.tfvars | ||
``` | ||
|
||
## Next Steps | ||
|
||
Next, see our [full documentation](https://www.consul.io/docs/ecs) when you're | ||
ready to deploy your own applications into the service mesh. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
# Copyright (c) HashiCorp, Inc. | ||
# SPDX-License-Identifier: MPL-2.0 | ||
|
||
locals { | ||
example_client_app_name = "example-client-app" | ||
example_client_app_log_config = { | ||
logDriver = "awslogs" | ||
options = { | ||
awslogs-group = module.cluster.log_group.name | ||
awslogs-region = var.region | ||
awslogs-stream-prefix = "client" | ||
} | ||
} | ||
} | ||
|
||
|
||
module "example_client_app" { | ||
source = "../../modules/mesh-task" | ||
family = local.example_client_app_name | ||
port = "9090" | ||
acls = true | ||
consul_server_hosts = module.dc1.dev_consul_server.server_dns | ||
tls = true | ||
consul_ca_cert_arn = module.dc1.dev_consul_server.ca_cert_arn | ||
upstreams = [ | ||
{ | ||
destinationName = "example-server-app" | ||
localBindPort = 1234 | ||
} | ||
] | ||
log_configuration = local.example_client_app_log_config | ||
container_definitions = [ | ||
{ | ||
name = "example-client-app" | ||
image = "docker.mirror.hashicorp.services/nicholasjackson/fake-service:v0.21.0" | ||
essential = true | ||
logConfiguration = local.example_client_app_log_config | ||
environment = [ | ||
{ | ||
name = "NAME" | ||
value = local.example_client_app_name | ||
}, | ||
{ | ||
name = "UPSTREAM_URIS" | ||
value = "http://localhost:1234" | ||
} | ||
] | ||
portMappings = [ | ||
{ | ||
containerPort = 9090 | ||
hostPort = 9090 | ||
protocol = "tcp" | ||
} | ||
] | ||
}] | ||
|
||
additional_task_role_policies = [aws_iam_policy.execute_command.arn] | ||
|
||
consul_ecs_image = var.consul_ecs_image | ||
} | ||
|
||
resource "aws_ecs_service" "example_client_app" { | ||
name = local.example_client_app_name | ||
cluster = module.cluster.ecs_cluster.arn | ||
task_definition = module.example_client_app.task_definition_arn | ||
desired_count = 1 | ||
network_configuration { | ||
subnets = module.dc1.private_subnets | ||
} | ||
launch_type = "FARGATE" | ||
propagate_tags = "TASK_DEFINITION" | ||
load_balancer { | ||
target_group_arn = aws_lb_target_group.example_client_app.arn | ||
container_name = "example-client-app" | ||
container_port = 9090 | ||
} | ||
enable_execute_command = true | ||
} | ||
|
||
resource "aws_lb" "example_client_app" { | ||
name = local.example_client_app_name | ||
internal = false | ||
load_balancer_type = "application" | ||
security_groups = [aws_security_group.example_client_app_alb.id] | ||
subnets = module.dc1.public_subnets | ||
} | ||
|
||
resource "aws_security_group" "example_client_app_alb" { | ||
name = "${local.example_client_app_name}-alb" | ||
vpc_id = module.vpc.vpc_id | ||
|
||
ingress { | ||
description = "Access to example client application." | ||
from_port = 9090 | ||
to_port = 9090 | ||
protocol = "tcp" | ||
cidr_blocks = ["${var.lb_ingress_ip}/32"] | ||
} | ||
|
||
egress { | ||
from_port = 0 | ||
to_port = 0 | ||
protocol = "-1" | ||
cidr_blocks = ["0.0.0.0/0"] | ||
} | ||
} | ||
|
||
resource "aws_security_group_rule" "ingress_from_client_alb_to_ecs" { | ||
type = "ingress" | ||
from_port = 0 | ||
to_port = 65535 | ||
protocol = "tcp" | ||
source_security_group_id = aws_security_group.example_client_app_alb.id | ||
security_group_id = module.vpc.default_security_group_id | ||
} | ||
|
||
resource "aws_security_group_rule" "ingress_from_server_alb_to_ecs" { | ||
type = "ingress" | ||
from_port = 8500 | ||
to_port = 8500 | ||
protocol = "tcp" | ||
source_security_group_id = module.dc1.dev_consul_server.lb_security_group_id | ||
security_group_id = module.vpc.default_security_group_id | ||
} | ||
|
||
resource "aws_lb_target_group" "example_client_app" { | ||
name = local.example_client_app_name | ||
port = 9090 | ||
protocol = "HTTP" | ||
vpc_id = module.vpc.vpc_id | ||
target_type = "ip" | ||
deregistration_delay = 10 | ||
health_check { | ||
path = "/health" | ||
healthy_threshold = 2 | ||
unhealthy_threshold = 10 | ||
timeout = 30 | ||
interval = 60 | ||
} | ||
} | ||
|
||
resource "aws_lb_listener" "example_client_app" { | ||
load_balancer_arn = aws_lb.example_client_app.arn | ||
port = "9090" | ||
protocol = "HTTP" | ||
default_action { | ||
type = "forward" | ||
target_group_arn = aws_lb_target_group.example_client_app.arn | ||
} | ||
} | ||
|
||
// Policy that allows execution of remote commands in ECS tasks. | ||
resource "aws_iam_policy" "execute_command" { | ||
name = "${var.name}-ecs-execute-command" | ||
path = "/" | ||
policy = <<EOF | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Effect": "Allow", | ||
"Action": [ | ||
"ssmmessages:CreateControlChannel", | ||
"ssmmessages:CreateDataChannel", | ||
"ssmmessages:OpenControlChannel", | ||
"ssmmessages:OpenDataChannel" | ||
], | ||
"Resource": [ | ||
"*" | ||
] | ||
} | ||
] | ||
} | ||
EOF | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Copyright (c) HashiCorp, Inc. | ||
# SPDX-License-Identifier: MPL-2.0 | ||
|
||
resource "aws_ecs_cluster" "this" { | ||
name = var.name | ||
capacity_providers = ["FARGATE"] | ||
} | ||
|
||
resource "aws_cloudwatch_log_group" "log_group" { | ||
name = var.name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Copyright (c) HashiCorp, Inc. | ||
# SPDX-License-Identifier: MPL-2.0 | ||
|
||
output "ecs_cluster" { | ||
value = aws_ecs_cluster.this | ||
} | ||
|
||
output "log_group" { | ||
value = aws_cloudwatch_log_group.log_group | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Copyright (c) HashiCorp, Inc. | ||
# SPDX-License-Identifier: MPL-2.0 | ||
|
||
variable "name" { | ||
description = "Name of the ECS cluster." | ||
type = string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Copyright (c) HashiCorp, Inc. | ||
# SPDX-License-Identifier: MPL-2.0 | ||
|
||
module "cluster" { | ||
source = "./cluster" | ||
name = var.name | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Copyright (c) HashiCorp, Inc. | ||
# SPDX-License-Identifier: MPL-2.0 | ||
|
||
module "ecs_controller" { | ||
source = "../../../modules/controller" | ||
|
||
name_prefix = var.name | ||
ecs_cluster_arn = var.ecs_cluster_arn | ||
region = var.region | ||
subnets = var.private_subnets | ||
consul_server_hosts = var.consul_server_hosts | ||
consul_ca_cert_arn = var.consul_ca_cert_arn | ||
launch_type = "FARGATE" | ||
|
||
consul_partitions_enabled = true | ||
consul_partition = var.consul_partition | ||
|
||
consul_bootstrap_token_secret_arn = var.consul_server_bootstrap_token_arn | ||
|
||
log_configuration = { | ||
logDriver = "awslogs" | ||
options = { | ||
awslogs-group = var.log_group_name | ||
awslogs-region = var.region | ||
awslogs-stream-prefix = "ecs-controller" | ||
} | ||
} | ||
|
||
consul_ecs_image = var.consul_ecs_image | ||
tls = true | ||
} |
Oops, something went wrong.