The granddaddy of Ansible

Ansible is an awesome tool for running lots of commands in sequence or parallel on a lot of servers at once. Its a configuration management and deployment tool which is built on python. As a config management tool it is agentless so there's no need to install anything and it just creates a bunch of parallel connections via a python library called paramiko to run a set of commands in sequence.

Ansible is great, when you know how to use it and have time to configure it, write the tasks that you want to run and have access to repositories that can download it. But say you want something more lightweight.... alot more lightweight.

Pythons paramiko library is an SSHv2 library that was used as a major part of ansible.

http://www.paramiko.org/index.html

Below you'll find an example of a paramiko script that i used to get me out of a tight spot at a large install in a big site.

#!/usr/bin/env python
import os 
import sys
import paramiko
from paramiko.ssh_exception import SSHException
from paramiko.client import SSHClient
from paramiko.client import AutoAddPolicy
​
def apply_commands(host, commands):
    with SSHClient() as client:
        client.set_missing_host_key_policy(AutoAddPolicy)
        client.load_system_host_keys()
​
        print "%s Connectiong as root on port 22.." %host
        client.connect(host, 22, 'root', 'password', None)
​
​
        for command in commands:
            print "%s over here doing this! %s" %(host, command.strip())
            stdin, stdout, stderr = client.exec_command(command)
            print(stdout.read().decode('utf-8'))
            print(stderr.read().decode('utf-8'))
​
    print "%s done " % host
​
​
if __name__ == '__main__':
​
​
    with open('cmds.txt') as f:
        commands = f.readlines() 
    with open('hosts.txt') as f:
        hosts = f.readlines()
​
    for host in hosts:
        host = host.strip()
        try:
            apply_commands(host, commands)
        except KeyboardInterrupt:
            break
        except SSHException as err:
            print "%s %s " % (host, err)
        continue

In the first stanza we import all of the packages we'll need.

#!/usr/bin/env python
import os 
import sys
import paramiko
from paramiko.ssh_exception import SSHException
from paramiko.client import SSHClient
from paramiko.client import AutoAddPolicy

These are all pretty standard, os + sys from the standard library to read files from the os we're running the script from. Then we import all the paramiko magic, an exception handler, client and keyimporting policy.

From there we define our main function

def apply_commands(host, commands):
    with SSHClient() as client:
        client.set_missing_host_key_policy(AutoAddPolicy)
        client.load_system_host_keys()
​
        print "%s Connectiong as root on port 22.." %host
        client.connect(host, 22, 'root', 'password', None)
​

We have our connection creation here where we define a function that accepts two items , a set of hosts and a set of commands. We auto add new ssh keys and load the keys we have in the system. Then we output a message to the console that we are connecting to X host on port 22.

​
        for command in commands:
            print "%s over here doing this! %s" %(host, command.strip())
            stdin, stdout, stderr = client.exec_command(command)
            print(stdout.read().decode('utf-8'))
            print(stderr.read().decode('utf-8'))
    print "%s done " % host

Then in this section for every command that is listed in the commands section we print our host and command then the output. The actual command gets pushed to the server in the line

            stdin, stdout, stderr = client.exec_command(command)

With that function defined we want to use it.

This is done in our main loop.

if __name__ == '__main__':
​
​
    with open('cmds.txt') as f:
        commands = f.readlines() 
    with open('hosts.txt') as f:
        hosts = f.readlines()
​
    for host in hosts:
        host = host.strip()
        try:
            apply_commands(host, commands)
        except KeyboardInterrupt:
            break
        except SSHException as err:
            print "%s %s " % (host, err)
        continue

Here we open the two files i mentioned previously "cmds.txt" and "hosts.txt". The cmds is a list of commands line by line of different commands to use on every host. Line by line. The "hosts.txt" is a list of hostips or dns names that you want to connect via ssh to and run commands against. The contents of those files is read in to a variable "hosts" and "commands" respectively and then we use the variables in our next loop.

​
    for host in hosts:
        host = host.strip()
        try:
            apply_commands(host, commands)
        except KeyboardInterrupt:
            break
        except SSHException as err:
            print "%s %s " % (host, err)
        continue

Then in this last section we're saying, for every host that is in the hosts command try running the function "apply_commands". This will as we outlined earlier, connect to each host and run whatever is in the commands.txt file line by line.

While its a handy script it has got a bunch of limitations. For example bash redirection wouldn't work with a tool like this so commands like

grep -v "ERROR" castor.log >> error.log

wouldn't work.

Also why use something like this when you have multiplexors like screen and tmux. Well its a bit of work to set that up on a large number of systems for something temporary. A tool like this is quick and dirty. Plus with tmux or screen the temptation is always there to edit text files live via vim or something similar. When you're multiplexing to over 10 clients there's a very good chance that you can get drops or repeated signals leading to lots of repeat characters which is a nightmare in a big install. This way is a little more streamlined.

If you'd like a more professional version of something similar take a look at PSSH

https://pypi.org/project/pssh/

good walkthrough on using it here.

https://www.cyberciti.biz/cloud-computing/how-to-use-pssh-parallel-ssh-program-on-linux-unix/

But for anything more complicated than a few lines nowadays i use ansible.

Cheers Tony


By Tony Lokko