Ansible is a popular automation platform allowing you to manage thousands of nodes at one time. One of the most useful features of Ansible is its ability to run ad-hoc commands on remote computers with the Ansible shell module.
The shell module allows you to run ad-hoc commands or even small scripts like you’re sitting in front of the local console of each machine. It’s a handy module if you need to quickly run a command on a managed node.
In this tutorial, you’re going to learn what the Ansible shell module is, how it works, and how to use the Ansible shell module to run commands on remote hosts.
Prerequisites
This post will be a step-by-step tutorial on the Ansible shell module. If you’d like to follow along, be sure you have the following in place:
- An Ansible controller host – This tutorial will be using Ansible v2.9.18 on an Ubuntu 18.04.5 LTS machine. Learn how to set up an Ansible controller host here.
- A user account on the Ansible controller host that will allow you to create simple playbooks and run them on a managed node.
- A remote computer to run commands on – This tutorial will be using a node called SRV1 and webserver.
Running Ad-Hoc Commands with the Ansible Shell Module
In its simplest form, the Ansible shell module can run a single command on a remote host. To do this, a one-line command on your Ansible controller will work. For example, let’s run a simple command on a remote host to print the PATH
environment variable‘s value on a remote machine.
Log onto your Ansible controller and run the following command. This command uses the shell module (-m
) to connect to the webserver machine and pass an argument (-a
) which is the command to execute. In this instance, it’s running echo $PATH
to return the PATH environment variable’s value.
# webserver is host
# -m is syntax which is used with any module
ansible webserver -m ansible.builtin.shell -a 'echo $PATH'
Running Commands within a Playbook
Looks can be deceiving when it comes to the Ansible shell module. You can run remote commands with this module in many different ways. But first, let’s ease in slowly and learn how to run a simple command.
Let’s say you need to create a text file on a managed node at /opt/new_file.txt on a Linux host. You’d like to use the Ansible shell module to do that.
1. SSH to your Ansible controller host.
2. Create a directory called ansible_shell_module_demo in your home directory. This directory will contain the [playbook] you’ll use to invoke the shell module.
mkdir ~/ansible_shell_module_demo
cd ~/ansible_shell_module_demo
3. Open your favorite text editor and create a file called my_playbook.yml in the ~/ansible_shell_module_demo directory and paste in the following YAML playbook contents. This playbook has a single task that uses the Ansible shell
module to send some text (I am creating a new file
) to the /opt/new_file.txt
text file.
Since the playbook will be creating the file in the /opt directory, Ansible will need to run with sudo permissions using the become
attribute.
Ansible playbooks are written in YAML. To learn more about YAML, [click here]
tasks:
- name: Create a text file in opt directory using /bin/sh shell
ansible.builtin.shell: echo "I am creating a new file" > /opt/new_file.txt
become: true # Ensure that the highest permissions are used on the remote host
By default, when you specify
shell
as the module to use for a task, it will use the Bash shell. To invoke the Ansible shell module, you must useansible.builtin.shell
. If you have Windows-based remote machines, you must use the ansible. windows.win_shell module instead.
4. Create a subfolder called SRV1 under ~/ansible_shell_module_demo. This folder represents the remote node the playbook Ansible will execute on.
5. Now, invoke the playbook to copy it to and execute the task on the remote host.
ansible-playbook my_playbook.yml
Inventory is basically a collection of remote hosts either by their hostnames or by their IP address. If you wish to read in-depth how to create inventories, click here.
You can see below that the TASK has a status of changed meaning the remote host wasn’t in the proper state and was changed to run the command.
Using Arguments
In the previous example, you ran the Ansible shell module to invoke a simple command. Sometimes, you need to customize that behavior using arguments. Arguments allow you to pass configuration values to the shell module as it runs.
For example, when you run a shell command, the command also has a working directory or directory the command knows it’s running in. Perhaps, you must ensure you run a command in the /opt directory. To change that behavior, you’d use the chdir
argument.
Assuming you’re still in your Ansible controller host:
1. Open the playbook in your favorite text editor you recently created.
2. Replace the playbook text with the below playbook. This playbook uses the args
attribute to pass the chdir
argument to the task which changes the working directory to /opt before the command executes.
tasks:
- name: Change the working directory to /opt before executing the command
ansible.builtin.shell: ls -lh >> my_text_file.txt
args:
chdir: /opt # Changes to /opt directory
You can see below the output is the same as previously.
3. Now, open the playbook again and paste in the following YAML. The below playbook performs the exact same action as the previous step. But, instead, it specifies the command to run (ls -lh
) on its own line via the cmd
attribute and removes the need for the args
attribute.
tasks:
- name: Change the working directory to /opt
ansible.builtin.shell:
cmd: ls -lh # To list files under /opt directory
chdir: /opt # changes to /opt directory
4. By default, the shell module uses the sh shell on remote Linux nodes. But, you can change that using the executable
argument. By specifying the executable
argument like below, the ls
command will now use the Bash shell.
tasks:
- name: Reads all logs files in /opt directory
ansible.builtin.shell: ls -lh
args:
executable: /bin/bash # Run ls in the Bash shell
Executing Multiple Commands
So far, you’ve only been running a single command a time. Let’s change that. You can also invoke the Ansible shell module to run many commands at once.
On your Ansible controller host:
1. Open a text editor again and create another playbook called my_playbook2.yml in the ~/ansible_shell_module_demo directory.
2. Copy and paste the following playbook into the my_playbook2.yml file. Notice that the shell module will not create four separate files by running four separate commands in the same playbook.
tasks:
- name: Create multiple text file in tmp directory with shell module
ansible.builtin.shell: | # Multiple commands in Ansible shell module
echo "This will go in log file" > /tmp/log_file.txt
echo "This will go in memory file"> /tmp/memory_file.txt
echo "This will go in disk file" > /tmp/disk_file.txt
echo "This will go in version file"> /tmp/version_file.txt
become: true
args:
chdir: /var/log # Changing the directory
3. Now, execute the playbook on the SRV1 node.
ansible-playbook my_playbook.yml --inventory SRV1
You should see that the TASK has a status of changed.
4. Verify Ansible created the files on the remote host as expected.
Generating Debug Output
Sometimes playbooks won’t do what you expect and it’s time to start troubleshooting. When troubleshooting, you’ll sometimes need more granular information as to what’s going on inside of the playbook. In that case, you’ll need to read the debug output.
1. Perhaps the previous playbook was causing problems and you want to investigate what’s going on. In that case, use the debug
parameter as shown below.
When Ansible runs this playbook, it will generate the output in JSON format displaying the detailed execution of commands.
tasks:
- name: Create multiple text file in tmp directory with shell module
shell: | # Multiple commands in Ansible shell module
echo "This will go in log file" > /tmp/log_file.txt
echo "This will go in memory file"> /tmp/memory_file.txt
echo "This will go in disk file" > /tmp/disk_file.txt
echo "This will go in version file"> /tmp/version_file.txt
register: shell_output
become: true
args:
chdir: /var/log # Changing the directory
- debug: var=shell_output # This will provide the output
2. Run the playbook against the SRV1 node again.
ansible-playbook my_playbook.yml --inventory SRV1
If all goes well, you should see output like below.
3. Now connect to the remote computer and verify the files were again created.
Conclusion
Ansible shell module is a great way to run your commands on remote hosts. It provides a variety of flavored functionalities and gives you a handy way to remotely execute commands.