CUBE SUGAR CONTAINER

技術系のこと書きます。

xinetd を使ってホストの状態を監視する

xinetd はスーパーサーバと呼ばれるプログラムで、あるポートに対してアクセスがあった際に設定ファイルを元にして特定のサービスを起動することができる。 以前はプログラムを常駐させないことでメモリの節約などに使うことが主な用途だったようだが、メモリを潤沢に使える今となってはその目的で使われることは減ってきたはず。 最近ではその用途に替わって、あるポートに対して簡易的なサービスを提供できることから監視を目的として使われることが増えてきたようだ。

まずは軽く触ってみる

本題に入る前に xinetd 自体がどういったものなのか見ていくことにする。 環境には CentOS7 を使った。

$ cat /etc/redhat-release 
CentOS Linux release 7.1.1503 (Core) 
$ uname -r
3.10.0-229.el7.x86_64

まずは xinetd を yum でインストールする。

$ sudo yum -y install xinetd

前述した通り xinetd はあるポートにアクセスがあった際にそれに登録されたサービスを起動する。 その設定ファイルは /etc/xinetd.d ディレクトリ以下にある。 最初から幾つかのサービスがサンプルとして用意されているが、すべて無効な状態になっている。

$ sudo grep -r "disable" /etc/xinetd.d
/etc/xinetd.d/chargen-dgram:    disable       = yes
/etc/xinetd.d/chargen-stream:   disable       = yes
/etc/xinetd.d/daytime-dgram:    disable       = yes
/etc/xinetd.d/daytime-stream:   disable       = yes
/etc/xinetd.d/discard-dgram:    disable       = yes
/etc/xinetd.d/discard-stream:   disable       = yes
/etc/xinetd.d/echo-dgram:   disable       = yes
/etc/xinetd.d/echo-stream:  disable       = yes
/etc/xinetd.d/tcpmux-server:    disable       = yes
/etc/xinetd.d/time-dgram:   disable       = yes
/etc/xinetd.d/time-stream:  disable       = yes

試しにその中の一つを有効にして使ってみよう。 echo-stream サービスは TCP でエコーサーバを提供するための設定だ。

$ sudo sed -i -e "s:\(disable.*=.*\)yes:\1no:" /etc/xinetd.d/echo-stream
$ sudo grep "disable" /etc/xinetd.d/echo-stream 
    disable       = no

echo-stream サービスを有効にしたら xinetd を開始する。

$ sudo systemctl start xinetd
$ sudo systemctl enable xinetd

echo-stream サービスが有効になったことで TCP の 7 番ポートが Listen される。

$ ss -ant | grep ::7
LISTEN     0      64                       :::7                       :::*

nc (netcat) を使って動作を確認してみよう。 エコーサーバなので入力した内容がそのまま出力として返ってくる。

$ sudo yum -y install nc
$ nc localhost 7
Hello, World
Hello, World
^C

自分でサービスを作ってみる

先ほどは組み込みのサービスを使ってみたので、次は自分で設定ファイルを書いてサービスを作ってみよう。 内容は先ほどと同じエコーサーバにする。

xinetd の設定ファイルは以下の通り。 'disable' から 'socket_type' までは設定が必須となる項目だ。 Web 上を見ると 'id' や 'type' を省略している場合が多いものの、設定ファイルを見るとこれらは Mandatory と書かれていた。 ポイントは 'server' に対してシェルスクリプトのパスを指定しているところだ。 このシェルスクリプトが提供するサービス本体になる。

$ cat << EOF | sudo tee /etc/xinetd.d/myecho-stream > /dev/null
service myecho-stream
{
        disable         = no
        id              = myecho-stream
        type            = UNLISTED
        wait            = no
        socket_type     = stream
        user            = root
        server          = /var/tmp/myecho.sh
        port            = 37564
        flags           = REUSE
        log_on_failure  += USERID
        per_source      = UNLIMITED
}
EOF

以下が上記の設定ファイルで指定したシェルスクリプト。 read で読み込んだ入力内容をそのまま echo で返している。

$ cat << EOF > /var/tmp/myecho.sh
#!/usr/bin/env sh

while read i ; do
  echo -e \${i}
done
EOF

シェルスクリプトに実行権限を付けたら設定を読み込み直すために xinetd を再起動する。

$ chmod +x /var/tmp/myecho.sh
$ sudo systemctl restart xinetd

上手くいけば TCP の 37564 番ポートを Listen し始める。 何かおかしいときは /var/log/messages にエラーが出るはずなので確認しよう。

$ ss -ant | grep 37564
LISTEN     0      64                       :::37564                   :::*     

先ほどと同様に nc を使って動作確認する。

$ nc localhost 37564
Hello, World
Hello, World
^C

ばっちり。

HTTP を喋るようにする

