1.问题描述
当前Knox和Spark History Server都是容器化部署,Knox转发Spark History WebUI的地址是pod的名称,当Spark History Server返回302 response时,response header中的Location是内网地址,也就是“pod名称+端口号+request path”,浏览器无法识别,导致无法访问此网站。
如下图所示:
如果Spark History Server返回非301或302 response时,则浏览器能正常得到200 response。
2.源码分析
经过多次测试,发现异常是偶现的,这里就需要通过源码来看看整个请求的过程。
(1)请求的入口在HistoryServer.loaderServlet。
(2)如果request path中不存在attemptId,则通过provider获取最后一个attemptId。
(3)然后会调用loadAppUi方法,把application ui页面信息加载到缓存中,缓存操作对象为ApplicationCache。缓存application的大小由参数spark.history.retainedApplications设置,默认值为50。ApplicationCache中会用到CacheLoader,这是个guava的类,当缓存key不存在或者缓存的内容过期时,调用get方法会触发一次重新的load。
(4)如果(2)中的attemptId,会进行一次重定向,返回302 response。
这里重定向触发的原因有:缓存中不存在此application;任务正在执行,本地缓存的内容与HDFS上event信息有差异,也就是缓存过期了。
3.解决方案
在重定向的地方,将redirect url进行改写,改成类似如下格式:
var redirectUrl = httpResponse.encodeRedirectURL(requestURI + queryStr)
val reqHost = httpRequest.getHeader("X-Forwarded-Host")
val proxyPrefix = s"${redirectProtocol}://${reqHost}/${redirectPrefix}/"
redirectUrl = s"${httpResponse.encodeURL(proxyPrefix)}${redirectUrl}"
httpResponse.sendRedirect(redirectUrl)
这里reqHost需要根据需要进行修改,因为有些请求不会带有X-Forwarded-Host header。
有三处需要进行修改:
(1)core/src/main/scala/org/apache/spark/ui/JettyUtils.scala中的ResponseWrapper.sendRedirect方法
(2)core/src/main/scala/org/apache/spark/deploy/history/HistoryServer.scala中的loaderServlet属性定义中
(3)core/src/main/scala/org/apache/spark/deploy/history/ApplicationCache.scala有个内部类方法ApplicationCacheCheckFilter.doFilter
修改完成后,对于redirect的相应,请求地址会变成远程地址,而不是内网地址。
当然,这里是从spark侧对问题进行修复。可能也可以从knox对response header进行修改,这里需要进一步的探索。