できる ssh

この記事は 2013 年 3 月 27 日に「Syn の独り言」で書いた記事を移行したものです。

ssh って便利だ。でも、「リモートログインするためのコマンド」程度の認識しかない人も多いのでは?自分は、この小さい働き者が大好きだ。なので、今回はその紹介

長いので先にアジェンダを書くと、こんな感じ

  • rsa 鍵を使って認証する
  • ssh-agent で秘密鍵を登録しておく
  • 認証情報を踏み台ごしに使用する
  • リモート上のコマンドを直接実行する
  • リモートの GUI を使う
  • config ファイルにデフォルト設定を記載しておく
  • ファイルを効率よく転送する
  • ディレクトリごと効率よく転送する
  • Port Forward で簡易 トンネリング・VPN を貼る

rsa 鍵を使って認証する

鍵認証とは、秘密鍵と公開鍵を割り符のように使う認証だ。ssh サーバーは公開鍵を持っており、それに対応する秘密鍵を持っているユーザーの認証を通す。

百聞は一見にしかずと言う事で、とりあえず rsa 鍵を作ってみて欲しい。ssh クライアント (ssh コマンドを実行する側)で
$ ssh-keygen -t rsa
と実行すると、公開鍵と秘密鍵が作成される。作成中に鍵の保存場所とパスワードが聞かれるが、ここは適当に決めて欲しい。

ただ、(ssh に限らず)鍵を使うアプリケーションのほとんどはデフォルトでこのパスを見に行くので特に理由がなければ変更しない事をおすすめする。

パスワードは認証に使うものではなく秘密鍵を暗号化するための物だ。秘密鍵を盗まれた時の時間稼ぎにしかなっていない事に注意しよう。

また、ssh 鍵を作成する際の -t rsa というオプションは rsa2 アルゴリズムを使用すると言う事だ。歴史的経緯により rsa1, dsa なども併記してあるサイトがあるが、2013 年 3月現在では rsa2 の方がデファクトスタンダードだ。悪い事は言わない、rsa2 を使っとけ。

さて、デフォルトのパスで作成すると、~/.ssh/id_rsa と ~/.ssh/id_rsa.pub というファイルが出来る。~/.ssh/id_rsa が秘密鍵で ~/.ssh/id_rsa.pub が公開鍵だ。

この ~/.ssh/id_rsa.pub を ssh サーバー (接続される方) の ~/.ssh/authorized_keys に記載すると、この鍵を用いて ssh の認証ができるようになる。

クライアントの id_rsa.pub をなんとかしてサーバーに転送したら、
$ mkdir -p ~/.ssh
$ chmod 700 ~/.ssh
$ cat id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

とすればよい。cat の部分は id_rsa.pub の中身を authorized_keys へ追記しているだけなので、エディタで authorized_keys を開いてコピペでも OK だ。

.ssh や authorized_keys のパーミッションは、Linux では本来どうでも良いはず(と、記憶している)が、Redhat 等の一部の OS ではこうしないとセキュリティーチェックにより動作しないので念のため。

または、最初からパスワード認証を用いて ssh クライアントからログイン出来る場合は、ssh クライアントで
$ ssh-copy-id [user@]hostname
を実行しても良い。

これ以降、ログインする際は必ず鍵認証を使うのであれば、認証用のパスワードは不要になる。サーバーで
$ sudo passwd -d user
とするのも良いだろう。こうするとユーザーのパスワードは空になり、秘密鍵が無いとログイン出来なくなる。通常の ssh サーバーは空パスワードのログインを禁止しているためだ。
(/etc/ssh/sshd_config に “PermitEmptyPasswords yes” と書かれていなければ大丈夫)

「パスワードが存在しない」というのは、パスワードクラックに対する最強の防御策だ。ただし、コンソールからは空パスワードでログインできるので注意しよう。

