Useful Ansible Stuff
Table of Contents
Ansible community logo by Ansible licensed under CC BY-SA 4.0. Anthro Shammers artwork by Koidel Coyote.
Intro
I’ve been using Ansible and crafting custom roles and playbooks for a few years now. I’m by no means an expert and these aren’t necessarily wizardry or amazing, complex operations. In fact, if you know how to look for them, you can find out all of this information yourself by going through Ansible’s decent documentation for commands.
I wanted to make a quick “cheat sheet” of useful commands and configurations that I’m constantly using and occasionally need to look up for myself and I hope that this quick reference will be helpful for you as well. Feel free to expand the table of contents above and jump to the parts that interest you the most.
I’ll probably update this post later with new stuff that I find useful, so check back every now and then if you’ve found this helpful.
My Favorite Way to Install Ansible
Ansible is just a Python tool. While you can use your package manager(s) to install it, it might not be up-to-date. The better option is to use Python 3’s pip. It’s also a good idea to install Ansible within a Python venv or virtualenv to keep it from mucking up your system. Personally, I use virtualenvs with the help of virtualenvwrapper.
You can install virtualenv
and virtualenvwrapper
on your system-wide Python in Linux without causing too many problems:
$ pip3 install virtualenv virtualenvwrapper
Some Linux distributions and macOS won’t let you install to the system Python installation. For Linux distros that won’t, you can install Ansible utilizing pipx
instead:
# Debian-based apt example
$ sudo apt install pipx
$ pipx install ansible
The same goes for macOS, but I personally opt to just install a Python version using Homebrew as well as virtualenv
and virtualenvwrapper
:
# Homebrew on macOS to install Python 3.12, virtualenv, and virtualenvwrapper
$ brew install python@3.12 virtualenv virtualenvwrapper
You may also be able to find virtualenv and virtualenvwrapper in your package manager for your system. If you can, then that tends to work well. virtualenvwrapper can also be installed via Homebrew on macOS. The hardest part about any of these install methods is just figuring out where it was put
Once you have virtualenvwrapper installed, you should locate the shell script it creates (this varies depending on the system and the installation method) and add a source
to it in your ~/.bashrc
or ~/.zshrc
(if you’re using zsh).
# ~/.zshrc on macOS
# installed via: brew install virtualenvwrapper
# ...
source /opt/homebrew/bin/virtualenvwrapper.sh
Then we just create a virtualenv using the command mkvirtualenv
, specifying the name and Python version we want:
$ mkvirtualenv -p python3.12 ansible
# This will auto-activate the environment
# In the case that it doesn't...
$ workon ansible
# Once in the environment...
(ansible) $ pip install ansible
# See where it is
(ansible) $ which ansible
# Check its version
(ansible) $ ansible --version
# When you're done...
(ansible) $ deactivate
$
Updating Ansible
If you used pip to install Ansible, you can activate whatever venv/virtualenv you have for it and upgrade from there:
$ workon ansible
(ansible) $ pip install --upgrade ansible
If you’re using pipx:
$ pipx install --upgrade ansible
Ansible Ad-Hoc Command
Ansible supports running “ad-hoc” commands using the various modules that Ansible provides. These are one-off things such as uninstalling a particular package on a particular host or maybe checking versions of software for vulnerabilities in your entire environment.
# Uninstalling a package quickly using apt on host0.example.com
# -i specifies inventory file
# -m specifies the module we want to use (apt)
# -a specifies what we want to handle with the module
# --become elevates privs, which is needed to do APT operations
$ ansible db0.example.com -i inventory.ini -m ansible.builtin.apt -a "name=hugo state=absent" --become
# I won't show you the output of this because it's very long
# Just trust that it'll give you a whole bunch of useful info if the package is present when ran!
# Run a shell command to get the version of Git installed
# We want to see if we're vulnerable to CVE-2024-32004 based on version
$ ansible all -i inventory.ini -m ansible.builtin.shell -a "git --version"
# output:
web0.example.com | CHANGED | rc=0 >>
git version 2.39.2
web1.example.com | CHANGED | rc=0 >>
git version 2.39.2
db0.example.com | CHANGED | rc=0 >>
git version 2.39.2
db1.example.com | CHANGED | rc=0 >>
git version 2.39.2
stats.example.com | CHANGED | rc=0 >>
git version 2.39.2
Excluding Systems
Occasionally, you may want to run a playbook against all your systems in a playbook except for a couple. Usually you’d do this if there’s some sort of mismatch between systems or if you want to do an ad-hoc kinda staggered deployment.
You can achieve this using the --limit
flag and using exclamation points to exclude the systems you don’t want a playbook to run on.
# Run on all database servers defined in the playbook except these two
$ ansible-playbook -e environments/production -i environments/production/hosts database.yml --limit "!chi-db-01:!chi-db-07"
Vault Password Config
When you’re using Ansible vaults to store encrypted secrets, you can put your passphrase into a dot-file and have it automatically included as part of ansible.cfg
.
Note: You’ll want to be careful NOT to add this to your Git repository. You might even just want to add this file to your .gitignore
.
# Create a file in your home dir called .ansible_vault_pass
# This is a dot-file so it's hidden from the default ls commands
$ echo "mylongsillypassphrasethatssecure" > ~/.ansible_vault_pass
# Only owner can read and write, nobody else (but sudo maybe)
$ chmod 0600 ~/.ansible_vault_pass
# ansible.cfg
# Edit your config file for Ansible and add this line:
vault_password_file=~/.ansible_vault_pass
When you use ansible.cfg
for your vault password file, you’ll need to use ansible-vault
from the same directory you have ansible.cfg
in.
Another option so you don’t have to be in the ansible.cfg
directory? Environment variables.
$ echo "mylongsillypassphrasethatssecure" > ~/.ansible_vault_pass
$ chmod 0600 ~/.ansible_vault_pass
# Add to your ~/.bashrc or ~/.zshrc for automagic encrypt/decrypt anywhere
export ANSIBLE_VAULT_PASSWORD_FILE=~/.ansible_vault_pass
Arbitrary SSH Key Generation
Need some SSH keys but don’t actually care what they are? There’s a module for that. In this particular scenario, say you have a handful of developers who need to be able to authenticate with GitHub easily without much effort. We’ll have Ansible generate ed25519 keys for each user and update their SSH configs to automatically use the generated private key. Then all the developers have to do is add the public keys you get to their GitHub accounts.
Note that you may have to install community.crypto
collection using ansible-galaxy
if you don’t already have this module.
---
# You'd probably want to populate a list of users from another source
# But for simplicity, we'll define the list here
- name: Create list of users
ansible.builtin.set_fact:
users: [ steve, amanda, jessica, grace ]
# Generate ed25519 keys to use for GitHub for a list of users
# We use inventory_hostname to add the host to the comment for the key
- name: Generate SSH key pairs for each user
community.crypto.openssh_keypair:
path: "/home/{{ item }}/.ssh/gitkey"
owner: "{{ item }}"
type: ed25519
comment: "{{ item }} - {{ inventory_hostname }} GitHub key"
loop: "{{ users }}"
register: keys
# Adding GitHub configuration to each user's .ssh/config
# We use "insertbefore: BOF" so it goes to the top of the file
- name: Add GitHub to SSH configs for each user
ansible.builtin.blockinfile:
path: "/home/{{ item }}/.ssh/config"
append_newline: true
insertbefore: BOF
backup: true
block: |
Host github.com
Hostname github.com
User git
IdentityFile ~/.ssh/gitkey
loop: "{{ users }}"
- name: Print pub keys to stdout
ansible.builtin.debug:
msg: "{{ item['public_key'] }}"
loop: "{{ keys['results'] }}"
Ad-Hoc KISS(es)
If you’ve created a bunch of files (labeled something like delete_me_00
, delete_me_01
, etc. etc.) in a place on all your systems and realized you don’t want them anymore and don’t want your environment to be full of snow flakes, you can utilize Ansible ad-hoc commands to delete those files.
If you’re someone who likes to follow “best practices” with Ansible, you might be thinking, “I’ll just add a task to my role that does this.” That’s cool and all, but you’re making a one-off task that you’re only realistically going to run once. Why waste the time and energy to do that?
“Ah, I’ll just craft an Ansible command using the file module!” That’s a better idea, but if you have a bunch of files that are named similarly in a directory, what do you do?
“I’ll use loops in the ad-hoc commands!”
Pump the brakes. Ansible’s built-in looping doesn’t work with Ansible ad-hoc commands. To do something similar, you’d need to do some shell scripting to loop through instead.
Or… you can apply the KISS principle (Keep It Simple, Stupid!). Ansible’s shell
module lets you run shell commands exactly as you would at the command line. It’s pretty powerful (and dangerous!).
# Use the usual shell globbing in an Ansible ad-hoc command
$ ansible all -i inventory.ini -m ansible.builtin.shell -a "rm -rf /tmp/delete_me_*"
# Output:
web0.example.com | CHANGED | rc=0 >>
web1.example.com | CHANGED | rc=0 >>
db0.example.com | CHANGED | rc=0 >>
db1.example.com | CHANGED | rc=0 >>
stats.example.com | CHANGED | rc=0 >>
Avoid the Red Waves
You should have a staging environment of some sort to test your Ansible deployment before going to production unless you’re doing something simpler and less mission-critical (like a silly little blog). Even with a staging environment, it’s a good idea to double-check your work and make sure things are looking good before attempting to deploy. If you’re the kind of person who’s been going full YOLO and just deploying, waiting for Ansible to bleed all over your screen in red letters, fixing the issues, and retrying… it doesn’t have to be that way.
# Use --syntax-check to check for issues before you deploy!
$ ansible-playbook -i inventory.ini --syntax-check my-playbook.yml
Lint Traps
Also a useful tool for any kind of language you write is a linting tool. Ansible provides ansible-lint
which will scour over your configurations and tell you whether your configs are following best practices or not.
ansible-lint
can be installed via pip. It’s recommended that you use venv/virtualenv/conda environments when possible to keep from mucking up your system Python installation.
# Switch to ansible virtualenv
# (this is how I install and use ansible!)
$ workon ansible
# Install ansible-lint
$ pip install ansible-lint
# Run ansible-lint in the root of your Ansible configurations
$ ansible-lint
Note that you can also just use ansible-lint
instead of doing a --syntax-check
at all, but it doesn’t hurt to combine these. If you’re putting your Ansible configurations into a Git repository (you should be…), you can have a hook that runs ansible-lint
against your branches before allowing merge to main.
Ignoring Linting Issues
Linting is not a bullet-proof solution and sometimes it doesn’t know exactly what you want. In these kinds of cases, you can tell ansible-lint
to ignore certain tasks it sees as issues:
# ansible-lint will usually complain about this
# update: false will keep it from pulling new stuff
# adding the tag "skip_ansible_lint" tells it to buzz off
- name: Grab git repository if doesn't exist
ansible.builtin.git:
repo: git@github.com:johnsmith123/personal-site.git
dest: /home/jsmith/personal-site
update: false
tags: skip_ansible_lint
Conclusion
That’s it for now. Again, I suggest you check back every now and then as I add more content to this and do the inevitable corrections.
Take care.