Ansible

Articles in section

Installation

Do not install Ansible from local OS repo - it is usually older in version there. Use latest Python pip repo to install a fresh version.

sudo apt install python3-distutils
wget http://bootstrap.pypa.io/get-pip.py
sudo python3 get-pip.py
sudo pip3 install ansible
ansible --version
Tip

Ansible will be the latest version supporting current Python in OS. So to get latest Ansible , Python must be updated as well!

Warning

Do not update the default Python in OS - it is used by system services, which may break!

Build Python:

sudo apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev

wget https://www.python.org/ftp/python/3.9.15/Python-3.9.15.tgz

tar -xf Python-3.9.15.tgz

cd Python-3.9.15
./configure --enable-optimizations

make -j 8

Installing Python:

  • Best way is to do checkinstall, create a .deb file to share with the team.
  • Use altinstall parameter to install Python in an alternative path. This is simpler & better than using Python virtualenv.
sudo make altinstall # now Python 3.9x is installed on separate path, while Python 3.7 in OS is unchanged

sudo python3.9 -m pip install --upgrade pip

sudo python3.9 -m pip install ansible

ansible --version # now ansible is the latest version

Architecture

graph LR
I[(Inventory)] --> R1(Role01);
M(Modules) --> R1;
I[(Inventory)] --> R2(Role02);
M(Modules) --> R2;
I[(Inventory)] --> RN(Role N);
M(Modules) --> RN;
R1 -->|include role| P(Playbook);
R2 -->|include role| P(Playbook);
RN -->|include role| P(Playbook);
P --> C(Ansible Config);
C --> Py(Python);
Py-->|SSH|Client01;
Py-->|SSH|Client02;
Py-->|SSH|Client_N;

Folder Structure

video.yml - main program:

---
- import_playbook: playbooks/db.yml
- import_playbook: playbooks/front.yml
- import_playbook: playbooks/zoneminder.yml
...

front.yml - role example:

---
- name: front-end play
  hosts: all
  gather_facts: yes
  become: yes

  tasks:
    - name: include role apache
      include_role:
        name: apache

    - name: include role php
      include_role:
        name: php
...

apache / tasks / main.yml - tasks example:

---
- name: install apache
  apt:
  name: apache2

- name: Enable service apache2
  ansible.builtin.systemd:
    name: apache2
    enabled: yes
    masked: no

- name: Make sure apache2 is running
  ansible.builtin.systemd:
    state: started
    name: apache2
...

Role structure

Directories inside a role:

- defaults - variable values by default
-- main.yml

- vars - variables defined by role (for other roles)
-- main.yml

- tasks - jobs to be completed
-- main.yml

- handlers - actions to be taken after checks
-- main.yml

- files - static files to be copied into client machine
- templates

Subsections of Ansible

Inventory

Initialization

Create inventory file with IP addresses. Check computers in inventory by doing a ping:

ansible all -i inventory -u vagrant -m ping -k
# -i - <inventory file>
# -u - <username>
# -m - <select module>
# -k - <interactive password prompt>
ansible all -i inventory -u admin -m ping -k -vvv # debug mode with verbosity 3

Inventory examples:

[db]
vm ansible_host=192.168.1.98 ansible_user=aagern

[app]
vm ansible_host=192.168.1.98 ansible_user=aagern

[front]
vm ansible_host=192.168.1.98 ansible_user=aagern

[all:vars]
ansible_ssh_extra_args="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
ansible_python_interpreter=/usr/bin/python3
web1 ansible_ssh_host=192.168.33.20
db1 ansible_ssh_host=192.168.33.30

[webservers]
web1

[dbservers]
db1

[datacenter:children]
webservers
dbservers

[datacenter:vars]
ansible_ssh_user=vagrant
ansible_ssh_pass=vagrant

Configuration

Order of precedence for the config file:

  1. $ANSIBLE_CONFIG env variable  (first place to be searched)
  2. ./ansible.cfg - local file in the working directory
  3. ~/.ansible.cfg - user home directory
  4. /etc/ansible/ansible.cfg - global config (last place to be searched)

First config file found wins, Ansible stops looking for other config files.

Environment override

Specify: $ANSIBLE_

Override settings on the fly:

$ export ANSIBLE_FORKS=10

[defaults] forks - how many parallel processes does Ansible handle. Default=5, production recommended=20

host_key_checking - check host key before sending commands. Default=True (for production), for dev/test systems =False (easy control)

log_path - Ansible logging. Default=Null, produstion recommended - set path all Ansible users can write to.

Target patterns

Patterns to choose hosts/groups:

  • OR pattern: group1:group2

Example:

ansible webservers:dbservers -i inventory -m service -a "name=iptables state=stopped" --sudo
  • NOT pattern: :!group2
  • Wildcard pattern: web.lab.local*
  • REGEX pattern: ~web[0-9]+
  • AND pattern: group1:&group2 - apply to hosts BOTH in group1 and group2 (intersection)
  • Complex patterns

