ノート:

Datadogを使用したPostgreSQLによるOracle Cloud Infrastructure Databaseの監視

イントロダクション

Oracle Cloud Infrastructure(OCI)は、最新の企業のニーズを満たすように設計された、堅牢でスケーラブルなクラウド・プラットフォームです。パフォーマンス、セキュリティおよびコスト効率のために最適化された、コンピューティング、ストレージ、ネットワーキング、データベースおよびアプリケーション開発のための包括的なサービス・スイートを提供します。OCIは、クラウドネイティブと従来の両方のワークロードの実行に最適であり、柔軟で信頼性の高いインフラストラクチャを企業に提供します。

Datadogは、組織がITインフラストラクチャ、アプリケーションおよびサービスをエンドツーエンドで可視化できるように設計された、包括的なクラウドベースの監視および分析プラットフォームです。動的なハイブリッド・クラウド環境全体にわたるリアルタイムの監視、トラブルシューティング、パフォーマンスの最適化を実現します。Datadogは、幅広いツール、プラットフォーム、およびサービスとシームレスに統合されているため、最新のDevOpsおよびIT運用チーム向けの汎用性の高いソリューションとなります。

このチュートリアルでは、OCI Database with PostgreSQLおよびDatadogユーザーが、OCI Connector HubおよびOCI Functionsを使用してOCIからDatadogにメトリックをシームレスに送信するための効率的でスケーラブルなソリューションをどのように設定できるかを示します。

目的

前提条件

タスク1: Datadogアカウントの作成

  1. Datadog Webサイトを使用して、Datadog統合ツールでアカウントを設定します。必要なアカウント詳細を指定し、適切な環境設定を構成してエージェントの設定を完了します。

  2. Datadogエージェントをインストールして、PostgreSQLを使用してOCIデータベースからメトリックおよびイベントを収集します。Datadogエージェントの設定および構成の詳細は、Setup Datadog Agentを参照してください。Datadog Agentでのトラブルシューティングおよびデバッグの詳細は、Basic Datadog Agent Usageを参照してください。

  3. 統合として「OCI」を選択し、インストールを続行します。次の図は、Datadog用のOCI統合のインストール後を示しています。

    イメージ

  4. 「テナンシの追加」をクリックし、テナンシOCIDおよびホーム・リージョンの情報を入力します。

    イメージ

タスク2: Datadog認証リソースの作成

Oracle Cloud Infrastructure (OCI)でDatadog認証ユーザー、グループおよびポリシーを作成します。

  1. ドメインを作成するには、「アイデンティティ」に移動し、DataDogという名前のドメインを作成します。

  2. DatadogAuthGroupというグループを作成します。

  3. 電子メール・アドレス(Datadogモニタリング・ツールへのログインに使用するのと同じ電子メール)を使用してDatadogAuthUserという名前のユーザーを作成し、グループとしてDatadogAuthGroupを割り当てます。

  4. 「ユーザーOCID」をコピーし、「Datadog OCI統合タイル」「ユーザーOCID」フィールドに貼り付けて、ユーザーOCIDを構成します。

  5. APIを設定します。

    1. プロファイルに移動し、ユーザー名を選択します。

    2. 左下隅の「リソース」に移動し、「APIキー」を選択します。

    3. 「APIキーの追加」をクリックし、秘密キーをダウンロードして「追加」をクリックします。

    4. 「構成ファイルのプレビュー」ウィンドウを閉じます。処置は不要です。

    5. 「フィンガープリント」値をコピーし、「Datadog OCI統合タイル」「フィンガープリント」フィールドに貼り付けます。

  6. 秘密キーを構成します。

    1. ダウンロードした秘密キー・ファイル(.pem)をテキスト・エディタで開くか、ターミナル・コマンド(catなど)を使用してその内容を表示します。

    2. -----BEGIN PRIVATE KEY-----および-----END PRIVATE KEY-----行を含むキー全体をコピーします。

    3. 秘密キーをDatadog OCI統合タイル「秘密キー」フィールドに貼り付けます。

  7. postgresqlinteg (ルート)コンパートメントにDataDogPolicyという名前のポリシーを作成します。

  8. 手動エディタ・モードでポリシー・ビルダーを使用して、必要なポリシー・ステートメントを入力します。

    Allow group DatadogAuthGroup to read all-resources in tenancy
    

次に、テナンシおよびユーザー詳細を追加した後のDatadog OCI統合タイルの例を示します。

