It’s an interesting time to be working in the IT industry. We no longer deliver software to our customers by installing a program on a single machine and calling it a day. Instead, we are all gradually turning into cloud engineers.
We now deploy software applications by stringing together services that run on a distributed set of computing resources and communicate over different networking protocols. A typical application can include web servers, application servers, memory-based caching systems, task queues, message queues, SQL databases, NoSQL datastores, and load balancers.
IT professionals also need to make sure to have the proper redundancies in place, so that when failures happen (and they will), our software systems will handle them gracefully. Then there are the secondary services that we also need to deploy and maintain, such as logging, monitoring, and analytics, as well as third-party services we need to interact with, such as infrastructure-as-a-service (IaaS) endpoints for managing virtual machine instances.1
You can wire up these services by hand: spinning up the servers you need, logging into each one, installing packages, editing config files, and so forth, but it’s a pain. It’s time-consuming, error-prone, and just plain dull to do this kind of work manually, especially around the third or fourth time. And for more complex tasks, like standing up an OpenStack cloud, doing it by hand is madness. There must a better way.
If you’re reading this, you’re probably already sold on the idea of configuration management and considering adopting Ansible as your configuration management tool. Whether you’re a developer deploying your code to production, or you’re a systems administrator looking for a better way to automate, I think you’ll find Ansible to be an excellent solution to your problem.
The example code in this book was tested against versions 4.0.0 and 2.9.20 of Ansible. Ansible 4.0.0 is the latest version as of this writing; Ansible Tower includes version 2.9.20 in the most recent release. Ansible 2.8 went End of Life with the release of 2.8.20 on April 13, 2021.
For years the Ansible community has been highly active in creating roles and modules—so active that there are thousands of modules and more than 20,000 roles. The difficulties of managing a project of this scale led creators to reorganize the Ansible content into three parts:
Core components, created by the Ansible team
Certified content, created by Red Hat’s business partners
Community content, created by thousands of enthusiasts worldwide
Ansible 2.9 has lots of built-in features, and later versions are more composable. This new setup makes it more easily maintainable as a whole.
The examples provided in this book should work in various versions of Ansible, but version changes in general call for testing, which we will address in Chapter 14.
It’s a science-fiction reference. An ansible is a fictional communication device that can transfer information faster than the speed of light. Ursula K. Le Guin invented the concept in her book Rocannon’s World (Ace Books, 1966), and other sci-fi authors have since borrowed the idea, including Orson Scott Card. Ansible cofounder Michael DeHaan took the name Ansible from Card’s book Ender’s Game (Tor, 1985). In that book, the ansible was used to control many remote ships at once, over vast distances. Think of it as a metaphor for controlling remote servers.
Ansible is often described as a configuration management tool and is typically mentioned in the same breath as Puppet, Chef, and Salt. When IT professionals talk about configuration management, we typically mean writing some kind of state description for our servers, then using a tool to enforce that the servers are, indeed, in that state: the right packages are installed, configuration files have the expected values and have the expected permissions, the right services are running, and so on. Like other configuration management tools, Ansible exposes a domain-specific language (DSL) that you use to describe the state of your servers.
You can use these tools for deployment as well. When people talk about deployment, they are usually referring to the process of generating binaries or static assets (if necessary) from software written by in-house developers, copying the required files to servers, and starting services in a particular order. Capistrano and Fabric are two examples of open-source deployment tools. Ansible is a great tool for deployment as well as configuration management. Using a single tool for both makes life simpler for the folks responsible for system integration.
Some people talk about the need to orchestrate deployment. Orchestration is the process of coordinating deployment when multiple remote servers are involved and things must happen in a specific order. For example, you might need to bring up the database before bringing up the web servers, or take web servers out of the load balancer one at a time to upgrade them without downtime. Ansible is good at this as well, and DeHaan designed it from the ground up for performing actions on multiple servers. It has a refreshingly simple model for controlling the order in which actions happen.
Finally, you’ll hear people talk about provisioning new servers. In the context of public clouds such as Amazon EC2, provisioning refers to spinning up new virtual machine instances or cloud-native Software as a Service (SaaS). Ansible has got you covered here, with modules for talking to clouds including EC2, Azure,2
Digital Ocean, Google Compute Engine, Linode, and Rackspace,3
as well as any clouds that support the OpenStack APIs.
Confusingly, the Vagrant tool, covered later in this chapter, uses the term provisioner to refer to a tool that does configuration management. It thus refers to Ansible as a kind of provisioner. Vagrant calls tools that create machines, such as VirtualBox and VMWare, providers. Vagrant uses the term machine to refer to a virtual machine and box to refer to a virtual machine image.
Figure 1-1 shows a sample use case of Ansible in action. A user we’ll call Alice is using Ansible to configure three Ubuntu-based web servers to run Nginx. She has written an Ansible script called webservers.yml. In Ansible, a script is called a playbook. A playbook describes which hosts (what Ansible calls remote servers) to configure, and an ordered list of tasks to perform on those hosts. In this example, the hosts are web1, web2, and web3, and the tasks are things such as these:
Install Nginx
Generate a Nginx configuration file
Copy over the security certificate
Start the Nginx service
In the next chapter, we’ll discuss what’s in this playbook; for now, we’ll focus on its role in the overall process. Alice executes the playbook by using the ansible-playbook command. Alice starts her Ansible playbook by typing two filenames on a terminal line: first the command, then the name of the playbook:
$ ansible-playbook webservers.yml
Ansible will make SSH connections in parallel to web1, web2, and web3. It will then execute the first task on the list on all three hosts simultaneously. In this example, the first task is installing the Nginx package, so the task in the playbook would look something like this:
- name: install nginx package: name: nginx
Ansible will do the following:
Generate a Python script that installs the Nginx package
Copy the script to web1, web2, and web3
Execute the script on web1, web2, and web3
Wait for the script to complete execution on all hosts
Ansible will then move to the next task in the list and go through these same four steps.
It’s important to note the following:
Ansible runs each task in parallel across all hosts.
Ansible waits until all hosts have completed a task before moving to the next task.
Ansible runs the tasks in the order that you specify them.
There are several open-source configuration management tools out there to choose from, so why choose Ansible? Here are 27 reasons that drew us to it. In short: Ansible is simple, powerful, and secure.
Ansible was designed to have a dead simple setup process and a minimal learning curve.
Ansible uses the YAML file format and Jinja2 templating, both of which are easy to pick up. Recall that Ansible configuration management scripts are called playbooks. Ansible actually builds the playbook syntax on top of YAML, which is a data format language that was designed to be easy for humans to read and write. In a way, YAML is to JSON what Markdown is to HTML.
You can inspect Ansible playbooks in several ways, like listing all actions and hosts involved. For dry runs, we often use ansible-playbook–check. With built-in logging it is easy to see who did what and where. The logging is pluggable and log collectors can easily ingest the logs.
To manage servers with Ansible, Linux servers need to have SSH and Python installed, while Windows servers need WinRM enabled. On Windows, Ansible uses PowerShell instead of Python, so there is no need to preinstall an agent or any other software on the host.
On the control machine (that is, the machine that you use to control remote machines), it is best to install Python 3.8 or later. Depending on the resources you manage with Ansible, you might have external library prerequisites. Check the documentation to see whether a module has specific requirements.
The authors of this book use Ansible to manage hundreds of nodes. But what got us hooked is how it scales down. You can use Ansible on very modest hardware, like a Raspberry Pi or an old PC. Using it to configure a single node is easy: simply write a single playbook. Ansible obeys Alan Kay’s maxim: “Simple things should be simple; complex things should be possible.”
Ansible works with simple abstractions of system resources like files, directories, users, groups, services, packages, web services.
By way of comparison, let’s look at how to configure a directory in the shell. You would use these three commands:
mkdir -p /etc/skel/.ssh chown root:root /etc/skel/.ssh chmod go-wrx /etc/skel/.ssh
By contrast, Ansible offers the file module as an abstraction, where you define the parameters of the desired state. This one action has the same effect as the three shell commands combined.
- name: create .ssh directory in user skeleton file: path: /etc/skel/.ssh mode: 0700 owner: root group: root state: directory
With this layer of abstraction, you can use the same configuration management scripts to manage servers running Linux distributions. For example, instead of having to deal with a specific package manager like dnf, yum or apt, Ansible has a “package” abstraction that you can use instead. But you can also use the system specific abstractions if you prefer.
If you really want to, you can write your Ansible playbooks to take different actions, depending on a variety of operating systems of the remote servers. But I try to avoid that when I can, and instead I focus on writing playbooks for the systems that are in use where I work: mostly Windows and Red Hat Linux, in my case.
Books on configuration management often mention the concept of convergence, or eventual consistent state. Convergence in configuration management is strongly associated with the configuration management system CFEngine by Mark Burgess. If a configuration management system is convergent, the system may run multiple times to put a server into its desired state, with each run bringing the server closer to that state.
Eventual consistent state does not really apply to Ansible, since it does not run multiple times to configure servers. Instead, Ansible modules work in such a way that running a playbook a single time should put each server into the desired state.
Having Ansible at your disposal can bring huge productivity gains in several areas of systems management.
You can use Ansible to execute arbitrary shell commands on your remote servers, but its real power comes from the wide variety of modules available. You use modules to perform tasks such as installing a package, restarting a service, or copying a configuration file.
As you will see later, Ansible modules are declarative; you use them to describe the state you want the server to be in. For example, you would invoke the user module like this to ensure there is an account named “deploy” in the web group:
user: name: deploy group: web
Chef and Puppet are configuration management systems that use agents. They are pull-based by default. Agents installed on the servers periodically check in with a central service and download configuration information from the service. Making configuration management changes to servers goes something like this:
You: make a change to a configuration management script.
You: push the change up to a configuration management central service.
Agent on server: wakes up after periodic timer fires.
Agent on server: connects to configuration management central service.
Agent on server: downloads new configuration management scripts.
Agent on server: executes configuration management scripts locally that change server state.
In contrast, Ansible is push-based by default. Making a change looks like this:
You: make a change to a playbook.
You: run the new playbook.
Ansible: connects to servers and executes modules, which changes server state.
As soon as you run the ansible-playbook command, Ansible connects to the remote servers and does its thing.
The push-based approach has a significant advantage: you control when the changes happen to the servers. You do not need to wait around for a timer to expire. Each step in a playbook can target one or a group of servers. You get more work done instead of logging into the servers by hand.
Push-mode also allows you to use Ansible for multi-tier orchestration, managing distinct groups of machines for an operation like an update. You can orchestrate the monitoring system, the load balancers, the databases, and the webservers with specific instructions so they work in concert. That’s very hard to do with a pull-based system.
Advocates of the pull-based approach claim that it is superior for scaling to large numbers of servers and for dealing with new servers that can come online anytime. A central system, however, slowly stops working when thousands of agents pull their configuration at the same time, especially when they need multiple runs to converge.
A sizable part of Ansible’s functionality comes from the Ansible Plugin System, of which the Lookup and Filter plugins are most used. Plugins augment Ansible’s core functionality with logic and features that are accessible to all modules. You can write your own plugins in Python (see Chapter 10).
You can integrate Ansible into other products, Kubernetes and Ansible Tower are examples of successful integration. Ansible-runner “is a tool and python library that helps when interfacing with Ansible directly or as part of another system whether that be through a container image interface, as a standalone tool, or as a Python module that can be imported.”
Using the ansible-runner library you can run an Ansible playbook from within a Python script:
#!/usr/bin/env python3 import ansible_runner r = ansible_runner.run(private_data_dir='./playbooks', playbook='playbook.yml') print("{}: {}".format(r.status, r.rc)) print("Final status:") prinr(r.stats)
Ansible modules cater for a wide range of system administration tasks. This list has the categories of the kinds of modules that you can use. These link to the module index in the documentation.
Large enterprises use Ansible successfully in production with tens of thousands of nodes and have excellent support for environments where servers are dynamically added and removed. Organizations with hundreds of software teams typically use AWX or a combination of Ansible Tower and Automation Hub to organize content, reach auditability and role-based access control. Separating projects, roles, collections, and inventories is a pattern that you will see often in larger organizations.
Automation with Ansible helps us to improve system security to security baselines and compliance standards.
Your authors like to think of Ansible playbooks as executable documentation. They’re like the README files that used to describe the commands you had to type out to deploy your software, except that these instructions will never go out of date because they are also the code that executes. Product experts can create playbooks that takes best practices into account. When novices use such a playbook to install the product, they can be sure they’ll get a good result.
If you set up your entire system with Ansible, it will pass what Steve Traugott calls the “tenth-floor test”: “Can I grab a random machine that’s never been backed up and throw it out the tenth-floor window without losing sysadmin work?”
Ansible has a clever way to organize content that helps define configuration at the proper level. It is easy to create a setup for distinct development, testing, staging and production environments. A staging environment is designed to be as similar as possible to the production environment so that developers can detect any problems before going live.
If you need to store sensitive data such as passwords or tokens, then ansible-vault is an effective tool to use. We use it to store encrypted variables in git. We’ll discuss it in detail in Chapter 8.
Ansible simply uses Secure Shell (SSH) for Linux and WinRM for Windows. We typically secure and harden these widely used systems-management protocols with strong configuration and firewall settings.
If you prefer using a pull-based model, Ansible has official support for pull mode, using a tool it ships with called ansible-pull. This book won’t cover pull mode, but you can read more about it in the official Ansible documentation.
Modules are also idempotent: if the deploy user does not exist, Ansible will create it. If it does exist, Ansible will not do anything. Idempotence is a nice property because it means that it is safe to run an Ansible playbook multiple times against a server. This is a vast improvement over the homegrown shell script approach, where running the shell script a second time might have a different (and unintended) effect.4
There is no Ansible agent listening on a port. Therefore, when you use Ansible, there is no attack surface.
The name Ansible refers to both the software and the company that runs the open-source project. Michael DeHaan, the creator of Ansible the software, is the former CTO of Ansible the company. To avoid confusion, I refer to the software as Ansible and to the company as Ansible, Inc.
Ansible, Inc. sells training and consulting services for Ansible, as well as a web-based management tool called Ansible Tower, which I cover in Chapter 19. In October 2015, Red Hat bought Ansible, Inc.; IBM bought Red Hat in 2019.
When Lorin was working an earlier edition of this book, the editor mentioned that “some folks who use the XYZ configuration management tool call Ansible a for-loop over SSH scripts.” If you are considering switching over from another configuration management tool, you might be concerned at this point about whether Ansible is powerful enough to meet your needs.
As you will soon learn, Ansible supplies a lot more functionality than shell scripts. In addition to idempotence, Ansible has excellent support for templating, as well as defining variables at different scopes. Anybody who thinks Ansible is equivalent to working with shell scripts has never had to support a nontrivial program written in shell. We will always choose Ansible over shell scripts for configuration management tasks if given a choice.
Worried about the scalability of SSH? Ansible uses SSH multiplexing to optimize performance, and there are folks out there who are managing thousands of nodes with Ansible (see chapter 12 of this book, as well as).
To be productive with Ansible, you need to be familiar with basic Linux system administration tasks. Ansible makes it easy to automate your tasks, but it is not the kind of tool that “automagically” does things that you otherwise would not know how to do.
For this book, we have assumed that you are familiar with at least one Linux distribution (such as Ubuntu, RHEL/CentOS, or SUSE), and that you know how to:
Connect to a remote machine using SSH
Interact with the Bash command-line shell (pipes and redirection)
Install packages
Use the sudo command
Check and set file permissions
Start and stop services
Set environment variables
Write scripts (any language)
If these concepts are all familiar to you, you are good to go with Ansible.
We will not assume you have knowledge of any particular programming language. For instance, you do not need to know Python to use Ansible unless you want to publish your own module.
This book is not an exhaustive treatment of Ansible. It is designed get you working productively in Ansible as quickly as possible. It also describes how to perform certain tasks that are not obvious from the official documentation.
We don’t cover all of Ansible’s modules in detail: there are more than 3,500 of them. You can use the ansible-doc command-line tool with what you have installed to view the reference documentation and the module index mentioned above.
Chapter 8 covers only the basic features of Jinja2, the templating engine that Ansible uses, primarily because your authors memorize only basic features when we use Jinja2 with Ansible. If you need to use more advanced Jinja2 features in templates, check out the official Jinja2 documentation.
Nor do I go into detail about some features of Ansible that are mainly useful when you are running it on an older version of Linux.
Finally, there are several features of Ansible we don’t cover simply to keep the book a manageable length. These features include pull mode, logging, and using vars_prompt to prompt the user for passwords or input. We encourage you to check out the official documentation to find out more about these features.
All the major Linux distributions package Ansible these days, so if you work on a Linux machine, you can use your native package manager for a casual installation (although this might be an older version of Ansible). If you work on macOS, I recommend using the excellent Homebrew package manager to install Ansible:
$ brew install ansible
On any Unix/Linux/macOS machine, you can install Ansible using one of the Python package managers. This way you can add Python-based tools and libraries that work for you, provided you add ~/.local/bin to your PATH shell variable. If you want to work with Ansible Tower or AWX, then you should install the same version of ansible-core on your workstation. Python 3.8 is recommended on the machine where you run Ansible.
$ pip3 install --user ansible==2.9.20
Installing ansible>=2.10 installs ansible-base as well. Use ansible-galaxy to install the collections you need.
As a developer, you should install Ansible into a Python virtualenv. This lets you avoid interfering with your system Python or cluttering your user environment. Using Python’s venv module and pip3, you can install just what you need to work on for each project.
$ python3 -m venv .venv --prompt A $ source .venv/bin/activate (A)
During activation of the environment, your shell prompt will change to (A) as a reminder. Enter deactivate to leave the virtual environment.
Windows is not supported to run Ansible, but you can manage Windows remotely with Ansible.5
Ansible plugins and modules might require that you install extra Python libraries.
(A) pip3 install pywinrm docker
In a way, the Python virtualenv was a precursor to containers: it creates a means to isolate libraries and avoid “dependency hell.”
Ansible-builder is a tool that aids in creating execution environments by controlling the execution of Ansible
from within a container for single-purpose automation workflows. It is based on the directory layout of ansible-runner. This is an advanced subject, and outside the scope of this book. If you’d like to experiment with it, refer to the source code repository that complements this book.
If you are feeling adventurous and want to use the bleeding-edge version of Ansible, you can grab the development branch from GitHub:
$ python3 -m venv .venv --prompt S $ source .venv/bin/activate (S) python3 -m pip install --upgrade pip (S) pip3 install wheel (S) git clone https://github.com/ansible/ansible.git --recursive (S) pip3 install -r ansible/requirements.txt
If you are running Ansible from the development branch, you need to run these commands each time to set up your environment variables, including your PATH variable, so that your shell knows where the Ansible and ansible-playbooks programs are:
(S) cd ./ansible (S) source ./hacking/env-setup
You need to have SSH access and root privileges on a Linux server to follow along with the examples in this book. Fortunately, these days it’s easy to get low-cost access to a Linux virtual machine through most public cloud services.
If you prefer not to spend the money on a public cloud, I recommend you install Vagrant on your machine. Vagrant is an excellent open-source tool for managing virtual machines. You can use it to boot a Linux virtual machine inside your laptop, which you can use as a test server.
Vagrant has built-in support for provisioning virtual machines with Ansible: we’ll talk about that in detail in Chapter 3. For now, we’ll just manage a Vagrant virtual machine as if it were a regular Linux server.
Vagrant needs a hypervisor like VirtualBox installed on your machine. Download VirtualBox first, and then download Vagrant.
We recommend you create a directory for your Ansible playbooks and related files. In the following example, we’ve named ours “playbooks.” Directory layout is important for Ansible: if you place files in the right places, the bits and pieces come together.
Run the following commands to create a Vagrant configuration file (Vagrantfile) for an Ubuntu/Focal 64-bits virtual machine image, and boot it:
$ mkdir playbooks $ cd playbooks $ vagrant init ubuntu/focal64 $ vagrant up
Note
The first time you use Vagrant, it will download the virtual machine image file. This might take a while, depending on your internet connection.
If all goes well, the output should look like this:
$ vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'ubuntu/focal64'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'ubuntu/focal64' version '20210415.0.0' is up to date... ==> default: Setting the name of the VM: playbooks_default_1618757282413_78610 ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Running 'pre-boot' VM customizations... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key default: default: Vagrant insecure key detected. Vagrant will automatically replace default: this with a newly generated keypair for better security. default: default: Inserting generated public key within guest... default: Removing insecure key from the guest if it's present... default: Key inserted! Disconnecting and reconnecting using new SSH key... ==> default: Machine booted and ready! ==> default: Checking for guest additions in VM... ==> default: Mounting shared folders... default: /vagrant => /Users/lorin/dev/ansiblebook/ch01/playbooks
You should be able to log into your new Ubuntu 20.04 virtual machine by running the following:
$ vagrant ssh
If this works, you should see a login screen like this:
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-72-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sun Apr 18 14:53:23 UTC 2021 System load: 0.08 Processes: 118 Usage of /: 3.2% of 38.71GB Users logged in: 0 Memory usage: 20% IPv4 address for enp0s3: 10.0.2.15 Swap usage: 0% 1 update can be installed immediately. 0 of these updates are security updates. To see these additional updates run: apt list --upgradable vagrant@ubuntu-focal:~$
A login with vagrant ssh lets you interact with the Bash shell, but Ansible needs to connect to the virtual machine by using the regular SSH client. Tell Vagrant to output its SSH configuration by typing the following:
$ vagrant ssh-config
On my machine, the output looks like this:
Host default HostName 127.0.0.1 User vagrant Port 2222 UserKnownHostsFile /dev/null StrictHostKeyChecking no PasswordAuthentication no IdentityFile /Users/lorin/dev/ansiblebook/ch01/playbooks/.vagrant/ machines/default/virtualbox/private_key IdentitiesOnly yes LogLevel FATAL
The important lines are shown here:
HostName 127.0.0.1 User vagrant Port 2222 IdentityFile /Users/lorin/dev/ansiblebook/ch01/playbooks/.vagrant/machines/default/virtualbox/private_key
Note
Starting with version 1.7, Vagrant has changed how it manages private SSH keys: it now generates a new private key for each machine. Earlier versions used the same key, which was in the default location of ~/.vagrant.d/insecure_private_key. The examples in this book use Vagrant 2.2.14.
In your case, every field should be the same except for the path of the identity file.
Confirm that you can start an SSH session from the command line by using this information. The SSH command also works with a relative path from the playbooks directory.
$ ssh [email protected] -p 2222 -i .vagrant/machines/default/virtualbox/private_key
You should see the Ubuntu login screen. Type exit to quit the SSH session.
Ansible can manage only the servers it explicitly knows about. You provide Ansible with information about servers by specifying them in an inventory. We usually create a directory called “inventory” to hold this information.
$ mkdir inventory
Each server needs a name that Ansible will use to identify it. You can use the hostname of the server, or you can give it an alias and pass other arguments to tell Ansible how to connect to it. We will give our Vagrant server the alias of testserver.
Create a text file in the inventory directory. Name the file vagrant.ini vagrant if you’re using a Vagrant machine as your test server; name it ec2.ini if you use machines in Amazon EC2.
The ini-files will serve as inventory for Ansible. They list the infrastructure that you want to manage under groups, which are denoted in square brackets. If you use Vagrant, your file should look like Example 1-1. The group [webservers] has one host: testserver. Here we see one of the drawbacks of using Vagrant: you need to pass extra vars data to Ansible to connect to the group. In most cases, you won’t need all this data.
[webservers] testserver ansible_port=2222 [webservers:vars] ansible_host=127.0.0.1 ansible_user = vagrant ansible_private_key_file = .vagrant/machines/default/virtualbox/private_key
If you have an Ubuntu machine on Amazon EC2 with a hostname like ec2-203-0-113-120.compute-1.amazonaws.com, then your inventory file will look something like this:
[webservers] testserver ansible_host=ec2-203-0-113-120.compute- 1.amazonaws.com [webservers:vars] ansible_user=ec2-user ansible_private_key_file=/path/to/keyfile.pem
Ansible supports the ssh-agent program, so you don’t need to explicitly specify SSH key files in your inventory files. If you login with your own userid, then you don’t need to specify that either. See “SSH Agent” in appendix A for more details if you haven’t used ssh-agent before.
We’ll use the ansible command-line tool to verify that we can use Ansible to connect to the server. You won’t use the ansible command often; it’s mostly used for ad hoc, one-off things.
Let’s tell Ansible to connect to the server named testserver described in the inventory file named vagrant.ini and invoke the ping module:
$ ansible testserver -i inventory/vagrant.ini -m ping
If your local SSH client has host-key verification enabled, you might see something that looks like this the first time Ansible tries to connect to the server:
The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' can't be established. RSA key fingerprint is e8:0d:7d:ef:57:07:81:98:40:31:19:53:a8:d0:76:21. Are you sure you want to continue connecting (yes/no)?
You can just type “yes.”
If it succeeds, the output will look like this:
testserver | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" }
If Ansible did not succeed, add the -vvvv flag to see more details about the error:
$ ansible testserver -i inventory/vagrant.ini -m ping -vvvv
We can see that the module succeeded. The “changed”: false part of the output tells us that executing the module did not change the state of the server. The “ping”: “pong” output text is specific to the ping module.
The ping module doesn’t do anything other than check that Ansible can start an SSH session with the servers. It’s a tool for testing that Ansible can connect to the servers: very useful at the start of a big playbook.
You had to type a lot to use Ansible to ping your testserver. Fortunately, Ansible has ways to organize these sorts of variables, so you don’t have to put them all in one place. Right now, we’ll add one such mechanism, the ansible.cfg file, to set some defaults so we don’t need to type as much on the command line.
Ansible looks for an ansible.cfg file in the following places, in this order:
File specified by the ANSIBLE_CONFIG environment variable
./ansible.cfg (ansible.cfg in the current directory)
~/.ansible.cfg (.ansible.cfg in your home directory)
/etc/ansible/ansible.cfg
We typically put ansible.cfg in the current directory, alongside our playbooks. That way, we can check it into the same version-control repository that our playbooks are in.
Example 1-2 shows an ansible.cfg file that specifies the location of the inventory file (inventory) and sets parameters that affect the way Ansible runs, for instance how the output is presented.
Since the user you’ll log onto and its SSH private key depend on the inventory that you use, it is practical to use the vars block in the inventory file, rather than in the ansible.cfg file, to specify such connection parameter values. Another alternative is your ~/.ssh/config file.
Our example ansible.cfg configuration also disables SSH host-key checking. This is convenient when dealing with Vagrant machines; otherwise, we need to edit our ~/.ssh/known_hosts file every time we destroy and re-create a Vagrant machine. However, disabling host-key checking can be a security risk when connecting to other servers over the network. If you’re not familiar with host keys, see Appendix A.
[defaults] inventory = inventory/vagrant.ini host_key_checking = false stdout_callback = yaml callback_enabled = timer
Ansible and Version Control
Ansible uses /etc/ansible/hosts as the default location for the inventory file. However, Bas never uses this because he likes to keep his inventory files version-controlled alongside his playbooks. Also, he uses file extensions for things like syntax formatting in an editor.
Although we don’t cover version control in this book, we strongly recommend you commit to using the Git version-control system to save all changes to your playbooks. If you’re a developer, you’re already familiar with version-control systems. If you’re a systems administrator and aren’t using version control yet, this is a perfect opportunity for you to really start with infrastructure as code!
With your default values set, you can invoke Ansible without passing the -i hostname arguments, like so:
$ ansible testserver -m ping
We like to use the ansible command-line tool to run arbitrary commands on remote machines, like parallel SSH. You can execute arbitrary commands with the command module. When invoking this module, you also need to pass an argument to the module with the -a flag, which is the command to run.
For example, to check the uptime of your server, you can use this:
$ ansible testserver -m command -a uptime
Output should look like this:
testserver | CHANGED | rc=0 >> 10:37:28 up 2 days, 14:11, 1 user, load average: 0.00, 0.00, 0.00
The command module is so commonly used that it’s the default module, so you can omit it:
$ ansible testserver -a uptime
If your command has spaces, quote it so that the shell passes the entire string as a single argument to Ansible. For example, to view the last ten lines of the /var/log/dmesg logfile:
$ ansible testserver -a "tail /var/log/dmesg"
The output from our Vagrant machine looks like this:
testserver | CHANGED | rc=0 >> [ 9.940870] kernel: 14:48:17.642147 main VBoxService 6.1.16_Ubuntu r140961 (verbosity: 0) linux.amd64 (Dec 17 2020 22:06:23) release log 14:48:17.642148 main Log opened 2021-04-18T14:48:17.642143000Z [ 9.941331] kernel: 14:48:17.642623 main OS Product: Linux [ 9.941419] kernel: 14:48:17.642718 main OS Release: 5.4.0-72-generic [ 9.941506] kernel: 14:48:17.642805 main OS Version: #80-Ubuntu SMP Mon Apr 12 17:35:00 UTC 2021 [ 9.941602] kernel: 14:48:17.642895 main Executable: /usr/sbin/VBoxService 14:48:17.642896 main Process ID: 751 14:48:17.642896 main Package type: LINUX_64BITS_GENERIC (OSE) [ 9.942730] kernel: 14:48:17.644030 main 6.1.16_Ubuntu r140961 started. Verbose level = 0 [ 9.943491] kernel: 14:48:17.644783 main vbglR3GuestCtrlDetectPeekGetCancelSupport: Supported (#1)
If we need root access, pass in the -b flag to tell Ansible to become the root user. For example, accessing /var/log/syslog requires root access:
$ ansible testserver -b -a "tail /var/log/syslog"
The output looks something like this:
testserver | CHANGED | rc=0 >> Apr 23 10:39:41 ubuntu-focal multipathd[471]: sdb: failed to get udev uid: Invalid argument Apr 23 10:39:41 ubuntu-focal multipathd[471]: sdb: failed to get sysfs uid: No data available Apr 23 10:39:41 ubuntu-focal multipathd[471]: sdb: failed to get sgio uid: No data available Apr 23 10:39:42 ubuntu-focal multipathd[471]: sda: add missing path Apr 23 10:39:42 ubuntu-focal multipathd[471]: sda: failed to get udev uid: Invalid argument Apr 23 10:39:42 ubuntu-focal multipathd[471]: sda: failed to get sysfs uid: No data available Apr 23 10:39:42 ubuntu-focal multipathd[471]: sda: failed to get sgio uid: No data available Apr 23 10:39:43 ubuntu-focal systemd[1]: session-95.scope: Succeeded. Apr 23 10:39:44 ubuntu-focal systemd[1]: Started Session 97 of user vagrant. Apr 23 10:39:44 ubuntu-focal python3[187384]: ansible-command Invoked with _raw_params=tail /var/log/syslog warn=True _uses_shell=False stdin_add_newline=True strip_empty_ends=True argv=None chdir=None executable=None creates=None removes=None stdin=None
You can see from this output that Ansible writes to the syslog as it runs.
You are not restricted to the ping and command modules when using the ansible command-line tool: you can use any module that you like. For example, you can install Nginx on Ubuntu by using the following command:
$ ansible testserver -b -m package -a name=nginx
If installing Nginx fails for you, you might need to update the package lists. To tell Ansible to do the equivalent of apt-get update before installing the package, change the argument from name=nginx to name=nginx update_cache=yes.
You can restart Nginx as follows:
$ ansible testserver -b -m service -a "name=nginx state=restarted"
You need the -b argument to become the root user because only root can install the Nginx package and restart services.
We will improve the setup of the test server in this book, so don’t become attached to your first virtual machine. Just remove it for now with:
$ vagrant destroy -f
This introductory chapter covered the basic concepts of Ansible at a general level, including how it communicates with remote servers and how it differs from other configuration management tools. You’ve also seen how to use the Ansible command-line tool to perform simple tasks on a single host.
However, using Ansible to run commands against single hosts isn’t terribly interesting. The next chapter covers playbooks, where the real action is.
1 For more on building and maintaining these types of distributed systems, check out Thomas A. Limoncelli, Strata R. Chalup, and Christina J. Hogan, The Practice of Cloud System Administration, volumes 1 and 2 (Addison-Wesley, 2014), and Martin Kleppman, Designing Data-Intensive Applications (O’Reilly, 2017).
2 Yes, Azure supports Linux servers.
3 For example, see “Using Ansible at Scale to Manage a Public Cloud” (slide presentation, 2013), by Jesse Keating, formerly of Rackspace.
4 If you are interested in what Ansible’s original author thinks of the idea of convergence, see Michael DeHaan, “Idempotence, convergence, and other silly fancy words we use too often,” Ansible Project newsgroup post, November 23, 2013.
5 To learn why Windows is not supported on the controller, read Matt Davis, “Why no Ansible controller for Windows?” blog post, March 18, 2020.
44.200.94.150