Installation

Installing Consul on ECS is a multi-part process:

  1. Task Module: Define the mesh-task Terraform module to create a task definition with the necessary sidecar containers for your application to join the service mesh.
  2. Routing: With your tasks as part of the mesh, you must specify their upstream services and change the URLs the tasks are using so that they’re making requests through the service mesh.
  3. Bind Address: Now that all communication is flowing through the service mesh, you should change the address your application is listening on to 127.0.0.1 so that it only receives requests through the sidecar proxy.

NOTE: This page assumes you’re familiar with ECS. See What is Amazon Elastic Container Service for more details.

Task Module

In order to add the necessary sidecar containers for your task to join the mesh, you must use the mesh-task module:

  1. module "my_task" {
  2. source = "hashicorp/consul-ecs/aws//modules/mesh-task"
  3. version = "<latest version>"
  4. family = "my_task"
  5. container_definitions = [
  6. {
  7. name = "example-client-app"
  8. image = "docker.io/org/my_task:v0.0.1"
  9. essential = true
  10. portMappings = [
  11. {
  12. containerPort = 9090
  13. hostPort = 9090
  14. protocol = "tcp"
  15. }
  16. ]
  17. cpu = 0
  18. mountPoints = []
  19. volumesFrom = []
  20. }
  21. ]
  22. port = "9090"
  23. retry_join = ["<address of the Consul server>"]
  24. }

All possible inputs are documented on the module reference documentation, however there are some important inputs worth highlighting:

  • family is used as the task definition family but it’s also used as the name of the service that gets registered in Consul.
  • container_definitions accepts an array of container definitions. This is where you include application containers.
  • port is the port that your application listens on. This should be set to a string, not an integer, i.e. port = "9090", not port = 9090.
  • retry_join is passed to the -retry-join option for the Consul agent. This tells the agent the location of your Consul servers so that it can join the Consul cluster.

NOTE: If your tasks run in a public subnet, they must have assign_public_ip = true in their network_configuration block so that ECS can pull the Docker images.

ECS Service

To define an ECS Service, reference the mesh-task module’s task_definition_arn output value in your aws_ecs_service resource:

  1. resource "aws_ecs_service" "my_task" {
  2. ...
  3. task_definition = module.my_task.task_definition_arn
  4. }

After running terraform apply, you should see your tasks registered in the Consul UI.

Routing

Now that your tasks are registered in the mesh, you’re able to use the service mesh to route between them.

In order to make calls through the service mesh, you must configure the sidecar proxy to listen on a different port for each upstream service your application needs to call. You then must modify your application to make requests to the sidecar proxy on that port.

For example, if your application web makes calls to another application called backend, then you would first configure the mesh-task module’s upstream(s): backend.

  1. module "web" {
  2. family = "web"
  3. upstreams = [
  4. {
  5. destination_name = "backend"
  6. local_bind_port = 8080
  7. }
  8. ]
  9. }
  • Set the destination_name to the name of the upstream service (in this case backend)
  • Set local_bind_port to an unused port. This is the port that the sidecar proxy will listen on. Any requests to this port will be forwarded over to the destination_name. This does not have to be the port that backend is listening on because the service mesh will handle routing the request to the right port.

If you have multiple upstream services they each need to be listed here.

Next, configure your application to make requests to localhost:8080 when it wants to call the backend service.

For example, if your service allows configuring the URL for backend via the BACKEND_URL environment variable, you would set:

  1. module "web" {
  2. family = "web"
  3. upstreams = [
  4. {
  5. destination_name = "backend"
  6. local_bind_port = 8080
  7. }
  8. ]
  9. container_definitions = [
  10. {
  11. name = "web"
  12. environment = [
  13. {
  14. name = "BACKEND_URL"
  15. value = "http://localhost:8080"
  16. }
  17. ]
  18. ...
  19. }
  20. ]
  21. ...
  22. }

Bind Address

To ensure that your application only receives traffic through the service mesh, you must change the address that your application is listening on to only the loopback address (also known as localhost, lo, and 127.0.0.1) so that only the sidecar proxy running in the same task can make requests to it.

If your application is listening on all interfaces, e.g. 0.0.0.0, then other applications can call it directly, bypassing its sidecar proxy.

Changing the listening address is specific to the language and framework you’re using in your application. Regardless of which language/framework you’re using, it’s a good practice to make the address configurable via environment variable.

For example in Go, you would use:

  1. s := &http.Server{
  2. Addr: "127.0.0.1:8080",
  3. ...
  4. }
  5. log.Fatal(s.ListenAndServe())

In Django you’d use:

  1. python manage.py runserver "127.0.0.1:8080"

Next Steps