How to Ansible

Jeffrey Fonseca

What is ansible?

  • Ansible is Configuration-as-Code (CaC)
    • Most popular CaC option.
    • Red Hat uses in downstream products
  • Massive community
    • Written in python, with a modular architecture, easy to add on to
    • Official package manager and repository called Galaxy
    • 30000+ custom ansible pieces to do anything you want, from installing proxmox on debian to configuring desktop Linux

What is ansible (cont.)

  • Uses Yet-Another-Markup-Language (yaml)
---
- name: Sample ansible playbook (playbook name here)
  hosts: all
  gather_facts: true # Gather facts about managed node

  tasks:
    - name: Install the latest version of Apache and MariaDB
      ansible.builtin.package:
        name:
          - httpd
          - mariadb-server
        state: latest

What is ansible (cont.)

  • In theory should work on any Linux Distro
    • Abstracts ansible’s package manager specific modules, for apt/dnf/portage/slackpkg/apk/pacman/pkg-ng (freebsd)
  • In practice, packages have different names
- name: Install the latest version of Apache and MariaDB
  ansible.builtin.package:
    name:
      - "{{ 'apache2' if ansible_os_family == 'Debian' else 'httpd' }}"
      - mariadb-server
    state: latest

It looks like python, but squished into one line… that’s because it is!

How it works

  • Ansible is fundamentally ssh + python
    • Generates python scripts, transfers them over ssh
    • Popular because it only requires python on remote nodes
    • “Agentless” — no software needs to be installed on machines being configured
  • Executes sequentially, difficult to have execute things in parallel
    • Despite imperative structure, good ansible code should be “idempotent”

Setup

Environment Setup

Follow the guide at https://moonpiedmplings.github.io/guides/ccdc-env/

End environment created should have:

  • Libvirt
  • Nix
  • Wezterm
  • Visual Studio Code (with relevant extensions)

Launching

  • Run nix-shell to create a temporary environment with the relevant tools
  • code . launches vscode in the current directory, in the nix environment
  • Zellij launches a terminal multiplexer

Vagrant

  • Infrastructure-as-Code (IaC)
    • Overshadowed by docker recently
  • Create virtual machines from code, by downloading “boxes” from the main repository, vagrant cloud
Vagrant.configure("2") do |config|
    config.vm.provision "shell", "scripts/packages.sh"
    config.vm.define "318" do |vmconfig| # 318 is the virtual machine name
        vmconfig.vm.provision "ansible" do |ansible|
            ansible.playbook = "ansible/inventory.yml"
        end
        vmconfig.vm.box = "generic/alpine318"
        vmconfig.vm.provider "libvirt" do |libvirt|
            libvirt.memory = 1024
            libvirt.cpus = 2
        end
    end
end

Vagrant (cont.)

  • In linux/testing, there are directories for several operating systems
  • With those as your working directory, vagrant up --provider libvirt machinename brings them up.
  • An ansible inventory will be generated in .vagrant/provisioners/ansible/inventory/

Usage: Inventory

Sample ansible inventory

inventory.yml
---
all:
  hosts:
    test_host:
      ansible_host: # Defaults to host alias, in this case test_host 
      ansible_user: 
      ansible_password:
      ansible_become_pass: # Password ansible uses to authenticate with sudo
      ansible_ssh_private_key_file: 

Ansible ping

[nix-shell:~/]$ ansible all -i inventory/ -m ping
test_host | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

pong means you can configure a machine

Your first playbook!

---
- name: Example playbook
  hosts: all
  gather_facts: true

  tasks:
    - name: Test privilege escalation
      become: true
      ansible.builtin.debug:
        msg: "Hello World!"

Roles

  • Ansible roles are akin to functions in other programming langauges
  • Special folder structure
.
|-- users
    |-- tasks
    |   |-- add_good_admins.yml
    |   |-- change_passwords.yml
    |   |-- main.yml
    |   `-- remove_bad_admins.yml
    |-- templates
    |   `-- gitignore.j2
    `-- vars
        `-- main.yml

Roles (cont.)

  • To use a role, simply refer to it’s “root” folder, or the canonical name
  • Use vars to pass variables to the role, same way you would pass variables to a function
    • roles almost always come with defaults
---
- name: Example playbook
  hosts: all
  gather_facts: true
  roles:
    # Or publisher.role, if installed via ansible-galaxy would be moonpie.ufw
    - role: "../roles/ufw" 
      vars:
        ufw_lockdown: true

Our roles

  • We have quite a few. Priority was given to a few cases:
    • Linux firewalls — accidental lockout is not fun
    • Database password shuffling
    • User management: Password shuffling, and controlling who is in admin equivalent groups (wheel, sudo, docker)
    • Backup and restore: Arbitrary folders, containers, and container volumes. Comes with a simple versioning system, as well.

Usage

  • They all do nothing by default. Pass variables to the roles, changing the default variables in vars/main.yml.
    • vars/main.yml also acts as documentation for each role, via the comments either next to, or above each variable.

Usable Roles

  • ufw, firewalld, iptables
  • users, keycloak (only version 23 of the api)
  • mysql, Postgres. Mongo might work, but I didn’t get around to testing it. Don’t bother with mysql backup, there is no restore, it’s better to just use the backup role pointed at the relevant mysql service data.
  • backup, podman, docker. Docker watchtower container update is untested.

Examples

---
- name: Example playbook
  hosts: all
  vars:
        podman_backup_version: initial
        podman_backup_containers: ["alpine"]
        backup_version: initial
        backup_restore_directories:
          - "/var/lib/www/"
        ufw_lockdown: true
        ufw_open_ports: 80
  roles:
    - role: "../roles/facts"
    - role: "../roles/ufw"
    - role: "../roles/podman"
    - role: "../roles/backup"

Best Practice

  • Roles that do many disk operations (any of the backups) take a very long time
    • They should be seperated into seperate playbooks, and ran independently, like using a terminal multiplexer (tabs but in your terminal)
  • Facts needs to be ran before any of the firewall playbooks (facts gathers the control node ip address)