全部產品
Search
文件中心

Container Service for Kubernetes:使用Log Service收集Spark作業日誌

更新時間:May 29, 2025

在ACK叢集中運行Spark作業時會產生大量的日誌分散在不同的Pods中,導致日誌管理變得困難。您可以通過Log Service(SLS)提供的一站式的日誌採集、加工、查詢與分析、可視化和警示等能力,實現對Spark日誌的高效管理。本文將介紹如何使用Log Service(SLS)對運行在ACK叢集中的Spark作業日誌進行管理。

前提條件

流程概述

本文將引導您完成如下步驟,協助您瞭解如何配置SLS以管理Spark作業產生的系統日誌和業務日誌。

  1. 構建Spark容器鏡像:構建包含了log4j JSON template layout依賴的Spark容器鏡像,並推送到您的鏡像倉庫中。

  2. 配置Log4j2日誌:建立一個ConfigMap資源,用於配置Log4j2日誌,設定記錄層級為INFO,並將日誌列印格式設定為JSONL格式。

  3. 建立日誌採集配置:建立一個AliyunConfig資源,Log Service將相應地在指定的日誌庫中建立採集配置,對通過Spark operator提交的Spark作業日誌進行收集。

  4. 提交樣本Spark作業:建立並運行樣本Spark作業,查看Pod日誌輸出是否為JSONL格式,並對部分欄位含義進行說明。

  5. 查詢和分析Spark日誌:登入SLS控制台,查詢和分析指定時間段內的Spark作業日誌。

  6. (可選)環境清理:在完成測試後,清理無需使用的Spark作業和資源,避免產生額外的費用。

步驟一:構建Spark容器鏡像

建立如下Dockerfile(本樣本使用Spark 3.5.3版本),並將所需的依賴項添加到Spark的類路徑中。構建完成後,將該鏡像推送到您的鏡像倉庫。為了方便日誌的收集和解析,我們將採用JSONL格式輸出日誌。

ARG SPARK_IMAGE=<SPARK_IMAGE>  # 需要將<SPARK_IMAGE>替換成您自己的Spark基礎鏡像。

FROM ${SPARK_IMAGE}

# Add dependency for log4j-layout-template-json
ADD --chown=spark:spark --chmod=644 https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-layout-template-json/2.24.1/log4j-layout-template-json-2.24.1.jar ${SPARK_HOME}/jars

步驟二:配置Log4j2日誌

使用如下內容建立一個名為spark-log-conf.yaml的檔案,並將記錄層級設定為INFO,同時配置日誌列印格式為JSONL格式,日誌模板採用Elastic Common Schema(ECS),一種標準化的日誌格式。有關更多配置請參見採集Log4j日誌

apiVersion: v1
kind: ConfigMap
metadata:
  name: spark-log-conf
  namespace: default
data:
  log4j2.properties: |
    # Set everything to be logged to the console and file
    rootLogger.level = info

    rootLogger.appenderRefs = console, file
    rootLogger.appenderRef.console.ref = STDOUT
    rootLogger.appenderRef.file.ref = FileAppender

    appender.console.name = STDOUT
    appender.console.type = Console
    appender.console.layout.type = JsonTemplateLayout
    appender.console.layout.eventTemplateUri = classpath:EcsLayout.json

    appender.file.name = FileAppender
    appender.file.type = File
    appender.file.fileName = /opt/spark/logs/spark.log
    appender.file.layout.type = JsonTemplateLayout
    appender.file.layout.eventTemplateUri = classpath:EcsLayout.json

執行如下命令建立ConfigMap資源。

kubectl apply -f spark-log-conf.yaml

預期輸出:

configmap/spark-log-conf created

步驟三:建立日誌採集配置

使用如下內容建立一個名為aliyun-log-config.yaml的AliyunLogConfig資訊清單檔,並在其中替換<SLS_PROJECT>為您的SLS Project的名稱,<SLS_LOGSTORE> 為您的SLS Logstore名稱。有關更多配置選項請參見使用AliyunLogConfig管理採集配置

