Docker Swarm Peer Discovery Backend #7515
-
I'm operating a RabbitMQ cluster in a Docker Swarm cluster. This generally works well, but I fought bitterly to get automatic node discovery working -- in the end, I had to settle for a suboptimal workaround. root@rabbitmq-foo-1:/# nslookup tasks.rabbitmq
Server: 127.0.0.11
Address: 127.0.0.11#53
Non-authoritative answer:
Name: tasks.rabbitmq
Address: 10.100.8.158
Name: tasks.rabbitmq
Address: 10.100.8.162
Name: tasks.rabbitmq
Address: 10.100.8.163 The IPs resolve to the following, however: root@rabbitmq-foo-1:/# nslookup 10.100.8.158
158.8.100.10.in-addr.arpa name = foo_rabbitmq.tjqxe6kpe5g4mnmgwoiuink16.xhosndlwm3eaoldu36udvjfi9.rabbitmq-net. for a container with a hostname configured to This means that currently, the only options are to run a separate service mesh, or configure the hosts statically using the classic file backend - which works, but isn't ideal (I have to modify and re-deploy the RMQ service anytime we add or remove a Swarm node). I would like to formally suggest adding a service discovery backend plugin for Docker Swarm; in theory, this isn't too complicated, but would make it easier to get RabbitMQ running dynamically in Swarm. In the mean time, if anyone is interested in my solution: We run the service in global mode, so one RabbitMQ instance is deployed to every Swarm node. This makes it possible to use a volume persistent to the host. Additionally, we set the task hostname to that of the host node, so we can configure it statically. services:
rabbitmq:
image: "rabbitmq:3-management"
# This ensures the node has a persistent, unique name we refer to in RABBITMQ_NODENAME.
hostname: "rabbitmq-{{.Node.Hostname}}"
networks:
- rabbitmq_net
volumes:
# As the task will be deployed to the same node, this works on service updates too!
- rabbitmq-data:/var/lib/rabbitmq
environment:
RABBITMQ_NODENAME: "rabbit@rabbitmq-{{.Node.Hostname}}"
# The uid/gid/mode arguments below prevent permission errors at runtime
configs:
- source: rabbitmq_config
target: /etc/rabbitmq/rabbitmq.conf
uid: "999"
gid: "999"
mode: 0600
secrets:
- source: rabbitmq_erlang_cookie
target: /var/lib/rabbitmq/.erlang.cookie
uid: "999"
gid: "999"
mode: 0600
- source: rabbitmq_definitions
target: /etc/rabbitmq/definitions.json
uid: "999"
gid: "999"
mode: 0600
# See https://www.rabbitmq.com/configure.html#kernel-limits
ulimits:
nofile:
soft: 64000
hard: 64000
deploy:
# Setting the mode to global ensures a single replica will be started on every node
mode: global
# Ensure stopped instances will be restarted. RabbitMQ seems to error with an exit
# code 0 sometimes for some reason, so `any` catches that too.
restart_policy:
condition: any
# Ensure we start one node after the other, strictly sequential, and make sure it
# is running before starting the next. This makes sure a cluster is formed instead
# of every node bootstrapping its own.
update_config:
parallelism: 1
monitor: 10s
order: stop-first
# This health check is used during service (re)deployments. While the timings could
# be improved, this works well.
healthcheck:
test: rabbitmq-diagnostics -q ping
interval: 10s
timeout: 3s
retries: 30
networks:
rabbitmq_net:
volumes:
rabbitmq-data:
secrets:
rabbitmq_definitions:
file: ./rabbitmq_definitions.json
rabbitmq_erlang_cookie:
file: ./rabbitmq_erlang_cookie.txt
configs:
rabbitmq_config:
file: ./rabbitmq.conf The last step requires some kind of deployment script or CI pipeline. While we use Bitbucket pipelines, here's the meaty bits from our script: # Fetch all swarm hosts from the Docker daemon. As we used the hostname for our containers,
# we can use it to infer the RabbitMQ cluster node name from it.
docker_hosts=$(docker node ls --format '{{.Hostname}}')
count=0
for host in ${docker_hosts}; do
count=$((count+1))
echo "cluster_formation.classic_config.nodes.${count} = rabbit@rabbitmq-${host}" >> rabbitmq.conf
done I hope this helps someone - I spent considerable time researching. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 8 replies
-
You could still assist us in doing this by providing explicit API calls, code samples, doc links etc that demonstrate how to register and remove nodes from whatever registry Docker Swarm provides. Right now you're asking us to do all the work but I'm certain you could contribute a lot without writing any Erlang. This would be very helpful because I don't think Team RabbitMQ members have much experience with Swarm. Thanks. |
Beta Was this translation helpful? Give feedback.
-
Consul, etcd, DNS peer discovery can be used with Docker Swarm. If there is an API that would allow for swam node discovery using a certain condition, it would be able to build a rough alternative to the Kubernetes peer discovery. Docker Swarm has extensive documentation but I could not find anything that would cover the API bits we need. |
Beta Was this translation helpful? Give feedback.
-
@lukebakken I'm sorry if the initial post did appear like that. I just wanted to initiate some discussion and see if the team is interested at all! If you are, I'm naturally doing everything I can to assist. |
Beta Was this translation helpful? Give feedback.
-
This is a short introduction from 10,000 feet on how Docker Swarm works, how it enables service discovery, and which options are available for peer discovery. I tried to be as concise as possible, but can go into detail on any of these sections if desired. How Docker Swarm worksSwarm forms a cluster from multiple Docker engines, organised as manager and worker nodes. Managers are elected via raft consensus and distribute workloads among all cluster nodes, including managers. Swarm clusters run collections of Docker containers referred to as stacks, which can conveniently be defined using docker compose – any valid compose file can be deployed to a Docker swarm, with additional deployment settings being available. Among them is the ability to create an arbitrary number of replicas tasks of a service, that is, instances of a container. Swarm follows a declarative approach to define desired cluster state and will attempt to reconcile any differences. Additionally, it forms a routing mesh to distribute socket connections among all members of a service; effectively this means the ingress controller is built-in. The documentation gives a good introduction: https://docs.docker.com/engine/swarm/key-concepts/ Service Discovery in SwarmAs previously mentioned, deployments in Swarm are organised like this: The stack groups all services an application consists of, somewhat similar to a Kubernetes deployment. A service maps closer to k8s: It represents a logical application with an arbitrary amount of replicas, defines reliance on external secrets, volumes, networks, etc. A single task (= replica) of a service is an actual Docker container running on one of the Swarm nodes. It doesn't matter which one, because Swarm forms a virtual network for each stack and transparently handles routing packets between the container instances. You can also assign additional networks to build a custom topology. To make it easier to work with dynamically assigned IP addresses, Swarm provides a simple DNS server on each container (127.11). It resolves service names to a single, virtual IP address that will be routed to the next best task; thus, one can use e.g.
This name can be queried again to return the IP of the task instance. Why the current RabbitMQ DNS backend doesn't work for SwarmAs outlined above, the service discovery DNS service provided by Swarm is insufficient for usage with the DNS peer discovery backend, because the reverse records reported by Swarm do not correspond to the hostnames reported to RabbitMQ instances themselves, that is: A replica task has an auto-generated (or manually configured) hostname, and Swarm DNS has A records for that name, but it will not report those hostnames in reverse records, nor provide a list of those hostnames. Options for RabbitMQ peer discoveryThat said, I do see two alternative approaches which could work: 1 - Not relying on RR recordsThis Swarm backend would work similar to the existing DNS backend, but instead of relying on the hostnames obtained from reverse records, it would attempt to connect to and query the identified instances for their node name. Open questions:
This approach has the advantage of not requiring privileged access to the Docker daemon - more on that issue in the next section. I suppose a lot of code from the DNS backend could be reused if not shared, so this would probably be the least invasive option. 2 - Asking the Docker daemonIn this scenario, cluster operators would need to mount the host's Docker socket into the service, or expose it to the host network. Both are possible, mounting the socket is favoured by the industry. By doing so, the new RabbitMQ peer discovery backend could connect to the Docker HTTP API, which opens up interesting capabilities - such as retrieving and manipulating the full service configuration. I still think this approach deserves some attention, as other major vendors use it to communicate with the Docker daemon, too; Additionally, this would allow the backend to actually perform similar work to the Kubernetes operator, even if that probably requires significant engineering work. @michaelklishin @lukebakken I hope this clears some things up - let me know if there are things missing or too vague. I'm going to devote some brain cycles to the Docker daemon approach and read the RabbitMQ Kubernetes operator thoroughly in the mean time! |
Beta Was this translation helpful? Give feedback.
-
@dtila and others, thanks for taking the time to look into this. FYI, there is no need to As a couple people have said here, the core issue is how DNS works in docker. I have run into the same problem using the DNS peer discovery plugin with @Radiergummi provides an idea to use the docker manager socket, would would work. Also, I hope everyone on this discussion is aware of the classic peer discovery backend, in which you list all node names in rabbitmq.conf. This means you must use pre-defined node names, but it works great with docker (https://github.com/lukebakken/docker-rabbitmq-cluster). Like @michaelklishin and I have said before, we'd welcome a contribution to get swarm working with RabbitMQ. Unless a customer with a support contract requests it, we probably won't work on it ourselves. |
Beta Was this translation helpful? Give feedback.
@dtila and others, thanks for taking the time to look into this. FYI, there is no need to
@
-ping people who have already commented on a discussion. They will be notified.As a couple people have said here, the core issue is how DNS works in docker. I have run into the same problem using the DNS peer discovery plugin with
docker compose
.@Radiergummi provides an idea to use the docker manager socket, would would work.
Also, I hope everyone on this discussion is aware of the classic peer discovery backend, in which you list all node names in rabbitmq.conf. This means you must use pre-defined node names, but it works great with docker (https://github.com/lukebakken/docker-rabbitmq-cluster).
L…