An easy way to install a docker SWARM cluster with ANSIBLE ?

In a previous, we installed a prometheus stack with ansible : node exporter, prometheus, grafana… And I propose to install a new stack : a docker swarm cluster and some tools.

In this post, we’ll just install a simple swarm cluster. And we’ll use vagrant to have our infrastructure to develop it. And after, with few lines of ansible code, we’ll have our docker cluster. And this is very very easy.

Our vagrant infrastructure

The vagrantfile

Vagrant.configure(2) do |config|

# Set some variables
common = <<-SHELL
sudo apt update -qq 2>&1 >/dev/null
sudo apt install -y -qq git vim tree net-tools telnet git python3-pip sshpass nfs-common 2>&1 >/dev/null
sudo echo "autocmd filetype yaml setlocal ai ts=2 sw=2 et" > /home/vagrant/.vimrc
sed -i 's/ChallengeResponseAuthentication no/ChallengeResponseAuthentication yes/g' /etc/ssh/sshd_config
sudo systemctl restart sshd

# set vagrant image = "ubuntu/focal64"
config.vm.box_url = "ubuntu/focal64"

# set servers list and their parameters
{ :hostname => "devans1", :ip => "", :cpus => 4, :mem => 1024 },
{ :hostname => "devans2", :ip => "", :cpus => 4, :mem => 1024 },
{ :hostname => "devans3", :ip => "", :cpus => 4, :mem => 1024 },
{ :hostname => "devans4", :ip => "", :cpus => 4, :mem => 1024 },
{ :hostname => "devans5", :ip => "", :cpus => 4, :mem => 1024 },
{ :hostname => "devans6", :ip => "", :cpus => 2, :mem => 1024 },
{ :hostname => "devans7", :ip => "", :cpus => 2, :mem => 1024 }

# define /etc/hosts for all servers
NODES.each do |node|
etcHosts += "echo '" + node[:ip] + " " + node[:hostname] + "' >> /etc/hosts" + "\n"
end #end NODES

# run installation
NODES.each do |node|
config.vm.define node[:hostname] do |cfg|
cfg.vm.hostname = node[:hostname] "private_network", ip: node[:ip]
cfg.vm.provider "virtualbox" do |v|
v.customize [ "modifyvm", :id, "--cpus", node[:cpus] ]
v.customize [ "modifyvm", :id, "--memory", node[:mem] ]
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
v.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
v.customize ["modifyvm", :id, "--name", node[:hostname] ]
end #end provider

#for all
cfg.vm.provision :shell, :inline => etcHosts
cfg.vm.provision :shell, :inline => common

end # end config
end # end nodes

I like to use the NODES list where I can describe the type of servers I want to use.

And now, ansible and the docker swarm

This is our 00_inventory.yml file :


I keep two nodes if we want to test to add a new manager.

And of course we need to install docker on all nodes. To do it I suggest you to create a docker role in roles directory.

mkdir -p roles/
ansible-galaxy init roles/docker

And we begin to edit tasks in the main file of tasks directory.

- name: add gpg key
url: "{{ docker_repo_key }} "
state: present

- name: Add repository
repo: "{{ docker_repo }}"

- name: install docker and dependencies
name: "{{ docker_packages }}"
state: latest
update_cache: yes
cache_valid_time: 3600
with_items: "{{ docker_packages}}"

- name: Add user to docker group
name: vagrant
group: docker

- name: start docker
name: docker
state: started
enabled: yes

Great and we can set these jinja variables in the default directory.

- apt-transport-https
- ca-certificates
- curl
- software-properties-common
- docker-ce
- docker-ce-cli
- python3-docker
docker_repo: deb [arch=amd64]{{ ansible_distribution|lower }} {{ ansible_distribution_release }} stable
docker_repo_key_id: 0EBFCD88

And now we need to create our playbook file with two roles : docker and swarm.

- name: install docker
hosts: all
become: yes
- docker
- swarm

Good, of course we need to write our swarm role. And this is very very easy.

The docker swarm cluster installation

ansible-galaxy init roles/swarm

And of course, we edit our tasks file in the tasks directory.

Why it’s easy ? we just need to add 3 tasks to init our cluster, add managers and our workers.

- name: check/init swarm
state: present
advertise_addr: enp0s8:2377
register: __output_swarm
when: inventory_hostname in groups['managers'][0]

- name: install manager
state: join
timeout: 60
advertise_addr: enp0s8:2377
join_token: "{{ hostvars[groups['managers'][0]]['__output_swarm']['swarm_facts']['JoinTokens']['Manager']}}"
remote_addrs: "{{ groups['managers'][0] }}"
when: inventory_hostname in groups['managers'] and inventory_hostname not in groups['managers'][0]

- name: install worker
state: join
timeout: 60
advertise_addr: enp0s8:2377
join_token: "{{ hostvars[groups['managers'][0]]['__output_swarm']['swarm_facts']['JoinTokens']['Worker'] }}"
remote_addrs: "{{ groups['managers'][0] }}"
when: inventory_hostname in groups['workers']

The subtlety is how we collect and use the join token (one for manager and one for worker).

In the first task we initialize our cluster on our first manager, the leader. To do it, we use the first index/node int the manager group. And we register the output of this initialize task in a register __output_swarm.

That allows us to add all other managers (not leader).

join_token: "{{ hostvars[groups['managers'][0]]['__output_swarm']['swarm_facts']['JoinTokens']['Manager']}}"

In this line we use on all other manager the join token that we have collected on the leader.

And we do the same task for workers.

That’s all we can run it now.

ansible-playbook -i 00_inventory.yml -u vagrant -k playbook.yml

And to check our swarm cluster, we can list all nodes in our docker cluster.

docker nodes ls

You can our leader, all manager and our worker.

Maybe you can add a node in your inventory and run your playbook again ??

All the code here : my gitlab

My blog :

Microservices architecture and opensource. I’m maintainer of xavki about opensource. My blog :