Debug Ansible and Tiny Introduction of PlayBook

Ansible is a popular dev-ops tools for us to execute ad-hoc commands immediately on large mounts of machines in parallel which accelerate our working speed.

It’s simple but powerful and compatible with different OS platforms. Even more, it has lots of pre-defined modules for us to use, which significantly make the dream of reusing Dev-Ops scripts come true.

However, when you’re goging to use this fantastic tool, how to debug when you’re executing the ansible play-book with flow of commands? It looks like a unstoppable flow.

So we need two things for debugging ansible playbook:

  • Stoppable
  • Print Debug Message

This article will also give some tiny intro about play-book. For more info, please review the official documents.

1. Stoppable

So, here we have a trick, using the fail module to stop the execution process.

Add next code snippet at wherever you want to stop.

1
2
3
4
5
# more is here

- name: "STOP ME"
fail: msg="This is the debugging stop"
when: 1==1

After that, you could see the message like below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# more is here ... 

TASK [pre-ansible : STOP ME] ******************************************************
fatal: [node-1-master]: FAILED! => {"changed": false, "failed": true, "msg": "This is the debugging stop"}
fatal: [node-2-slave-1]: FAILED! => {"changed": false, "failed": true, "msg": "This is the debugging stop"}
fatal: [lcoj-judger]: FAILED! => {"changed": false, "failed": true, "msg": "This is the debugging stop"}

NO MORE HOSTS LEFT *************************************************************
to retry, use: --limit @cluster.retry

PLAY RECAP *********************************************************************
lcoj-judger : ok=5 changed=1 unreachable=0 failed=1
node-1-master : ok=5 changed=1 unreachable=0 failed=1
node-2-slave-1 : ok=5 changed=1 unreachable=0 failed=1

2. Dry Run with --check

Next won’t execute the whole playbook, but will give a run through.

1
ansible-playbook foo.yml --check

3. Print Debugging Message with Var

Of course you can print debug message with the fail module. However, it has a born behavior: stop the process, which maybe unexpected.

Here we use the debug module to print statements during execution.

Next is an example of printing the eth1 address of each nodes in the inventory file.

1
2
3
4
5
# more is here...

- debug:
msg: "hosts mapping is: {{ hostvars[item]['ansible_eth1'].ipv4.address }} {{item}}"
with_items: "{{ groups['all'] }}"

Here is the output of such debug msg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
TASK [pre-ansible : debug] *****************************************************
ok: [node-2-slave-1] => (item=node-1-master) => {
"item": "node-1-master",
"msg": "hosts mapping is: 192.168.33.10 node-1-master"
}
ok: [node-2-slave-1] => (item=node-2-slave-1) => {
"item": "node-2-slave-1",
"msg": "hosts mapping is: 192.168.33.11 node-2-slave-1"
}
ok: [node-2-slave-1] => (item=lcoj-judger) => {
"item": "lcoj-judger",
"msg": "hosts mapping is: 192.168.33.12 lcoj-judger"
}
ok: [node-1-master] => (item=node-1-master) => {
"item": "node-1-master",
"msg": "hosts mapping is: 192.168.33.10 node-1-master"
}
ok: [node-1-master] => (item=node-2-slave-1) => {
"item": "node-2-slave-1",
"msg": "hosts mapping is: 192.168.33.11 node-2-slave-1"
}
ok: [node-1-master] => (item=lcoj-judger) => {
"item": "lcoj-judger",
"msg": "hosts mapping is: 192.168.33.12 lcoj-judger"
}
ok: [lcoj-judger] => (item=node-1-master) => {
"item": "node-1-master",
"msg": "hosts mapping is: 192.168.33.10 node-1-master"
}
ok: [lcoj-judger] => (item=node-2-slave-1) => {
"item": "node-2-slave-1",
"msg": "hosts mapping is: 192.168.33.11 node-2-slave-1"
}
ok: [lcoj-judger] => (item=lcoj-judger) => {
"item": "lcoj-judger",
"msg": "hosts mapping is: 192.168.33.12 lcoj-judger"
}

In your play-book, please setup the gather_facts to be true. In this way, we can print the ipv4.adderss of this host.
And the pre-ansible is the role where the above code snippet lies in.

1
2
3
4
5
6
7
- hosts: all
gather_facts: true
sudo: yes
roles:
- pre-ansible
tags:
- pre-ansible

Also, this debug module with msg can print an object with all its field values. So if our msg above changes to

1
"hosts mapping is: {{ hostvars[item]['ansible_eth1'].ipv4 }} {{item}}"

it will print more message.

3.1. Register the response and then print using debug msg

1
2
3
4
5
6
7
8
9
- hosts: all
gather_facts: false
tasks:
- name: Get OS Version
become: true
shell: "echo `cat /etc/os-release | grep PRETTY_NAME`"
register: res
- debug:
msg: "{{ res.stdout }}" # we have to add double quote here

It will print msg like below:

1
2
3
ok: [localhost.domain] => {
"msg": "PRETTY_NAME=\"Ubuntu 16.04.1 LTS\""
}

4. Intro about Play-Book

In the section of Print Debugging Message with Var, we already saw one easy play, here is another playbook with only one play. Please remember we do have --- at the first line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---
- hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum: name=httpd state=latest

- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
notify:
- restart apache

- name: ensure apache is running (and enable it at boot)
service: name=httpd state=started enabled=yes

handlers:
- name: restart apache
service: name=httpd state=restarted

4.1. Roles

With Roles, we can reuse the tasks commands. For example, next play, we will execute the play with roles pre-ansible. Of course, we can execute more roles, just append the role directory name under roles field.

