作者:牧思

背景

随着云原生的普及,Golang 编程语言变得越来越热门。相比 Java,Golang 凭借其轻量,易学习的特点得到了越来越多工程师的青睐,然而由于 Golang 应用需要被编译成二进制文件再进行运行,Golang 编程语言相比 Java 这样的有运行时虚拟机的编程语言损失了较多的灵活性:比如在可观测领域,Java 应用通常利用 JVM 提供的字节码增强机制通过 Java Agent 来进行观测数据的采集,而对于 Golang 应用,通常需要更改源代码重新编译或者使用类似 opentelemetry-go-auto-instrumentation【1】 的编译时注入工具替换原本的 Golang 工具链才能达到类似的效果。本文将介绍一种基于 Docker 多阶段构建的无侵入 Golang 应用观测方法,通过此方法用户无需对 Golang 应用源代码或者编译指令做任何改造,即可零成本为 Golang 应用注入可观测能力。

Docker 多阶段构建

Docker 允许用户将镜像构建拆成多个阶段【2】,在下一个阶段可以获取到上一阶段的一些输出,Golang 开发者在构建 Golang 应用的镜像流水线时通常会编写如下所示的 Dockerfile:

# stage 1
FROM golang:1.22-alpine3.19 as builder

RUN go version
RUN go build -v -o /workspace/demo
# stage 2
FROM alpine

COPY --from=builder /workspace/demo /demo
export ENV1=e1
# 指定默认的启动命令
CMD ["/demo"]

在这个 Dockerfile 中,主要分为两个阶段,第一个阶段是构建阶段,在这个阶段中一般会通过组合 Golang 提供的各种编译工具,构建出需要执行的二进制文件并作为输出传递给第二阶段。第二阶段一般为运行阶段,第二阶段将会获取到第一阶段中构建好的二进制文件,之后设置运行时需要的环境变量后启动二进制文件,从而将 Golang 应用成功运行起来。

那么我们需要做什么改造,才能让这样一个经过 Docker 多阶段构建的 Golang 应用镜像拥有可观测能力呢?请看下文:

Step1:替换编译阶段基础镜像

# stage 1
# 替换为ARMS编译镜像,其余保持一致
FROM registry-cn-hangzhou.ack.aliyuncs.com/acs/golangbuilder-alpine-linux-amd64:0.0.1 as builder
RUN go version
RUN go build -v -o /workspace/demo
# stage 2
FROM alpine

COPY --from=builder /workspace/demo /demo
ENV ENV1=e1
# 指定默认的启动命令
CMD ["/demo"]

只需要把编译阶段的基础镜像替换为 ARMS 的编译镜像,其余的内容保持一致即可自动使用 ARMS 的 instgo 编译工具【3】进行混合编译,用户可以使用 docker build 命令构建该 Dockerfile 对应的镜像:

可以看到,在把编译阶段的基础镜像替换为 ARMS 的编译镜像后,在执行 Golang 应用构建时实际上是通过 ARMS 的 instgo 编译工具来进行的,同时对于其他的原生 Golang Tool,比如 go version,instgo 的执行效果与原生 Golang Tool 完全一致。

以上步骤完成后,我们就成功构建出了具有可观测能力的 Golang 应用镜像。

Step2:通过 ack-onepilot 为运行时镜像添加环境变量

在相关应用镜像构建完成后,用户需要把相关镜像运行起来,比如运行在阿里云的容器服务 ACK 产品【4】中,在相关应用运行前,用户通常需要去指定一些配置,比如在接入可观测能力后相关的观测数据上报到哪个地域的哪个应用下,是否启动观测数据的上报等等。自然地,用户可能会在 Dockerfile 中添加一些环境变量:

# stage 1
# 替换为ARMS编译镜像,其余保持一致
FROM registry-cn-hangzhou.ack.aliyuncs.com/acs/golangbuilder-alpine-linux-amd64:0.0.1 as builder
RUN go version
RUN go build -v -o /workspace/demo
# stage 2
FROM alpine

COPY --from=builder /workspace/demo /demo
ENV ENV1=e1
# 手动添加运行时环境变量
ENV ARMS_ENABLE=true
ENV ARMS_APP_NAME={AppName}
ENV ARMS_REGION_ID={regionId}
ENV ARMS_LICENSE_KEY={licenseKey}
# 指定默认的启动命令
CMD ["/demo"]

上面这种方案虽然可行,但是灵活性较差,如果相关的环境变量值需要发生变化,比如某天用户不想打开可观测能力,就需要重新修改 Dockerfile 构建镜像,同时如果用户需要接入的应用较多,这样的改动成本对用户来说成本过高。

另一种无感的使用方式是使用 ARMS 提供的 ack-onepilot 组件,首先在阿里云 ACK 的运维管理界面点击组件管理,并且在右上角通过关键字搜索 ack-onepilot 组件,并在卡片上点击安装。

在安装好 ack-onepilot 组件后,只需要在创建工作负载时将以下 labels 添加到 spec.template.metadata 层级下,即可完成 Golang 应用的接入:

labels:
  aliyun.com/app-language: golang # Go应用必填,标明此应用是Go应用。
  armsPilotAutoEnable: 'on'
  armsPilotCreateAppName: "<your-deployment-name>"    #请将<your-deployment-name>替换为您的应用名称。

加上相关标签后,在 ARMS 控制台上即可查看到对应 Golang 应用,点击应用进入详情页即可查看该应用详细的观测数据。

总结与展望

基于 Docker 多阶段构建的无侵入观测方案有效降低了用户接入 Golang Agent 的成本,并已商业化上线至阿里云公有云,为客户提供强大的监控能力。这项技术最初的设计初衷是为了让用户能够在不改动现有代码的前提下轻松地插入监控代码,从而实现对应用程序性能状态的实时监测与分析,但它的实际应用领域不止如此,包括服务治理、代码审计、应用安全、代码调试等,甚至在许多未被探索的领域中也展现出潜力。

我们已经将这项创新方案开源,并成功捐赠给 OpenTelemetry 社区【5】。开源不仅促进技术共享与提升,借助社区的力量还可以持续探索该方案在更多领域上的可能。

最后诚邀大家试用我们的商业化产品,并加入我们的钉钉群(开源群:102565007776,商业化群:35568145),共同提升 Go 应用监控与服务治理能力。通过群策群力,我们相信能为 Golang 开发者社区带来更加优质的云原生体验。

【1】opentelemetry-go-auto-instrumentation

https://github.com/alibaba/opentelemetry-go-auto-instrumentation

【2】多阶段|Docker 文档

https://docs.docker.com/build/building/multi-stage/

【3】Instgo 工具介绍

https://help.aliyun.com/zh/arms/application-monitoring/develo...

【4】容器服务 Kubernetes 版 ACK

https://help.aliyun.com/zh/ack/

【5】OpenTelemetry 社区

https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation


阿里云云原生
1.1k 声望317 粉丝