在Slack,我们管理一个复杂的Jenkins基础架构,以在发布之前不断构建和测试我们的移动应用程序。我们有数百个在各种不同环境中运行的工作。有一天很奇怪的事情发生了 - 我们的Jenkins UI停止工作,尽管工作继续运行。这篇文章是我们在这种状态的最终结束以及我们如何解决问题的细目。从这种体验中,我们还在分享一些关于jenkins问题的通用技巧。
作为我们自动化设置的一部分,我们不断运行完整性作业来检查我们的Jenkins节点。这些作业检查系统配置和属性,并查看是否有任何节点都会失败。当任何这些检查都失败并通知我们的移动版本时,检查会将Jenkins节点标记为脱机。通过懈怠释放团队。
通过这种知识,通过重新启动主要实例以便恢复Jenkins UI以便我们的Dev团队畅通无阻,我们开始进行调查。
我们认为诚信作业与UI之间存在一些联系,但UI的错误消息没有透露太多。然后我们试图重现错误。在一个单独的Jenkins暂存环境中,它运行了与我们的Jenkins生产环境相同的Jenkins版本和插件,我们以目的地操纵其中一个节点并重新划分的诚信作业。又一次,UI破坏了,所以我们可以重现这个问题。
注意:“Hudson.slaves”是Jenkins提供的界面的一部分;在我们自己的代码中,我们完全将“控制器”和“代理”作为首选术语完全转移到“控制器”和“代理”中。
我们使用一个常见的模式,将Jenkins Java API调用了offlineMessage类的实例,以提供特定节点的原因被标记为脱机以提供上下文:
def marknodeoffline(){ def node = getCurrentNode(env.node_name) node.tocomputer()。SeteMporallyOffline( True,Offlineause.Create(新的OfflineMessage()) ) } class offlinemessage扩展了org.jvnet.localizer.localizable { def消息 offlinemessage(){ 超级(null,null,[]) def timestr =新日期()。格式( " hh:mm dd / mm / yy z",timezone.getdefault() ) 此.Message ="由于损坏的主机文件和#34,该节点以$ {timestr}脱机; } 字符串toString(){ 这条信息 } 串toString(java.util.locale l){ ToString() } }
我们将典型的@ noncps注释添加到受影响的方法(toString())并重新启动作业。错误从作业日志消失,但jenkins ui仍然破产。因此,虽然它可能与缺少的注释有关时,这种变化并没有解决问题。再次再次再现行为,我们开始在尝试加载UI时拖尾日志。我们注意到以下错误:
2020-12-05 02:44:44.201 + 0000 [ID = 24]警告HIIINSTALLUNCAughtExceptionHandler#hargeException:捕获了未处理的异常与id a4bf874a-5029-4f37-b6e6-217ca8bb76de org.apache.commons.jelly.jellytagexception:jar:文件:/var/cache/jenkins/war/war/war/war/war/war/jenkins-core-2.235.5.jar!/hudson/model/computer/index.jelly:63:66:拒绝Undandboxed属性获取:offlinemessage.message
@Override. 公共对象IngetProperty( Invoker Invoker,Object Receiver,String属性 )扔扔扔{ 抛出新的securityException( "拒绝Undandboxed属性获得:" + getClassName(接收器)+"。" +财产 ); }
追溯这段代码变更的起源,我们发现最近的CVE(CVE-2020-2279)已由Jenkins维护者解决。作为安全修复的一个,Groovy-Sandbox限制了某些交互的访问。该库在脚本安全插件中打包,该插件已更新,我们的最新Jenkins升级更新。在我们的舞台环境中暂时降级了插件和管道的重新定位证实了我们的理论。当我们找到根本原因时,UI没有打破这个时间。现在,让我们解决它!
Jenkins的沙箱通常是构建管道问题的根本原因,因此我们的目标是用更简单的东西取代offlinemessage类以避免类似的问题。事实证明,Jenkins Java API提供了一个直接的接口,它将字符串作为参数,以描述脱机的节点的原因(与实现本地化的类的实例相比):hudson.slaves.offlineause.bycli。而不是实现自己的邮件类,而不是在内部使用的字符串用于离线导致消息,我们现在只需调用ByCli构造函数:
def createmessage(){ def timestr =新日期()。格式( " hh:mm dd / mm / yy z",timezone.getdefault() ) "由于损坏的主机文件" .tostring(),该节点是以$ {timestr}脱离的$ {timestr}; .tostring() } def marknodeoffline(){ def node = getCurrentNode(env.node_name) node.tocomputer()。SeteMporallyOffline( TRUE,NEW OFFLINEAUE.BYCLI(CreateMessage()) ) }
通过修复推出,我们使我们的Jenkins Integrity Check作业再次启用,并且能够确认它不会再打破Jenkins UI。欢呼!
这里有很少的经验教训,普遍适用于Jenkins供电的CI / CD基础架构的管理和故障排除。
有单独的环境和遵循分阶段的卷展览,特别是在云中,以减少爆炸半径,并尽可能快地向用户发货到用户身上的爆炸半径并检测潜在问题。拥有专门的Jenkins暂存环境,镜像我们的生产环境为我们提供了完美的实验场,以确认我们的假设对问题的根本原因。维护镜像需要精力,以确保环境之间的奇偶校验(例如插件版本)和自定义为Jenkins作业以在非生产环境中以“干运行”模式执行。我们在我们的维护任务和管道设置中占此占据。
Jenkins强大,揭示了许多方便的函数,向流水线运行。对于超出标准功能的任何内容,Jenkins允许通过其API进行自定义,这些API可以备份到Jenkins Pipeline库和自定义Groovy代码中。然而,这将管道和工作紧紧地耦合到底层的Jenkins基础设施。因此,我们建议使用Jenkins API保持自定义集成作为精简和简单,以降低Jenkins版本或插件更新时介绍破坏更改时捕获后卫的风险。
Groovy通常是用自定义代码扩展Jenkins的首选语言。它在特殊的Groovy Sandbox中执行,以增加安全姿势。 Jenkins维护者们做了一个惊人的工作,让沙箱的安全级别高(例如寻址CVES),这通常会导致收紧默认值。这可以通过惊喜捕获团队,特别是在更新插件作为常规维护任务时。始终建议研究变更乐,但特别推荐用于Jenkins生态系统的核心插件。
持续更新的且记录良好的流程图可以节省您的头痛,并且花在阅读Jenkins日志的数小时。这实际上适用于Devops World。在我们的情况下,这意味着Jenkins核心和关键插件的完全记录的升级过程。它还意味着拥有所有重要的管道和Jenkins就业,与我们升级和验证流程中包含的Jenkins API互动。
沿着Jenkins Internals的兔子洞表明我们的舞台环境的优势,也揭示了我们升级过程中的缺点。我们希望这个故障排除一直是对我们有富敏感!