Cでサーバー監視のプログラムを作ってみよう その2

まずは子プロセスを動かして、子プロセスで監視スクリプトを動かす簡単なものを作ってみる

aprですが、ハッシュテーブルの他にも色々なものを持っているようで、apr_pallocというmallocと同じくメモリ確保をするんだけど、メモリリークを防ぐ機能を持っているようだ。

前回のハッシュテーブルを使ったプログラムに付け加えて、参考サイトのWhileループを利用してforkするサンプルコードを利用して、監視スクリプトを動かすものを組み合わせてみました。

そーす

#include <stdio.h>
#include <string.h>
#include <apr_hash.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

#define Max_Children 3 /* 子プロセスの最大数 */

/* INCLUDE: apr_hash.h
 * apr_hash_make / apr_hash_set / apr_hash_get /
 * apr_hash_count / apr_hash_first / apr_hash_next /
 * apr_hash_this */

/* ハッシュテーブルの例
 * HASHTABLE: apr_hash_make / apr_hash_set / apr_hash_get /
 * apr_hash_count / apr_hash_first / apr_hash_next / apr_hash_this */

static apr_pool_t *pool = NULL;
static apr_hash_t *hash = NULL;

int main(void)
{
    //aprを初期化
    apr_initialize();

    //メモリプールを作成
    apr_pool_create(&pool, NULL);

    //ハッシュを作成する。
    hash = apr_hash_make(pool);

    //親プロセスのループ
    while(1){
        while(apr_hash_count(hash) >= Max_Children){
                int status;
                pid_t child_pid = wait(&status); //子プロセスの終了を待つ
                apr_hash_set(hash, &child_pid, sizeof(child_pid), NULL);
                        //なくなった子プロセスはテーブルから削除
        }

        pid_t *pid = apr_palloc(pool, sizeof(pid_t));
        /* エラー復帰時などにメモリを解放し忘れた場合にも 
         * apacheがプール(apr_pool_tによって管理されている)解放時に
         * 自動的に解放を行ってくれるため、メモリリークを防ぐことができる。
         * らしい */

            *pid = fork();

            if(*pid==0){
                while(1){
                 system("bash check.sh");
                 sleep(1);
                        }
                }

        apr_hash_set(hash, pid, sizeof(pid_t), 1); //子プロセスをハッシュテーブルに追加する。
        usleep(100);
        }
        return 0;
}

まだまだ参考サイトにおんぶにだっこな状態なのです。

実行結果

とりあえずLoadAverage上げるためにYes攻撃。

[root@localhost program]# yes > /dev/null &
[2] 65232
[root@localhost program]# yes > /dev/null &
[3] 65238
[root@localhost program]# yes > /dev/null &
[4] 65254
[root@localhost program]# yes > /dev/null &
[5] 65255
[root@localhost program]# yes > /dev/null &
[6] 65271

すると、テストのためにLoadAverage2以上から動作するようになっているので、すぐに反応する。

[root@localhost program]# ./pre_1
Load average 2
Load average 2
Load average 2
Load average 2
Load average 2
Load average 2
Load average 2
Load average 2
Load average 2

TOPで見てると、一応子プロセスたちが動いているようだ。

子プロセス
62400 root      20   0 17368  264  104 S  0.0  0.0   0:00.07 pre_1
62402 root      20   0 17368  264  104 S  0.3  0.0   0:00.06 pre_1
62403 root      20   0 17368  264  104 S  0.0  0.0   0:00.05 pre_1

親プロセス
62399 root      20   0 17368  680  520 S  0.0  0.1   0:00.00 pre_1

今後の予定のQEMUですが、ホストサーバーから見るとQEMUもプロセスなので、QEMUの負荷が上がったときは、今のYes攻撃に似た感じになるはずなので、基本的な動作はこれでいいはず。。。

ただ今のままでは、ホストサーバーのLoadAverageが高くなったよ~と知らせるだけなので、QEMUにたいして何らかの動作をするプログラムにしようと思います。

 

テストとして、まずはCPU使用率が閾値以上のプロセスをKILLしてみよう。

ここであの言葉が浮かんだ。いつやるの、いまでしょ。っという事で、ささっとLoadAverageが閾値以上かつ、CPU利用率が閾値以上のプロセスをKILLする監視プログラムに書き換えました。

QEMUとYes攻撃では閾値はまるで違うことになると思うけど、今はYes攻撃に合わせて、100%のうち3プロセスで使いきる予定なので30以上とした。

LoadAverage3以上かつCPU使用率30以上でKILLが発動

実行結果

とりあえずYes攻撃を3個やって、監視プログラムを起動。TOPで様子を伺う。

