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を構成する必要がありますね。
こんな感じでしょうか。
下の例は インスタンス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
上だけでは、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の使い方わかんないからです><
ではでは。
お役に立てば幸いですが、
例によって、これにより、損害を被ってもこちらとしては
責任とれませんので、よろしくです。