開発部の8lukaです。今回もニッチな小ネタです。
はじめに:再起動を挟む作業
サーバの設定を変えて shutdown -r
して再起動を待つ。こういう場面は定番ですよね。
たいていの場合、再起動は作業の一番最後です。
ところで、私が20XX年頃に遭遇した場面では再起動の 前後の両方で 設定を書き換えたりデータを取り出したり流し込んだりする長時間の作業がありました。
それも、200余りのホストを、さまざまな日程で。
明らかに、これらを私ひとりでやるのはよくないです。というか無理。渡したい。ぜったいに渡したい。むしろ最初の1、2回を見届けたら任せきりにしたい。
というわけで事情の許す限りの自動化と手順化を 専属アサインされ 試みていた中、 最初につまづいたのが ホストの再起動が終わったら次へ進む という地味な手順です。
今回の記事では、これを netcat で手軽に自動化した体験をご紹介します。
制約と手法
クラウド基盤をAPIで叩くことができれば自動化の幅も広がったのでしょうが、事情によりハードルが高かったため、以下の制約を設けていました。
- 作業当日、対象ホストにおいては OSの上でのみ仕事する
- 対象ホストが複数にまたがるため、作業者は管理ホスト上でコマンドをたたく
- 対象ホストへ ssh でリモートコマンド実行する
つまり、とりあえず sshd が(落ちてから)上がったことを確認 すれば、再起動が無事終わって続きの作業ができる、と見なしてよさそうです。
nc コマンド
sshd が上がっているかどうかを確かめるには現地で ps
や systemctl
などがありますが、今回は管理ホストからの ssh で全てを行うため、肝心の sshd が上がっていない状況では使えません。
もちろん「sshのリモートコマンドが通るかどうか」で確かめることもできますし、最終的にはそれが必要なのですが、いろいろと重たいです。
かといって ping では不十分です。
必要十分なものを、と探していたところ記憶の片隅に見つけた nc
コマンド(netcat) で TCP/22 を監視する方法が、再起動中のような微妙な場面で便利そうだったので採用することにしました。
オプション -z
と -w
本来の nc
コマンドは標準入力や標準出力をTCPのストリームと直結しますが、今回は疎通確認さえできればよいです。この目的のために -z
というオプションがあるので使うことにします。
ところで疎通確認が即座に失敗するとは限らず、しばらく待たされてしまうかもしれません。なるべく驚きの少ないオペレーションのためには、タイムアウトがほしいです。ほしいですよね。こちらもオプションあります、 -w
です。
以上をまとめると、「対象ホストのTCP/22番ポートへ3秒以内に接続できれば成功、それ以外は失敗」のチェックを、たったこれだけのコマンドラインで実現できます。
nc -z -w3 <host> 22
この記事で伝えたかったことは以上です。ここまで読んでくださり、ありがとうございました!
スクリプトへの組み込み
・・・と「おしまい」にしてしまいたいところだったのですが、実際にこれで使い始めようとすると 1回だけなら偶然の成功や失敗もあるかもしれないし、5回くらいは接続成功するまで待ちたいなー、となると思います。 もっと言うと、「そもそも再起動ちゃんと始まったの?再起動してないからssh繋がるままなんじゃない?」みたいな目に遭わないように、最初に3回くらい接続失敗するまで待ちたいなー、となると思います。
もちろん、どちらも簡単に実現できます。 以下は実際に使ったスクリプトからの抜粋です。
INPROGRESS= progress() { echo 1>&2 -n "$@" INPROGRESS=1 } progress_clean() { [ "$INPROGRESS" = "" ] || echo 1>&2 INPROGRESS= } loop() { local COMMAND="$1" local SUCC=0 local FAIL=0 local FIRST=1 while [ "$SUCC" != "$MINSUCC" ] do if [ "$FAIL" = "$MAXFAIL" ] then progress_clean echo 1>&2 "retry limit exceeded." exit 1 fi if [ "$FIRST" = 1 ] then FIRST= else sleep "$INTERVAL" fi if nc -z -w"$TIMEOUT" "$REMOTEHOST" "$PORT" then case "$COMMAND" in U) SUCC=$(( $SUCC + 1 )); progress o ;; D) FAIL=$(( $FAIL + 1 )); progress O ;; *) echo 1>&2 "internal error: unknown: $COMMAND"; exit 1 ;; esac else case "$COMMAND" in D) SUCC=$(( $SUCC + 1 )); progress x ;; U) FAIL=$(( $FAIL + 1 )); progress X ;; *) echo 1>&2 "internal error: unknown: $COMMAND"; exit 1 ;; esac fi done progress_clean return 0; }
bashスクリプト中に この loop
関数を定義して、
MINSUCC
に「最低この回数だけ思い通りになるまで繰り返す」を入れて (3
とか5
とか)、MAXFAIL
に「思い通りにならないことがこの回数だけあったらあきらめる」を入れて(-1
なら実質無限ループ)、TIMEOUT
は見ての通りnc
の-w
に渡す値を入れて (3
とか)、INTERVAL
に繰り返しの間の待ち秒数を入れて (1
とかでいいと思います)、REMOTEHOST
に対象ホストを入れて、PORT
に対象ポートを入れて (ふつうは22
ですね)、
以下のどちらかで呼び出します。
loop U
(つながるまで待つ) またはloop D
(つながらなくなるまで待つ)
これで、再起動待ちを自動化するための基本的な部品が準備できました。やったー。
なお「progress
」系の関数は「いま何回くらい思い通りになった?ならなかった?」をリアルタイム表示しています。
おわりに:補足
2点、補足させてください。
1点目:nc
コマンドにはバリエーションがあり、どの派生版なのか、どのバージョンなのかでオプションや挙動が異なります。
この記事で使用した nc
コマンドの情報は以下のとおりです。
$ nc -h 2>&1 | head -n1 OpenBSD netcat (Debian patchlevel 1.130-3)
2点目:前述の抜粋スクリプトを汎用するには以下のような課題がありそうです。
- 再起動がとても早く終わってしまうような場合、「落ちてからつながるまで待つ」の「落ちる」の検出に失敗しそう。
- つながるまで無限に待ちたいときの
-1
があやしい。ちょっと手抜きすぎでは・・・
ともあれ、このときの自動化はなんとか無事に役割を全うしたみたいです。さよなら 20XX年!
採用情報
朝日ネットでは新卒採用・キャリア採用を行っております。