Edited at

pssh メモ (sudo + pssh)

主に自分用の備忘録です。


psshとは

psshは複数のサーバーに対してsshコマンドを同時に送ります。

複数台のサーバーがある場合、一つずつログインしていくのはとても大変なので、psshコマンドを使ってまとめて処理します。

他にもpscp, psftp, prsync など用途に応じてコマンドがあります。

私がMacを使っているので、Macを前提に以下、説明します。


psshのインストール (Mac: brew)

# brewのインストール

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null

# psshのインストール
$ brew install pssh


インストール時に起こる可能性があるエラー

もし下記のようなエラーが出たら

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

Error: Failure while executing; `git config --local --replace-all homebrew.private true` exited with 1.

Xcodeのコマンドラインツールをインストールする必要があります。

$ xcode-select --install

もしPermission deniedエラーになったら

Error: An unexpected error occurred during the `brew link` step

The formula built, but is not symlinked into /usr/local
Permission denied @ dir_s_mkdir - /usr/local/Frameworks
Error: Permission denied @ dir_s_mkdir - /usr/local/Frameworks

$ brew doctorを実行してみましょう。

$ brew doctor

Please note that these warnings are just used to help the Homebrew maintainers
with debugging if you file an issue. If everything you use Homebrew for is
working fine: please don't worry or file an issue; just ignore this. Thanks!

Warning: The following directories do not exist:
/usr/local/Frameworks
/usr/local/sbin

You should create these directories and change their ownership to your account.
sudo mkdir -p /usr/local/Frameworks /usr/local/sbin
sudo chown -R $(whoami) /usr/local/Frameworks /usr/local/sbin

Warning: You have unlinked kegs in your Cellar
Leaving kegs unlinked can lead to build-trouble and cause brews that depend on
those kegs to fail to run properly once built. Run `brew link` on these:
python@2

下記ディレクトリがないぞ、って怒られてます。

[Warning]: The following directories do not exist:

/usr/local/Frameworks
/usr/local/sbin

ご丁寧に解決方法も教えてくれます。どうせなら実行してくれればいいのに。

You should create these directories and change their ownership to your account.

sudo mkdir -p /usr/local/Frameworks /usr/local/sbin
sudo chown -R $(whoami) /usr/local/Frameworks /usr/local/sbin


ヘルプの確認

*ヘルプの説明は日本語訳してあります。

(pssh version: 2.3.1)

Usage: pssh [OPTIONS] command [...]

Options:
--version バージョンナンバーの表示
--help このヘルプを表示
-h HOST_FILE, --hosts=HOST_FILE
ホストファイルの指定(1行ずつ "[user@]host[:port]")
-H HOST_STRING, --host=HOST_STRING
追加でホスト名を指定 ("[user@]host[:port]")
-l USER, --user=USER ユーザー名 (オプション)
-p PAR, --par=PAR 並列接続する最大数 (オプション)
-o OUTDIR, --outdir=OUTDIR
各ホストからの標準出力を指定のディレクトリ以下にファイルで出力 (オプション)
-e ERRDIR, --errdir=ERRDIR
各ホストからの標準エラー出力を指定のディレクトリ以下にファイルで出力 (オプション)
-t TIMEOUT, --timeout=TIMEOUT
各ホストでの実行タイムアウト時間 () (0 = タイムアウトしない) (オプション)
-O OPTION, --option=OPTION
SSH オプションの指定 (オプション)
-v, --verbose psshの警告や診断メッセージの表示 (オプション)
-A, --askpass パスワードを尋ねる (オプション)
-x ARGS, --extra-args=ARGS
コマンドライン引数の展開。空白、クォート、バックスラッシュで複数指定可。
-X ARG, --extra-arg=ARG
コマンドライン引数の展開
-i, --inline 各サーバーごとに標準出力とエラーを集めて表示する
--inline-stdout 各サーバーからの標準出力をインラインで表示する
-I, --send-input 標準入力を読み取って、それをsshの標準入力に送る
-P, --print プログラムが受け取ったデータをそのまま表示

Example: pssh -h hosts.txt -l irb2 -o /tmp/foo uptime


ホストファイルの作り方

複数のサーバーをまとめて指定するのに、-hオプションでファイルを指定できます。

ファイルにはsshサーバーのリストを書いておきます。


hosts.txt

admin@server1.mydomain.com

admin@server2.mydomain.com
admin@server3.mydomain.com
...

~/.ssh/config でサーバーに名前をつけておけば、いろいろsshオプションを指定できます。

.ssh/configファイルの書き方はここを参照してみてください。


~/.ssh/config

Host gateway

HostName hogehoge.mydomain.com
IdentityFile ~/.ssh/id_rsa
User admin
Port 22
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null

Host *-*
User admin
HostName localhost
TCPKeepAlive yes
StrictHostKeyChecking no
UserKnownHostsFile=/dev/null

Host surprise_az-main
ProxyCommand ssh -W %h:3101 gateway

