线上kafka使用了 kerberos 认证,每隔24小时,票据过期,无法自动续期,出现消息发送失败问题。
从日志可以发现会有如下报错:
2023-09-14 17:48:47,144 [kafka-kerberos-refresh-thread-kafka/hdp-1@HADOOP.COM] [] WARN [o.a.kafka.common.security.kerberos.KerberosLogin] KerberosLogin.java:216 - [Principal=kafka/hdp-1@HADOOP.COM]: Error when trying to renew with TicketCache, but will retry
java.io.IOException: Cannot run program "/usr/bin/kinit": CreateProcess error=2, 系统找不到指定的文件。at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1128)at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1071)at org.apache.kafka.common.utils.Shell.runCommand(Shell.java:85)at org.apache.kafka.common.utils.Shell.run(Shell.java:76)at org.apache.kafka.common.utils.Shell$ShellCommandExecutor.execute(Shell.java:204)at org.apache.kafka.common.utils.Shell.execCommand(Shell.java:268)at org.apache.kafka.common.utils.Shell.execCommand(Shell.java:255)at org.apache.kafka.common.security.kerberos.KerberosLogin.lambda$login$0(KerberosLogin.java:212)at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.io.IOException: CreateProcess error=2, 系统找不到指定的文件。at java.base/java.lang.ProcessImpl.create(Native Method)at java.base/java.lang.ProcessImpl.<init>(ProcessImpl.java:492)at java.base/java.lang.ProcessImpl.start(ProcessImpl.java:153)at java.base/java.lang.ProcessBuilder.start(ProcessBuilder.java:1107)... 8 common frames omitted
由于线上系统日志输出比较多,也并没有对warn级别日志做单独的过滤,所以最初并没有发现这个提示。从报错信息看,相关业务逻辑是在 KerberosLogin 类中。
其实每次启动项目(springboot+kakfa),KerberosLogin 会初始化 kerberos 认证过程,如下图:
yml文件相关配置:
spring:application:name: kerberoskafka:consumer:bootstrap-servers: hdp-1:6667,hdp-2:6667,hdp-3:6667group-id: kafka-examplekey-deserializer: org.apache.kafka.common.serialization.StringDeserializervalue-deserializer: org.apache.kafka.common.serialization.StringDeserializerenable-auto-commit: trueauto-commit-interval: 1000auto-offset-reset: earliestproducer:bootstrap-servers: hdp-1:6667,hdp-2:6667,hdp-3:6667acks: -1key-serializer: org.apache.kafka.common.serialization.StringSerializervalue-serializer: org.apache.kafka.common.serialization.StringSerializersecurity:protocol: SASL_PLAINTEXTjaas:enabled: truelogin-module: com.sun.security.auth.module.Krb5LoginModuleoptions:useKeyTab: truestoreKey: trueuseTicketCache: falsekeyTab: file:D:/code/kerberos/kafka.keytabprincipal: kafka/hdp-1@HADOOP.COMproperties:sasl:mechanism: GSSAPIkerberos:service:name: kafkamin:time:before:relogin: 1
查看 KerberosLogin 源码,isUsingTicketCache (是否使用票据缓存),对应配置中的useTicketCache:
如果 useTicketCache 设置成 true,会经过如下逻辑:
tips: 如果大家查看源码的话,可以关注一下 KerberosLogin 的 login 方法,此方法创建了一个 定时任务的线程,用来解决票据刷新问题的,具体代码我就不贴图啦。
此方法会调用 shell 命令:
后续有查看了kafka的官方文档,发现有相关的配置项:
文档链接:Kafka 中文文档 - ApacheCNApache Kafka: A Distributed Streaming Platform.https://kafka.apachecn.org/documentation.html#producerconfigs
至此找到了问题出现的原因,由于线上项目 useTicketCache 设置成了 true, 导致每次票据刷新的定时任务都会经过上述逻辑,调用 Kerberos kinit 命令,但是项目运行的服务器并没有 kinit,所以出现异常,票据刷新失败。