1
2
3
4
5
- hosts: all
gather_facts: true
sudo: yes
roles:
- pre-ansible

Here is a glance of directory structure, we can see pre-ansible directory in the roles directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[03:38 PM morganwu@promote noj-deploy]$ tree  -L 2
.
├── ansible.cfg
├── cluster.yml
├── group_vars
│   └── all.yml
├── inventory.me
├── roles
│   ├── common
│   ├── docker
│   ├── etcd
│   ├── flannel
│   ├── kubernetes
│   ├── kubernetes-addons
│   ├── leetcode
│   ├── leetcode-backend
│   ├── master
│   ├── nginx
│   ├── node
│   ├── opencontrail
│   ├── opencontrail-provision
│   └── pre-ansible
├── setup.sh
└── setup_leetcode.sh

4.1.1. Use Condition when Choosing Roles

We even can use condition expression when choosing specific roles,

1
2
3
4
5
6
7
8
9
- hosts:
- etcd
- masters
- nodes
sudo: yes
roles:
- { role: flannel, when: networking == 'flannel' }
tags:
- network-service-install

This will only execute the roles of flannel when the networking varialbe is flannel.

4.2. Tags

With Tags, we can run specific play and it makes our dev-ops work more flexible, in a non-linear style.

We will still using the example above. Here we have defined a tag pre-ansible

1
2
3
4
5
6
7
- hosts: all
gather_facts: true
sudo: yes
roles:
- pre-ansible
tags:
- pre-ansible

When we execute playbook with --tags, it will only execute this play and skip all other plays without this specific tag.

1
[03:44 PM morganwu@promote noj-deploy]$ ansible-playbook -i inventory.me cluster.yml --tags=pre-ansible

4.2.1. Execute Multiple Tags

If you want to execute multiple tags once, just append with more tag name at the --tags, eg.

1
[03:44 PM morganwu@promote noj-deploy]$ ansible-playbook -i inventory.me cluster.yml --tags="pre-ansible,etcd,docker"

This will execute the pre-ansible, etcd, docker tags.

4.3. Var/String in a condition

Here is an example inventory_hostname is a varaible, but “codential” and “node-1-master” are all strings.

The most important here is: the condition in the when, we use ' to wrap up.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- include: frontend.yml
when: '(inventory_hostname in groups["codential"]) or (inventory_hostname == groups["nodes"][0])'
vars:
service_port: 8001
node_port: '{{ cfg[env].node_port_frontend }}'

- name: register nginx proxy
become: true
when: 'inventory_hostname == "node-1-master"'
include: roles/leetcode/tasks/register-nginx-proxy.yml
vars:
service_port: 8001
node_port: '{{ cfg[env].node_port_frontend }}'
nginx_template: "frontend/{{ env }}-nginx-conf.j2"
nginx_conf: "/etc/nginx/conf.d/{{ namespace }}-{{ env }}.conf"
iptables_comment: "{{ namespace }}-{{ env }}-node-port"

Extra to learn: iptables related diagram:

From https://cesarti.files.wordpress.com/2012/02/iptables.gif

5. Execute SUDO commands, without NO_PASSWORD settings

Last but not the least, however sometimes very useful. Is for Ad-Hoc command in Ansible.

We all know it’s possible to execute ad-hoc ansible command for bunch of servers. But problems will come when you need to execute sudo command, the command from the server side will always be waiting for inputing the password to be continued.

We can use --sudo/-b to solve this. However, you have to make sure all the users in the nodes server list has the same password here. If you provide with the ansible_sudo_pass password in inventory file.

1
host1 ansible_ssh_host=xxx.xxx.xxx.xxx ansible_ssh_port=22 ansible_ssh_user=user1 ansible_ssh_pass=password1 ansible_sudo_pass=password1

If we don’t have ansible_sudo_pass, then we need to provide with the SUDO password at the runtime.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# This is the command, append using --sudo or we can use -b --become-user=<username> 
[05:16 PM morganwu@morgan-yinnut my-deploy]$ ansible -i inventory.me nodes -m shell -a 'curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-xenial-td-agent2.sh | sh' --sudo
SUDO password:
node1 | SUCCESS | rc=0 >>
==============================
td-agent Installation Script
==============================
This script requires superuser access to install apt packages.
You will be prompted for your password by sudo.
OK
Ign:1 http://apt.newrelic.com/debian newrelic InRelease
Hit:2 http://apt.newrelic.com/debian newrelic Release
Hit:3 http://mirrors.linode.com/ubuntu xenial InRelease
Get:4 http://mirrors.linode.com/ubuntu xenial-updates InRelease [102 kB]
Get:5 http://mirrors.linode.com/ubuntu xenial-backports InRelease [102 kB]
Get:6 http://packages.treasuredata.com/2/ubuntu/xenial xenial InRelease [2,578 B]
Get:8 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]
Get:9 http://packages.treasuredata.com/2/ubuntu/xenial xenial/contrib amd64 Packages [399 B]
Get:10 http://packages.treasuredata.com/2/ubuntu/xenial xenial/contrib i386 Packages [399 B]
Ign:11 https://get.docker.com/ubuntu docker InRelease
Hit:12 https://get.docker.com/ubuntu docker Release
Fetched 310 kB in 0s (381 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
td-agent
0 upgraded, 1 newly installed, 0 to remove and 137 not upgraded.
# More output comes here

6. Conclusion

Ansible is easy but powerful with lots of pre-defined modules.

  • We can use fail module to stop execution process ann use debug module to print message with variables.
  • Roles are designed for task reuuse.
  • Tags are designed to execute the specific Play(s) in one playbook.
  • Use --sudo to execute sudo commands in ad-hoc style.
0%