线上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: kerberos
kafka:
consumer:
bootstrap-servers: hdp-1:6667,hdp-2:6667,hdp-3:6667
group-id: kafka-example
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
enable-auto-commit: true
auto-commit-interval: 1000
auto-offset-reset: earliest
producer:
bootstrap-servers: hdp-1:6667,hdp-2:6667,hdp-3:6667
acks: -1
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
security:
protocol: SASL_PLAINTEXT
jaas:
enabled: true
login-module: com.sun.security.auth.module.Krb5LoginModule
options:
useKeyTab: true
storeKey: true
useTicketCache: false
keyTab: file:D:/code/kerberos/kafka.keytab
principal: kafka/hdp-1@HADOOP.COM
properties:
sasl:
mechanism: GSSAPI
kerberos:
service:
name: kafka
min:
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,所以出现异常,票据刷新失败。