Oracle Cloud Monitoring Serviceによるカスタム・アプリケーション・メトリクスの公開と分析 (2020/04/03)

Oracle Cloud Monitoring Serviceによるカスタム・アプリケーション・メトリクスの公開と分析 (2020/04/03)

https://blogs.oracle.com/developers/publishing-and-analyzing-custom-application-metrics-with-the-oracle-cloud-monitoring-service

投稿者:Todd Sharp



数週間前、データベースチームの優秀なプロダクトマネージャーの一人が、私のところに興味深い依頼をしてきました。彼は、誰かが公開したブログ記事を共有しました。その記事には、別のクラウドプロバイダーのサーバーレスサービスを使用してOracle Databaseの使用状況データを取得し、そのデータをプロバイダーのメトリクスサービスに定期的にパブリッシュする例が示されていました。そのPMは、同じことをOracle Cloudで実現するにはどうすればよいかと尋ねてきました。私はこのユースケースに興味を持ち、これまでブログであまり話してこなかったカスタムメトリクスを披露する良い機会だと思いました。





モニタリングサービスは、既存のサービス(コンピュート、オブジェクト・ストレージなど)の使用統計を調べたり、アラーム定義を作成したりすることができるサービスです。

これにより、Oracle Cloud上のビルトインサービスの利用状況やパフォーマンスについての洞察が得られますが、モニタリングサービスがカスタムメトリクスをサポートしていることはご存知ではないかもしれません。これにより、独自のデータをメトリクス・サービスに公開し、それをスライス、ダイス、ソテーして、組織のニーズに合った完璧なレシピに仕上げることができます。


アクションプラン


この小さなプロジェクトとブログ記事のきっかけとなった記事では、毎分実行されるようにスケジュールされたサーバーレスファンクションで問題に対処することを提案していました。これは議論の余地があるかもしれませんが、私の意見では、それはサーバーレスの良いユースケースではなく、月末のコストが高くなる可能性があります。もし、サーバーレスファンクションが「コールド」になることがないのであれば、あなたの問題に対する別の解決策を検討するべきだと思います。私のアプローチでは、クラウド上のVMにシンプルなサービスをデプロイして、JDBCクエリでDB使用率の数値を取得し、60秒ごとにスケジュールされたベースでメトリックデータをパブリッシュするのが良いと判断しました。このアプローチの良いところは、既存のサービスに統合することも、スタンドアロンでデプロイすることも可能であるということです。


実際に作ってみよう


このアプリケーションのコードをいくつか見てみると、この手法を既存のアプリケーションにどのように組み込むことができるかがわかるでしょう。


注意してください。 ここではDBメトリクスについて説明していますが、パブリッシュするデータは少しの変更で必要なものになります。アプリケーションからカスタム・メトリクスを発行する方法については、このままお読みください。


ここではMicronautアプリケーションを使っていますが、お好きなフレームワークを使って構築することができます。単に実行可能なJARを用意して、CRONで定期的に呼び出されるようにスケジュールすることもできます。

OCI Python SDKを使って、Pythonで簡単なスクリプトを書くこともできます。可能性は無限大で、どんな環境にも対応できるアプローチがあります。


DB メトリクスサービス


