附註:

使用 Datadog 使用 PostgreSQL 監控 Oracle Cloud Infrastructure Database

簡介

Oracle Cloud Infrastructure (OCI) 是一個強大且可高度擴展的雲端平台,旨在滿足現代企業的需求。它提供適用於運算、儲存、網路、資料庫和應用程式開發的全方位服務套件,針對效能、安全性和成本效益進行最佳化。OCI 適合執行雲端原生和傳統工作負載,為企業提供彈性且可靠的基礎架構。

Datadog 是一個全方位的雲端監控和分析平台,可協助組織取得 IT 基礎架構、應用程式和服務的端對端可見性。它可跨動態的混合雲端環境實現即時監控、疑難排解和效能最佳化。Datadog 與廣泛的工具、平台和服務緊密整合,使其成為現代 DevOps 和 IT 營運團隊的多功能解決方案。

本教學課程示範如何使用 PostgreSQL 和 Datadog 使用者建立有效率且可擴展的解決方案,以使用 OCI Connector Hub 和 OCI Functions 將度量從 OCI 順暢地傳輸至 Datadog。

目標

必要條件

工作 1:建立 Datadog 帳戶

  1. 使用 Datadog 網站在 Datadog 整合工具中設定帳戶。提供必要的帳戶詳細資訊,並透過設定適當的環境設定值來完成代理程式設定。

  2. 安裝 Datadog 代理程式,以 PostgreSQL 從 OCI 資料庫收集度量和事件。如需有關設定及設定「Datadog 代理程式」的詳細資訊,請參閱設定 Datadog 代理程式。如需有關 Datadog Agent 疑難排解和除錯的其他詳細資訊,請參閱基本資料代理程式使用狀況

  3. 選取 OCI 作為整合,然後繼續進行安裝。下圖顯示 Datadog 的 OCI 整合後續安裝。

    圖像

  4. 按一下新增租用戶,然後輸入您的租用戶 OCID原建立區域資訊。

    圖像

作業 2:建立 Datadog 認證資源

在 Oracle Cloud Infrastructure (OCI) 中建立 Datadog 認證使用者、群組和原則。

  1. 若要建立網域,請導覽至識別並建立名為 DataDog 的網域。

  2. 建立名為 DatadogAuthGroup 的群組。

  3. 使用您的電子郵件地址建立名為 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

若要設定識別原則並在 OCI 中部署 Datadog 整合的度量轉送堆疊,請依照下列作業進行:

作業 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. 請將租用戶值保持未修改,輸入您的資料通道 API 金鑰,然後選取 US5 端點 (ocimetrics-intake.us5.datadoghq.com)。

  5. 若為網路組態,請確定已勾選建立 VCN ,並且選取適當的區間以建立 VCN。

  6. 函數設定值區段中,將預設應用程式資源配置保留為 GENERIC_ARM。輸入 OCI Docker 登錄使用者名稱和密碼 (使用者自建物件密碼)。

  7. Service Connector Hub 批次大小設為 5000 ,然後按一下下一步

  8. 按一下建立

作業 3.3:完成組態

  1. 返回 Datadog OCI 整合磚,然後按一下建立組態以完成設定。

  2. 此流程可確保已正確設定 Datadog 指標和功能,以與 OCI 整合。

    圖像

作業 4:建立 OCI 函數

若要在 OCI 主控台中建立應用程式,請依照下列步驟進行:

  1. 導覽至應用程式,然後選取建立應用程式

  2. 輸入應用程式名稱,選取適當的虛擬雲端網路 (VCN) 和子網路詳細資訊,然後按一下建立

  3. 若要存取新建立的應用程式,請在資源底下,選取開始使用

    圖像

  4. 按一下啟動 Cloud Shell ,然後從使用您區域的相關資訊環境複製下列命令。

    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 取代為三位數的區域代碼。

  7. 使用認證權杖作為您的密碼以登入登錄。

    docker login -u 'TENACNY_NAME/OCI_USERNAME' phx.ocir.io
    
  8. 系統將會提示您輸入密碼。提供您適當的密碼。

    注意:

    • phx 取代為三位數的區域碼。
    • 如果您使用 Oracle Identity Cloud Service,您的使用者名稱就是 <tenancyname>/oracleidentitycloudservice/<username>
  9. 產生 hello-world 樣板函數。

    fn list apps
    fn init --runtime python datadog
    

    fn init 命令將產生名為 datadog 的資料夾,其中包含三個檔案;func.pyfunc.yamlrequirements.txt

  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:檢視資料對話方塊中的度量

OCI Connector Hub 現在設定為觸發函數,可在偵測到新的度量時,將度量擷取至資料狗。在「資料狗整合」磚塊中,瀏覽至度量並複查摘要以檢視 OCI 相關度量。

圖像

在「資料狗整合」磚塊中,按一下總管即可視需要分析並選取所需的 OCI 度量,

圖像

疑難排解

如果測量結果摘要頁面中沒有顯示任何資料,請選取啟用日誌,讓您的函數能夠複查日誌並除錯問題。

圖像

認可

其他學習資源

探索 docs.oracle.com/learn 上的其他實驗室,或存取 Oracle Learning YouTube 頻道上的更多免費學習內容。此外,請造訪 education.oracle.com/learning-explorer 以成為 Oracle Learning Explorer。

如需產品文件,請造訪 Oracle Help Center