DEV Community

Ali Sherief
Ali Sherief

Posted on

Ansible at Lightspeed ⚡

Today I'm going to show you how what Ansible is, how to get started using it to provision machines, and common deployment commands used inside it. Without wasting any time, let's begin.

What is Ansible?

Ansible is a deployment program used to install packages and write configuration files on target machines. Ansible's official website says "Ansible is the simplest way to automate apps and IT infrastructure." OK let's go into detail on what that's supposed to mean.

There are other deployment tools such as Puppet and Chef but these require you set up a main server that which target servers ask for provisioning instructions. It turns out that this setup is complicated when you only have a few machines to provision which will often be the case with personal projects and tasks.

For those use cases you want to SSH into the servers directly and run commands in each of them. And that's exactly what Ansible does. It automates the SSH connection process and command running so that you don't have to do it, saving you valuable time and eliminating potential inconsistencies and partial deployments which result from manually provisioning the target machines. This is also much easier then setting up Puppet or Chef infrastructure.

How can I get started with Ansible?

It's very easy to install Ansible on Unix-like machines (usually the deployment is done on Linux or macOS, but it's also doable on Windows because it's a Python package). On Ubuntu you should be able to run apt-get install ansible to get it. On macOS you can use pip install ansible. On Red Hat and CentOS it's yum install ansible.

As you can see, that was dead-easy. You now have all of Ansible's deployment features at your fingertips.

How do I run Ansible?

Before you can run an Ansible deployment, you have to create deployment files which will be used there. The files are written in the YAML language. If you do not understand YAML then there's a short and quick tutorial you can go read here and come back. YAML files have the extension .yml at the end of their filenames, though it's common to see these kind of files floating around in Linux with no filename extension at all; it reads the beginning of the file to determine the file type.

First, you create a folder to store all of your files in, fpr example, ansible-deployment. Then depending on the complexity of your deployment, there will be two to four files/folders in that directory.

\ ansible-deployment\
|
|-\ hosts
|
|-\ site.yml
|
|-\ roles\
|
+-\ vars\
Enter fullscreen mode Exit fullscreen mode

You are not required to have this folder structure. You can run Ansible using just a single .yml file and hosts file, or even with no files at all and specify all the deployment commands as arguments on the command line (this last part is out of scope of this post).

The entries that end with a slash are folders, the rest are files. The site.yml file is what's called the Ansible playbook. This is the file that contains all of the commands that will be ran over the SHH connection.

The inventory

The \hosts file inside the root directory is called the inventory. The inventory stores all of the target systems that Ansible will connect to. You specify target systems by IP address, port, user name and whether public key authentication should be used. And for the purposes of the deployment, you can give the host a label for Ansible to remember. Beware; this label is not the same concept as the hostname. Setting a label here does not change the hostname of the target. You should use the hostname Ansible module for that (more details on that later).

Here is what a sample inventory would look like:

all:
  hosts:
    my_host1:
      my_variable: 1
      ansible_port: 28497
      ansible_host: ssh5.zenulabidin.github.io
      ansible_ssh_private_key_file: private-key.pem
    my_host2:
      my_variable: abc
      ansible_port: 28496
      ansible_host: ssh4.zenulabidin.github.io
      ansible_ssh_private_key_file: private-key.pem
    mygroup_ofhosts:
      my_host3:
        my_variable: abc
        ansible_port: 28495
        ansible_host: ssh3.zenulabidin.github.io
        ansible_ssh_private_key_file: private-key.pem
Enter fullscreen mode Exit fullscreen mode

All of the above variables are optional except for all (which is what an inventory file begins with) and ansible_host. In fact, my_variable isn't even an Ansible property: You can make unique named variables such as my_variable for each host to give each host a different value. This is very useful if you are running commands that have different modes for each host.

Here I am configuring Ansible with two targets: my_host1 and my_host2, and both of these connect to ssh5.zenulabidin.github.io:28497 and ssh4.zenulabidin.github.io:28496 respectively. You can even have two targets that connect to the same IP address but different ports because this just makes an SSH connection for each entry internally.

I also have a group of hosts named mygroup_ofhosts which currently includes only the host my_host3. Organizing your hosts into groups allowed you to write rules for some of them at once without repeating yourself.

The playbook

This is our sample playbook:

---
- name: Install Rclone, and compile GNU coreutils
  hosts: all
  remote_user: ubuntu
  become: yes
  become_method: sudo

  vars_files:
   - vars/rclone.yml
   - vars/coreutils.yml
  roles:
    - role: stefangweichinger.ansible_rclone
    - coreutils
Enter fullscreen mode Exit fullscreen mode

