RACのサービスをSQL+シェルで無理やり監視する

またまた一部のマニアックな人しか喜ばないようなエントリで
ごめんなさい。とくにMixiから来ていただいている方には、
ほとんどタガログ語を読んでいるのとかわらないような意味不明度でしょう・・・。
ほんとごめんなさい。


さて・・・、
今回はRACのサービスをシェルで監視してみます。


サービスの監視ということで、
たぶんOEMとかを使えばすいすいっといけるんでしょうけど、
既に、社内で標準の監視/通知ツールが入っていて、連携させるのが面倒!とか、
そもそも連携できねー!とか、あと、大きな声では言えないですが、OEMは信用してない!
とかまぁ、いろいろある理由で、OEMは使っていても、
監視/通知の機能を使っていないところは多いんじゃないでしょうか?*1
ならば仕方ない、シェルの出番だ。ということで、
監視/通知シェルを作ることになるわけです。


さて、サービスの監視ということで方法としては、

  • crs_stat の結果を perl なんかでねじくりまわす。

というやり方もありますけど、今回は、Oracleが持っている、
service_namesパラメータを参照して、状況確認をするシェルを
作ってみたいと思います。
ええ、そうです。perlわかんないんです。
SQL万歳。

やりたいこと

本来動くべきインスタンスで動いていないサービスを
リストアップして、ログ出力する。

実装方法

「本来ここで稼動するべき」という情報はOracleでは残念ながら持っていないので、*2
これを登録するテーブルを作って、手動でレコード登録しておく。
このテーブルと gv$parameter2 の「service_names」の値を比較して、
期待しているところで動いていないものや、
期待していないところで動いているものを引っ張ります。
gv$parameter ではなく、gv$parameter2 を使用するのは、
gv$parameter の場合、パラメータ値ごとに1レコードですが、
gv$parameter2 は設定値ごとに1レコードなので、扱いやすいんです。
こんな感じ。

SQL> col name for a20
SQL> col value for a80
SQL> select name ,value from v$parameter where name = 'service_names';

NAME                 VALUE
-------------------- --------------------------------------------------------------------------------
service_names        orcl.world, orclsrv1, orclsrv2, orclsrv3

SQL> select name ,value from v$parameter2 where name = 'service_names';

NAME                 VALUE
-------------------- --------------------------------------------------------------------------------
service_names        orcl.world
service_names        orclsrv1
service_names        orclsrv2
service_names        orclsrv3
ちなみに・・・

既にご存知の方多数と思いますが、
RACのサービスリソースは、「service_names」パラメータと
リモートリスナーの仕組みを使って成り立っています。
単純にいうと、「service_names」パラメータに、
サービスリソース名を設定しているだけです。


たとえば、
db_name:orcl
domain_name:world
service_name:orclsrv1
という環境があり、
service「orclsrv1」がインスタンス「orcl1」で起動している状態で、
「orcl2」に切り替えるコマンドを発行するとします。


$ srvctl relocate service -d orcl -s orclsrv1 -i orcl1 -t orcl2


このとき、Oracleの内部では下記のようなSQLが実行されています。


インスタンスorcl1 では・・・
SQL> alter system set service_names = 'orcl.world';


インスタンスorcl2 では・・・
SQL> alter system set service_names = 'orcl.world ,orclsrv1';


サービスって聞くと、なんか中ですごいことやってて、
クラスタウェアとデータベースがくんずほぐれずの大捕り物!みたいな感じがしますが、
幽霊の正体見たり枯れ尾花ってなもんで、既に存在した技術の組み合わせなんですね。
Oracleってそんなん多いですよね。DataGuardとか、DataGuardとか、あとDataGuardとか。

テーブルの仕様

「本来ここで稼動するべき」という情報を格納するテーブルは下記のように作ります。
もうめんどくさいのでSYSAUXに作ってしまいました。
単純にサービス名とそれが本来稼動しているインスタンスを紐付けたものです。
複数インスタンスにまたがって稼動するサービスの場合は
複数レコードを登録すればOKだと思います。


まぁ、後から考えると、ここのテーブル名はどう考えても service_list ですよね。
ネーミングセンスねーなぁ・・・。

SQL> create table service_check (instance_name varchar2(10) ,service_name varchar2(10) ) tablespace sysaux;

表が作成されました。

SQL> desc service_check
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 INSTANCE_NAME                                      VARCHAR2(10)
 SERVICE_NAME                                       VARCHAR2(10)

SQL> insert into service_check values ('orcl1','orclsrv1');

1行が作成されました。

SQL> insert into service_check values ('orcl2','orclsrv2');

1行が作成されました。

SQL> insert into service_check values ('orcl3','orclsrv3');

1行が作成されました。

SQL> select * from service_check;

INSTANCE_NAME        SERVICE_NAME
-------------------- ------------------------------
orcl1                orclsrv1
orcl2                orclsrv2
orcl3                orclsrv3

ではでは実際に作成していきましょう。

まずはSQLを作る。