複数のユーザーやクライアントからサーバーにログインしたい場合は、それぞれのクライアントで鍵を生成して両方の id_rsa.pub をサーバーの authorized_keys に登録すれば良い。手順を見てもらえるとわかるが、id_rsa.pub は authorized_keys へ追記する形となっているので同一の authorized_keys へ複数の id_rsa.pub を登録できる。

また、同じ鍵で複数のサーバーやサイトにログインできるようにする事も問題ない。同じ id_rsa.pub を複数サーバーの authorized_keys や GitHub などに登録できる。

普通は、作業マシンでは秘密鍵(と、ペアの公開鍵)が 1個、それ以外のサーバーでは authorized_keys に公開鍵が複数登録されている状態になると思う。
(authorized_keys に 1個しか登録されていないと、その作業マシンが壊れた際に少し危険)

ただし、決して1個の秘密鍵を2人のユーザーや2台のマシンで共有してはいけない。
(公開鍵はやってもいいよ。)
転送した際にどんなミスがあるか分からない。盗聴されるかもしれない。

欲を言えば、移動やコピー、標準出力への出力も可能な限り控えたい所だ。cat で内容を出力するだけでもだめだ。どこかに画面出力のログが残っているかもしれないし、画面を後ろから見ている人がいるかもしれない。移動やコピーについても、アクセス権を間違えて誰かに見られる状態になるかもしれない。また、別パーティションにコピーされて別ハードディスクに保存されればハードディスク盗難のリスクが増える。

「大げさな」と思うかもしれないが、鍵を盗まれるのはそれだけ重大なことだ。バグの無いプログラムが無いように、ミスの無い人間もいない。こういう些細な気遣いが、重大な事件を起こすかどうかの境目だと思う。

ちなみに、aws (Amazon Web Service) ではサーバーログインのための秘密鍵をダウンロードするが、上記の理由から一時的な物と考えた方が良いだろう。最初のログイン時に初期パスワードを変更するサービスのように、初回ログイン時に鍵を変更したい。

ssh-agent で秘密鍵を登録しておく

ssh する度に秘密鍵のパスワードを入力するのは面倒だ。そんな時は、ssh-agent を用いれば良い。

client 上で
$ ssh-agent
$ ssh-add [path_to_secret_key]

とすると、秘密鍵のパスワードを聞かれるので入力しよう。すると、次からパスワードを入力する手間が省ける。

詳細にいうと、ssh-agent は /tmp にディレクトリを作成し、そこに UDS (Unix Domain Socket) を作成する。daemon としてバックグラウンドで動作する ssh-agent はそのソケットを通じて ssh が鍵の情報をやり取りするのだ。

UDS のパスや ssh-agent の PID は shell 変数に保存される。なので、同じ shell 変数が使用される限り異なる端末でも使用可能だ。screen や tmux などの仮想端末を使用する際は、仮想端末を立ち上げる前にこの作業を行うと全ての window で ssh-agent を使用できる。

なお、この作業は後述の agent forward にも通じている。秘密鍵にパスワードを設定していなかったとしても、agent forward を使用する場合は必要だ。

認証情報を踏み台ごしに使用する

例えば以下のように、目的のサーバーまで ssh でアクセスする際に間に踏み台(Step)を挟む場合。
(ネットワークの都合などで直接 ssh 出来ない事は良くある。)

踏み台で鍵を作っても良いが、管理が面倒になる。秘密鍵はローカルサーバーだけ、踏み台と目的のサーバーの authorized_keys にはローカルサーバーの公開鍵を登録する方法。

手順はこんな感じになる。

  1. ローカルサーバーの公開鍵を踏み台、サーバー両方の authorized_keys に登録する
  2. ローカルサーバーで ssh-agent と ssh-add を実行する
  3. ローカルサーバーから踏み台へ ssh ログインする際に -A オプションを付与する
    $ ssh -A step_server

こんな感じに

  • 踏み台サーバーから目的のサーバーへログインする
    $ ssh server_name

(踏み台で ssh-agent や ssh-add を実行してはダメ)すると、2回目のログインでもローカルの鍵情報が使える。

