Go 自动埋点,编译时插桩是未来方向
在过去的几年中,Datadog 一直致力于实现 APM 应用程序检测工作的自动化。我们通过使用特定于运行时的检测技术来实现这一点,这些技术允许 SRE 通过配置或环境变量启用分布式跟踪,而无需修改原始源代码。这简化了跟踪设置,并且通常完全消除了手动操作的需要。
不幸的是,并非所有运行时都提供此类功能。Go(我们用户最喜爱的语言之一)会编译为本机二进制文件,这使得在运行时注入检测变得困难。因此,之前用户需要花费大量开发时间手动修改 Go 应用程序来做埋点对接 APM。
这就是我们创建 Orchestrion 的原因,这是一个新工具,可以在编译时处理 Go 源代码并自动插桩以生成 Datadog APM 跟踪数据。
Orchestrion 简介
Orchestrion 与标准 Go 工具链交互,在将源代码发送到编译器时对其进行检查和修改。在抽象语法树 (AST) 级别操作代码意味着对程序所做的所有更改都由 Go 编译器进行验证和类型检查,与其他手写的 Go 代码一样,并无区别对待。这使得 Orchestrion 可以不受限制地访问应用程序的所有行为(直至标准库),同时防止可能由直接修改编译二进制文件而导致的大量错误。由于所有代码都经过正常的 Go 编译器,因此修改也不会受到某些编译器优化(例如内联)的影响,并且修改后的代码会经过所有常见的编译器优化过程,从而减少运行时开销。
Orchestrion 还在修改后的源代码中插入 Go //line
指令,以便行号不受修改的影响,并且被检测应用程序生成的堆栈跟踪指向原始源代码中的正确位置。
Orchestrion 建立在受面向方面编程 (AoP) 启发的框架上,其中代码修改通过配对连接点来指定 - 选择要修改 AST 的哪些部分 - 描述要进行的修改。这使得为 Orchestrion 编写新的集成变得容易,并且代码级修改比二进制级检测更容易推理。
为什么我们选择编译时埋点
在选择编译时埋点作为我们的新方法之前,我们考虑了业界正在使用的两种替代技术:二进制补丁和 eBPF。
我们将二进制补丁定义为一组技术,涉及修改已编译应用程序的机器代码和内存,以注入检测代码并传播跟踪和跨度 ID。对于 eBPF,我们指的是使用写入用户空间内存的 uprobe 和 eBPF 程序来实现相同目的的方法。
我们的研究揭示了每种技术各自的优点和缺点:
虽然上表提供了一个简化的概述,但比较不同技术的实际情况非常复杂,并且依赖于许多假设。我们可以专门写几篇文章来讨论这个话题,但现在我们将尝试涵盖我们考虑过的最重要的方面。
安全性、可靠性和数据质量
在安全性、可靠性和数据质量方面,我们重点关注损害应用程序或产生不正确或丢失数据的风险。Go 使用优化编译器,生成包含调度程序、垃圾收集器和各种内置数据结构的二进制文件。二进制补丁需要对这些组件进行仔细的逆向工程,以便挂接到应用程序内不同函数的执行位置。小错误很容易产生错误的数据,导致应用程序崩溃,甚至损坏数据。考虑到编译器、运行时和目标库的复杂性和不断发展,我们认为随着时间的推移,此类问题在实践中出现的可能性是中等的。eBPF 通过依赖 uprobe 内核机制来挂载函数执行以及在内核内部的安全虚拟机中执行大多数检测代码,从而降低了这些风险。不过,uprobes 仍然存在导致应用程序崩溃的风险。也许更重要的是,eBPF 仍然需要写入用户内存才能传播跟踪和跨度 ID,这使其面临与二进制修补相同的数据损坏风险。
自动化程度
二进制补丁和 eBPF 的一个引人注目的优势是它们提供的自动化水平。对于这两种方法,在主机系统上部署单个代理就足以检测所有已部署的应用程序。Orchestrion 需要对构建过程进行一些小的修改,并重新部署应用程序本身,因此其自动化程度略有降低。
性能开销
就性能开销而言,eBPF 略微落后,因为 uprobe 的触发需要在用户空间和内核之间进行上下文切换,这对于热代码路径来说是无法承受的。我们也知道可以通过在用户空间中实现 eBPF 来克服这个问题;这种方法可以匹配二进制补丁的性能,但也伴随着相关的风险。
支持的环境
eBPF 通常仅限于具有提升权限的 Linux 环境,这排除了 AWS Lambda 和 Fargate 等无服务器环境。此外,eBPF 和二进制补丁都需要特定于架构的实现。这通常使得支持除 amd64 和 arm64 之外的环境在商业上不可行。
总体能力
最后但同样重要的一点是,我们认为 eBPF 在整体功能方面具有限制性,因为 uprobe 机制不允许我们阻止函数调用以保护被检测应用程序的安全。从理论上讲,二进制修补的功能是不受限制的,但在实践中,由于与 Go 运行时的复杂交互以及附加逻辑在用户空间执行可能会导致应用程序崩溃,因此它们的实现会增加风险。
最终,我们必须在自动化水平和相关客户风险之间做出选择。我们的理念是安全性和可靠性永远放在第一位,这就是我们创建 Orchestrion 的原因。然而,随着替代方法的发展和成熟,我们将继续评估它们。
Orchestrion 之于安全
代码级操作允许 Orchestrion 可以在关键点改变程序控制流,从而可以实现运行时应用程序自我保护 (RASP) 功能,允许应用程序自我保护以抵御常见漏洞,例如 SQL 注入或本地文件包含(均为 OWASP Top-10 之一)。这些功能无法使用基于 eBPF 的解决方案构建,因为它们仅限于观察应用程序。
用另一个 API 完全替代特定 API 的能力也意味着开发人员不再需要考虑传递上下文。上下文值贯穿其业务逻辑的所有层,其目的仅仅是为了允许跟踪上下文链接:这可以在编译时透明地完成。
总结
本文讲解了 Datadog 为什么选择编译时插桩的原因。还讨论了其他替代方法,以及它们的优缺点。我们相信 Orchestrion 是一个强大的工具,可以帮助我们实现更好的 APM 检测,同时保持应用程序的安全性和可靠性。
本文翻译自:https://www.datadoghq.com/blog/go-instrumentation-orchestrion/