私のアプリケーションの最初のステップは、ロードとストレージのDBメトリクスを取得することです。私が一緒に仕事をしていたPMは、このデータを取得するために必要なクエリを提供してくれたので、このアプリケーションでの大部分の作業を行うDB メトリクスサービスを作成しました。 PMはロードデータとストレージデータ用に別々のクエリを提供してくれたので、2つの別々のサービスメソッドを作成しました(このブログ記事の完全なソースはGitHubにあります。データの収集に使用した2つのクエリは以下の通りです。


DB Load

/* DB Load */
WITH rdb_load AS (
    SELECT
        inst_id,
        executions,
        usercalls,
        parses,
        commits,
        rollbacks,
        logons,
        totalphysicalreads,
        totalphysicalwrites,
        phyreadtotalioreqs,
        phywritetotalioreqs
    FROM
        TABLE ( gv$(CURSOR(
            SELECT
                to_number(userenv('INSTANCE')) AS inst_id, SUM(decode(name, 'execute count', value, 0)) executions, SUM(decode(name
                , 'user calls', value, 0)) usercalls, SUM(decode(name, 'parse count (total)', value, 0)) parses, SUM(decode(name,
                'user commits', value, 0)) commits, SUM(decode(name, 'user rollbacks', value, 0)) rollbacks, SUM(decode(name, 'logons cumulative'
                , value, 0)) logons, SUM(decode(name, 'physical read total bytes', value, 0)) totalphysicalreads, SUM(decode(name
                , 'physical write total bytes', value, 0)) totalphysicalwrites, SUM(decode(name, 'physical read total IO requests'
                , value, 0)) phyreadtotalioreqs, SUM(decode(name, 'physical write total IO requests', value, 0)) phywritetotalioreqs
            FROM
                v$sysstat
            WHERE
                con_id = 0
            GROUP BY
                to_number(userenv('INSTANCE'))
        )) )
), rdb_time AS (
    SELECT
        inst_id,
        dbcpu,
        dbtime
    FROM
        TABLE ( gv$(CURSOR(
            SELECT
                to_number(userenv('INSTANCE')) AS inst_id, SUM(decode(stat_name, 'DB CPU', value / 10000, 0)) dbcpu, SUM(decode(stat_name
                , 'DB time', value / 1000000, 0)) dbtime
            FROM
                v$sys_time_model
            WHERE
                con_id = 0
            GROUP BY
                to_number(userenv('INSTANCE'))
        )) )
), user_io AS (
    SELECT
        inst_id,
        useriotime
    FROM
        TABLE ( gv$(CURSOR(
            SELECT
                to_number(userenv('INSTANCE')) AS inst_id, time_waited_fg / 100 AS useriotime
            FROM
                v$system_wait_class
            WHERE
                wait_class = 'User I/O'
                AND con_id = 0
        )) )
)
SELECT
    sum(rdb_load.executions) as executions,
    sum(rdb_load.usercalls) as usercalls,
    sum(rdb_load.parses) as parses,
    sum(rdb_load.commits) as commits,
    sum(rdb_load.rollbacks) as rollbacks,
    sum(rdb_load.logons) as logons,
    sum(rdb_load.totalphysicalreads) as totalphysicalreads,
    sum(rdb_load.totalphysicalwrites) as totalphysicalwrites,
    sum(rdb_load.phyreadtotalioreqs) as phyreadtotalioreqs,
    sum(rdb_load.phywritetotalioreqs) as phywritetotalioreqs,
    sum(rdb_time.dbcpu) as dbcpu,
    sum(rdb_time.dbtime) as dbtime,
    sum(user_io.useriotime) as useriotime
FROM
    rdb_load,
    rdb_time,
    user_io
WHERE
    rdb_load.inst_id = rdb_time.inst_id
    AND rdb_time.inst_id = user_io.inst_id
/* DB Storage */
WITH tbsp_stats AS (
    SELECT
        SUM(tablespace_space) AS total_tablespace_space,
        SUM(space_used) AS total_space_used
    FROM
        (
            SELECT
                m.tablespace_name,
                t.contents,
                MAX(round((m.tablespace_size) * t.block_size / 1024 / 1024 / 1024, 9)) tablespace_space,
                MAX(round((m.used_space) * t.block_size / 1024 / 1024 / 1024, 9)) space_used,
                MAX(round(m.used_percent, 2)) used_pct
            FROM
                cdb_tablespace_usage_metrics   m,
                cdb_tablespaces                t
            WHERE
                t.tablespace_name = m.tablespace_name
            GROUP BY
                m.tablespace_name,
                t.contents
        )
)
SELECT
    tbsp_stats.total_tablespace_space,
    tbsp_stats.total_space_used,
    tbsp_stats.total_space_used / tbsp_stats.total_tablespace_space AS total_used_pct
FROM

    tbsp_stats


これらのクエリをDB メトリクスサービスに接続し、各クエリの結果を保存するシンプルなBeanを作成しました。これらをそれぞれgetDBLoad()とgetDBStorage()という個別のサービスメソッドにまとめました。


以下は、私が何を収集しているかを知るためのビーンの定義です。

public class DBLoad {
    private BigDecimal executions;
    private BigDecimal userCalls;
    private BigDecimal parses;
    private BigDecimal commits;
    private BigDecimal rollbacks;
    private BigDecimal logons;
    private BigDecimal totalPhysicalReads;
    private BigDecimal totalPhysicalWrites;
    private BigDecimal phyReadTotalIOReqs;
    private BigDecimal phyWriteTotalIOReqs;
    private BigDecimal dbCpu;
    private BigDecimal dbTime;
    private BigDecimal userIOTime;

}

--

public class DBStorage {
    private BigDecimal totalTablespaceSpace;
    private BigDecimal totalSpaceUsed;
    private BigDecimal totalUsedPct;

}


メトリクスのパブリッシュ


メトリクスサービスに公開したいデータを引き出す方法がわかったので、あとはOCI SDKを使ってメトリクスデータを公開するだけです。そのためには、OCI Java SDKの依存関係を組み込む必要があります。


compile 'com.oracle.oci.sdk:oci-java-sdk-full:1.15.2'


SDKを使用するための準備


ここでは、Oracle Cloudの非常に優れた機能を利用してSDKで認証し、Instance Principalプロバイダを使用します。ドキュメントによると


IAMのサービス機能で、インスタンスがサービスリソースに対してアクションを実行するための認可されたアクター(またはプリンシパル)となることを可能にします。各コンピュートインスタンスは独自のアイデンティティを持ち、インスタンスに追加された証明書を用いて認証を行います。これらの証明書は自動的に作成され、インスタンスに割り当てられ、ローテーションされるため、ホストへの証明書の配布やローテーションを行う必要がありません。


インスタンスプリンシパルを使用するには、まず動的グループを作成する必要があります。私は「instance-principals」と呼んでいますが、このルールは特定のコンパートメントのすべてのインスタンスに適用されます。





あとは、適切なポリシー・ステートメントを適用して、動的グループに権限を与えるだけです。


allow dynamic-group instance-principals to use metrics in tenancy





メトリクスのパブリッシュ


DB メトリクスサービスのコンストラクタでは、MonitoringClientを作成する必要があります。Oracle Cloudで動作している場合はインスタンスプリンシパルを使用しますが、そうでない場合はローカルの設定ファイルを使用します。


注意:MonitoringClientは、POST操作に他の操作とは異なるエンドポイントを使用するため、クライアントを構築する際に適切なエンドポイントを指定する必要があります(または、クライアント上で後からsetEndpoint()メソッドを使用します)。リージョンや操作ごとの適切なモニタリングエンドポイントは、ここに記載されています。

BasicAuthenticationDetailsProvider provider;
if( config.getUseInstancePrincipal() ) {
    provider = InstancePrincipalsAuthenticationDetailsProvider.builder().build();
}
else {
    provider = new ConfigFileAuthenticationDetailsProvider(config.getOciConfigPath(), config.getOciProfile());
}
monitoringClient = MonitoringClient.builder()
        .endpoint("https://telemetry-ingestion.us-phoenix-1.oraclecloud.com")

        .build(provider);


メトリクスをパブリッシュするためには、いくつかの変数が必要です。具体的には以下が必要です。


  •     メトリクスデータを保存するコンパートメントのOCID
  •     ネームスペース名(何でも構いませんが、メトリクスをグループ化するために使用します。
  •     メトリック・データの一意の識別子(私はDBのOCIDを使用しました。
  •     リソース・グループ(任意のもの、メトリクスのための別のサブレベル・グループ


これらの値の多くは、Micronautアプリのconfig beanに格納されています。


これらの情報を収集したら、いよいよメトリクスを発行します。 DB メトリクスサービスのpublishMetrics()メソッドでは、データを取得し、MetricDataDetailsを格納するリストを作成しています。

DBLoad dbLoad = getDBLoad();
DBStorage dbStorage = getDBStorage();

List<MetricDataDetails> metricDataDetailsList = new ArrayList<>();


次に、MicronautのBean Introspectionを使用して、Beanのプロパティをループし、データポイントとメトリクスデータの詳細オブジェクトを構築し、各詳細オブジェクトをパブリッシュする詳細リストに追加します。これを両方のビーンに対して行いますが、ほとんど同じなので片方だけをお見せします。

for (String loadPropertyName : dbLoadBeanIntrospection.getPropertyNames()) {
    BeanProperty<DBLoad, BigDecimal> loadProp = dbLoadBeanIntrospection.getRequiredProperty(loadPropertyName, BigDecimal.class);
    BigDecimal currentValue = loadProp.get(dbLoad);
    Datapoint loadDp = Datapoint.builder()
            .value(currentValue != null ? currentValue.doubleValue() : 0)
            .timestamp(new Date())
            .build();
    MetricDataDetails loadMetricDataDetails = MetricDataDetails.builder()
            .compartmentId(config.getMetricsCompartmentOcid())
            .namespace(config.getMetricsNamespace())
            .dimensions(Map.of(
                    "dbId", config.getDbOcid()
            ))
            .resourceGroup("db-load")
            .name(loadPropertyName)
            .datapoints(List.of(loadDp))
            .build();
    metricDataDetailsList.add(loadMetricDataDetails);

}


次に、PostMetricDataDetailsオブジェクトを構築し、詳細のリストを追加し、MonitoringClientを使ってメトリクスデータのリクエストをポストしました。

PostMetricDataDetails postMetricDataDetails = PostMetricDataDetails.builder().metricData(metricDataDetailsList).build();
PostMetricDataRequest postMetricDataRequest = PostMetricDataRequest.builder()
        .postMetricDataDetails(postMetricDataDetails)
        .build();

monitoringClient.postMetricData(postMetricDataRequest);


この時点で、サービスメソッドを直接呼び出して、メトリクスをパブリッシュすることができます。パブリッシュをテストするために、コントローラ・メソッドを追加しました。

@Get("/test-publish")
public HttpResponse testPublish() throws SQLException {
    dbMetricsService.publishMetrics();
    return HttpResponse.ok();

}


しかし、定期的にメトリックデータのストリームを確実に公開できるように、呼び出しをスケジュールする方が理にかなっていると思います。これを実現するために、Micronautでジョブを作成し、@Scheduledアノテーションを使って60秒ごとにサービスを呼び出すようにしました。

@Singleton
public class MetricsPublisherJob {
    private static final Logger LOG = LoggerFactory.getLogger(MetricsPublisherJob.class);
    private final DBMetricsService dbMetricsService;
    public MetricsPublisherJob(DBMetricsService dbMetricsService) {
        this.dbMetricsService = dbMetricsService;
    }
    @Scheduled(fixedDelay = "60s")
    void publishMetricsEverySixtySeconds() throws SQLException {
        LOG.info("Publishing metrics...");
        dbMetricsService.publishMetrics();
        LOG.info("Metrics published!");
    }

}


パブリッシュされたメトリクスの表示


アプリケーションをデプロイしてしばらく実行した後、バーガーメニューの「Monitoring」→「Metrics Explorer」を選択すると、Oracle Cloudコンソールでメトリックデータを表示できます。




メトリクス・エクスプローラーでは、必要に応じてクエリを作成し、カスタムメトリクスを調べることができます。ここでは、物理的な読み取りと物理的な書き込みをプロットするビューを作成しました。




また、これらのカスタムメトリクスに基づいて、アラーム定義を作成することもできます。ここでは、インスタンスの平均CPUが5分間にわたって85%以上になったときにメールを送信するアラームを作成する例を示します。


アラームに名前を付け、本文と深刻度を指定します。




コンパートメント、ネームスペース、リソースグループ、メトリック名、インターバル、統計値を選択する。




アラームルールの条件を適用します。




そして、それが破られたときに我々の警報を公表する先。




まとめ 


この記事では、Oracle Cloudでアプリケーションからカスタムメトリクスデータをパブリッシュして探索する方法を見てみました。


参考資料

The full source code for this example is available on GitHub.

Photo by Isaac Smith on Unsplash

コメント

このブログの人気の投稿

Oracle RACによるメンテナンスのためのドレインとアプリケーション・コンティニュイティの仕組み (2023/11/01)

Oracle Cloud Infrastructure Secure Desktopsを発表: デスクトップ仮想化のためのOracleのクラウドネイティブ・サービス (2023/06/28)

新しいOracle Container Engine for Kubernetesの機能強化により、大規模なKubernetesがさらに簡単になりました (2023/03/20)