これは1回目の ssh が踏み台でも UDS を作成し、ローカルの認証情報を踏み台からでも使えるようにするからだ。鍵そのものを転送するわけではないのでセキュリティー的にはそこまで心配する必要は無い。

ssh の度に -A オプションをつければ Agent Forward は下記のような多段構成でも出来る。そのため、Agent Forward を使用すれば秘密鍵はローカルマシンでのみ作成すればよくその他のサーバーにはローカルで作成した共有鍵を登録すれば良い事になる。

ただし、セキュリティーが完全に担保されるわけではない事は覚えておいた方が良い。最も大きいリスクは、踏み台の UDS を悪用される事だろう。この UDS は ssh でログインしたユーザーしか使用できないようにパーミッションが設定されているが root ユーザーからはアクセス権を無視して使用出来る。

root を信頼できないサーバーを踏み台にする際は控えた方が良いだろう。

リモート上のコマンドを直接実行する

話は少し変わって、ssh だけでコマンドまで実行する方法。
ssh [user@]hostname command [arg1 [arg2 [...]]]と実行すると、hostname 上でコマンドを実行してくれる。

例えば、自分の環境でやってみるとこんな感じ
$ ssh sagittarius.chaos.com ls -a /usr
.
..
bin
games
include
lib
local
sbin
share
src

念のため言っておくと、sagittarius.chaos.com というのは自分で管理している VM だ。

リモート上でコマンドを実行する時は、通常のログイン時と同じ環境変数(ホームディレクトリなど)が設定されている。そのため、
$ ssh wbcchsyn@sagittarius.chaos.com ls
と実行すると、wbcchsyn のホームディレクトリのファイル一覧を表示する。

