LinuxPowershell

PowerShellで複数サーバーにSSH接続して結果をCSVにまとめる

スポンサーラベル
1台ずつ叩くと待ち時間が積み上がる Linux

当サイトはアフィリエイト広告を利用しています。

サーバーが数台なら手作業で十分ですが、10台20台とディスク使用率を確認して回るとなると手に負えなくなります。以前 PowerShellでSSH接続してコマンドを実行する で1台への接続を扱いましたが、実務で困るのはその次の「全台に流して一覧にする」ところです。

この記事では、PowerShell から複数台へ SSH でコマンドを流し、結果を1本の CSV にまとめる方法をまとめます。応答しない機器で処理が止まらないようにする書き方と、台数が増えたときの並列化まで、手元の Windows 11 + PowerShell 7.4 で動作を確認しています。

1台ずつ叩くと待ち時間が積み上がる

1. 追加インストールなしで始められる

Windows 10 / 11 には OpenSSH クライアントが標準で入っています。手元の環境で確認すると次のとおりでした。

# クライアントのバージョンを確認する
ssh -V
# OpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2

つまりモジュールを入れなくても ssh コマンドがそのまま使えます。前回の記事で使った Posh-SSH モジュールは、パスワード認証をスクリプトから扱いたい場合に必要になるものです。複数台へ定期的に流すなら、先に鍵認証を通しておくほうが結果的に楽です。パスワードをスクリプトに書かずに済みます。

2. まず1台に対して流す

SSH はコマンドを引数に渡すと、ログインせずにそれだけ実行して結果を返します。

# ディスク使用率をルートパーティションだけ取得する
ssh admin@web01 'df -h / | tail -1'

複数台を回すスクリプトでは、ここに2つのオプションを必ず付けます。

ssh -o BatchMode=yes -o ConnectTimeout=5 admin@web01 'df -h / | tail -1'
  • BatchMode=yes … パスワードやパスフレーズを聞かれても入力待ちにせず、その場で失敗させる
  • ConnectTimeout=5 … 応答が無いときに待つ秒数の上限

これが無いと、1台応答しないだけでスクリプト全体が止まります。夜間バッチで走らせて朝まで固まっていた、という事故はこれが原因のことが多いです。

3. 成功したかどうかを判定する

SSH は接続に失敗すると終了コード 255 を返します。手元で到達できないアドレスに接続して確認しました。

# -l でユーザー名を指定する書き方。到達できないアドレスへ2秒で打ち切る
ssh -o BatchMode=yes -o ConnectTimeout=2 -l admin 192.0.2.1 'hostname'
# ssh: connect to host 192.0.2.1 port 22: Connection timed out
$LASTEXITCODE
# 255

255 は SSH 自体が失敗したときのコードです。接続はできたがリモート側のコマンドが失敗した場合は、そのコマンドの終了コードがそのまま返ります。両者を区別したいときはこの違いが使えます。

# 存在しないパスを見にいくと、df の終了コード(1)が返る
ssh admin@web01 'df -h /notfound'
$LASTEXITCODE
# 1

4. 複数台へ広げてCSVにする

台数分ループを回し、1台につき1行のオブジェクトを作って CSV に書き出します。

# 対象ホスト。実際は Import-Csv でホスト一覧ファイルから読み込むとよい
$targets = 'web01', 'web02', 'db01'
$stamp   = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'

$rows = foreach ($h in $targets) {
    # 応答しない機器で止まらないよう、必ず2つのオプションを付ける
    $raw = ssh -o BatchMode=yes -o ConnectTimeout=5 "admin@$h" 'df -h / | tail -1' 2>&1
    $ok  = ($LASTEXITCODE -eq 0)

    [pscustomobject]@{
        ホスト   = $h
        結果     = if ($ok) { 'OK' } else { 'NG' }
        使用率   = if ($ok) { [int]($raw -replace '.*\s(\d+)%.*', '$1') } else { $null }
        メッセージ = if ($ok) { '' } else { ($raw -join ' ') }
        取得時刻 = $stamp
    }
}

