kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

MackerelのOracleプラグインをビルドして使ってみた

Mackerelでは、RDBMySQLPostgreSQLのメトリック監視のプラグインは公式提供(PostgreSQLを含むプラグイン集およびmackerel-plugin-mysql)しているのですが、Oracle DBについては(意外なことに?)公式では提供がありません。

Oracle DBのメトリックを取得するサードパーティープラグインとしては、mattnさんが作られたmackerel-plugin-oracleというものがあります。最近にこの利用実験をしてちまちまと落とし穴にハマったので、後世のためにまとめておきます。

ビルドと設定の結論

グダグダ書いていると「ビジネスのスピードじゃない」と言われそうなので、プラグインのビルドと設定を最初に書いておきます。

Oracle Linux 8 + Oracle 19cの組み合わせで動作するプラグインを、Docker環境で構築します。元のコードからforkしてビルド支援をいくつか追加しています。

$ git clone https://github.com/kmuto/mackerel-plugin-oracle.git
$ cd mackerel-plugin-oracle
$ git checkout oracle19.20
$ (https://www.oracle.com/jp/database/technologies/instant-client/linux-x86-64-downloads から19.20用のBasicとSDKのzipファイルをダウンロードし、ここに展開)
$ docker compose run --rm builder

これで、mporacleというプラグインバイナリができます。これをOracle DBホストにMackerelエージェントとともに配置し、/etc/mackerel-agent/mackerel-agent.confを設定します。

[plugin.metrics.oracle]
command = ["/home/oracle/mporacle", "-dsn=sys/パスワード@?as=SYSDBA"]
env = { LD_LIBRARY_PATH="/u01/app/oracle/product/19.0.0.0/dbhome_1/lib", ORACLE_HOME="/u01/app/oracle/product/19.0.0.0/dbhome_1", ORACLE_SID="データベース名" }
  • プラグイン/home/oracleフォルダ内に置いた想定です。
  • dsnで接続用DSNを指定します。ユーザー名sysとしていますが実際の環境に合わせてください。パスワードも実際のDB設定に基いて指定してください。@の後ろは本当はDB名が入ったりするのかもしれないですが、ORACLE_SID環境変数を指定しているせいか、ローカルDBにはこれで問題ないようです。asで権限を指定していますが、SYSOPERでは権限不足エラーが出てしまったので、おそらくSYSDBAである必要があるのでしょう。
  • envで環境変数を指定しています。oracleユーザーであればこのあたりは自動で設定されているのですが、mackerel-agentはroot権限で動くので、oracleユーザーに設定されている設定内容をコピーしました。

エージェントをsystemctl restart mackerel-agentで再起動すると、メトリックが取得されます!

ここでは少なめなメトリックですが、リポジトリREADME.mdにあるとおり、イベント名を-eventオプションで指定して、ほしい情報を取捨選択できるようです。限られた時間内での実験だったので、この辺りは深入りしませんでした。

らくがき

以降は試行錯誤のらくがきです。

もともとOCIのBaseDBに接続してみるお試しをしていました。BaseDBはコントロールするOracle LinuxOracle DBの構成になっており、公式サポートはちょっと難しそうな形ではあるのですが、理論上はエージェントを入れてOracleの監視はできそうな雰囲気でした。

プラグインビルドについてはシュッと作れたように見えるかもしれませんが、実際にはだいぶ苦戦しています。マニュアル見ないでまずやってしまうのがいけないとも言いますね!

$ git clone https://github.com/mattn/mackerel-plugin-oracle.git
$ cd mackerel-plugin-oracle
$ go build
go: cannot find main module, but found .git/config in /home/kmuto/work/mackerel-plugin-oracle
    to create a module there, run:
    go mod init

はい。mackerel-plugin-oracleが作られたのは6年前。メッセージを読みながらgo mod init mporaclego mod tidyだけで済みますが。

$ go mod init mporacle
go: creating new go.mod: module mporacle
go: to add module requirements and sums:
    go mod tidy
$ go mod tidy
go: finding module for package github.com/mattn/go-oci8
go: finding module for package github.com/mackerelio/go-mackerel-plugin-helper
go: finding module for package github.com/mattn/mackerel-plugin-oracle/lib
go: finding module for package github.com/mackerelio/golib/logging
go: found github.com/mattn/mackerel-plugin-oracle/lib in github.com/mattn/mackerel-plugin-oracle v0.0.0-20171214002109-de30cde9cd26
go: found github.com/mackerelio/go-mackerel-plugin-helper in github.com/mackerelio/go-mackerel-plugin-helper v0.1.2
go: found github.com/mackerelio/golib/logging in github.com/mackerelio/golib v1.2.1
go: found github.com/mattn/go-oci8 in github.com/mattn/go-oci8 v0.1.1
$ go build
go build github.com/mattn/go-oci8:
# pkg-config --cflags  -- oci8
Package oci8 was not found in the pkg-config search path.
Perhaps you should add the directory containing `oci8.pc'
to the PKG_CONFIG_PATH environment variable
Package 'oci8', required by 'virtual:world', not found
pkg-config: exit status 1

はいはい。mackerel-plugin-oracle自体はシンプルに見えていたのですが、実際のOracleとの接続を担うのはやはりmattnさんのライブラリであるgo-oci8でした。

oci8.pcをREADMEの記載を参考にして用意します。

Oracle Full clientやSDKダウンロードも必要とのことですね。Linux x86-64のVersion 19.20.0.0.0を使うことにします(英語サイトだと19.21.0.0.0になっているんですが違いはなんなんでしょうね?)。おそらくこの辺りの事情があってビルド済みのものも出せていなかったのかな〜という推測をしました。

Basic Package(ZIP)、SDK Package(ZIP)をmackerel-plugin-oracleの作業フォルダ内に展開すると、instantclient_19_20ができます。

oci8.pcもmackerel-plugin-oracleフォルダ内に用意します。

prefix=/home/kmuto/work/mackerel-plugin-oracle/instantclient_19_20
includedir=${prefix}/sdk/include
libdir=${prefix}

Name: oci8
Description: Oracle Instant Client
Version: 19.20
Cflags: -I${includedir}
Libs: -L${libdir} -lclntsh

このファイルを探すようPKG_CONFIG_PATHをセットします。

$ export PKG_CONFIG_PATH=`pwd`
$ go build
# mporacle
/home/kmuto/sdk/go1.21.0/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/bin/ld: warning: libnnz19.so, needed by /tmp/mackerel-plugin-oracle/instantclient_19_20/libclntsh.so, not found (try using -rpath or -rpath-link)
 ...

なるほど、それはそうですね。

$ export LD_LIBRARY_PATH=`pwd`/instantclient_19_20
$ go build

成功しました! go-oci8については2021年にv0.0.1がリリースされて以来更新がないのが不安だったのですが、ビルドはこれでできました。

$ ./mporacle
2023/12/XX 10:52:41 ERROR <metrics.plugin.oracle> Failed to select resource. ORA-12162: TNS:net service name is incorrectly specified
2023/12/XX 10:52:41 OutputValues:  ORA-12162: TNS:net service name is incorrectly specified

エラーはパラメータの話なので、バイナリとしては機能していそうです。

では、OracleをコントロールしているOracle Linux上で実行してみましょう。Oracle Linuxホストにコピーしてテスト。

$ ./mporacle
./mporacle: error while loading shared libraries: libclntsh.so.19.1: cannot open shared object file: No such file or directory

あーはいはい。

$ export LD_LIBRARY_PATH=/u01/app/oracle/product/19.0.0.0/dbhome_1/lib
./mporacle
./mporacle: /lib64/libc.so.6: version `GLIBC_2.32' not found (required by ./mporacle)
./mporacle: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by ./mporacle)

