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

Add terraform config to support locality aware routing in ECS #219

Merged
merged 8 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ BREAKING CHANGES
IMPROVEMENTS
* examples/cluster-peering: Add example terraform to illustrate Consul's cluster peering usecase on ECS. [[GH-194]](https://github.com/hashicorp/terraform-aws-consul-ecs/pull/194)
* examples/service-sameness: Add example terraform to illustrate Consul's service sameness group usecase on ECS. [[GH-202]](https://github.com/hashicorp/terraform-aws-consul-ecs/pull/202)
* examples/locality-aware-routing: Add example terraform to demonstrate Consul's locality aware routing feature between ECS tasks [[GH-219]](https://github.com/hashicorp/terraform-aws-consul-ecs/pull/219)


## 0.6.1 (Jul 20, 2023)
Expand Down
Binary file added _docs/locality-aware-app-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _docs/locality-aware-dc1-failover-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _docs/locality-aware-dc1-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _docs/locality-aware-routing-arch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
143 changes: 143 additions & 0 deletions examples/locality-aware-routing/README.md
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.
Ganeshrockz marked this conversation as resolved.
Show resolved Hide resolved

![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.
Ganeshrockz marked this conversation as resolved.
Show resolved Hide resolved

## Requirements

* `jq`
* `curl`
* Terraform >= 1.2.2
* Authentication credentials for the [Terraform AWS provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication)
Ganeshrockz marked this conversation as resolved.
Show resolved Hide resolved

## 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%
```
cthain marked this conversation as resolved.
Show resolved Hide resolved

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"
```
Ganeshrockz marked this conversation as resolved.
Show resolved Hide resolved

### 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.
Ganeshrockz marked this conversation as resolved.
Show resolved Hide resolved

#### 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.
176 changes: 176 additions & 0 deletions examples/locality-aware-routing/client-app.tf
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

}
11 changes: 11 additions & 0 deletions examples/locality-aware-routing/cluster/ecs_cluster.tf
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
}
10 changes: 10 additions & 0 deletions examples/locality-aware-routing/cluster/outputs.tf
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
}
7 changes: 7 additions & 0 deletions examples/locality-aware-routing/cluster/variables.tf
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
}
7 changes: 7 additions & 0 deletions examples/locality-aware-routing/clusters.tf
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
}
Loading
Loading