イメージ

タスク3: OCIスタックの作成

イメージ

「アイデンティティ」セクションに移動し、ルート・コンパートメントの下にポリシー・スタックを作成します。これにより、コネクタ・ハブはメトリックを読み取り、次の文で関数を呼び出すことができます。

Allow dynamic-group DatadogAuthGroup to read metrics in tenancy
Allow dynamic-group DatadogAuthGroup to use fn-function in tenancy
Allow dynamic-group DatadogAuthGroup to use fn-invocation in tenancy

Datadog統合のためにOCIでアイデンティティ・ポリシーを構成し、メトリック転送スタックをデプロイするには、次のタスクに従います:

タスク3.1: ポリシー・スタックの作成(ORM_policy_stack)

  1. Datadog OCI統合タイル「ポリシー・スタックの作成」をクリックし、提供されたリンク(必要なTerraformスクリプトを含む)を使用して、Oracle使用条件に同意します。

  2. 「作業ディレクトリ」ドロップダウン・メニューをクリックし、datadog-oci-orm/policy-setupを選択します。

  3. 「カスタムTerraformプロバイダの使用」の選択を解除します。

  4. わかりやすい名前(datadog-metrics-policy-setupなど)を入力し、デプロイメントするコンパートメントを選択します。

  5. 「次へ」をクリックし、動的グループおよびポリシーに名前を付け(またはデフォルト名を使用)、テナンシのホーム・リージョンが選択されていることを確認して、「作成」をクリックします。

タスク3.2: メトリック転送スタックの作成

リソースは、指定されたコンパートメントにデプロイされます。スタックを実行しているユーザーに適切なアクセス権があることを確認します。

  1. 「Datadog OCI統合」タイル「ポリシー・スタックの作成」をクリックし、Oracle使用条件に同意します。

  2. 「作業ディレクトリ」ドロップダウン・メニューをクリックし、datadog-oci-orm/metrics-setupを選択して、「カスタムTerraformプロバイダの使用」の選択を解除します。

  3. スタックに名前を付けてデプロイメント・コンパートメントを選択し、「次へ」をクリックします。

  4. テナンシ値は変更せずに、Datadog APIキーを入力し、US5エンドポイント(ocimetrics-intake.us5.datadoghq.com)を選択します。

  5. ネットワーク構成の場合、「VCNの作成」が選択されていることを確認し、VCN作成に適したコンパートメントを選択します。

  6. 「関数設定」セクションで、デフォルトのアプリケーション・シェイプをGENERIC_ARMのままにします。OCI Dockerレジストリのユーザー名とパスワード(アーティファクト・パスワード)を入力します。

  7. 「サービス・コネクタ・ハブ」バッチ・サイズを5000に設定し、「次へ」をクリックします。

  8. 「作成」をクリックします。

タスク3.3: 構成の確定

  1. Datadog OCI統合タイルに戻り、「構成の作成」をクリックして設定を完了します。

  2. このプロセスにより、Datadogのメトリックと機能がOCIとの統合用に適切に構成されます。

    イメージ

タスク4: OCIファンクションの作成