Oh…。まぁDebian bookwormのlibc(2.36)で作ったので、Oracle Linux 8のlibc 2.28とABI非互換な可能性は十分ありますよね……。

Oracle Linux 8のネイティブバイナリを作るために、Dockerでビルド環境を構築することにしました。

  • ベースイメージは「oralelinux:8」
  • gcc(ライブラリの関係上cgoを使う必要)、pkg-configをインストール
  • Go環境もインストールしておく
# cd /work
# export PATH=/usr/local/go/bin:$PATH
# export PKG_CONFIG_PATH=`pwd`
# export LD_LIBRARY_PATH=`pwd`
# go build

無事にビルドできました。Docker内で実行してみます。

# ./mporacle
./mporacle: error while loading shared libraries: libnsl.so.1: cannot open shared object file: No such file or directory

libnsl…2023年も暮れようとしているのにNISの名前を聞くことになるとは……!(もう若い人はNISもYPも知らんのじゃよ……) ビルド環境にlibnslパッケージをインストールしておきます(Dockerfileにも入れておく)。

# yum install libnsl
# ./mporacle
2023/12/XX 02:14:09 ERROR <metrics.plugin.oracle> Failed to select resource. ORA-12162: TNS:net service name is incorrectly specified
2023/12/XX 02:14:09 OutputValues:  ORA-12162: TNS:net service name is incorrectly specified

