tmux cssh and ansible
Starting a tmux CSSH session based on Ansible inventory.
Ansible is a great automation and orchestration tool.
There are times though, where one needs to cluster ssh into multiple systems to action something.
clusterssh and multi-ssh are two common projects which work well.
I much prefer to use tmux though. Mainly for these reasons:
- I can customise it to work with my key bindings.
- I can easily copy and paste in and out of tmux using the keyboard without any mouse, ever.
Often when one uses your “cssh” choice of tool, one maintains a separate clusterssh list of systems to connect to. This is duplication and is especially annoying when many software engineers each have their own list of servers to connect to and don’t collaborate the list.
In the interest of fixing this I came up with the following solution.
I hacked together the below improved tmux cssh bash script. It basically allows one to start a tmux “CSSH” session based on an Ansible inventory.
Note: This is a modification of Pierre Guinoiseau tmux-cssh project.
It also allows for:
- Host selection via Ansible host pattern matching.
- Host selection by environment, dev, int, preprod, production.
- Further limiting the hosts using regular expressions, again by
using ansible’s
--limit
option.
It solves duplicating your CSSH inventory groups. Now ansible’s inventory group is the source of truth. One place to update the information.
A few examples to demonstrate it’s usefulness.
Launch a cssh session using tmux for all dev webservers:
tssh -e dev webservers
The same but for prod:
tssh -e prod webservers
Now limit it to one datacenter but using regex:
tssh -e prod -l '.*-dc1-' webservers
The above filters all hostnames with -dc1-
which is assumed to be
hosts in datacenter 1.
Since all the intelligence is done in ansible you can really use any fancy pattern matching that you can with Ansible. That is well documented.
Here’s the script it it’s current form:
#!/usr/bin/env bash # by Divan Santana <divan@santanas.co.za> # Starts a tmux CSSH session based on Ansible inventory. # TODO: Add support for -e 'all' so can cssh across environments. ######## # docs # ######## # You need the following to make this work: # - Your search domain must include .x.example.com, as we use DNS to # resolve names. # - ansible, tmux installed and in your $PATH # - Clone ansible code repository[1] somewhere locally # [1]: https://code.example.com:7990/projects/DEVOPS/repos/ansible # - Set src_ansible to path of local git ansible repository # Ideally symlink this script into your $PATH like so: # ln -s ~/src/work/digital-ansible/extras/tssh ~/.local/bin/tssh # - run it, like so: # tssh -e int webserver_hosts # or # tssh -e prod -l '~.*-dc1- webserver_hosts ############# # debugging # ############# # export DEBUG='true' # to enable debugging before running this. [ "$DEBUG" == 'true' ] && set -x ################## # config options # ################## # Set this to where you clone the ansible site project. src_ansible="${HOME}/src/work/digital-ansible" ############## # help usage # ############## usage() { echo "Usage: $0 [options] hostpattern" >&2 echo "" >&2 echo "Starts a tmux CSSH session based on Ansible inventory." >&2 echo "" >&2 echo "Options:" >&2 echo " -h Show help" >&2 echo " -e <environment> Ansible environment" >&2 echo " -l <limit> Further limit selected hosts to an additional pattern" >&2 } while [ $# -ne 0 ]; do case $1 in -e) shift; if [ $# -eq 0 ]; then usage exit 2 fi env="$1"; shift ;; -l) shift; if [ $# -eq 0 ]; then limit=""; shift fi limit="$1"; shift ;; -h) usage exit 0 ;; -*) usage exit 2 ;; *) gname=$1; shift ;; esac done if [ -z "${gname}" ]; then usage exit 2 fi ###################### # requirements check # ###################### if ! type tmux > /dev/null 2>&1 ; then echo "tmux not found. Is it installed?" >&2 exit 2 fi if ! type ansible > /dev/null 2>&1 ; then echo "ansible not found. Is it installed?" >&2 exit 2 fi if ! [ -d "${src_ansible}" ] ; then echo "ansible code base '${src_ansible}' not found." >&2 echo "Set var 'src_ansible' to location of ansible site git checkout." >&2 exit 2 fi cd "${src_ansible}" if ! [ -f "inventories/manual_inventory_${env}.ini" ] ; then echo "File inventories/manual_inventory_${env}.ini not found." >&2 echo "Set environment to either dev, int, pp, lt or prod. " >&2 exit 2 fi ######## # main # ######## _tmux_session_name="tmux-${env}-${gname}" # trim tmux session name, cut _hosts tmux_session_name=`echo ${_tmux_session_name}|awk -F'_hosts' '{print $1}'` _hosts=`ansible -i inventories/manual_inventory_${env}.ini --list-hosts ${gname} -l "${limit}" | sed -e '1,1d'` # trim whitespace. hosts="$(echo ${_hosts}|xargs)" if [ -z "${hosts}" ]; then exit 1 fi # Find a name for a new session n=0; while tmux has-session -t "${tmux_session_name}-${n}" 2>/dev/null; do n=$(($n + 1)); done tmux_session="${tmux_session_name}-${n}" # Open a new session and split into new panes for each SSH session for host in ${hosts}; do if ! tmux has-session -t "${tmux_session}" 2>/dev/null; then tmux new-session -s "${tmux_session}" -d "ssh ${ssh_options} ${host}" else tmux split-window -t "${tmux_session}" -d "ssh ${ssh_options} ${host}" # We have to reset the layout after each new pane otherwise the panes # quickly become too small to spawn any more tmux select-layout -t "${tmux_session}" tiled fi done # Synchronize panes by default tmux set-window-option -t "${tmux_session}" synchronize-panes on if [ -n "${TMUX}" ]; then # We are in a tmux, just switch to the new session tmux switch-client -t "${tmux_session}" else # We are NOT in a tmux, attach to the new session tmux attach-session -t "${tmux_session}" fi exit 0
The inventory would look something along these lines, with
environments separated per ini
file.
$ tree inventories inventories ├── manual_inventory_dev.ini ├── manual_inventory_int.ini ├── manual_inventory_lt.ini ├── manual_inventory_pp.ini └── manual_inventory_prod.ini