Host surprise_az-sub
ProxyCommand ssh -W %h:3102 gateway

Host westminster_ca-main
ProxyCommand ssh -W %h:3103 gateway

Host westminster_ca-sub
ProxyCommand ssh -W %h:3104 gateway



hosts.txt

surprise_az-main

surprise_az-sub
westminster_ca-main
westminster_ca-sub
...


psshの基本的な使い方

psshは次のように使います。

$> pssh -h hosts.txt -l <login username> -i "<shell command>"

例えば各マシンごとの起動時間を知りたければ、このようになります。

$> pssh -h hosts.txt -l admin -i "uptime"

[1] 12:10:40 [SUCCESS] p19santikos-main
14:10:40 up 10 min, 0 users, load average: 3.62, 3.38, 1.98
Stderr: Warning: Permanently added 'server1.mydomain.com' (RSA) to the list of known hosts.
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
Killed by signal 1.

[2] 12:10:40 [SUCCESS] brenden_vacaville-main
12:10:40 up 10 min, 0 users, load average: 4.34, 3.78, 2.15
Stderr: Warning: Permanently added 'server1.mydomain.com' (RSA) to the list of known hosts.
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
Killed by signal 1.
...

-iオプションの代わりに --inline-stdoutを使うとこうなります。シンプルですね。

$> pssh -h hosts.txt -l admin --inline-stdout "uptime"

[1] 12:12:37 [SUCCESS] brenden_vacaville-main
12:12:37 up 12 min, 0 users, load average: 3.86, 3.79, 2.35
[2] 12:12:37 [SUCCESS] p19santikos-main
14:12:37 up 12 min, 0 users, load average: 3.56, 3.45, 2.17
[3] 12:12:37 [SUCCESS] westminster_ca-main
12:12:37 up 12 min, 0 users, load average: 3.42, 3.43, 2.22
[4] 12:12:38 [SUCCESS] muvico_ca-main
12:12:38 up 12 min, 0 users, load average: 1.12, 1.01, 0.58
[5] 12:12:38 [SUCCESS] vannuys_ca-main
12:12:38 up 12 min, 0 users, load average: 1.39, 1.14, 0.70
[6] 12:12:38 [SUCCESS] brenden_nv-main
12:12:38 up 12 min, 0 users, load average: 3.55, 3.31, 2.02
[7] 12:12:39 [SUCCESS] p22santikos-main
14:12:38 up 12 min, 0 users, load average: 3.58, 3.43, 2.20


タイムアウトを設定する

ときどきコマンドの処理時間が長いことがあります。-tでタイムアウト時間を秒数で設定できます。

0を指定すれば、タイムアウトしません。

$> pssh -h hosts.txt -l admin --inline-stdout -t 0 "cd ~/code/; git pull"


tail -f をしたい

ログをファイルに出力している時、tail -f <log file>でリアルタイムに確認したいことが多々あります。

-Pオプションを使えば可能です。

$> pssh -h hosts.txt -l admin --inline-stdout -t 0 -P "tail -f ~/logs/monitoring.log"

p19santikos-main: "socketIoStatus": "connected",
"socketIoFailCnt": 0,
"currentFragment": "Welcome"
}
{
"ip": "192.168.*.*",
"isAlive": true,
"pingFailCount": 0,
"timestamp": "2016-02-17 14:34:00 CST"
}
brenden_vacaville-main: "socketIoStatus": "connected",
"socketIoFailCnt": 0,
"currentFragment": "Welcome"
}
{
"ip": "192.168.*.*",
"isAlive": true,
"pingFailCount": 0,
"timestamp": "2016-02-17 12:34:00 PST"
}
brenden_nv-main: "socketIoStatus": "connected",
"socketIoFailCnt": 0,
"currentFragment": "Welcome"
}
{
"ip": "192.168.*.*",
"isAlive": true,
"pingFailCount": 0,
"timestamp": "2016-02-17 12:34:00 PST"
}
...


ファイルに保存したい

-oオプションでディレクトリをすれば、標準出力をファイルにホストごとに保存してくれます。

同様に、-eオプションで標準エラーです。

ディレクトリがなければ作成してくれます。ファイルが在ると上書きされます。

そのとき、hostnameコマンドを一緒に実行しておけば、ファイルの先頭にホスト名が入るので、あとで便利です。

$> pssh -h hosts.txt -l admin -o ./logs/ "hostname;uptime"

[1] 12:43:25 [SUCCESS] brenden_nv-sub
[2] 12:43:25 [SUCCESS] brenden_vacaville-sub
[3] 12:43:25 [SUCCESS] vannuys_ca-sub
[4] 12:43:26 [SUCCESS] muvico_ca-sub
[5] 12:43:26 [SUCCESS] westminster_ca-sub
[6] 12:43:26 [SUCCESS] p22santikos-sub
[7] 12:43:26 [SUCCESS] p19santikos-sub