# Excelで開くので BOM 付きUTF-8にする
$rows | Export-Csv -Path .\disk-usage.csv -NoTypeInformation -Encoding utf8BOM

ここで大事なのは、失敗した台も NG として1行残すことです。成功分だけを書き出すと、CSV を見たときに「その機器が正常なのか、そもそも繋がらなかったのか」が分かりません。

文字コードは utf8BOM を指定します。手元で書き出したファイルの先頭は EF BB BF になり、Excel で開いても日本語の列名が化けませんでした。BOM 無しにすると Excel が Shift_JIS と解釈して文字化けします。

なお、使用率の抜き出しに使っている -replace'$1'必ずシングルクォートで囲みます。ダブルクォートにすると PowerShell が先に変数として展開してしまい、空文字になります。この挙動は PowerShellの正規表現でハマる4つのポイント にまとめました。

5. 台数が増えたら並列にする

1台5秒でも20台なら100秒かかります。PowerShell 7 の ForEach-Object -Parallel を使うと、同時に走らせられます。

$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'

$rows = $targets | ForEach-Object -Parallel {
    # 並列ブロックの中からは、外側の変数がそのままでは見えない
    $h   = $_
    $raw = ssh -o BatchMode=yes -o ConnectTimeout=5 "admin@$h" 'df -h / | tail -1' 2>&1
    $ok  = ($LASTEXITCODE -eq 0)

    [pscustomobject]@{
        ホスト   = $h
        結果     = if ($ok) { 'OK' } else { 'NG' }
        使用率   = if ($ok) { [int]($raw -replace '.*\s(\d+)%.*', '$1') } else { $null }
        取得時刻 = $using:stamp   # 外の変数は $using: を付けて渡す
    }
} -ThrottleLimit 10

手元で待ち時間を測ったところ、0.4秒かかる処理を4件流して直列なら1.6秒のところが0.52秒で終わりました。-ThrottleLimit が同時実行数の上限です。

つまずきやすいのが変数の扱いです。並列ブロックの中は別のセッションなので、外で定義した変数はそのままでは見えません$using:stamp のように書く必要があります。付け忘れても構文エラーにはならず、値が空のまま進むので気づきにくいところです。

6. ホスト一覧はファイルで持つ

対象をスクリプトに直書きすると、増減のたびにスクリプトを触ることになります。CSV で持っておくと運用が楽になります。

# hosts.csv (name,user の2列)を読み込んで対象にする
$targets = Import-Csv .\hosts.csv

$rows = foreach ($t in $targets) {
    $raw = ssh -o BatchMode=yes -o ConnectTimeout=5 "$($t.user)@$($t.name)" 'uptime'
    [pscustomobject]@{ ホスト = $t.name; 結果 = $raw }
}

タスクスケジューラに登録して毎朝実行し、使用率が閾値を超えた行だけ抜き出せば、簡易的な監視になります。

# 80%を超えた行だけを対象にする
$rows | Where-Object { $_.使用率 -gt 80 }

7. まとめ

PowerShell から複数台へ SSH でコマンドを流し、CSV にまとめる方法をまとめました。要点は次の3つです。

  • BatchMode=yesConnectTimeout は必ず付ける。1台の無応答で全体が止まるのを防げる
  • 失敗した台も NG の行として残す。成功分だけの CSV は判断材料にならない
  • 台数が増えたら ForEach-Object -Parallel に切り替える。外の変数には $using: が必要

まずは3台くらいの小さな一覧から始めて、タスクスケジューラに載せるところまで作ってしまうと、以降の棚卸しがかなり楽になります。

PowerShellでSSH接続してコマンドを実行する【パスワード対応】

長いコマンドを複数行に分割して実行する方法(Windows)

PowerShellの学習におすすめの書籍

PowerShell を体系的に学ぶなら、PowerShell Core 以降のクロスプラットフォーム対応をふまえた解説書が分かりやすいです。Windows の自動化にとどまらず、macOS や Linux でも動く現在の PowerShell を、基礎から実務での使い方まで通して押さえられます。

PowerShell実践ガイドブック クロスプラットフォーム対応の次世代シェルを徹底解説

コメント

タイトルとURLをコピーしました