tmux + ssh + Mackerel API を組み合わせたとにかくモダンなサーバオペレーション

冗長化させたホストやスケールアウトさせたホストなどの同じサーバ構成をもつホストグループや、あるサービスに所属するホスト全てに同時にsshして同時に操作したいことがある。 複数のホストに同時ログインするツールとして cssh があるけど、毎回複数のホスト名をチマチマ入力したり、すぐに古くなるホスト一覧ファイルを手元に持ちたくない。Immutable Infrastructure 時代にはそぐわない。Immutable Infrastructure 時代にはホスト名なんて毎日変化するし誰も覚えてない。サーバ管理ツール上のグループ名を使ってグループ配下のホストに同時にsshしたい。 あと、cssh は個人的に挙動がなんか微妙なので、代わりに tmux と ssh を組み合わせている。 cssh はマスタとかスレーブとか気持ちはわかるけど、複数ウィンドウ操作は使い慣れたターミナルマルチプレクサを使いたい。

Clusterssh demo - YouTube

もう1台1台丁寧に ssh したり、毎回複数のホスト名をチマチマ指定して cssh する時代は終わった。 (タイトルに Mackerel と書いてるけど、半分以上は Mackerel に依存しない話です。)

デモ

http://i.imgur.com/Bvj9pes.gif

tssh

tmux を使って、複数ホストに同時 ssh ログインして同時にオペレーションできる。

GitHub - dennishafemann/tmux-cssh: TMUX with a "ClusterSSH"-like behaviour. を使ってもよいし、下記のようなスクリプトを使ってもよい。 挙動はほぼ同じで、tmux を使うと30行足らずで実現できるがすごい。

https://github.com/y-uuki/opstools/blob/master/tssh

#!/bin/bash

hosts=("$@")
session_name="tmux-ssh-$$"

tmux start-server

is_first="true"
for host in ${hosts[@]}; do
    cmd="ssh $SSH_OPTION $USER@$host"
    if [ "${is_first}" == "true" ]; then
        tmux new-session -d -s $session_name "$cmd"

        is_first="false"
    else
        tmux split-window  -t $session_name "$cmd"
        tmux select-layout -t $session_name tiled 1>/dev/null
    fi
done

tmux set-window-option -t $session_name synchronize-panes on
tmux select-pane -t 0
tmux attach-session -t $session_name

Usage

$ tssh blogapp001.domain blogapp002.domain blogapp003.domain ...
$ tssh blogapp00{1,2,3}.domain # ditto

このあと、だいたい top 叩くか ログファイルを tail したりする。

おまけとして、特定のホストのみを操作したいときは tmux の synchronize-panes と ペイン移動を使う。 ~/.tmux.conf に下記の設定を書いておいて、 + g でペインの同期をオフにして、操作したいホストに割り当てられたペインに移動して操作するとよさそう。

bind-key g setw synchronize-panes

tssh だけでも十分便利だが、Mackerel の RESTful API を使うことで、ホスト名または IP アドレスに依存しないオペレーションができる。、

Mackerel

Mackerel: A Revolutionary New Kind of Application Performance Management

サーバ管理ツール Mackerel では、ホストをロールで管理する。 具体的には、サービス名とロール名の組で、Saba-Blog::proxy や Saba-Blog::app、Saba-Blog::db みたいな感じ。

Mackerel はモニタリングだけでなく、RESTful API を使ったサーバ管理の自動化も促進しようとしている。

Mackerel API ドキュメント(v0) - Mackerel API ドキュメント (v0)

今回は、ロールに所属するホスト一覧が取りたいので下記のような curl と jq を組み合わせたワンライナーを使う。 これくらいの用途なら API クライアントを使うまでもない。

$ curl -s -H "X-Api-Key:<apikey>" -X GET 'https://mackerel.io/api/v0/hosts.js?service=Saba-Blog&role=app' | jq -a -M -r ".hosts[].name
blogapp001.domain
blogapp002.domain
blogapp003.domain

毎回ワンライナーを叩くのもだるいのでスクリプトにする。~/bin/role に以下のようなスクリプトを置いてる。

#!/bin/bash

if [[ $1 == '' ]]; then
  echo "requried service name"
  exit 1
fi
if [[ $2 == '' ]]; then
  echo "requried role name"
  exit 1
fi

curl -s -H "X-Api-Key:<apikey>" -X GET "https://mackerel.io/api/v0/hosts.json?service=$1&role=$2" | jq -a -M -r ".hosts[].name

上記 role コマンドにサービス名とロール名を指定してやれば簡単にホスト一覧を取得できる。

$ role Saba-Blog app
blogapp001.domain
blogapp002.domain
blogapp003.domain

role コマンドの他に service コマンドも置いてる。さっきとほとんど同じ。

#!/bin/bash

if [[ $1 == '' ]]; then
  echo "requried service name"
  exit 1
fi
curl -s -H "X-Api-Key:<apikey>" -X GET 'https://mackerel.io/api/v0/hosts.js?service=$1' | jq -a -M -r ".hosts[].name
service Saba-Blog
blogproxy001.domain
blogproxy002.domain
blogproxy003.domain
blogapp001.domain
blogapp002.domain
blogapp003.domain
...

tssh + Mackerel

$ tssh `role Saba-Blog app`

これでなんでもできる。 Zabbix や Datadog など他のサーバ管理ツール/サービスを使っても、もちろん同じことはできるだろうけど、Mackerel はホストのグルーピングの指針が定まっているので、グルーピング方針に迷うことが少ないと思う。

社内でこの手の連携は昔からやってて、とにかく便利で毎日使ってる。