OCIコンソールでアプリケーションを作成するには、次のステップを実行します。

  1. 「アプリケーション」にナビゲートし、「アプリケーションの作成」を選択します。

  2. アプリケーション名を入力し、適切なVirtual Cloud Network (VCN)およびサブネットの詳細を選択して、「作成」をクリックします。

  3. 新しく作成したアプリケーションにアクセスするには、「リソース」「開始」を選択します。

    イメージ

  4. 「クラウド・シェルの起動」をクリックし、「リージョンのコンテキストの使用」から次のコマンドをコピーします。

    fn list context
    fn use context <region name>
    
  5. コンテキストを更新して、ファンクションのコンパートメントIDを含めます。

    fn update context oracle.compartment-id <compartment-id>
    
  6. コンテキストを更新して、使用するレジストリの場所を含めます。

    fn update context registry phx.ocir.io/<tenancy_name>/[YOUR-OCIR-REPO]
    

    ノート:コンテキスト内のphxを3桁のリージョン・コードに置き換えます。

  7. authトークンをパスワードとして使用してレジストリにログインします。

    docker login -u 'TENACNY_NAME/OCI_USERNAME' phx.ocir.io
    
  8. パスワードを要求されます。適切なパスワードを指定します。

    ノート:

    • phxを3桁のリージョン・コードに置き換えます。
    • Oracle Identity Cloud Serviceを使用している場合、ユーザー名は<tenancyname>/oracleidentitycloudservice/<username>です。
  9. hello-worldボイラープレート関数を生成します

    fn list apps
    fn init --runtime python datadog
    

    fn initコマンドは、func.pyfunc.yamlおよびrequirements.txtという3つのファイルを含むdatadogというフォルダを生成します。

  10. cd datadogコマンドを実行します。

  11. func.pyを開き、ファイルの内容を次のコード・スニペットに置き換えます。

    # oci-monitoring-metrics-to-datadog version 1.0.
    #
    # Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
    # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
    
    import io
    import json
    import logging
    import os
    import re
    import requests
    from fdk import response
    from datetime import datetime
    
    """
    This sample OCI Function maps OCI Monitoring Service Metrics to the DataDog
    REST API 'submit-metrics' contract found here:
    
    https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
    
    """
    
    # Use OCI Application or Function configurations to override these environment variable defaults.
    
    api_endpoint = os.getenv('DATADOG_METRICS_API_ENDPOINT', 'not-configured')
    api_key = os.getenv('DATADOG_API_KEY', 'not-configured')
    is_forwarding = eval(os.getenv('FORWARD_TO_DATADOG', "True"))
    metric_tag_keys = os.getenv('METRICS_TAG_KEYS', 'name, namespace, displayName, resourceDisplayName, unit')
    metric_tag_set = set()
    
    # Set all registered loggers to the configured log_level
    
    logging_level = os.getenv('LOGGING_LEVEL', 'INFO')
    loggers = [logging.getLogger()] + [logging.getLogger(name) for name in logging.root.manager.loggerDict]
    [logger.setLevel(logging.getLevelName(logging_level)) for logger in loggers]
    
    # Exception stack trace logging
    
    is_tracing = eval(os.getenv('ENABLE_TRACING', "False"))
    
    # Constants
    
    TEN_MINUTES_SEC = 10 * 60
    ONE_HOUR_SEC = 60 * 60
    
    # Functions
    
    def handler(ctx, data: io.BytesIO = None):
        """
        OCI Function Entry Point
        :param ctx: InvokeContext
        :param data: data payload
        :return: plain text response indicating success or error
        """
    
        preamble = " {} / event count = {} / logging level = {} / forwarding to DataDog = {}"
    
        try:
            metrics_list = json.loads(data.getvalue())
            logging.getLogger().info(preamble.format(ctx.FnName(), len(metrics_list), logging_level, is_forwarding))
            logging.getLogger().debug(metrics_list)
            converted_event_list = handle_metric_events(event_list=metrics_list)
            send_to_datadog(event_list=converted_event_list)
    
        except (Exception, ValueError) as ex:
            logging.getLogger().error('error handling logging payload: {}'.format(str(ex)))
            if is_tracing:
                logging.getLogger().error(ex)
    
    
    def handle_metric_events(event_list):
        """
        :param event_list: the list of metric formatted log records.
        :return: the list of DataDog formatted log records
        """
    
        result_list = []
        for event in event_list:
            single_result = transform_metric_to_datadog_format(log_record=event)
            result_list.append(single_result)
            logging.getLogger().debug(single_result)
    
        return result_list
    
    
    def transform_metric_to_datadog_format(log_record: dict):
        """
        Transform metrics to DataDog format.
        See: https://github.com/metrics/spec/blob/v1.0/json-format.md
        :param log_record: metric log record
        :return: DataDog formatted log record
        """
    
        series = [{
            'metric': get_metric_name(log_record),
            'type' : get_metric_type(log_record),
            'points' : get_metric_points(log_record),
            'tags' : get_metric_tags(log_record),
        }]
    
        result = {
            'series' : series
        }
        return result
    
    
    def get_metric_name(log_record: dict):
        """
        Assembles a metric name that appears to follow DataDog conventions.
        :param log_record:
        :return:
        """
    
        elements = get_dictionary_value(log_record, 'namespace').split('_')
        elements += camel_case_split(get_dictionary_value(log_record, 'name'))
        elements = [element.lower() for element in elements]
        return '.'.join(elements)
    
    
    def camel_case_split(str):
        """
        :param str:
        :return: Splits camel case string to individual strings
        """
    
        return re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', str)
    
    
    def get_metric_type(log_record: dict):
        """
        :param log_record:
        :return: The type of metric. The available types are 0 (unspecified), 1 (count), 2 (rate), and 3 (gauge).
        Allowed enum values: 0,1,2,3
        """
    
        return 0
    
    
    def get_now_timestamp():
        return datetime.now().timestamp()
    
    
    def adjust_metric_timestamp(timestamp_ms):
        """
        DataDog Timestamps should be in POSIX time in seconds, and cannot be more than ten
        minutes in the future or more than one hour in the past.  OCI Timestamps are POSIX
        in milliseconds, therefore a conversion is required.
    
        See https://docs.datadoghq.com/api/latest/metrics/#submit-metrics
        :param oci_timestamp:
        :return:
        """
    
        # positive skew is expected
        timestamp_sec = int(timestamp_ms / 1000)
        delta_sec = get_now_timestamp() - timestamp_sec
    
        if (delta_sec > 0 and delta_sec > ONE_HOUR_SEC):
            logging.getLogger().warning('timestamp {} too far in the past per DataDog'.format(timestamp_ms))
    
        if (delta_sec < 0 and abs(delta_sec) > TEN_MINUTES_SEC):
            logging.getLogger().warning('timestamp {} too far in the future per DataDog'.format(timestamp_ms))
    
        return timestamp_sec
    
    
    def get_metric_points(log_record: dict):
        """
        :param log_record:
        :return: an array of arrays where each array is a datapoint scalar pair
        """
    
        result = []
    
        datapoints = get_dictionary_value(dictionary=log_record, target_key='datapoints')
        for point in datapoints:
            dd_point = {'timestamp': adjust_metric_timestamp(point.get('timestamp')),
                        'value': point.get('value')}
    
            result.append(dd_point)
    
        return result
    
    
    def get_metric_tags(log_record: dict):
        """
        Assembles tags from selected metric attributes.
        See https://docs.datadoghq.com/getting_started/tagging/
        :param log_record: the log record to scan
        :return: string of comma-separated, key:value pairs matching DataDog tag format
        """
    
        result = []
    
        for tag in get_metric_tag_set():
            value = get_dictionary_value(dictionary=log_record, target_key=tag)
            if value is None:
                continue
    
            if isinstance(value, str) and ':' in value:
                logging.getLogger().warning('tag contains a \':\' / ignoring {} ({})'.format(key, value))
                continue
    
            tag = '{}:{}'.format(tag, value)
            result.append(tag)
    
        return result
    
    
    def get_metric_tag_set():
        """
        :return: the set metric payload keys that we would like to have converted to tags.
        """
    
        global metric_tag_set
    
        if len(metric_tag_set) == 0 and metric_tag_keys:
            split_and_stripped_tags = [x.strip() for x in metric_tag_keys.split(',')]
            metric_tag_set.update(split_and_stripped_tags)
            logging.getLogger().debug("tag key set / {} ".format (metric_tag_set))
    
        return metric_tag_set
    
    
    def send_to_datadog (event_list):
        """
        Sends each transformed event to DataDog Endpoint.
        :param event_list: list of events in DataDog format
        :return: None
        """
    
        if is_forwarding is False:
            logging.getLogger().debug("DataDog forwarding is disabled - nothing sent")
            return
    
        if 'v2' not in api_endpoint:
            raise RuntimeError('Requires API endpoint version "v2": "{}"'.format(api_endpoint))
    
        # creating a session and adapter to avoid recreating
        # a new connection pool between each POST call
    
        try:
            session = requests.Session()
            adapter = requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=10)
            session.mount('https://', adapter)
    
            for event in event_list:
                api_headers = {'Content-type': 'application/json', 'DD-API-KEY': api_key}
                logging.getLogger().debug("json to datadog: {}".format (json.dumps(event)))
                response = session.post(api_endpoint, data=json.dumps(event), headers=api_headers)
    
                if response.status_code != 202:
                    raise Exception ('error {} sending to DataDog: {}'.format(response.status_code, response.reason))
    
        finally:
            session.close()
    
    
    def get_dictionary_value(dictionary: dict, target_key: str):
        """
        Recursive method to find value within a dictionary which may also have nested lists / dictionaries.
        :param dictionary: the dictionary to scan
        :param target_key: the key we are looking for
        :return: If a target_key exists multiple times in the dictionary, the first one found will be returned.
        """
    
        if dictionary is None:
            raise Exception('dictionary None for key'.format(target_key))
    
        target_value = dictionary.get(target_key)
        if target_value:
            return target_value
    
        for key, value in dictionary.items():
            if isinstance(value, dict):
                target_value = get_dictionary_value(dictionary=value, target_key=target_key)
                if target_value:
                    return target_value
    
            elif isinstance(value, list):
                for entry in value:
                    if isinstance(entry, dict):
                        target_value = get_dictionary_value(dictionary=entry, target_key=target_key)
                        if target_value:
                            return target_value
    
    
    def local_test_mode(filename):
        """
        This routine reads a local json metrics file, converting the contents to DataDog format.
        :param filename: cloud events json file exported from OCI Logging UI or CLI.
        :return: None
        """
    
        logging.getLogger().info("local testing started")
    
        with open(filename, 'r') as f:
            transformed_results = list()
    
            for line in f:
                event = json.loads(line)
                logging.getLogger().debug(json.dumps(event, indent=4))
                transformed_result = transform_metric_to_datadog_format(event)
                transformed_results.append(transformed_result)
    
            logging.getLogger().debug(json.dumps(transformed_results, indent=4))
            send_to_datadog(event_list=transformed_results)
    
        logging.getLogger().info("local testing completed")
    
    
    """
    Local Debugging
    """
    
    if __name__ == "__main__":
        local_test_mode('oci-metrics-test-file.json')
    
  12. func.yamlを次のコードで更新します。DATADOG_TOKENをDatadog APIキーに置き換え、DATADOG_HOSTをRESTエンドポイント(https://http-intake.logs.datadoghq.com/v1/input)に置き換えます。RESTエンドポイントの詳細は、ログ収集および統合を参照してください。

    schema_version: 20180708
    name: datadogapp
    version: 0.0.1
    runtime: python
    entrypoint: /python/bin/fdk /function/func.py handler
    memory: 1024
    timeout: 120
    config:
    DATADOG_HOST: https://http-intake.logs.datadoghq.com/v1/input
    DATADOG_TOKEN: ZZZZZzzzzzzzzzz
    
  13. requirements.txtを次のコードで更新します。

    fdk
    dattime
    requests
    oci
    
  14. 次のコマンドを実行してアプリケーションを作成し、機能をデプロイして設定を完了します。

    fn create app datadog01 --annotation oracle.com/oci/subnetIds='["Provide your subnet OCID"]'
    
  15. 次のコマンドを実行してファンクションをデプロイし、設定を完了します。

    fn -v deploy --app datadog
    

タスク5: OCIコネクタ・ハブの設定

  1. OCIコンソールに移動し、「ロギング」「コネクタ」に移動して、「コネクタの作成」をクリックします。

  2. 「ソース」「モニタリング」に、「ターゲット」「ファンクション」に設定します。

  3. 「ソース接続の構成」で、適切な「メトリック・コンパートメント」および「ネームスペース」を選択します。たとえば、データベース・モニタリングの場合はoci_postgresqlです。

  4. 「ターゲットの構成」で、タスク4で作成した「コンパートメント」「ファンクション・アプリケーション」および「ファンクション」を選択します。

  5. プロンプトが表示されたら、「作成」をクリックして必要なポリシーを作成します。

  6. 「作成」をクリックして、OCIコネクタ・ハブの設定を確定します。

イメージ

タスク6: Datadogでのメトリックの表示

OCI Connector Hubは、関数をトリガーするように構成され、新しいメトリックが検出されるたびにDatadogにメトリックを取り込むことができます。「Datadog統合」タイルで、「メトリック」に移動し、サマリーを確認してOCI関連のメトリックを表示します。

イメージ

「Datadog統合」タイルで、「エクスプローラ」をクリックして、必要に応じて必要なOCIメトリックを分析および選択します。

イメージ

トラブル・シューティング

「メトリック・サマリー」ページにデータが表示されない場合は、「ログの有効化」を選択して、ファンクションがログを確認して問題をデバッグするためのロギングを有効にします。

イメージ

承認

その他の学習リソース

docs.oracle.com/learnの他のラボを確認するか、Oracle Learning YouTubeチャネルで無料のラーニング・コンテンツにアクセスしてください。また、education.oracle.com/learning-explorerにアクセスしてOracle Learning Explorerになります。

製品ドキュメントについては、Oracle Help Centerを参照してください。