バッチ処理を行うスクリプトを作成するときには、rsyncなどを利用して 異なるサーバ間で通信をして処理するものも多かと思います。 (むしろそれが出来るからこそBash/Shell Scriptを利用するかと思いますが・・・)

意外とBashスクリプトのTipsは無かったりして、それぞれ我流でやられており、 普段LLアプリケーションのみ作っている人が書いしたものを見ると、 たまにOopsと叫びたくなってしまうこともあります笑。

個人的なポイントは以下3点。

  1. データを配置するな、取得せよ
  2. 秘密鍵もちゃんと用意せよ
  3. 接続先変更に柔軟性を持たせる

1. データを配置するな、取得せよ

リモートサーバ上で処理されたファイルをWebサーバへ配置し、ユーザへ公開したい。

$ REMOTE_SERVER="192.168.33.12"
$ sync_files=$@
$ rsync -av $sync_files $REMOTE_SERVER:/var/www/html/myapp/htdocs/assets/upfiles

まずパッとみて、リモートファイルのPATH変更に弱すぎますし、データを受け取るタイミングを選べません。 フロント開発者Webアプリ側のフォルダ変更をしたくなった場合、いちいちこのバッチまで考えていなければならない なんて面倒くさすぎます。そしてデータを受け取るタイミングをWebサーバごとに変えたいとしても 作業コストが大きくなってしまいます。

$ ln -s /var/www/html/myapp/htdocs/pdf /var/www/html/myapp/htdocs/assets/upfiles

こんな風に勝手にシンボリックリンクを貼って、とりあえず動けばいいやで無理やり解決する人が出てくる可能性もあります。笑。

重要なのがスコープ範囲を狭めることです。意外とアプリ開発では意識出来たりしますが、ShellScriptになった途端に スッポ抜けてしまいがちです。今回の例でいえば、Rsyncのデータ配置バッチはWebアプリケーション側に設置すべきです。

$ REMOTE_PATH="192.168.33.13:/path/to/sync/dir"
$ rsync -av $REMOTE_PATH ./

だいぶスッキリしました。Webアプリ側のパス変更の作業がWebアプリ側のサーバを内部だけで完結出来ます。 自分が欲しいものは自分が欲しいタイミングで取りに行くという状態になり、

<?php
if ($update_user_data) {
  exec('./fetch_data.sh');
}

アプリケーション側でも自由にデータを取ってくる処理を好きなタイミングでKickすることが出来ます。

もちろんREMOTE側のPATHが変わった時には対応しなければならないことに変わりはありませんが、 フロント開発者が自分の範囲だけは自由に変更出来るのは非常に強みです。

2. 秘密鍵もちゃんと用意せよ

gitからバッチファイルを取得して、いざ実行!そしてsshで弾かれる or パスワードを求められるシーンが多いかなと思います。 そこから手動でssh-copy-idなど実行して都度設置者にauthorized_keys設定させるのは少々不親切です。

vagrantなどでローカル仮想環境を作って作業していたとして、そんなワンタイムのssh公開鍵のauthorized_keys設定を勝手に追加する 人が出てくる場合、目もあてられないかと・・・思います。

$ ssh-keygen -f id_rsa_mybatch

なので、バッチ専用の鍵をssh-keygenコマンドで用意してやります。-fオプションで新しいファイル名のものを作成可能です。

あとは任意のサーバ側にこの作成したsshキーの秘密鍵に対する公開鍵の許可設定をすればOKです。 バッチ側で接続する場合には以下のようにsshのidentityファイル指定をして、通信します。

$ rsync -e "ssh -i /root/.ssh/id_rsa_mybatch" -av $REMOTE_PATH ./

この鍵の公開鍵さえ登録しておけば、どれだけサーバが増えたとしても公開鍵の許可設定追加の必要は一切ありません。 プロジェクトさえ持ってこれれば、いきなり通信できることが保証されます。 バッチスクリプトのサーバ移動も容易になりますし、非常にメリット多いです。

セキュリティに関してはある程度トレードオフにはなります。とはいえ、authorized_keysに登録するユーザを特定の権限の弱いユーザにすれば 良かったりと工夫する余地はあります。

3. 接続先変更に柔軟性を持たせる

上のサンプルでも記載しておりますが、バッチ側にIPをベタで書くケースも多いと思います。

REMOTE_SERVER="192.168.33.12"

もちろん利用頻度や設備の大きさによっては、上記で問題ないケースは多いです。(むしろ殆ど?) とはいえ、サーバの移動が頻繁に行われるケースなどは考える余地はあります。

おすすめのTipsとしては

  1. 引数としてIP/RemoteHostを指定出来るようにする
  2. 名前解決出来るhosts名にする

1つ目のものはシンプルです。

$ ./batch_script.sh 192.168.33.12

このようにして引数としてIP(host名)を与えます。rsyncで相互にデータをコピーさせたいときなどは有効です。 もちろんこれにより、大切な引数が1つ失われてしまうため、引数無しで実行できるようなShell Scriptに対して組み込む程度が 実際の活用方法として良いかな?とは感じます。

そして、2番目のですが、IPを直接書くのではなくhost名で記載に変更します。

#REMOTE_SERVER="192.168.33.12"
REMOTE_SERVER="srv01.mistdev.com"

こうすることにより、バッチスクリプト自体を書き換えなくても外部から接続先サーバを変更することが出来ます。 host名自体は/etc/hostsに書いてもよいですし、内部DNSへ書いてもよいかと思います。 スクリプトを書き換えなくてもよいというのは結構重要で、Ops側の人間としても安心感はあります。 だいたいソースコードに変更があったタイミングで障害が発生するので、そのリスクを少しでも減らせます。

この書き方での注意点が1つだけあります。sshの場合は接続先ホストのfingerprintをみており、このままでは単純に/etc/hosts側等でサーバのIP変更すると

$ ssh srv01.mistdev.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is

のようにエラーが出てしまいます。 (man-in-the-middle attackとアラートを親切にだしており、もちろんこれは重要な挙動ではあります。) こうなってしまうと、ssh-keygen -Rで律儀にサーバのknown_hostsを削除しなければなりません。

これを防ぐ方法が1点あり、StrictHostKeyChecking及びUserKnownHostsFileをオフにします。

$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null srv01.mistdev.com

常にKnowHostsを無視し、新しいssh接続をする状態となります。 UserKnownHostsFileだけの場合だと「The authenticity of host ‘[192.168.33.12]:22 ([192.168.33.12]:22)’ can’t be established.」と通常の確認が出てくるため それをStrictHostKeyCheckingで無視させます。

この設定を追加することで、自由にリモートサーバを変更出来るようになります。 memo: Siguniang’s Blog: OpenSSHの警告メッセージを黙らせる

最後に補足!

上記の設計方針はもちろん規模や状況やサイズ感によって異なります。 完全にPrivateなサーバ群に対しては、ある程度セキュリティを下げて利便性を追加したり、 グローバルのみの接続であればsshの接続にてfingerprint checkの無視などは出来ないかと思います。 それを加味した上で、リスクと利便性のバランスを理解して組み立てることが重要かと思います。