pssh メモ (sudo + pssh)

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

# psshとは

psshは複数のサーバーに対してsshコマンドを同時に送ります。
複数台のサーバーがある場合、一つずつログインしていくのはとても大変なので、psshコマンドを使ってまとめて処理します。
他にもpscp, psftp, prsync など用途に応じてコマンドがあります。

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

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

```bash
# 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
```


# ヘルプの確認

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

```bash
(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サーバーのリストを書いておきます。

```lang:hosts.txt
admin@server1.mydomain.com
admin@server2.mydomain.com
admin@server3.mydomain.com
...
```

~/.ssh/config でサーバーに名前をつけておけば、いろいろsshオプションを指定できます。
.ssh/configファイルの書き方は[ここを参照](http://qiita.com/hayajo/items/7ec273f90f8750489304#%E8%B8%8F%E3%81%BF%E5%8F%B0)してみてください。

```lang:~/.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

```

```lang:hosts.txt
surprise_az-main
surprise_az-sub
westminster_ca-main
westminster_ca-sub
...
```

# psshの基本的な使い方

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

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

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

```bash
$> 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を使うとこうなります。シンプルですね。

```bash
$> 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を指定すれば、タイムアウトしません。

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

## tail -f をしたい

ログをファイルに出力している時、`tail -f <log file>`でリアルタイムに確認したいことが多々あります。
`-P`オプションを使えば可能です。

```bash
$> 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コマンドを一緒に実行しておけば、ファイルの先頭にホスト名が入るので、あとで便利です。

```bash
$> 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でパスワード無しとかは「したくない」**です。
いくつか方法がありますが、私が最終的にたどり着いたのはこれ。

```bash
(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の日本語訳はこちら](http://euske.github.io/openssh-jman/ssh.html)

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


つまり入力画面がなくても擬似的に割り当ててくれるわけです。
sudoコマンドは、ttyがないと実行できません。なので**-tt**でごまかしているわけです。

```bash
$ (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コマンドでもできます。

```bash
(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を使わずにファイルをコピーする](http://labs.timedia.co.jp/2011/04/rawssh-filecopy.html)の解説が参考になります。

```bash
$> ssh hostA 'cat > destfile' < srcfile
```

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

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

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

```bash
$> 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"とかいてあります。

```bash
$> 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
```

成功！