Files
article/spark/spark kerberos认证与 YARN 的代理机制.md
2025-10-14 10:58:35 +08:00

8.2 KiB
Raw Blame History

spark kerberos认证与 YARN 的代理机制

在 Spark 中,自行实现 Kerberos 认证与依赖YARN 的代理认证机制存在本质区别,尤其当 YARN 本身已启用 Kerberos 时,需要明确两者的协作方式。以下是详细说明:

一、Spark 自行认证 vs YARN 代理认证的核心区别

维度 Spark 自行实现认证 YARN 代理认证机制
认证主体 直接使用 Spark 应用的 Kerberos Principalspark@EXAMPLE.COM YARN 作为代理,使用 YARN 的 Principal 替 Spark 完成认证
凭证管理 由 Spark 应用手动加载 Keytab 并定期刷新 TGT如通过定时任务 YARN 自动为 Spark 分配临时凭证Delegation Token并管理生命周期
适用场景 - 长期运行的应用(如 Streaming
- 非 YARN 模式Standalone/K8s
- 需要细粒度控制认证逻辑
- 短期批处理作业
- YARN 集群模式
- 依赖 YARN 统一管理凭证
配置方式 代码中手动调用 UserGroupInformation.loginUserFromKeytab(),需指定 Keytab 路径 通过 --keytab--principal 提交参数,依赖 YARN 自动处理
凭证分发范围 需确保 Keytab 能被 Driver 和 Executor 访问(如通过 --files 分发) YARN 自动将临时凭证分发到所有容器Container无需显式处理

二、当 YARN 已启用 Kerberos 时的处理方式

若 YARN 集群本身启用了 Kerberos 认证(即访问 YARN 资源管理器需要 Kerberos 凭证Spark 应用需要同时处理两层认证

  1. Spark 与 YARN 的认证:证明 Spark 有权限提交作业到 YARN。
  2. Spark 与其他服务的认证:证明 Spark 有权限访问 HDFS、Hive、Kafka 等服务。

此时需结合两种认证方式,具体配置如下:

1. 提交作业时的基础配置

必须通过 --keytab--principal 告知 YARN 应用的身份,确保 Spark 能成功提交到 YARN 并获取资源:

spark-submit \
  --master yarn \
  --deploy-mode cluster \
  --keytab /path/to/spark.keytab \          # Spark 应用的 Keytab
  --principal spark@EXAMPLE.COM \           # Spark 应用的 Principal
  --files /path/to/hive-site.xml \          # 可选:分发 Hive 配置
  --class com.example.SparkApp \
  your-application.jar

核心作用

  • YARN 通过 --keytab--principal 验证 Spark 提交者的身份,允许作业提交。
  • YARN 会自动为 Spark 申请访问 HDFS、Hive 等服务的临时凭证Delegation Token并分发给 Executor。

2. 长期运行应用的特殊处理

对于 Streaming 等长期运行的应用YARN 自动分配的临时凭证可能过期(默认有效期较短,通常几小时),需结合自行认证逻辑刷新凭证:

import org.apache.hadoop.security.UserGroupInformation
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.streaming.Trigger
import java.util.concurrent.{Executors, TimeUnit}
import org.slf4j.LoggerFactory

object SparkYARNKerberosApp {
  private val logger = LoggerFactory.getLogger(SparkYARNKerberosApp.getClass)
  
  // 从提交参数中获取 Principal通过 --conf 传递)
  private def getPrincipal(spark: SparkSession): String = {
    spark.conf.get("spark.yarn.principal")
  }
  
  // 获取 YARN 分发的 Keytab 本地路径(通过 --files 分发)
  private def getKeytabPath(): String = {
    val keytabFileName = System.getenv("SPARK_KEYTAB_FILENAME")  // 从环境变量传入文件名
    new java.io.File(keytabFileName).getAbsolutePath
  }

  def main(args: Array[String]): Unit = {
    // 1. 创建 SparkSession已通过 --keytab 和 --principal 完成 YARN 认证)
    val spark = SparkSession.builder()
      .appName("Spark on YARN with Kerberos")
      .enableHiveSupport()
      .getOrCreate()
    
    // 2. 获取 YARN 传递的认证信息
    val principal = getPrincipal(spark)
    val keytabPath = getKeytabPath()
    logger.info(s"YARN 认证信息 - Principal: $principal, Keytab: $keytabPath")
    
    // 3. 启动长期任务的凭证刷新机制(处理 YARN 临时凭证过期问题)
    startCredentialRefresh(principal, keytabPath)
    
    // 4. 运行长期 Streaming 任务
    runStreamingJob(spark)
    
    spark.stop()
  }
  
  /**
   * 启动凭证刷新任务,同时兼容 YARN 代理凭证和手动认证
   */
  private def startCredentialRefresh(principal: String, keytabPath: String): Unit = {
    val scheduler = Executors.newSingleThreadScheduledExecutor()
    
    // 每 12 小时刷新一次(短于 YARN 临时凭证有效期)
    scheduler.scheduleAtFixedRate(() => {
      try {
        val currentUser = UserGroupInformation.getCurrentUser()
        
        // 优先使用 YARN 代理的凭证刷新
        if (currentUser.hasKerberosCredentials) {
          currentUser.checkTGTAndReloginFromKeytab()
          logger.info("通过 YARN 代理刷新凭证成功")
        } else {
          // YARN 凭证失效时,手动重新登录
          logger.warn("YARN 代理凭证失效,尝试手动登录")
          UserGroupInformation.loginUserFromKeytab(principal, keytabPath)
        }
      } catch {
        case e: Exception =>
          logger.error("凭证刷新失败,强制重新登录", e)
          UserGroupInformation.loginUserFromKeytab(principal, keytabPath)
      }
    }, 0, 12, TimeUnit.HOURS)
    
    // 应用退出时关闭线程池
    sys.addShutdownHook {
      scheduler.shutdown()
    }
  }
  
  /**
   * 示例访问需要认证的服务Hive/Kafka 等)
   */
  private def runStreamingJob(spark: SparkSession): Unit = {
    import spark.implicits._
    
    // 从 Hive 读取数据(依赖 YARN 分配的 Hive 凭证)
    val hiveDF = spark.sql("SELECT * FROM secure_db.secure_table")
    
    // 写入 HDFS依赖 YARN 分配的 HDFS 凭证)
    val query = hiveDF.writeStream
      .outputMode("append")
      .format("parquet")
      .option("path", "hdfs:///user/spark/streaming/output")
      .trigger(Trigger.ProcessingTime("5 minutes"))
      .start()
    
    query.awaitAnyTermination()
  }
}

3. 关键配置说明

  • YARN 代理凭证的局限性YARN 分配的临时凭证(如 HDFS Delegation Token有有效期通常由 dfs.delegation.token.lifetime 控制,默认 7 天),长期运行的应用需主动刷新。
  • 双重认证的协作
    • 短期作业:仅依赖 YARN 代理认证即可(无需手动处理)。
    • 长期作业:需在代码中添加定时刷新逻辑,优先使用 YARN 代理凭证,失效时通过 Keytab 重新登录。
  • 安全配置传递:确保 hive-site.xmlcore-site.xml 等配置文件通过 --filesHADOOP_CONF_DIR 传递给 Spark避免认证配置缺失。

三、常见问题与解决方案

  1. YARN 提交失败:No valid credentials

    • 原因:未通过 --keytab--principal 提供 Spark 应用的身份,或 Keytab 权限错误。
    • 解决:确保 Keytab 权限为 600,且 --principal 与 Keytab 中的主体一致。
  2. 长期运行后 HDFS 访问失败:Token expired

    • 原因YARN 分配的 HDFS 临时凭证过期,且未手动刷新。
    • 解决:在代码中添加定时刷新逻辑(如示例中的 startCredentialRefresh 方法)。
  3. Executor 认证失败:Keytab not found

    • 原因Keytab 未通过 --files 分发到 Executor或路径硬编码导致无法访问。
    • 解决:通过 --files 分发 Keytab代码中通过当前工作目录动态获取路径new File(keytabFileName).getAbsolutePath)。

总结

  • 短期作业:直接依赖 YARN 代理认证(--keytab + --principal),无需额外代码处理。
  • 长期作业:需结合 YARN 代理认证和手动刷新逻辑,确保凭证不过期。
  • 核心原则YARN 负责 Spark 与集群资源的认证Spark 自行处理与其他服务的长期认证,两者协作实现全链路安全访问。