$> cat logs/*
brenden_nv-sub
12:43:25 up 12 min, 0 users, load average: 3.46, 3.05, 1.84
brenden_vacaville-sub
12:43:25 up 12 min, 0 users, load average: 3.36, 3.38, 2.29
muvico_ca-sub
12:43:25 up 13 min, 0 users, load average: 3.55, 3.18, 1.97
p19santikos-sub
14:43:26 up 13 min, 0 users, load average: 3.39, 3.17, 2.00
p22santikos-sub
14:43:26 up 13 min, 0 users, load average: 3.43, 3.28, 2.02
vannuys_ca-sub
12:43:25 up 13 min, 0 users, load average: 3.25, 3.23, 2.15
westminster_sub
12:43:25 up 13 min, 0 users, load average: 3.19, 3.21, 2.19


sudoコマンドを実行したい

ときどきsudoコマンドを実行したくなります。でもsudoでパスワード無しとかは「したくない」です。

いくつか方法がありますが、私が最終的にたどり着いたのはこれ。

(echo "<password>")|pssh -h hosts.txt -l (login username) -o ./logs/  -x '-tt' -I "sudo whoami"

-Iオプションはpsshに対する標準入力をsshの標準入力に渡すオプションです。

つまりecho "password"の結果を標準入力に渡します。

-xオプションはサーバーにログインした後のsshに対して、SSHコマンドの引数を展開します。

そこでは-ttを指定しているわけですが、man sshを読むと次のように書いてあります。

*man sshの日本語訳はこちら


-t

強制的に仮想端末を割り当てます。これはリモートマシン上で任意の画面ベースのプログラムを実行するとき(たとえば、メニューサービスを実装するときなど)に非常に便利です。複数の-t をつけると、たとえssh がローカル側での端末を持っていない場合でも強制的に仮想端末を割り当てます。


つまり入力画面がなくても擬似的に割り当ててくれるわけです。

sudoコマンドは、ttyがないと実行できません。なので-ttでごまかしているわけです。

$ (echo "sudo_password")|pssh -h hosts_linux1.txt -l admin  -x '-tt' --inline-stdout -I "sudo whoami"

[1] 10:32:55 [SUCCESS] linux1
[sudo] password for admin:
root
[2] 10:32:55 [SUCCESS] linux2
[sudo] password for admin:
root
[3] 10:32:55 [SUCCESS] linux3
[sudo] password for admin:
root
[4] 10:32:55 [SUCCESS] linux4
[sudo] password for admin:
root

sudoコマンドの代わりに、suコマンドでもできます。

(echo "<password>")|pssh -h hosts.txt -l admin -o ./logs/  -x '-tt' -I "su -c 'echo \"vm.overcommit_memory = 1\"  >> /etc/sysctl.conf'"


(おまけ)ファイルを転送したい

psftpとかpscpとかいろいろあるけど、Macで使う場合は、psftpはないです(少なくとも私が探した範囲では。)

困ったことに、転送先のsshサーバーがscpに対応していないことがあります。

(AndroidでSSHサーバーを動かしていると、対応していないアプリがある。。。orz)

単純なファイル転送はこんな感じです。

SSHでscpを使わずにファイルをコピーするの解説が参考になります。

$> ssh hostA 'cat > destfile' < srcfile

でもこの方法だと、srcfileのファイルサイズ * N (Nは台数)となり辛いので、tarで圧縮して転送して、tarで解凍して保存します。

$> tar -cf - test.txt | ssh westminster_ca-stick "cd /sdcard/; tar -xf -"

これをpsshでまとめて転送するには、次のようにやります。

$> tar -cf - test.txt | pssh -h hosts.txt -l admin -t 0 -I "tar -xf -"

[1] 13:50:29 [SUCCESS] brenden_nv-sub
[2] 13:50:29 [SUCCESS] p19santikos-sub
[3] 13:50:29 [SUCCESS] vannuys_ca-sub
[4] 13:50:29 [SUCCESS] westminster_ca-sub
[5] 13:50:29 [SUCCESS] brenden_vacaville-sub
[6] 13:50:29 [SUCCESS] p22santikos-sub
[7] 13:50:30 [SUCCESS] muvico_ca-sub

確認のため、見てみましょう。test.txtには"hoge"とかいてあります。

$> pssh -h hosts.txt -l admin --inline-stdout "cat test.txt"

[1] 13:51:06 [SUCCESS] p19santikos-sub
hoge
[2] 13:51:06 [SUCCESS] vannuys_ca-sub
hoge
[3] 13:51:06 [SUCCESS] muvico_ca-sub
hoge
[4] 13:51:06 [SUCCESS] brenden_vacaville-sub
hoge
[5] 13:51:06 [SUCCESS] westminster_ca-sub
hoge
[6] 13:51:06 [SUCCESS] brenden_nv-sub
hoge
[7] 13:51:07 [SUCCESS] p22santikos-sub
hoge

成功!