特定ポートでシェルスクリプトを使った入出力ができるようになったので、汎用性を考えて次は HTTP を喋らせてみよう。

xinetd の設定ファイルは先ほどとほとんど変わらない。

$ cat << EOF | sudo tee /etc/xinetd.d/http-stream > /dev/null
service http-stream
{
        disable         = no
        id              = http-stream
        type            = UNLISTED
        wait            = no
        socket_type     = stream
        user            = root
        server          = /var/tmp/http.sh
        port            = 8080
        flags           = REUSE
        log_on_failure  += USERID
        per_source      = UNLIMITED
}
EOF

シェルスクリプトも出力内容が HTTP になっただけ。

$ cat << EOF > /var/tmp/http.sh
#!/usr/bin/env sh

MSG="Hello, World"
MSG_LEN=\$(( \$(echo \${MSG} | wc -c) + 2 ))

/bin/echo -en "HTTP/1.1 200 OK\r\n"
/bin/echo -en "Content-Type: text/plain\r\n"
/bin/echo -en "Content-Length: \${MSG_LEN}\r\n"
/bin/echo -en "\r\n"
/bin/echo -en "\${MSG}\r\n"
/bin/echo -en "\r\n"

EOF

スクリプトに実行権限を付けて xinetd を再起動する。

$ chmod +x /var/tmp/http.sh
$ sudo systemctl restart xinetd
$ ss -ant | grep 8080
LISTEN     0      64                       :::8080                    :::*     

今度は nc の代わりに curl を使って動作確認する。

$ sudo yum -y install curl
$ curl http://localhost:8080
Hello, World

上手くいった。

MariaDB の状態を xinetd で公開する

次はいよいよプログラムの状態を xinetd を使って外部に公開してみる。

対象は MariaDB にしよう。

$ sudo yum -y install mariadb-server

xinetd の設定ファイルは先ほどと同様。

$ cat << EOF | sudo tee /etc/xinetd.d/mariadb-healthcheck > /dev/null
service mariadb-healthcheck
{
        disable         = no
        id              = mariadb-healthcheck
        type            = UNLISTED
        wait            = no
        socket_type     = stream
        user            = root
        server          = /var/tmp/mariadb-healthcheck.sh
        port            = 33060
        flags           = REUSE
        log_on_failure  += USERID
        per_source      = UNLIMITED
}
EOF

実行するシェルスクリプトでは mysqladmin ping コマンドの結果によって出力内容を変えている。 mysqladmin ping コマンドの実行が成功 (返り値が 0) であればステータスコードを 200 にしているが、失敗 (返り値が非 0) のときは 503 だ。 Content-Body についてもステータスコード毎に変えている。 今回はテスト用途なので内容がおおざっぱ。 実際には HA の状態とか他にも色々と確認した方がいいはず。

$ cat << EOF > /var/tmp/mariadb-healthcheck.sh
#!/usr/bin/env sh

function _result() {
  STATUS=\$1
  MSG=\$2
  MSG_LEN=\$(( \$(echo \${MSG} | wc -c) + 2 ))

  /bin/echo -en "HTTP/1.1 \${STATUS}\r\n"
  /bin/echo -en "Content-Type: text/plain\r\n"
  /bin/echo -en "Content-Length: \${MSG_LEN}\r\n"
  /bin/echo -en "\r\n"
  /bin/echo -en "\${MSG}\r\n"
  /bin/echo -en "\r\n"
}

function _ok () {
  _result "200 OK" "\$1"
}

function _ng() {
  _result "503 Service Unavailable" "\$1"
}

mysqladmin ping >/dev/null 2>/dev/null

if [ \$? -eq 0 ]; then
  _ok "MariaDB is alive"
else
  _ng "MariaDB is dead"
fi

EOF

スクリプトに実行権限をつけたら MariaDB を起動して xinetd を再起動する。

$ chmod +x /var/tmp/mariadb-healthcheck.sh
$ sudo systemctl start mariadb
$ sudo systemctl restart xinetd
$ ss -ant | grep 33060
LISTEN     0      64                       :::33060                   :::*     

先ほどと同様 curl を使って動作を確認する。 MariaDB のサービスが動作しているため alive と表示される。

$ curl http://localhost:33060
MariaDB is alive

次に MariaDB を停止した状態で確認する。

$ sudo systemctl stop mariadb

今度はサービスが停止しているので dead と表示された。

$ curl http://localhost:33060
MariaDB is dead

あとは上記のサービスを外部から監視する。

まとめ

今回は xinetd を使ってホストの状態を外部に公開する方法について書いた。 ヘルスチェック用のスクリプトを書いて、それを xinetd 経由で外部に公開することでホストの状態を監視できる。 ちなみに、このやり方は RDB の HA 状態を監視するのによく使われているようだ。