使うテーブルは、

  • 作成した service_check テーブル
  • gv$parameter2
  • gv$instance


の3つです。
で、取りたい情報を考えると、2つに分けてSQLを構成する必要がありますね。

  • インスタンスから見た場合に、本来動いているべきなのに動いていないサービスをリストアップするSQL

こんな感じでしょうか。
下の例は インスタンスorcl1 で サービスorclsrv1 が
動いているべきなのに、動いていない場合の出力例です。

SQL> select s.service_name ,s.instance_name
  2    from sys.service_check s
  3   where not exists
  4        (select '*'
  5           from gv$parameter2 p
  6               ,gv$instance i
  7          where i.inst_id = p.inst_id
  8            and p.name = 'service_names'
  9            and i.instance_name = s.instance_name
 10            and p.value = s.service_name);

SERVICE_NAME                   INSTANCE_NAME
------------------------------ --------------------
orclsrv1                       orcl1
  • サービスから見た場合に、本来と異なるインスタンスで動いているサービスをリストアップするSQL

上だけでは、1ノードだけで動かしたかったのに、
なぜか2ノードで動いてしまっている。
という状態を検出できないので、これもフォローしないとですね。
こんな感じでしょうか。
これで、違うインスタンスで動いているサービスと、
動いているインスタンスが出力できます。
仮に、サービスorclsrv1 が インスタンスorcl1 で動いていないといけないのに、
インスタンスorcl2 で動いていた場合にはこんな出力になります。

SQL> col value for a20
SQL> col instance_name for a20
SQL> select p.value ,i.instance_name
  2    from gv$parameter2 p
  3        ,gv$instance i
  4   where p.inst_id = i.inst_id
  5     and p.name = 'service_names'
  6     and p.value <> 'orcl.world'
  7     and not exists
  8        (select '*'
  9           from sys.service_check s
 10          where i.instance_name = s.instance_name
 11            and p.value = s.service_name);

VALUE                INSTANCE_NAME
-------------------- --------------------
orclsrv1             orcl2
シェルにSQLを組み込む。

今日は雨が降っているので、cshで組みますか。


こんな感じで書けばいいですかね。
ちなみに、シェルはほんと独学なので、突っ込みどころが多いと思いますけど、
すみません。ほんとすみません。
だれに謝っといたらいいんですかね?
あ、あと、SQLをシェルに乗っけたときによくやるのが、「$」ね。
「$」の前に「\」。これ忘れずに。

#!/bin/csh
setenv LOGFILE          /var/log/oracle_service_check.log

sqlplus -s scott/tiger@orcl << EOF > /dev/null
set pagesize 1000
set linesize 150
set feedback off
set heading off
spool ${LOGFILE} append
select to_char(sysdate,'yyyy/mm/dd hh24:mi:ss') ||
       ' ' ||
       s.service_name ||
       'が' ||
       s.instance_name ||
       'で稼動していません。'
  from sys.service_check s
 where not exists
      (select '*'
         from gv\$parameter2 p
             ,gv\$instance i
        where i.inst_id = p.inst_id
          and p.name = 'service_names'
          and i.instance_name = s.instance_name
          and p.value = s.service_name);
select to_char(sysdate,'yyyy/mm/dd hh24:mi:ss') ||
       ' ' ||
       p.value ||
       'が本来とは異なる' ||
       i.instance_name ||
       'で稼動しています。'
  from gv\$parameter2 p
      ,gv\$instance i
 where p.inst_id = i.inst_id
   and p.name = 'service_names'
   and p.value <> 'orcl.world'
   and not exists
      (select '*'
         from sys.service_check s
        where i.instance_name = s.instance_name
          and p.value = s.service_name);
spool off
exit
EOF


出力例はこんな感じになります。

$ more /var/log/oracle_service_check.log

2008/06/20 16:28:47 orclsrv1がorcl1で稼動していません。

2008/06/20 16:28:47 orclsrv1が本来とは異なるorcl2で稼動しています。


なんだか、改行加減がイヤな感じですけど、
気になる人はawkとかperlとか使って何とかしてください。
sedでもいけるかな。やらないけど。

cronに登録

で、これをcronに登録と。
そんなに負荷を気にするようなものでもないんで、
1分間隔ぐらいで実行しとけばいいでしょ。
なんかほかにスケジューラがあるんだったら、それに登録しちゃってくださいね。

$ crontab -l
*/1 * * * * /opt/oracle/test/service_check.sh > /dev/null 2>&1
ログを監視

仕上げに、あなた様の会社で持っている監視/通知ツールにログを監視させてください。
っつーか、loggerとかつかって、syslogにはかせるようにしたらもっと使いやすいのにね。
なんでしないかって?
そりゃ、おれがloggerの使い方わかんないからです><


ではでは。
お役に立てば幸いですが、
例によって、これにより、損害を被ってもこちらとしては
責任とれませんので、よろしくです。

*1:あれ?ウチだけ??

*2:たぶん。