Ansible to ease my day to day work


Brief introduction

During the past year I was working for an educational institute in the Netherlands. One might think that an institute as such would make use of the latest and greatest technologies, however this was not quite the case. Hence, there was no configuration management available at all. All tasks were done manually over and over again.
Also, unfortunately most of the systems couldn’t authenticate against LDAP or Active Directory. Instead a local user with a different password on each and every system was used to authenticate and gain root access. The first thing to do was to make sure all systems were able to authenticate against LDAP to make life easier and I provided an Ansible playbook together with a role to make this possible.

Installing and configuring Ansible

Install Ansible with your (favorite) package manager, e.g. yum (Red Hat), apt (Debian/Ubuntu) or pip (Python). Now edit or create the Ansible config file.

  • /etc/ansible/ansible.cfg
[defaults]
remote_tmp = /tmp
ask_sudo_pass = True
ask_pass = True
host_key_checking = False
remote_user = $username
[privilege_escalation]
become=True
become_method=sudo

Let’s explain some bits and pieces of this config file:

  • By default Ansible will use your home dir as the ‘remote tmp’, in case of kerberized home dirs on NFS and sudo this will not work, /tmp is the most obvious alternative.
  • If you use password based SSH (and sudo) you want Ansible to prompt for your passwords.
  • If ‘host key checking’ is set to True, Ansible will wait if a host is not already in your known_hosts file.
  • The ‘remote user’ is the user you use to SSH to the target hosts.
  • You set ‘become’ to True if you want to become a different user, e.g. root, mostly by ‘sudo’ or ‘su’.
  • You can choose the ‘become user’ which is root by default and the ‘become method’ which is ‘sudo’ by default.
  • Default options which are not defined in the Ansible config file can be overridden as options to the ‘ansible’ or ‘ansible-playbook’ commands.

We need an Ansible hosts file as well. You can define one in a non-default location with the -i option to the ‘ansible’ or ‘ansible-playbook’ commands. Let’s create an Ansible hosts file on the default location.

  • /etc/ansible/hosts
[webservers]
servera
serverb
serverc
[dbservers]
serverd
servere
serverf

Creating your first playbook

I will discuss the first part of the playbook here, just to get a basic idea how things work. You could define everything in just one monolithic playbook, but to keep things clear and readable it’s recommended to create a so called role. Starting from scratch I needed a global playbook which calls a role to run its tasks from.

  • ansible/ldap.yml:
---
- hosts: webservers
  roles:
  - { role: ldap }

Instead of creating the role structure by hand ansible-galaxy can create a basic skeleton for you with all the required directories each containing an emtpy main.yml file. Run the ansible-galaxy command within the roles dir with the role name as an argument to it. Create the first two tasks to install the SSSD package on the target systems. We create a different task for SuSE and Red Hat, since they use different software installers.

  • ansible/roles/ldap/tasks/main.yml
---
- name: Install SSSD package (RHEL only)
  yum:
    name: "sssd"
    state: installed
  when: ansible_os_family == "RedHat"
  register: pkg_installed
- name: Install SSSD package (SLES only)
  zypper:
    name: "sssd"
    state: installed
  when: ansible_os_family == "Suse"
  register: pkg_installed

Now we need an additional task to provide the SSSD and LDAP config files, which will be exactly the same for SuSE and Red Hat systems. This task only runs when the previous task is done. The previous task will set ‘pkg_installed’ to true while the following task only runs if that particular variable has been set.

  • ansible/roles/ldap/tasks/main.yml
- name: Distribute SSSD and LDAP config files
  copy:
    src: "{{ item.value.src }}"
    dest: "{{ item.value.dest }}"
    owner: root
    group: root
    mode: 0600
  with_dict: "{{ ldap_conf_files }}"
  when: pkg_installed
  register: ldap_conf_done

In the task above a dictionary (called associative array in most programming languages) is used which holds the variables. Let’s look into that dict in the role’s vars file.

  • ansible/roles/ldap/vars/main.yml
---
ldap_conf_files:
  sssd_conf:
    src: sssd/sssd.conf
    dest: /etc/sssd/sssd.conf
  ldap_conf:
    src: openldap/ldap.conf
    dest: /etc/openldap/ldap.conf

To be really clear it’s actually a list (called array in most programming languages) of dictionaries. When we loop this list with the ‘with_dict’ keyword, it basicly works as follows. The list containing the dicts is called ‘ldap_conf_files’ and all dicts are expanding to the ‘item’ keyword. The ‘value’ keyword is then used to tell Ansible that we need the value of the key defined next, e.g. ‘src’ or ‘destination’. However, it probably sounds much more difficult then it actually is. Unfortunatly there are no Ansible modules for authconfig (Red Hat) and pam-config (SuSE), so for this I’m using the ‘command’ module. When you need any pipes in your command you can’t use the ‘command’ module but instead you need the ‘shell’ module.

  • ansible/roles/ldap/tasks/main.yml
- name: Configure PAM (RHEL only)
    command: /usr/sbin/authconfig --enablesssd --enablesssdauth --disableldap --disableldapauth --disablekrb5 --enablemkhomedir --update
    when: ldap_conf_done and ansible_os_family == "RedHat"
    register: pam_conf_done
- name: Configure PAM (SLES only)
    command: /usr/sbin/pam-config -a --sss --mkhomedir
    when: ldap_conf_done and ansible_os_family == "Suse"
    register: pam_conf_done

This is not yet the whole playbook, but now you’ve got a rough idea of how it can work for you.

Running your first playbook

It’s time to run our first playbook. You can do this with the ‘ansible-playbook command’:

$ ansible-playbook ldap.yml

The ‘webservers’ group is being used to run the playbook against. This group is defined earlier in the Ansible hosts file.

Using ad-hoc commands

Ansible can also be used to fire so called ad-hoc commands. This commands are just one task and one shot, so you don’t really need a playbook for them.

  • You want to restart the ‘mysql-server’ daemon, followed by the xymon-client daemon on the ‘dbservers’ group of hosts:
$ ansible -m service -a "name=mysql-server state=restarted" logstash
$ ansible -m service -a "name=xymon-client state=restarted" logstash
  • You need some facts from all hosts, this command will build a tree in /tmp/facts:
$ ansible -m setup -a 'filter=*distribution*' --tree /tmp/facts all
  • You want to copy your public SSH key to all hosts:
$ ansible -m authorized_key -a "user=$(whoami) state=present key=\"{{ lookup('file', '$HOME/.ssh/id_rsa.pub') }}\"" all
  • You need to check which hosts in the ‘dbservers’ group can reach the LDAP server on port 636:
$ ansible -m wait_for -a "host=ldap.example.com port=636 timeout=2" dbservers

Last but not least — ansible-doc

You can use ansible-doc to lookup information about a certain module. Or you can list the modules and grep on the output. This becomes very handy if you need to use a module you never used before or if you need a module but don’t know where you’re exactly looking for.

  • If you want to know how the setup module works and/or if you want to know for sure which options are mandatory or not, just fire the following simple command:
$ ansible-doc setup
  • If you want to find all the modules which can do something with mysql users, you can do the following:
$ ansible-doc --list | grep mysql | grep user
mysql_user                             Adds or removes a user from a MySQL database.
proxysql_mysql_users                   Adds or removes mysql users from proxysql admin interface.

Leave a comment

Your email address will not be published. Required fields are marked *