Hi there! I'm Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.
Continuing our Ansible journey, let’s wire up Consul — HashiCorp’s service mesh and internal DNS provider — using clean Ansible roles.
We’ll install it using HashiCorp’s apt repository, configure it in a role-driven fashion, and deploy Consul agents as either servers or clients using tags. Let's get to it.
The Playbook
Your consul.yml
playbook defines which hosts should run the role and how we want to tag their responsibilities:
- name: Install and configure Consul
hosts: all
become: yes
roles:
- consul
tags:
- master
- client
This gives us the flexibility to run only server or client setup by tagging the playbook execution later.
🗂 Folder Layout
Here’s how the consul
role is structured:
ansible-galaxy init roles/consul --offline
roles/consul/
├── defaults/main.yml
├── handlers/main.yml
├── tasks/
│ ├── install.yml
│ ├── configure.yml
│ └── main.yml
├── templates/
│ ├── client_consul.hcl.j2
│ ├── master_consul.hcl.j2
│ └── master_server.hcl.j2
├── vars/main.yml
Installing Consul the Right Way
Use the official HashiCorp GPG key and apt repo setup (the new secure way):
tasks/install.yml
- name: Add HashiCorp GPG key (new method)
ansible.builtin.shell: |
curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /etc/apt/keyrings/hashicorp-archive-keyring.gpg > /dev/null
args:
creates: /etc/apt/keyrings/hashicorp-archive-keyring.gpg
- name: Add HashiCorp repository (new method)
ansible.builtin.shell: |
echo "deb [signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
args:
creates: /etc/apt/sources.list.d/hashicorp.list
- name: Run apt update
ansible.builtin.shell: apt update
- name: Install Consul
ansible.builtin.apt:
name: consul
state: present
update_cache: no
⚠️ You can replace the
shell
tasks withansible.builtin.get_url
andapt_repository
for a cleaner, idempotent install — but this approach is closer to the manual HashiCorp documentation and works well for quick setups.
Templates: Server & Client Configs
Define separate configuration templates for master/server and clients.
templates/master_consul.hcl.j2
node_name = "consul-{{ inventory_hostname }}-server"
server = true
bootstrap = true
datacenter = "dc1"
data_dir = "consul/data"
log_level = "INFO"
bind_addr = "0.0.0.0"
client_addr = "0.0.0.0"
advertise_addr = "{{ internal_ip }}"
ui_config {
enabled = true
}
connect {
enabled = true
}
dns_config {
enable_truncate = true
}
Use this for Consul servers that need to bootstrap the cluster and expose the UI.
templates/master_server.hcl.j2
log_level = "DEBUG"
server = true
bootstrap_expect = 1
advertise_addr = "{{ internal_ip }}"
connect {
enabled = true
}
ui_config {
enabled = true
}
This can act as an additional config layer — or be merged into the main server template if needed.
templates/client_consul.hcl.j2
node_name = "consul-{{ inventory_hostname }}-client"
server = false
datacenter = "dc1"
data_dir = "/opt/consul"
log_level = "INFO"
bind_addr = "{{ internal_ip }}"
advertise_addr = "{{ internal_ip }}"
retry_join = ["{{ groups['nomadclientandservers'] | map('extract', hostvars, 'internal_ip') | list | first }}"]
dns_config {
enable_truncate = true
}
service {
id = "dns"
name = "dns"
tags = ["primary"]
address = "localhost"
port = 8600
check {
id = "dns"
name = "Consul DNS TCP on port 8600"
tcp = "localhost:8600"
interval = "10s"
timeout = "1s"
}
}
This config sets up DNS forwarding and a basic health check for clients. Ensure
internal_ip
is defined in your host vars.
Configuring Consul with Ansible
Now, wire it all together in the configure.yml
:
tasks/configure.yml
- name: Create consul data directory
ansible.builtin.file:
path: /opt/consul
state: directory
owner: consul
group: consul
mode: 0755
tags: [master, client]
- name: Set proper permissions for consul directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: consul
group: consul
recurse: yes
loop:
- /opt/consul
- /etc/consul.d
tags: [master, client]
- name: Deploy master Consul config
ansible.builtin.template:
src: master_consul.hcl.j2
dest: /etc/consul.d/consul.hcl
owner: consul
group: consul
mode: 0644
when: "'master' in ansible_run_tags"
tags: master
- name: Deploy master server config
ansible.builtin.template:
src: master_server.hcl.j2
dest: /etc/consul.d/server.hcl
owner: consul
group: consul
mode: 0644
when: "'master' in ansible_run_tags"
tags: master
- name: Deploy client Consul config
ansible.builtin.template:
src: client_consul.hcl.j2
dest: /etc/consul.d/consul.hcl
owner: consul
group: consul
mode: 0644
when: "'client' in ansible_run_tags"
tags: client
- name: Restart consul service
ansible.builtin.systemd:
name: consul
state: restarted
tags: [master, client]
Running the Playbook
Split your deployment by tags:
ansible-playbook consul.yml --tags master -v
ansible-playbook consul.yml --tags client -v
This gives you full control over when to deploy servers vs clients.
What You Achieved
- Installed Consul using HashiCorp’s secure apt setup
- Bootstrapped a Consul server with the UI enabled
- Deployed DNS-aware clients with health checks
- Used tags to keep roles clean and reusable
This approach brings modularity, repeatability, and clean separation to your infra automation.
🧠 Bonus: Let LiveAPI Handle Your APIs
LiveAPI helps you get all your backend APIs documented in a few minutes.
With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.
If you're tired of updating Swagger manually or syncing Postman collections, give it a shot.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.