top - 18:06:44 up 2 days,  1:59,  2 users,  load average: 1.32, 1.42, 1.40
Tasks:  80 total,   4 running,  76 sleeping,   0 stopped,   0 zombie
Cpu(s): 18.6%us,  0.4%sy,  0.0%ni, 80.7%id,  0.2%wa,  0.0%hi,  0.1%si,  0.0%st
Mem:   1012548k total,   576636k used,   435912k free,    64312k buffers
Swap:  2031608k total,      676k used,  2030932k free,   377748k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
28865 root      20   0 98.5m  588  500 R 31.5  0.1   0:01.31 yes
28896 root      20   0 98.5m  584  500 R 31.5  0.1   0:00.84 yes
28897 root      20   0 98.5m  584  500 R 29.5  0.1   0:00.61 yes

ぼーっとしてたら、発動したけど見逃した(笑)

もっかいやってみると

[root@localhost program]# ./pre_1
Load average 3
33
Load average 3
32
Load average 3
32
Load average 3
33
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0
Load average 3
0

止まった。。TOPの方は?

top - 18:08:05 up 2 days,  2:01,  2 users,  load average: 2.14, 1.70, 1.50
Tasks:  77 total,   1 running,  76 sleeping,   0 stopped,   0 zombie
Cpu(s):  5.0%us,  4.7%sy,  0.0%ni, 90.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1012548k total,   576124k used,   436424k free,    64336k buffers
Swap:  2031608k total,      676k used,  2030932k free,   377748k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
28928 root      20   0 15020 1296 1004 R  0.3  0.1   0:00.06 top
    1 root      20   0 19232 1528 1292 S  0.0  0.2   0:01.84 init
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.01 kthreadd



[1]   終了しました      yes > /dev/null
[2]-  終了しました      yes > /dev/null
[3]+  終了しました      yes > /dev/null

おおお、KILLされた。穴だらけなのは承知の上だけど、一応こうなるようにスクリプトを作ったので、当たり前なんだけどうれしい。
TOPからプロンプトに戻ると、KILLの様子があった。
QEMUだったら、再起動なりなんなりすればいいということだ。
このタイプの実装だったら、簡易fail2banもできそうな気がするけど、実際やってみたら難しいんだろうか。

すんなりいけたけど、なんだかC言語でやったっていう実感がない・・・。

 

ところで、何かの拍子に子プロセスがKILLとなった場合はどうなるのか、もちろんMax_Clientsならぬ、Max_Childまでのプロセスが起動するようになっているので復活する。
いわゆるオートリレイズだ。

[root@localhost program]# ps l | grep pre
0     0 29379  7583  20   0  17368   676 wait   S+   pts/3      0:00 ./pre_1
1     0 29380 29379  20   0  17368   260 hrtime S+   pts/3      0:00 ./pre_1
1     0 29381 29379  20   0  17368   260 hrtime S+   pts/3      0:00 ./pre_1
1     0 29382 29379  20   0  17368   260 hrtime S+   pts/3      0:00 ./pre_1
0     0 60823 19903  20   0 107456   940 pipe_w S+   pts/0      0:00 grep pre
[root@localhost program]#
[root@localhost program]# kill 29381
[root@localhost program]#
[root@localhost program]# ps l | grep pre
0     0 29379  7583  20   0  17368   676 wait   S+   pts/3      0:00 ./pre_1
1     0 29380 29379  20   0  17368   260 hrtime S+   pts/3      0:00 ./pre_1
1     0 29382 29379  20   0  17368   260 hrtime S+   pts/3      0:00 ./pre_1
1     0 61124 29379  20   0  17368   260 hrtime S+   pts/3      0:00 ./pre_1
0     0 61226 19903  20   0 107456   940 pipe_w S+   pts/0      0:00 grep pre

こんな感じ。

クラスと継承とかのネーミングを見たときにも、しろまほう れんぞくま とか思い出した。
FF作った人はプログラマーなのかね。まぁいいや。
いきなり話が脱線してごめんなさい。

これはApacheのPreforkでいうところの、StartServersにあたるかな。Apacheは動的にforkされるのと、KeepAliveがあるので、こんな単純なものではない。でもどんな風に実装されているのか考えるとなんだかわくわくする。

 

最終的にはサーバー管理をいろいろできるようなアプリを作りたいので、目的地はおそらくJavaになるのかな。とにかく今は前進なのだ。

 

参考サイト

yoshifumi1975’s diary
C言語でprefork型のデーモンを書く(1): 非デーモン prefork サンプル

redhat カスタマーポータル
第22章 virsh でゲストを管理

espresso3389の日記
apache 2.0用のモジュール作成

 

Related Posts


投稿者: Takeken

インターネット利用者のITリテラシーを向上したいという設定の2次元キャラです。 サーバー弄りからプログラミングまで手を付けた自称エッセイストなたけけんの物語。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です