Ansible notes

configuration-managementansibleprovisioningcloud

What is Ansible?

Ansible is a tool that essentially automate all the common tasks that you do in your daily basis. It is a tool that allows you to automate the configuration of multiple servers at the same time.

You write code, ansible execute the code, and that code defines the state that your servers should be in and Ansible makes sure that your servers are in that state. Ansible is essentially like a state managemente machine

Use cases

  • Automate common tasks
  • Automate complex tasks
  • Is a Configuration as code (CaC) tool

We write ansible code in Yaml format and this code can be tracked by a version control system like Git.

Write ansible will bring us happiness and productivity because we can automate all the common tasks that we do in our daily basis and we can automate all of these things using a CI/CD tool like Github Actions.

What problem does Ansible solve?

  • Human error
  • (A lack of) transparency
  • (A lack of) repeatability
  • Documentation for the entire state

Installation

  • Linux (Ubuntu)
# Actualizamos los repositorios
sudo apt update
# Install python if ypu don't have it
sudo apt install python3 python3-pip -y
# install ansible
pip install ansible
# check the version
~/.local/bin/ansible --version
# add the path to the .bashrc file
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# reload the bashrc file
source ~/.bashrc
# check the version
ansible --version

System inventory

  • Inventory: Is a list of all the servers that we want to manage with Ansible. This list can be in a file or in a database.
    • This file can contain hostnames and/or IPS
    • We can use groups for organizing systems
    • These ips can also be provided by the CLI
    • Or can be generated dynamically

Creating an inventory file

touch inventory.init
# inventory.init

## define groups and IPs
[webservers]
x.x.x.x
x.x.x.x

[databases]
x.x.x.x

Tell to ansible which inventory file we want to use

Before accomplish this step, we need to have a ssh key pair in our local machine and in the remote machine. Know how to create one here: SSH key pair

ansible -i inventory.init -u leninner -m ping all

If all goes well, we should see something like this:

x.x.x.x | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
x.x.x.x | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
x.x.x.x | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Using group variables in an Ansible inventory

# inventory.init

## define groups and IPs
[webservers]
x.x.x.x
x.x.x.x

[databases]
x.x.x.x

[all:vars]
ansible_user=leninner
ansible_ssh_private_key_file=~/.ssh/id_rsa

[webservers:vars]
ansible_python_interpreter=/usr/bin/python3

[databases:vars]
ansible_python_interpreter=/usr/bin/python3

What are ad-hoc commands?

An ad-hoc command is a single Ansible task to perform quickly, but don’t want to save for later. It is a command that you run from the command line to perform a quick task.

Are:

  • One-off jobs and runs
  • Pulling information
  • Fun, but limited

Create a linux user

ansible -i inventory.ini -u root -m user -a "name=test state=present" all

You should see something like this:

x.x.x.x | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1000,
    "home": "/home/test",
    "name": "test",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1000
}

Install nginx

# To install
ansible -i inventory.ini -u root -m package -a "name=nginx state=present" webservers

# To uninstall
ansible -i inventory.ini -u root -m package -a "name=nginx state=absent" webservers

What are playbooks?

Playbooks are Ansible's configuration, deployment, and onchestration languaje. They can describe a policy you want your remote systems to enforce, or a set of steps in a general IT process.

Playbook is the core component of Ansible. It is a YAML file that describes the desired state of the system. It is a file that contains a list of tasks that we want to execute on a particular remote server.

With playbook:

  • The real fun begins
  • Very powerful and flexible
  • Can be nested
  • A collection of plays
    • Is a collection of tasks
    • which execute modules

Let’s create a playbook:

  • A playbook document contains plays
# PATH: master.yaml

# first play
- name: Configure WebServers
  hosts: webservers
  become: yes
  tasks: # this is a list of tasks
    - name: Create a non-root user
      user: # this is a module
        name: leninner
        state: present

    - name: Install nginx
      package: # this is a module
        name: nginx
        state: present

# second play
- name: Configure Databases
  hosts: databases
  become: yes
  tasks:
    - name: Install MySQL Server
      package:
        name: mysql-server
        state: present

To run the playbook:

ansible-playbook -i inventory.ini master.yaml -u root