apiVersion: log.alibabacloud.com/v1alpha1
kind: AliyunLogConfig
metadata:
  name: spark
  namespace: default
spec:
  # (可選)目標project名稱(預設為 k8s-log-<Your_Cluster_ID>)
  project: <SLS_PROJECT>

  # Logstore 名稱。如果您所指定的Logstore不存在,Log Service會自動建立。
  logstore: <SLS_LOGSTORE>

  # 採集配置。
  logtailConfig:
    # 採集配置名稱。
    configName: spark

    # 資料來源類型,file表示文本日誌
    inputType: file

    # 日誌輸入的相關配置。
    inputDetail:
      # 記錄檔所在目錄。
      logPath: /opt/spark/logs

      # 記錄檔名稱,支援萬用字元。
      filePattern: '*.log'

      # 記錄檔編碼。
      fileEncoding: utf8

      # 日誌類型。
      logType: json_log
      localStorage: true
      key:
      - content
      logBeginRegex: .*
      logTimezone: ''
      discardNonUtf8: false
      discardUnmatch: true
      preserve: true
      preserveDepth: 0
      regex: (.*)
      outputType: LogService
      topicFormat: none
      adjustTimezone: false
      enableRawLog: false

      # 採集容器中的文本日誌。
      dockerFile: true

      # 進階配置。
      advanced:
        # 容器元資訊預覽。
        collect_containers_flag: true

        # Kubernetes採集配置。
        k8s:
          # 按照標籤過濾Pod。
          IncludeK8sLabel:
            sparkoperator.k8s.io/launched-by-spark-operator: "true"

          # 按照容器名稱過濾容器。
          K8sContainerRegex: "^spark-kubernetes-(driver|executor)$"

          # 額外的日誌標籤配置。
          ExternalK8sLabelTag:
            spark-app-name: spark-app-name
            spark-version: spark-version
            spark-role: spark-role
            spark-app-selector: spark-app-selector
            sparkoperator.k8s.io/submission-id: sparkoperator.k8s.io/submission-id

      # Tlog外掛程式。
      plugin:
        processors:
        # 日誌分隔。
        - type: processor_split_log_string
          detail:
            SplitKey: content
            SplitSep: ''

        # 欄位JSON解析。
        - type: processor_json
          detail:
            ExpandArray: false
            ExpandConnector: ''
            ExpandDepth: 0
            IgnoreFirstConnector: false
            SourceKey: content
            KeepSource: false
            KeepSourceIfParseError: true
            NoKeyError: false
            UseSourceKeyAsPrefix: false

        # 日誌時間戳記提取。
        - type: processor_strptime
          detail:
            SourceKey: '@timestamp'
            Format: '%Y-%m-%dT%H:%M:%S.%fZ'
            KeepSource: false
            AdjustUTCOffset: true
            UTCOffset: 0
            AlarmIfFail: false

執行如下命令建立日誌配置。

kubectl apply -f aliyun-log-config.yaml

您可以按照以下步驟查看建立的日誌庫和日誌配置。

  1. 登入Log Service控制台

  2. 在Project列表地區,單擊目標Project。

    image

  3. 日誌儲存 > 日誌庫頁簽中,單擊目標日誌庫前面的>,依次選擇資料接入 > Logtail配置

    image

  4. 單擊目標Logtail採集配置,查看Logtail採集配置詳情。

步驟四:提交樣本Spark作業

使用以下內容建立一個名為spark-pi.yaml的SparkApplication資訊清單檔。

apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: <SPARK_IMAGE>
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.3.jar
  arguments: 
  - "5000"
  sparkVersion: 3.5.3
  sparkConfigMap: spark-log-conf
  driver:
    cores: 1
    memory: 512m
    serviceAccount: spark-operator-spark
  executor:
    instances: 1
    cores: 1
    memory: 4g

執行如下命令提交作業。

kubectl apply -f spark-pi.yaml

等待作業執行結束後,查看Driver Pod日誌的最後10行。

kubectl logs  --tail=10 spark-pi-driver  