Many things to note here. First we have hosts which can be either the label of a single host, a group of hosts, or just all which runs this on all defined targets.

Then we have remote_user. This is the user that we are going to run the playbook as. become tells us if we want to use a privilege-changing program such as su or sudo to switch users, and in become_method we supply the command-line program that changes the user for us.

Most of the time we just want to get root privileges so use use user: root, followed by either become_method: sudo for Ubuntu-based hosts or become_method: su for everything else.

This is followed by vars_files and this is a list of the files that Ansible has to read to collect additional variable names that are not defined in the inventory file. This feature is used to segregate variables based on their business logic.

A variables file isn't very special, it's also a YAML file, but the structure of the YAML variables depends on how you are going to use them in the playbook. For example if you are going to access your variable like a list, you could have a variable like this:

list_variable:
  - value1
  - value2
  - value3
Enter fullscreen mode Exit fullscreen mode

It is best practice to have the variables file under a subdirectory with a descriptive name under the vars folder.

So now that we got variables out of the way, let's go over the roles. Now the roles files are segregated for the same reason that Ansible allows variables to be segregated - so different teams can work on different parts of the playbook. You give each one of them a role and they will only have to manage the role they are working with.

Under the roles folder, you have the names of each role as subfolders, and in each subfolders you see three folders: files, handlers and tasks. Your YAML file for this module goes inside tasks/main.yml. I won't be going over handlers in this post so that folder can be left empty (but existing nonetheless).

The files folder contains files that are to be copied to the target machine in a temporary directory known only to Ansible. This directory will be the current directory relative to all of the commands in the playbook. You can't just run cd in the playbook and change the current directory for all future commands - each time a new command is run it "resets" to the temporary directory beforehand.

The advantage of this behavior is now you can refer to files in the files folder relatively, without any path, because you have no way of knowing what the path before the copied files will be on all systems, and even if you do, you can't easily specify one path in the playbook especially if the machines are running different Linux distributions, or some are Macs and the others are Linux boxes.

At any time, if you want to change the current working directory for any command, pass chdir: /new/path as one of the parameters to the command.

One final caveat: If any of the commands fail to run, the whole deployment is aborted for that particular host but the others go on. To prevent this, pass ignore_errors: true as one of the parameters to any command.

Commonly used commands

In the roles file, or in the single .yml file if that's how you're running Ansible, are all of the commands that you want the machines to run. All commands need to have the - name element at the beginning before the command itself and its value is just a human-readable description of what you want this command to do.

yum and apt

Installs RPM or DEB packages depending on the distribution.

Example:

apt
    name: ['build-essential', 'python3-pip', 'python3-setuptools', 'git', 'cron', 'rsyslog']
   state: present
Enter fullscreen mode Exit fullscreen mode

file

Ensures that files and folders exist at that point of the playbook.

Example:

  # Creates folder
  file:
    path: /home/ubuntu/work
    state: directory

  # Creates file
  file:
    path: /home/ubuntu/work
    state: touch
Enter fullscreen mode Exit fullscreen mode

copy

Copies a file from one place to another. If you have any files in the files folder of your role, these are located in the (fixed) current directory.

Example:

  copy:
    src: /source/folder/file.txt  # If src is a folder, this recursively copies it.
    dest: /dest/folder/file2.txt
Enter fullscreen mode Exit fullscreen mode

command and shell

Run an arbitrary command in a terminal. The prime difference between the two is that shell spins up a bash shell, or whatever the default shell is, to run the commands, allowing you to use special shell operators in your Ansible playbook. Most of the time you don't need this functionality so you can choose to use the otherwise equivalent command instead.

Example:

  command:
    cmd: make --quiet
Enter fullscreen mode Exit fullscreen mode

service

Enables, disables, stops and starts services on the target system, using Sysvinit or Systemd, depending on what is installed on your system.

Example:

  # Starts the rsyslog service
  service:
    name: rsyslog
    state: started
Enter fullscreen mode Exit fullscreen mode

hostname

Sets the hostname of the target system.

Example:

  hostname:
    name: web01
Enter fullscreen mode Exit fullscreen mode

reboot

Reboots the target and waits for a maximum of 600 seconds (10 minutes) for it to come online again to reconnect to it. This value can be changed.

Example:

  reboot:
    reboot_timeout: 3600  # default is 600
Enter fullscreen mode Exit fullscreen mode

And we're done

There was simply too many features to cover in Ansible to go over them in one session. If you want to learn more about Ansible playbooks, head over to their Getting Started guide to become an Ansible rockstar.

If you see any errors in this post, please let me know so I can correct them.

Discussion (0)