Hi,
I was encouraged to write here to persist this knowledge for future generations (ty Luke for the help on slack). :)
We are currently doing a proof of concept (PoC) setup where we use AWS ECS and Fargate for a cluster. As of Fargate platform version 1.4.0 it supports using AWS EFS as persistent storage. As far as we can tell EFS is accessed from a Fargate task using NFS. So far we've done everything manually in the AWS Console but will move over to CloudFormation soon.
Our PoC setup is at time of writing as follows:
- 2 RMQ nodes (it's a PoC, we will expand to 3 in the final configuration)
- 2 ECS services named "rabbitmq-node1" and "rabbitmq-node2"
- 2 ECS tasks named "rabbitmq-node1" and "rabbitmq-node2"
- 2 EFS volumes named as above
- AWS Cloud Map for service discovery
Basic concepts:
- a service launches N instances of a task
- a task mounts zero or more EFS volumes
- a task consists of one or more containers
- a service maps to one logical domain name in Cloud Map/Service discovery
Constraints:
- nodename should be "known", i.e. must persist over restarts since the RMQ datastore files depend on nodename
- a task cannot expose several containers that listen on the same port
- Fargate requires using the `awsvpc` network mode, which basically means instances get dynamic IPs and hostnames on each restart and there's not much you can do about it, except AWS service discovery
Which leads to:
- the same task can't launch multiple instances and at the same time have known nodenames
- we cannot have two RMQ containers in one task
- we need one service and task per node in the cluster
Environment variables:
| Name | Value |
| --------------------- | --------------------------------------- |
| RABBITMQ_NODENAME | rabbit@rabbitmq-node{N}.example.private |
| RABBITMQ_USE_LONGNAME | true |
Configuration:
| Config | Value |
| ---------------------------------------- | ------------------------------------- |
| cluster_formation.peer_discovery_backend | rabbit_peer_discovery_classic_config |
| cluster_formation.classic_config.nodes.1 | rab...@rabbitmq-node1.example.private |
| cluster_formation.classic_config.nodes.2 | rab...@rabbitmq-node2.example.private |
The resolution of hostnames has been one of the key issues for us. Each time a task instance is spun up it is dyanamically allocated IP address and hostname. Thankfully service discovery does allow us to assign a DNS A record to the instance once it has spun up. But there is a race condition between service discovery and the node trying to resolve its own name:
`ERROR: epmd error for host rabbitmq-node2.example.private: nxdomain (non-existing domain)`
In [this writeup](
https://medium.com/thron-tech/clustering-rabbitmq-on-ecs-using-ec2-autoscaling-groups-107426a87b98) Andrea/THRON overcame the problem by adding an entry in `/etc/hosts`. We were unfortunately unable to do so, in our experiments we tried doing this as an `echo "rabbitmq-node1... localhost" >> /etc/hosts` in the docker CMD but were denied with an error stating it's a read only filesystem. Doing this as part of the docker ENTRYPOINT failed with the same error. So we worked around it by doing a `sleep 60s` inside our container before spinning up rabbitmq. 60s coincides with the refresh rate we've set for the DNS A record in Cloud Map/Service Discovery.
Current issues:
- the 60s sleep feels very hacky and is bad for failover, it would be nice to get THRON's solution working
- we have yet to solve providing a single DNS name for our clients but we haven't looked into it yet
Other thoughts and comments:
- we haven't tried the AWS specific peering even though we are loading the plugin, we're unsure whether it would work on Fargate instead of EC2 and since classic peering works we may just stick with that
I will most likely be converting this into a blog post on
dev.to but I'm guessing there will be a few more iterations on it first.
Any suggestions for improvement or solutions to our problems are warmly welcome.
Best regards,
Rasmus