What are roles?

Roles are ways of automatically loading certain vars_files, tasks, and handlers based on a known file structure.

Grouping content by roles also allows easy sharing of roles with other users.

  • Are repeatable and reusable unit of code
  • Used for managing sets of related resources

We are going to create the following structure:

mkdir -p roles/users/tasks roles/users/defaults

Inside the roles/users/tasks folder, we are going to create a file called main.yaml and inside this file we are going to write the following code:

# PATH: roles/users/tasks/main.yaml
---
- name: Create or delete user accounts
  loop: "{{ users_list }}"
  ansible.builtin.user:
    name: "{{ item.username }}"
    state: "{{ item.state }}"

Inside the roles/users/defaults folder, we are going to create a file called main.yaml and inside this file we are going to write the following code:

# PATH: roles/users/defaults/main.yaml
---
users_list:
  - username: leninsin
    state: absent

  - username: stalin
    state: absent

  - username: pepe
    state: absent

Variables and conditions

  • Variables
# play
- name: Using vargs
  hosts: all
  gather_facts: false
  vars:
    hello: Hello World # this is a variable
    number_list: [1, 2, 3, 4, 5] # this is a list
    string_list: ['hello', 'world'] # this is a list
    another_number_list: # this is a list
      - 1
      - 2
      - 3
    number_map: # this is a map
      one: 1
      two: 2
      a_map_inside:
        one: 1
        two: 2
    list_of_maps: # this is a list of maps
      - name: lenin
        age: 24
      - name: stalin
        age: 25
    map_of_lists: # this is a map of lists
      names: ['lenin', 'stalin']
      ages:
        - 24
        - 25
  tasks:
    - name: Print hello
      debug:
        msg: "Hello {{ number_map.a_map_inside.one }}" # using the variable
    - name: Print list of maps
      debug:
        msg: "{{ list_of_maps[0].name }}"
    - name: Print map of lists
      debug:
        msg: "{{ map_of_lists.names[0] }}"
  • Conditions
# play
- name: Using conditions
  hosts: all
  gather_facts: false
  vars:
    number: 10
    another_number: 20
  tasks:
    - name: Print number
      debug:
        msg: "The number is {{ number }}"
      when: number > 5 and number < 15
    
    - name: Print another number
      debug:
        msg: "The another number is {{ another_number }}"
      when: 
        - another_number > 5
        - or # this is necessary for `or` condition, if you have `and` condition you don't need this
        - another_number < 15

Loops

# play
- name: Using loops
  hosts: localhost
  vars:
    number_list: [1, 2, 3, 4, 5] # this is a list
    map_list: # this is a list of maps
      name: lenin
      age: 24
  tasks:
    - name: Print hello
      ansible.builtin.debug:
        msg: "Hello {{ item }}" # using the item of the list
      loop: "{{ number_list }}" # looping through the list

    - name: Iterating over a map
      ansible.builtin.debug:
        msg: "Hello {{ item.key }} and {{ item.value }}" # using the item of the list
      loop: "{{ map_list | dict2items }}" # looping through the map

    - name: Using a loop condition
      ansible.builtin.debug:
        msg: "Hello {{ item }}"
      loop: "{{ number_list }}"
      when: item > 3

What are tags?

Are:

  • Great at the task level
  • Can be used to skip certain tasks or only execute certain task
  • Great for speeding up development times during experimentation
  • Speeding up execution, especially in CI/CD pipelines
# play
- name: Using tags
  hosts: all
  gather_facts: false
  vars:
    number: 10
    another_number: 20
  tasks:
    - name: Print number
      debug:
        msg: "The number is {{ number }}"
      when: number > 5 and number < 15
      tags: # this is a tag
        - number
        - number2

    - name: Print another number
      debug:
        msg: "The another number is {{ another_number }}"
      when: 
        - another_number > 5
        - or # this is necessary for `or` condition, if you have `and` condition you don't need this
        - another_number < 15
      tags: # this is a tag
        - another_number

To run the playbook with tags:

ansible-playbook -i inventory.ini master.yaml -u root --tags "number, number2"

To run the playbook without tags:

ansible-playbook -i inventory.ini master.yaml -u root --skip-tags "number, number2"