Executing Commands on Remote Servers with the Ansible Shell Module

Ansible is a widely adopted configuration management tool that simplifies the management of multiple servers. It streamlines tasks like server setup and application installation through automation, allowing you to handle numerous remote servers and run tasks sequentially from a central core node. Also, Ansible is popular because of its numerous modules and its ability to operate seamlessly over SSH without the need for additional software installations on the target servers. 

In this tutorial, we will learn how to run commands on remote servers with the Ansible shell module. In addition, we will also explore how to run Ansible ad-hoc commands and create a playbook to execute different shell commands.

Prerequisites

You must have the following prerequisites to use the Ansible shell module:

  1. In this guide, we are using Ubuntu 22.04. So, the Ansible should be installed and configured on your Ubuntu system.
  2. A remote host should be configured on which you will run the commands using the Ansible shell module.

What Is the Ansible Shell Module?

The Ansible shell module allows users to execute commands on the shell of the target systems. It enables users to run complex commands with pipes and redirection on remote targets that maintain the originality of the command execution. 

While running commands in the Ansible shell module, it takes the command name, parameters or arguments separated with white space delimiters and executes these commands on the remote hosts. 

Difference: Ansible Shell Module vs. Command Module

Both the Ansible shell module and command module are quite similar and give the same results. However, a few differences exist between these two modules that we have listed below:

  1. The Ansible shell module supports all special operators, environment variables, pipes, and redirections. Whereas command modules don’t support the operators (<, >, &,;, | |), environment variables, and pipes. 
  2. The command module doesn’t allow users to run commands directly on the shell. In this case, the Ansible shell module offers more flexibility than the command module. With the shell module, you can directly execute commands on the shell of the target hosts. It uses the “/bin/sh” shell by default to run commands. You can also configure other shells for running commands on remote hosts.
  3. Ansible users prefer to use command modules in terms of security while executing commands on target systems. Ansible-run commands in the command module are safer and offer more predictable options than the Ansible shell module. In addition, the command module doesn’t affect the user’s remote shell environment.

Getting Started with Ansible Shell Commands: Run Ad-hoc Commands 

Ad-hoc commands are one-liner commands that are not reusable in the future. However, you can use the Ansible ad-hoc shell commands to quickly run individual tasks. The basic syntax of Ad-hoc commands is given below:

ansible [pattern] -m [ansible-module] -a {commands with options}

In the syntax above, the pattern represents the host group to which the target host belongs. The option ‘-m’ specifies the module type, and the option ‘-a’ will take the command arguments.

Let’s look at an example to explain the Ansible Ad-hoc command syntax. In this example, we’ll try to display the date and time of the targeted host. Here, we have only connected to one host and created a separate user, “ansible.” Use the following command to display the date and time of the remote host:

$ ansible 192.168.1.14 -u ansible -m shell -a “timedatectl”

executing commands on remote servers with the ansible shell module

The above Ansible ad-hoc command will connect to the remote host at IP address “192.168.1.14” using the username "ansible" over SSH. After that, the “timedatectl” command will execute on the remote host, which is used to display information about the system's date and time settings. 

$ ansible <ip-address> -u ansible -m shell -a “timedatectl | grep Time”


With the above ansible ad-hoc command, it will run the “timedatectl” command and then using the grep command, it will filter and display lines containing "Time.”

Similarly, you can display system information using the “uptime” command and the load average. To do this, use the following command:

$ ansible <ip-address> -u ansible -m shell -a “uptime”

executing commands on remote servers with the ansible shell module

When you run the above Ansible shell command, it will execute the uptime command after connecting to the remote host.  

$ ansible <ip-address> -u ansible -m shell -a setup

The above command serves to collect system facts and information about the remote host using Ansible.

executing commands on remote servers with the ansible shell module

How to Use the Ansible Shell Module?

Before running the Ansible shell commands, it is essential that you know the basic shell parameters that you will need to pass while using the Ansible shell module:


chdir - It will change the current directory before command execution.

cmd - A string holds the command to run, along with any associated arguments.

executable - Requires an absolute path to change the shell that a user is using.

removes - It takes the filename and is used to exclude steps when a file doesn’t exist.

stdin - Lets the user set the “stdin” of a command to a specific value.

warn - Takes (default) yes or no, enabling or disabling task warnings.


With that out of the way, let’s get to some examples of how to use Ansible shell.

Ansible Run Commands with Shell Module

The real power of Ansible is the use of playbooks, which allow you to do advanced tasks on target hosts. Besides using ad-hoc commands, you can also utilize the Ansible shell module within playbooks to define tasks for remote hosts.


Run a Single Command using the Ansible Shell Module

You can also run a single command with the Ansible shell module. To do this, create a playbook and paste the following lines of code into this file:


- name: Shell module example
  hosts: database_servers
  tasks:
  
  - name: Check system information
    shell:
    	"df  -h"
    register: "os_info"
    
  - debug:
    	msg: "{{"os_info".stdout_lines}}"


Now, save the changes in this file and exit from it using “Ctrl+X.” Then, run this playbook using the following command:


$ ansible-playbook --ask-become-pass -i hosts environment_var.yml


After running the above command, you will see the following information on the terminal window:

executing commands on remote servers with the ansible shell module

Ansible Run Multiple Commands with Shell Module

You can run multiple tasks sequentially using Ansible. To create an Ansible playbook, create an empty file in your source code editor. In this guide, we are using the nano editor to create a playbook.

$ sudo nano testplaybook.yml

executing commands on remote servers with the ansible shell module

The above Ansible playbook performs different tasks, like creating a user, updating the package cache, and installing or upgrading the Nginx package on the specified remote target host. It uses variables to make the playbook more flexible and reusable. 

Save the above file using “Ctrl+O” and exit from this file using “Ctrl+x.” 

Here, we are using the custom inventory file “hosts.” To run this playbook, use the following command:

$ ansible-playbook --ask-become-pass -i hosts testplaybook.yml

In the above command, the “--ask-become-pass” will ask you to enter the root password and then will display the following output on the screen:

executing commands on remote servers with the ansible shell module

Install a Package with the Ansible Shell Module

You can also install a package on the targeted remote host. Consider the following playbook to install “apache2” packages on the targeted host using Ansible:


hosts: database_servers
become: yes
tasks:

name: 
apt:
    update_cache: yes
name: install apache2 package
apt:
    name: apache2


After running the above playbook, you will notice that apache2 is installed on the remote servers. 

executing commands on remote servers with the ansible shell module


Prevent Shell Command Injection

As we discussed earlier, the command module in Ansible is considered a safer choice for executing commands on remote systems. However, it comes with certain limitations in terms of functionality. 

To safely use the shell module, you can employ a technique called "quote filtering" to prevent variable names from potential command injection. The example below demonstrates this to the command injection process to ensure the security of your shell commands.


- name: Create a table in the { database name } database

    `shell`: `mysql -u root -psecret -e "CREATE TABLE { quote| table name | quote } (id INT, name VARCHAR(255));"`
    become: yes
- name: Ansible Quote Filter Example
  hosts: database servers
  vars:
    - database name: mydb
    - table name: my table


The above Ansible playbook creates a MySQL table with a unique name in a specified database, using the quote filter to protect against potential issues with special characters in the table name.

Conclusion

The Ansible shell module is a flexible and robust tool that enhances user control and simplifies remote server configurations. In this article, we've explored how to use the Ansible shell module, its parameters, and the required arguments. We hope with this knowledge, you will be able to effectively utilize the Ansible shell module on your Linux system.