預期輸出:

{"@timestamp":"2024-11-20T11:45:48.487Z","ecs.version":"1.2.0","log.level":"WARN","message":"Kubernetes client has been closed.","process.thread.name":"-937428334-pool-19-thread-1","log.logger":"org.apache.spark.scheduler.cluster.k8s.ExecutorPodsWatchSnapshotSource"}
{"@timestamp":"2024-11-20T11:45:48.585Z","ecs.version":"1.2.0","log.level":"INFO","message":"MapOutputTrackerMasterEndpoint stopped!","process.thread.name":"dispatcher-event-loop-7","log.logger":"org.apache.spark.MapOutputTrackerMasterEndpoint"}
{"@timestamp":"2024-11-20T11:45:48.592Z","ecs.version":"1.2.0","log.level":"INFO","message":"MemoryStore cleared","process.thread.name":"main","log.logger":"org.apache.spark.storage.memory.MemoryStore"}
{"@timestamp":"2024-11-20T11:45:48.592Z","ecs.version":"1.2.0","log.level":"INFO","message":"BlockManager stopped","process.thread.name":"main","log.logger":"org.apache.spark.storage.BlockManager"}
{"@timestamp":"2024-11-20T11:45:48.596Z","ecs.version":"1.2.0","log.level":"INFO","message":"BlockManagerMaster stopped","process.thread.name":"main","log.logger":"org.apache.spark.storage.BlockManagerMaster"}
{"@timestamp":"2024-11-20T11:45:48.598Z","ecs.version":"1.2.0","log.level":"INFO","message":"OutputCommitCoordinator stopped!","process.thread.name":"dispatcher-event-loop-1","log.logger":"org.apache.spark.scheduler.OutputCommitCoordinator$OutputCommitCoordinatorEndpoint"}
{"@timestamp":"2024-11-20T11:45:48.602Z","ecs.version":"1.2.0","log.level":"INFO","message":"Successfully stopped SparkContext","process.thread.name":"main","log.logger":"org.apache.spark.SparkContext"}
{"@timestamp":"2024-11-20T11:45:48.604Z","ecs.version":"1.2.0","log.level":"INFO","message":"Shutdown hook called","process.thread.name":"shutdown-hook-0","log.logger":"org.apache.spark.util.ShutdownHookManager"}
{"@timestamp":"2024-11-20T11:45:48.604Z","ecs.version":"1.2.0","log.level":"INFO","message":"Deleting directory /var/data/spark-f783cf2e-44db-452c-83c9-738f9c894ef9/spark-2caa5814-bd32-431c-a9f9-a32208b34fbb","process.thread.name":"shutdown-hook-0","log.logger":"org.apache.spark.util.ShutdownHookManager"}
{"@timestamp":"2024-11-20T11:45:48.606Z","ecs.version":"1.2.0","log.level":"INFO","message":"Deleting directory /tmp/spark-dacdfd95-f166-4b23-9312-af9052730417","process.thread.name":"shutdown-hook-0","log.logger":"org.apache.spark.util.ShutdownHookManager"}

輸出日誌已按JSONL格式列印,各欄位含義如下:

  • @timestamp:日誌記錄產生時間。

  • ecs.version:Elastic Common Schema (ECS) 版本號碼(ECS為標準化日誌格式)。

  • log.level:記錄層級。

  • message:日誌訊息。

  • process.thread.name:產生該日誌的線程名稱。

  • log.logger:記錄該日誌的logger名稱。

步驟五:查詢和分析Spark日誌

您可以通過查詢與分析快速指引,指定作業執行的時間範圍,以確認日誌是否已成功收集。

image

(可選)步驟六:環境清理

如果您已體驗完本教程,相關資源如不再需要,可以通過執行以下命令進行刪除。

執行如下命令刪除Spark作業。

kubectl delete -f spark-pi.yaml

執行如下命令刪除採集配置。

kubectl delete -f aliyun-log-config.yaml

執行如下命令刪除Log4j2日誌配置。

kubectl delete -f spark-log-conf.yaml