ちなみに、ssh に -t オプションをつけると、コマンド実行前にリモートサーバーで仮想端末 (tty) を作成してくれる。何が嬉しいかと言うと、パスワード入力や yes,no の確認等を促す、tty にのみ表示される文字列も ssh ごしに表示される。対話的なコマンドを実行する際に便利だ。
$ ssh sagittarius.chaos.com touch /tmp/hoge
$ ssh sagittarius.chaos.com rm -i /tmp/hoge
rm: remove regular empty file `/tmp/hoge'?

もちろん、ここで y と入力すると削除されるし、n と入力すると削除はキャンセルされる。

こういう tty にのみ表示される文字列については、以前ここにかいたので、良かったら見て下さい。

リモートの GUI を使う

Linux の GUI は、Client-Server システムになっている。例えば firefox の場合、firefox が X-Client となり「このような内容の表示をして欲しい」と X-Server に伝える。firefox のリクエストを受け付ければ X-Server はディスプレイに firefox の GUI を表示する。

X-Client と X-Server は同じサーバーで動く事が多いが、異なるサーバーで動かす事も可能だ。X-Server は Windows でも cygwin を使えばインストール出来る。

GUI を表示させたいマシンで X-Server を立ち上げたら、X-Client を動かしたいサーバーへ-X オプションをつけて ssh 接続し、コマンドを実行する。この時、ssh でログインしてからコマンドを実行しても良いが、先ほどの方法で直接実行しても良い。
$ ssh -X sagittarius.chaos.com firefox
みたいに。

ちなみに、これをやると firefox が終わるまで terminal のプロンプトが戻ってこない。それが嫌な場合は最後に & をつけてバックグラウンドで実行するとかして頑張って下さい。

自分はよく Windows から ubuntu の emacs を使うが、その際は cygwin の xterm から
$ run ssh -X sagittarius.chaos.com emacs&
とやるとうまくいく。

まあ、ここいらのテクニックは OS や cygwin のバージョンによっても異なるので適当に試して下さい。

あと、当然 OS-X 以降の mac からでも出来ます。(それ以前は知らない)

config ファイルにデフォルト設定を記載しておく

ssh の度に -A とか -X を入力したり、長い FQDN を記載するのは面倒だ。そんな時は、~/.ssh/config にその設定を記載すれば良い。

書式としては下記のような感じだ。
Host *
StrictHostKeyChecking no
Host sagittarius
HostName sagittarius.chaos.com
User wbcchsyn
ForwardAgent yes
ForwardX11 yes
Host aries
HostName aries.chaos.com
...

Host の次が、ssh コマンドで実際に入力するホスト名。ここで * と入力すると下記のオプションは全てのホストに対して適用される。

StrictHostKeyChecking を yes にすると、初めて ssh ログインする時はに聞かれる ssh-server の fingerprint 確認にデフォルトで yes と答えてくれる。HostName は本当のホスト名(又は IP)。ssh が名前解決に使う。User はそのホストにログインする時のユーザー名、ForwardAgent を yes にすると、-A オプションをデフォルトでつける。ForwardX11 を yes にすると、-X オプションをデフォルトでつける。
(-X オプションをつけていても、違いは X-Client のコマンドを実行した時だけでそれ以外は普通の ssh コマンドとして使える。)

上記の場合、$ ssh sagittariusと入力するとssh -AX wbcchsyn@sagittarius.chaos.comを実行した事になる。初めてログインするサーバーでも FingerPrint の確認は出てこない。

その他にも色々とオプションがあるので、興味のある人は man ssh_config を見て下さい。

ファイルを効率よく転送する

リモートで実行した結果がローカルの端末に表示されたりローカルで入力した y や n の文字列がリモートで実行されるのは、ローカルの標準入出力とリモートの入出力がパイプでつながっているからだ。

これを利用すると、ファイルの転送を行ったり出来る。たとえば、mysqldump で MySQL をバックアップした物を gzip 圧縮してバックアップサーバーに保存したい場合。バックアップサーバーから
$ ssh sagittarius.chaos.com mysqldump -uroot -pMyPassword --all-databases | gzip -c > path_to_backup
のように実行すると、1行で見事リモートバックアップが実行される。

解説すると、mysqldump は mysql のデータをダンプして標準出力に渡す。

これをローカル(バックアップサーバー)がパイプで受け取り、gzip 圧縮をする。gzip の -c オプションは結果を標準出力に出すというコマンドだ。gzip は通常、ファイルを受け取ってそのファイル名に “.gz” 拡張子をつけた名前で保存するが、今回はファイルでは無く標準入力から受け取っているので、一回標準出力に出す。

最後に、gzip の結果をパイプでファイルに落としている。

このスクリプトの良い所は mysqldump と gzip が並列に動いている事だ。mysqldump した物をファイルに落としてから gzip 圧縮するより圧倒的に速い。次に、転送、gzip、ファイルへの書き込みまで全てのコマンドの確認が戻り値だけで分かることだ。この 1行スクリプトの直後に$ echo $?とかやって、0 が表示されたらバックアップはほぼ成功したと見なしてよいだろう。これは、ssl のプロトコル自体に転送時のエラーチェックのような物が含まれているためだ。

ただし、このスクリプトには 1点だけ改良の余地がある。mysqldump の結果を転送してから、ローカルで圧縮している点だ。圧縮してから転送した方が、帯域を節約できる。

帯域を節約したい場合は、以下のようにすればよい。
$ ssh sagittarius.choas.com mysqldump -uroot -pMyPassword --all-databases '|' gzip -c > path_to_backup
gzip の前のパイプ ( | ) を quotation ( ‘ ) でくくっているので、ローカルではこの | はパイプではなく文字列と見なされる。つまり、ローカルの shell が ‘|’ を文字列 | とみなし、リモートに転送する。リモートでは | は quotation でくくられていないので、通常のパイプとみなされる。よって、mysqldump の結果が gzip で圧縮されてからローカルに転送される。

以上をふまえて、cron で mysqldump を用いた日時バックアップをするスクリプトは以下で充分だ。
#/bin/sh
tmp=`mktemp` && \
ssh sagittarius.choas.com mysqldump -uroot -pMyPassword --all-databases '|' gzip -c > "$tmp" &&\
mv $tmp /var/backup/dump_`date +'%Y%m%d'.gz`

一応言っておくと、mktemp は一時ファイルを安全に作成してくれる。その一時ファイルに mysqldump の結果を保存し、成功したら /var/backup/dump_YYYYMMDD.gz にリネームする。

途中で失敗した場合、このコマンドは最後まで行かないので /var/backup 以下には正しいバックアップしか残らない。

また、これらの shell コマンドは失敗したらその場で stderr にエラーメッセージが出る。
(特別な場合は例外があるかもしれないが。)
メール送信の設定さえしておけば、cron は stdout や stderr に表示された文字列があるとその内容をメール送信する。なので、失敗したらメール通知してくれる仕組みまでバッチリだ。

なお、おまけだが quotation ( ‘ ) でくくるとリモートで解釈されるのはパイプ ( | ) だけではない。&&、;、||、* なども同じだ。また、変数 ($HOME など)もそのままではローカルの値が、quotation ( ‘ ) でくくるとリモートの値が使用される。

ディレクトリごと効率よく転送する

先ほどの例では gzip を使ったファイル転送の例を上げたが、tar を使えばディレクトリごと転送出来る。たとえば、Apache で公開するための静的コンテンツを ~/git/html というディレクトリで git 管理しているとする。これを、sagittairus.chaos.com の /var/www/html に転送するには、以下のようにすればよい。
$ tar czf - -C ~/git html | ssh wbcchsyn@sagittarius.chaos.com sudo tar xzf - -C /var/www
ただし、このコマンドを実行するには sudo 周りの権限や設定をする必要がある。例えば、こんな感じ。
$ sudo sh - c "echo 'wbcchsyn ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/wbcchsyn"
$ sudo chown root:root /etc/sudoers.d/wbcchsyn
$ sudo chmod 0440 /etc/sudoers.d/wbcchsyn
$ sudo sed -i 's/^Defaults[ \t]*requiretty/#Defaults requiretty/' /etc/sudoers

ユーザー wbcchsyn にパスワード無しで全コマンドを sudo 実行出来る権限を付与し、sudo 一般の設定として、tty が無くとも実行出来るようにしている。(一部のディストリビューションでは tty が無いと sudo を実行出来ないように設定してある。)

tar は通常、tar czf file_name directory1 [directory2 [...]]という書き方で directory1, directory2, … を圧縮、アーカイブし、file_name というファイルに出力する。しかし、ファイル名を – とすると出力を標準出力へ出す。gzip の時と同様に、出力をパイプでリモートへ渡すためだ。

-C オプションは、「このディレクトリにチェンジディレクトリしたつもりになって」という意味。

例えば、
$ cd ~
$ tar czf html.tar.gz git/html

とした場合、html.tar.gz を解凍すると最初に git というディレクトリができて、その下に html ディレクトリが展開される。
しかし、
$ cd ~/git
$ tar czf html.tar.gz html

とすると、html.tar.gz を解凍した際に html ディレクトリが作業ディレクトリ直下に直接展開される。

-C をつけた$ tar czf - -C ~/git html は ~/git/html を、解凍した時に html ディレクトリが直接出来る形でアーカイブし、標準出力に渡す。

tar が作成したアーカイブの標準出力はパイプに渡され、sagittarius.chaos.com でやはり tar を用いて解凍される。圧縮時と同様、ファイル名の – は標準入力を、-C オプションは「このディレクトリに cd したつもりになって」という意味だ。

この1行で、 ローカルの ~/git/html が sagittarius.chaos.com の /var/www/html へ転送される。

ただ、この作業も改善の余地が多いにある。

まず、最初にこの作業では .git ディレクトリも転送されてしまう。.git ディレクトリは git でレポジトリを管理するための物であり、開発中には必要だが本番では必要ない。なので、tar でアーカイブを作成する際にこのディレクトリは除くように –exclude ‘.git’ オプションを付与する。

また、tar はデフォルトではファイルのオーナーやパーミッションも保存してしまう。しかし、おそらくローカルではオーナーは作業用ユーザーであり、リモートでは apache をオーナーにしたいだろう。そのような場合、圧縮時に –owner=’apache’ オプションを付与する。

まとめると、~/git/html を sagittarius.chaos.com の /var/www/html に転送する手順は以下のようになる。
$ tar czf - -C ~/git --excoude '.git' --owner='apache' | ssh wbcchsyn@sagittarius.chaos.com sudo tar xzf - -C /var/www

Port Forward で簡易 トンネリング・VPN を貼る

例えば、学校や会社の FW の中で動いている http サーバーに外からアクセスしたいような場合。Port Forward (ポートフォワード) を用いれば FW をすり抜ける事が出来る。

上記のように、FW 内の aries で動いてる http サーバーに FW 外部の taurus からアクセスしたい場合。同じく FW 内にある sagittarius から例えば以下のように taurus に予めポートフォワードの ssh を接続しておけばよい。
$ ssh -R 8000:aries:80 taurus
この状態で taurus から localhost の 8000番ポートにアクセスすると、aries の 80 番ポート(http の内容)が表示される。
(ssh の接続を切るとポートフォワードも切れてしまうので気をつけよう。)

ちなみに、taurus と sagittarius 間の通信は ssh により暗号化されているので FW の外部における盗聴の可能性は限りなく低い。

しかし、この状態では taurus の 8000番ポートには taurus 自身からしかアクセス出来ない。taurus 以外のマシンからでも taurus の 8000番ポート経由で aries の 80番ポートにアクセスするためにはtaurus の ssh デーモンを GatewayPorts オプションを有効にした上で
$ ssh -R 0.0.0.0:8000:aries:80 taurus
と実行する。
(もちろん、ネットワーク的に taurus の 8000 番へアクセス出来る事は必要。)

ssh デーモンの GatewayPorts を有効にするには、/etc/ssh/sshd_config に GatewayPorts yesと書き込んで ssh デーモンを再起動すればよい。

このように、目的のサーバーと同じ FW 内から外部に ssh アクセスする事でポートフォワードする場合の一般的なオプションは以下になる。
$ ssh -R [bind_address]:port:host:hostport [user@]hostname

なお、ssh 接続の向きは上記と逆にする事も可能だ。


上記のように、taurus から sagitarius に ssh コネクションを張れる場合は、taurus 上で
$ ssh -L 8000:aries:80 sagittarius
とすると tausus から localhost の 8000番経由で aries の 80番ポートにアクセス出来る。

-R の時と同様、このままでは taurus 以外のマシンからは taurus の 8000番ポートへアクセス出来ない。他のマシンからでも taurus の 8000番ポート経由で aries の 80番ポートへアクセスしたい場合は
$ ssh -L 0.0.0.0:8000:aries:80 sagittarius
と実行する。-L の場合は -R と違って ssh デーモンの GatewayPorts の設定が必要ない事は少し嬉しい。

-L と -R は ssh の向きが違うだけで動作はほとんど同じだ。-L を使うと ssh client 上でポートを開き、目的のサーバーへポートフォワードする。-R を使うと ssh server でポートを開き、目的のサーバーへポートフォワードする。

-L と -R は client を左側、server を右側に書いた時、ポートを開く場所が left, right という事らしい。ただ自分は「client は L の文字が含まれ、server は R の文字が含まれる」と覚えている。

今回の例では、-R を使うと、taurus から aries を閲覧中に万一 ssh コネクションが切れると、sagittarius から再度 ssh を張り直さなくては行けない。実用にのせるためには sagittairus で ssh コネクションを監視するか、taurus からも sagittarius にログイン出来る仕組みを作る必要がある。

そのため、今回の例に限って言えば個人的には -L を使う方が良いと思う。-L を使う場合は、taurus から sagittairus へ ssh アクセスできれば全て解決だ。

どちらの場合も言える事だが、ポートフォワードは実質的に FW に穴をあける事に等しい。そもそも、FW 的にアクセス出来ないのはそれだけの理由があるかもしれない。多くの場合、ネットワーク管理者がユーザーのポートフォワードを禁止する事は難しいのが現状だが、だからと言って乱用は避けた方が良いだろう。

以上、駆け足でしたが ssh の紹介でした。

だからそんな事に spawn を使うなと!

この記事は 2013 年 1 月 16 日の「Syn の独り言」の記事を移行したものです。

いらん所で spawn を使おうとしている人がいたので、つい。

普通、プログラムを書くときはデフォルトで stdin, stdout, stderr という入出力ストリームが始めから開いている。でも、tty 上にはこれ以外の文字列が表示される事もある。

たとえば、sudo コマンド実行時にパスワードが聞かれたりする場合。
$ sudo env >/dev/null 2>&1
と実行すると、stdout も stderr も /dev/null へ捨てられるはず。でも、パスワードの入力を促す文字列(プロンプト)はちゃんと tty に表示される。

だから、「パスワード入力を促されたら “MyPass” と入力する」プログラムを書こうとした場合、「パスワード入力を促されたら」の確認部分を stdout や stderr を監視で実装する事はできない。繰り返しになるが、入力を促す文字列は tty にのみ出力されるからだ。

ではどうしたら良いか?
spawn を使えば実装できる。

spawn とは fork してから仮想 tty を開く関数の一般名。色々な言語で実装されている。そして、tty に出力される文字列を全て (stdout や stderr も含めて) 取得できる。もちろんプロンプトもだ。

ただ、安直に spawn を使う前に考えてほしい。

何で sudo はパスワード入力を stdout や stderr ではなく、プロンプトに出力するのか?

それは、sudo 実装者が「プログラムではなく、人間に見せたい」と考えたからだろう。つまり、そもそも論として「sudo のパスワードをに入力するプロラム」自体がsudo 実装者の意図に反しており、根本の設計から良くない可能性がある。

個人的には「プロンプトを制御する」とは、「例外を握りつぶす」と似ていると思う。絶対に間違っているとは言わないが、基本的には避けて通りたい。

では、プログラムの中で sudo コマンドを実行したい場合、どうすればいいか?パスワード入力をやめればいい。sudo の場合は設定次第でパスワード無しで実行可能に出来る。だから、そもそもプログラムからパスワードを入力する必要が無いのだ。

なぜ spawn を使うよりパスワード無しにした方が良いのか。例えば、パスワードを入力させるためにはどこかにパスワードを記載する必要があるから。もしプログラムにパスワードを記載すると、subversion などのアクセス権のある人全員にパスワードがばれてしまう。

もちろんソースコードを厳重に管理するとか、実行可能な sudo コマンドを制限するとか方法はいくらでもある。でも、いらんタスクや制限を増やすのは優秀なエンジニアのする事ではない。「コード中にパスワードを絶対に書くな」とは言わないけれど、それが sudo の思想なのだ。素直に従うべきだろう。

sudo に限らず、tty に出力をするコマンドはプログラムから実行するための抜け道を用意している事が多い。その抜け道がどうしても見つからない場合や、何等かの原因で抜け道を実行する事が出来ない場合は spawn を使ってもいいかもしれない。
でも、その前に「本当にそれでいいのか?」と自問自答をする必要がある。

spawn が本当に役に立つのはクロスプラットフォームで動作する、汎用性の高いプログラムやライブラリを書く時ではないだろうか?

spawn は windows でも unix でもインターフェースが似ているのでコードの OS 依存部分が少なくなる。

また、sudo の例で言うとオプションでパスワードを入力できるようにすれば何らかの理由で sudo の設定を変更出来ない環境でも使用できるようになる。
(もちろん、設定変更できる場合に備えて空パスでも動くように実装するべき)

まあ、「動けばいい」っていう考え方もあるんだけど、こういう所からバグって生まれる気がするんだよね。

rsync の隠れた利点と強制終了

この記事はSyn の独り言 から移行、修正したものです。

とりあえず、週 1回は blog を書こうと思ったのもつかの間。前回からあっという間に 1ヶ月がすぎてしまった。

とりあえず、gmail にログインしたら Buzz という物ができて、Python についても少しずつ分かってきて、書きたい事は盛りだくさん。

もうすぐ、東京 Ruby 会議 03 もあるし。

そんな事はさておき、本日は rsync の話。

rsync と言えば、linux の差分バックアップコマンド。
主な特徴は以下

      ssh を通して、リモートのマシンにもバックアップ (コピー) 可能
      以前のバックアップ結果と比較して、新しいファイルのみコピーする事が可能
      当然、前回のバックアップ時から消されたファイルを削除する事も出来る
      Linux のハードリンクを使用することで、複数のバージョンのバックアップをとっても、更新されていないファイルは HDD 上に 1個だけにする事が可能(ディスク容量が少なくて済む)
      ファイルのバイナリ差分を取ることで、大きいファイルの更新された部分だけ差分バックアップを取ることが可能

端的に言うと、scp と cp コマンドに差分とハードリンク機能を加えたような物。詳しい事は、rsync で検索してください。この 2個の記事なんかが詳しい。
はじめてrsyncを使う方が知っておきたい6つのルール
rsyncで差分バックアップを行うための「–link-dest」オプション

さて、上記の特徴だけ見ると良いことだらけに思えるかもしれない。実際、悪い評判は聞いたことがない。でも、私は今までこのコマンドを毛嫌いしていた。

そもそも、rsync を使うのはどんな時だろう。このコマンドは shell に手入力するような物ではない。そんな時は、scp と cp だけで十分だ。

では、運用中のサーバーで定期的にバックアップを取る時はどうだろう。これには、信頼性が低い。rsync は、しょせん shell のコマンドだ。サーバーやネットワークに異常があれば、すぐにエラーとなる。

堅牢性のためにはストレージ等のハードウェアの機能や、DRBD のような信頼性の高い機能を使うべき。

費用面で難しければ、詳細なログを書き出すバックアップスクリプトを作成すればよい。信頼性が足りない分、せめて詳細なログを残したい。バイナリ差分についてはハードルが高いかもしれないが、それ以外は誰でも十分に実装する事が可能だろう。

要は、rsync とは、バックアップという重要な場面において信頼性と利便性が中途半端なコマンドと認識していた。

ところが、先日、自分の意思には反しつつも rsync を使用したshell スクリプトを作成する事があった。

スクリプトを作成したら、その後はテスト。異常発生時の挙動を調べるため、プロセスを kill したくてps コマンドにて rsync のプロセス ID を調べた。

すると、複数の rsync プロセスが存在するではないか。もちろん、rsync は 1回しか実行していない。おそらく、バックアップ速度を上げるために fork して複数プロセスが立ち上がったのだろう。

これは便利。
子プロセスの数をどうやって決定しているのかは知らないが、ほとんどの場面で、バックアップの速度が上がるだろう。

スクリプトで同様の実装をすることも可能だが、自前で fork をするとテストや引継ぎが急に難しくなる。その点、rsync ならば十分にテストされているはずだし、ドキュメントもいたる所にある。

「rsync の利便性が低い」というのは、実は私の勘違いだったのだ。やっぱり、世間の評判が高い事にはそれなりの理由が有ったのだ。

あと、信頼性はやっぱり低い。特定の子プロセスを kill したりすると、失敗した。

なお、マルチプロセスで立ち上がった rsync を意図的に途中で止めたいときは、
$ pkill rsync
とかやると良い。
タイミングによっては全て止まらない可能性もあるので、その場合は再度上記のコマンドを実行。ただ、このコマンドは全部の rsync プロセスを殺そうとするので注意してください。