Complex pattern example: webservers:&production:!python3 # apply to web servers in production but not in the Python3 group

Modules

Help on modules

ansible-doc -l # all installed modules list
ansible-doc <module-name> # module man
ansible-doc -s <module-name> # playbook snippets code examples on module

Module examples

Copy module

  • Copies a file from the local box to a remote system - useful for copying config files to remote system
  • can do backups
  • can do remote validation

Fetch module

  • Copy a file from remote system to local box
  • validate file using md5 checksum

Setup module

  • Gather info and facts on remote system
  • Inventory analysis of the system
ansible -i inventory web1 -m setup # gather all available system info
ansible -i inventory web1 -m setup -a "filter=ansible_eth*" # gather info on NICs
ansible -i inventory all -m setup --tree ./setup # form an inventory of files in /setup/ folder with info on targeted systems

Apt module (for Debian systems) / Yum module (RedHat systems)

  • Install, update, delete packages
  • Can update entire system Example for Yum:
ansible webservers -i inventory -m yum -a "name=httpd state=present" -u vagrant --sudo
# name - name of package (Apache)
# present - if package is not there, install. If it is there - do nothing and report "changed: false" (idempotence test)

Service module

  • Start/stop/restart services
  • Set autostart option for services
ansible webservers -i inventory -m service -a "name=httpd state=started enabled=yes" -u vagrant --sudo

# name - name of service (Apache)
# state = started/stopped - make idempotence test and change if necessary
# enabled = yes/no - autostart on system boot

Playbooks

General tasks usage

# Tasks in a playbook are executed top down. Tasks use modules.
tasks:
  - name: Name the task for readability
    module: parameters=go_here

# Example:
  - name: Deploy Apache Configuration File
    copy: src=../../ansible/files/configuration/httpd.conf

          dest=/etc/httpd/conf/

Playbook execution: ansible-playbook my_playbook.yml

Playbook example:

---
# -------- Global play declaration
- hosts: webservers    
  ## ----- Variables per play
  vars:
    git_repo: https://github.com/repo.git
    http_port: 8081
    db_name: wordpress

  ## ------------------------
  ### ---- Declare user to run tasks
  sudo: yes
  sudo_user: wordpress_user

  ### ------------------------------
  gather_facts: no # dont't gather facts with SETUP module (default gathers facts - expensive in time)
  remote_user: root
  tasks:

# --------------------------------
  - name: Install Apache
    yum: name=httpd state=present
  - name: Start Apache
    service: name=httpd state=started

Including files

Use “- include” and “- include_vars” directives to include playbook files:

tasks:
- include: wordpress.yaml
  vars:
    sitename: My Awesome Site
    
- include: reverse_proxy.yaml
- include_vars: variables.yaml

Register task output

Use the output of one task for another task:

tasks:
- shell: /usr/bin/whoami
  register: username

- file: path=/home/myfile.txt
  owner: {{ username }}

Debugging tasks with Debug module

Add screen output and print content of variables:

tasks:
  - debug: msg="This host is {{ inventory_hostname }} during execution"

  - shell: /usr/bin/whoami
    register: username
    
  - debug: var=username

Input during playbook execution

Promt user during execution:

- hosts: web1
  vars_prompt:
  - name: "sitename"
    prompt: "What is the new site name?"

  tasks:
    - debug: var=username

Playbook handlers

A handler can be informed to execute a task (restart service) only if state=changed.

  • Run after all tasks
  • Run only once no matter how many times they are called

Handlers syntax is the same as tasks syntax:

  tasks:
  - name: Copy Apache Config files
  - copy: src=../../files/httpd.conf
          dest=/etc/httpd/config/

    notify:
      - Apache Restart

  handlers:
  - name: Apache Restart
    service: name=httpd state=restarted

“When” condition

Commence a task if condition is True:

tasks:
- name: Install Httpd Package
  apt: name=httpd state=present
  when: ansible_os_family == "RedHat"

- name: Install Apache2 Package
  yum: name=apache2 state=present
  when: ansible_os_family == "Debian"

Check output of previous task as condition to run next task:

tasks:
- name: Stop iptables now
  service: name=iptables state=stopped
  register: result
  ignore_errors: yes   # supress default stop on error

- debug: msg="Failure!"
  when: result|failed  # Debug message will only be shown if task has failed
                       # other conditions are "result|success", "result|skipped"

Checking variables with WHEN condition

Bring variable check to BOOL check:

- name: "test"
  hosts: local
  vars_prompt:
    - name: "os_type"
      prompt: "What OS? (centos or ubuntu)"
      default: "centos"
      private: no
  vars:
    - is_ubuntu: "{{os_type == 'ubuntu'}}"
    - is_debian: "{{os_type == 'debian'}}"
  tasks:
    - debug: msg="this shows the conditional if variable equals ubuntu"
      when: is_ubuntu|bool
      
    - debug: msg="this shows the conditional if variable equals centos"
      when: is_centos|bool