よさそうです。ビルドにlibnsl.aが必要なのかとしばらく困っていたのですが、mporacleのビルドには別段関係ありませんでした(それはともかくOracleさんにはlibnsl.so.1にリンクするのは勘弁してほしい気持ちでいっぱいです)。

再度DB環境で試します。

$ export LD_LIBRARY_PATH=/u01/app/oracle/product/19.0.0.0/dbhome_1/lib
$ ./mporacle
2023/12/XX 06:14:09 ERROR <metrics.plugin.oracle> Failed to select resource. ORA-12162: TNS:net service name is incorrectly specified
2023/12/XX 06:14:09 OutputValues:  ORA-12162: TNS:net service name is incorrectly specified

いけそうです。

さて、ここまでいけばあとはDBに接続です。mackerel-plugin-oracleのREADMEを見たところ、dsnオプションを使うらしく、scott/tiger@XEというのがあります。Oracle人ならscottと言えばtigerとわかるらしいです。私はOracle人ではないので、長時間わからなくて困りました。

要はscottをユーザー名、tigerをパスワードと見たてて、/で区切る。@の後にDB名が入るという形式のようでした。

BaseDBで試していたところでは、oracleユーザー環境ではDB名は省略できるようです(ORACLE_SID環境変数が効いているのかも)。冒頭で書いたように、asパラメータで権限を設定しないと怒られ、かつSYSOPERではダメだったのでSYSDBAを指定します。

$ ./mporacle -dsn sys/パスワード@?as=SYSDBA
oracle.resource.processes    73.000000    1703637718
oracle.resource.sessions    97.000000    1703637718
oracle.waitclass.administrative    0.000000    1703637718
oracle.waitclass.cpu    0.004000    1703637718
oracle.waitclass.cpu_os    0.132000    1703637718
oracle.waitclass.concurrency    0.000000    1703637718
oracle.waitclass.configuration    0.000000    1703637718
oracle.waitclass.network    0.000000    1703637718
oracle.waitclass.other    0.001000    1703637718

ヤッター。

あとはmackerel-agent.confの設定です。mackerel-agentはroot権限で動いており、プラグインだけ特定のユーザー権限で動かすのはできなくもないけれども、変に悩むことが増えそうなので、普通にrootで動くようにしておきます。

root権限で直接mporacle -dsnを実行してみると動かないことがいろいろあったので、環境変数設定済みのoracleユーザーと比較しながらそれっぽい環境変数を設定してはmporacle -dsnを実行していきました。

最終的にこれで動いたな、という環境変数mackerel-agent.confプラグインenvエントリに含めていきます。Oracle 19cのBaseDB環境では以下のようになりました。

env = { LD_LIBRARY_PATH="/u01/app/oracle/product/19.0.0.0/dbhome_1/lib", ORACLE_HOME="/u01/app/oracle/product/19.0.0.0/dbhome_1", ORACLE_SID="データベース名" }

ということで、自ら穴にはまったところもありますが、ひとまず一応動くものができました(最終的にビルド用に作成したDockerfiledocker-compose.ymlbuild.sh)。

今もしOracleプラグインを改めて実装するとしたら、Pure Goなgo-oraを使うか、あるいはシンプルにsqlplusコマンドを叩いてその結果を解析するほうがよいのかもしれませんね。

では良い御年を、Happy Hacking!

Instagram連携が現在停止しているので毎日の料理を貼れない……kmuto Instagramには上げ続けています。