構築したサーバ郡の設定を自動で収集してくるシェルスクリプトを作りました

#作成経緯

最近はInfra as codeの流れで、[ansible](http://www.ansible.com/)等で設定し、[serverspec](http://serverspec.org/)等でチェックするというパターンが多くなっていると思います。

ですが、まだまだ全自動化しきれない環境で、チェックリストを見ながら手動で設定して、その後第三者がダブルチェックするといったケースもあると思います。
筆者が構築の仕事をさせて貰っているクラウドもそうでした。

同時に３０台も４０台もVMを構築していると、人間がやることなので漏れや間違いがどうしても出てきてしまいます。

そこで最初に思いついたのが、各種設定内容をシェルで一つのテキストにまとめて出力して、後で全部のサーバのそのテキストを[rekisa](http://frozenlib.net/rekisa/)などの大量ファイル同時diff確認ツール等で横並びで比較してみて、漏れを探す手法でした。

これが結構効果的で、漏れているサーバは差分がくっきり出るのでパッ見ですぐ気づきました。

ただ、数が多いと出力したテキストを一箇所に集めるのが面倒くさいという問題がありました。
そこでテキストへの設定出力と一箇所に収集する処理を一発で実行できるシェルを作れないかな？と思い立ったのがきっかけです。

# 環境と条件

OS：CentOSで使用していますが、expectがインストールできてbashが使えるLinuxなら、だいたい動くと思います。

認証：鍵認証でもパスワード認証でもOKですが、踏み台を乗り越えてログインする機能は作っていません。

権限：サーバの管理者権限をパスワード入力なしで取得できるような設定がsudoersに入っている必要があります。

# チェック対象サーバリストのフォーマット
```text:hosts.txt
IPアドレス1　ホスト名1
IPアドレス2　ホスト名2
```
上記をズラ～っと並べます。
IPは接続するときに使用しますが、ホスト名は設定内容を保存するファイル名に使用します。

# 確認したい設定内容の指定フォーマット

以下の様な感じで、標準出力に内容が表示されるコマンドを羅列するだけです。

```bash:chk_setting_hoge.sh
echo ***user***
cat /etc/passwd

echo ***openssl***
rpm -qa | grep openssl

echo ***OS***
cat /etc/redhat-release

echo ***bash***
rpm -qa | grep bash

echo ***crond_status***
/sbin/service crond status

echo ***crond_chkconfig***
/sbin/chkconfig --list crond

echo ***cpu***
cat /proc/cpuinfo | grep processor | wc -l

echo ***memory***
cat /proc/meminfo | grep MemTotal

echo ***network***
cat /etc/sysconfig/network

echo ***hosts***
cat /etc/hosts

echo ***ifcfg-eth0***
cat /etc/sysconfig/network-scripts/ifcfg-eth0

echo ***ifcfg-eth1***
cat /etc/sysconfig/network-scripts/ifcfg-eth1

echo ***UDEV***
cat /etc/udev/rules.d/70-persistent-net.rules

echo ***resolv.conf***
cat /etc/resolv.conf

echo ***hosts.allow***
cat /etc/hosts.allow

echo ***ntp***
cat /etc/ntp.conf

echo ***hostname***
hostname

echo ***ifconfig***
/sbin/ifconfig

echo ***sshd_config***
cat /etc/ssh/sshd_config
```

これらは、構築の内容によって変更したりしなかったりします。

# 実行するシェル

```bash:ExecGatherer.sh
cat ./hosts.txt | xargs -n 2 /home/hogehoge/LogGatherer/gatherer.sh
```
hosts.txtが置かれている場所に一緒に置きます。
gatherer.shを置く場所を変えたら、この記載も変更する感じです。

# 設定内容を収集してくる処理を書いたシェル

```bash:gatherer.sh
#!/bin/sh

if [ $# -ne 2 ]; then
    echo "Arg num: $#"
    echo "Arg1: IP Address or server name"
    echo "Arg2: Logfile name"
    exit 1
fi

echo SERVER : ${1}
echo LOGNAME: ${2}

SERVER=${1}
LOGNAME=${2}.log

USER="hogehoge"
PASSWD="ahoaho"
KEY="/home/hogehoge/.ssh/id_rsa"
PORT="22"

echo "Login to ${SERVER}"

expect -c "
  set timeout -1
  spawn scp -P ${PORT} /home/${USER}/LogGatherer/chk_setting_hoge.sh ${USER}@${SERVER}:~/

  while {1} {
    expect {
      \"password:\" {send \"${PASSWD}\n\"}
      \"Are you sure you want to continue connecting\" {send \"yes\n\"}
      \"passphrase\" {send \"${PASSWD}\n\"}
      eof {
        catch wait result
        exit [lindex ${result} 3]
      }
    }
  }
interact
"

expect -c "
  set timeout -1
  spawn ssh -p ${PORT} -l ${USER} ${SERVER}

  while {1} {
    expect {
      \"password:\" {send \"${PASSWD}\n\"}
      \"Are you sure you want to continue connecting\" {send \"yes\n\"}
      \"passphrase\" {send \"${PASSWD}\n\"}
      \"$ \" {send \"sudo sh /home/${USER}/chk_setting_hoge.sh > ${LOGNAME}\n\"; send \"exit\n\"}
      eof {
        catch wait result
        exit [lindex ${result} 3]
      }
    }
  }

interact
"

expect -c "
  set timeout -1
  spawn scp -P ${PORT} ${USER}@${SERVER}:~/${LOGNAME} ./

  while {1} {
    expect {
      \"password:\" {send \"${PASSWD}\n\"}
      \"Are you sure you want to continue connecting\" {send \"yes\n\"}
      \"passphrase\" {send \"${PASSWD}\n\"}
      eof {
        catch wait result
        exit [lindex ${result} 3]
      }
    }
  }

interact
"
```

ユーザ名とパスワードと鍵のフルパスとsshポートは最初の方の以下の変数で定義しているので、そこを変えるだけです。
```
USER="hogehoge"
PASSWD="ahoaho"
KEY="/home/hogehoge/.ssh/id_rsa"
PORT="22"
```

# シェルスクリプトの処理の概要

まず設定を出力するシェルスクリプトを目的のサーバにscpで送ります。
初めて繋ぐサーバでもフィンガープリントのyes／noで止まらないようにしています。

次に管理者権限でさっき送ったシェルを実行してテキストに吐き出しています。

最後に吐出されたテキストをscpで自分のところに引っ張ってきます。

これら３つの処理を一回のexpectの中でやろうともしたのですが、なかなかうまくいかなかったので、長々とハマってる訳にもいかず、3回のexpectに分けてさっさと動くようにした感じです。

# おわりに

最近流行りの自動化ツールの機能限定版をシェルスクリプトだけで作ったような感じですが、結構これでも役に立っていて、設定漏れの発見の高速化に貢献しています。

流行りのツール類は、rubyの環境構築でまずドハマりしたりとか、初期投資が結構かかるのでとっつきにくかったのですが、シェルだけでこれができれば、ひとまず要件は満たしているので、現在も愛用中です。
もし皆さんのお役に立てれば幸いです。