Go 探针

安装Go 探针

使用UniAgent Linux版

当服务器上存在多种语言的应用时,推荐使用UniAgent安装,安装步骤请参见UniAgent Linux部署说明

影响:ps进程列表中原本Go应用进程的位置会被agentinject替换,Go应用进程的父进程id会变为1,如果Go应用由启停脚本或服务维护,请务必在测试环境验证启停脚本或服务的有效性并做适当修改。

方式一:修改应用启动脚本

注意:仅适用于版本大于2.5.7.0版本的探针

UniAgent安装完毕后,在应用启动脚本内增加/opt/tingyun-oneagent/agent/go_current/bin/agentinject,重启应用即可。

例如,原Go应用启动脚本如下:

/opt/go-app/go-demo

更改后的应用启动脚本如下:

...
/opt/tingyun-oneagent/agent/go_current/bin/agentinject /opt/go-app/go-demo

卸载探针:将应用启动脚本恢复为原来的脚本并重启应用。

嵌码原理:使用bin/agentinject启动Go应用, agentinject利用ptrace技术替换Go应用中相关的函数并获取性能数据。

方式二:修改UniAgent配置

注意:仅适用于版本大于2.5.8.0版本的探针

  1. 手工开启Go应用嵌码开关

    修改/opt/tingyun-oneagent/conf/interceptor.conf文件中go_enabledgo_enabled=true

  2. 重启UniAgent服务

    sudo systemctl restart tingyun-oneagent

    Go探针产生的trace需要通过中间进程转发到Collector,重启UniAgent服务后,如果配置是go_enabled=true,会启动中间进程接收trace数据。

    如果不重启UniAgent服务,Go探针日志'golang_agent.log'里面会有Connect file:///opt/tingyun-oneagent/agent/go_version/run/goagent.sock failed日志,说明和中间进程通讯失败。

  3. 手工修改Go应用的识别模式

    Go应用识别采用白名单的方式,修改/opt/tingyun-oneagent/conf/interceptor.conf文件中的go.namelist=,多个名称以英文逗号分隔。

    例如要监控 /app/go-demo1/test/go-test2, 需要修改配置为go.namelist=go-demo1,go-test2

  4. 重启Go应用

    如果Go应用是通过shell脚本直接启动,则可以直接重启Go应用。

    如果Go应用是直接执行命令启动,并且当前shell是在UniAgent服务启动之前打开的,需要新开一个shell进行操作或执行sh -c 'Go应用及参数'启动;如果当前shell是在UniAgent服务启动之后打开的,则可以直接重启Go应用。

    如果Go应用是通过系统服务直接启动(systemctl),需要修改服务为/bin/sh -c 'Go应用及参数'的方式启动。

    注入探针需依赖于父进程在启动Go应用时对应用嵌码,这要求Go应用的父进程必须是动态链接C运行时库的进程,如果Go应用是由静态链接的进程拉起,则无法嵌码。

对于运行在容器内的Go应用需满足以下要求:

  • C 运行时依赖:

    容器基础镜像必须包含完整的 C 运行时库(glibc 或 musl libc)

    不支持使用无 C 运行时的精简镜像,如 scratch 或 busybox:musl。

    可以通过检查命令 docker exec -it 容器名称 cat /proc/self/maps 的输出结果是否包含*.so来确认容器镜像是否包含完整的 C 运行时库。

  • 进程关系依赖:

    注入探针需依赖于父进程在启动Go应用时对应用嵌码,这要求Go应用的父进程必须是动态链接C运行时库的进程,如果Go应用是由静态链接的进程拉起,则无法嵌码。

    如果Go应用为entrypoint入口 (pid=1),可以将entrypoint修改为sh -c 'Go应用及参数'的方式启动Go应用。

  • 权限依赖:

    注入探针时需要父进程拥有PTRACE_ME权限

    如果容器内运行Go应用探针加载失败,golang_bootstrap.log 内有Operation not permitted日志,需要在运行容器时增加参数 --cap-add=SYS_PTRACE

卸载探针:Go应用识别采用白名单的方式,修改/opt/tingyun-oneagent/conf/interceptor.conf文件中的go.namelist=,移除不需要监控的应用名称。

嵌码原理:利用Preload技术(/etc/ld.so.preload)由父进程加载预加载模块(libinterceptor.so), 预加载模块会监控父进程启动子进程的动作, 如果发现子进程名称在go.namelist白名单之内, 则使用bin/agentinject替换为原来的子进程,并使用bin/agentinject启动Go应用, agentinject利用ptrace技术替换Go应用中相关的函数并获取性能数据。

使用UniAgent Kubernetes版

当使用Kubernetes或Openshift云平台时,安装步骤请参见UniAgent Kubernetes部署说明

探针需要至少50M内存资源,如果Pod设置了request和limit,请增加CPU和内存的资源设置。

注意:2.5.7.0版本的探针仅适用于直接启动Go应用的Pod

  • 通过shell脚本直接启动Go应用,如果镜像包含完整的 C 运行时库(glibc 或 musl libc):打标签tingyun-go-enabled: enabled

  • 通过shell脚本直接启动Go应用,但使用了无 C 运行时的精简镜像(例如busybox:musl):不支持,无法嵌码。

  • 如果Go应用是由静态链接的进程拉起,不支持,无法嵌码。

  • 镜像直接启动Go应用(例如scratch):打标签tingyun-go-entrypoint: enabled,且确认command不为空,如果command为空,需要将command修改为Go应用路径和Go应用参数

例1:

通过构建Dockerfile时指定ENTRYPOINT ['/app/go-demo', 'param'],部署Deployment时没有指定command

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo1
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo1
  template:
    spec:
      containers:
      - name: demo1
        image: test/go-demo:1.0

需要将tingyun-go-entrypoint设置为enabled,并且增加command,内容为镜像Dockerfile的ENTRYPOINT值,配置如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo1
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo1
  template:
    metadata:
      labels:
        tingyun-go-entrypoint: enabled
    spec:
      containers:
      - name: demo1
        image: test/go-demo:1.0
        command: ['/app/go-demo', 'param']

例2:

通过构建Dockerfile时指定ENTRYPOINT ['/app/go-demo'],部署Deployment时没有指定command,但指定了args

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo1
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo1
  template:
    spec:
      containers:
      - name: demo1
        image: test/go-demo:1.0
        args: ['param1', 'param2']

需要将tingyun-go-entrypoint设置为enabled,删除args,增加command,内容为镜像Dockerfile的ENTRYPOINT值加上原有args,配置如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo1
  namespace: default
spec:
  selector:
    matchLabels:
      app: demo1
  template:
    metadata:
      labels:
        tingyun-go-entrypoint: enabled
    spec:
      containers:
      - name: demo1
        image: test/go-demo:1.0
        command: ['/app/go-demo', 'param1', 'param2']

在Kubernetes平台中容器默认拥有SYS_PTRACE能力,但也不排除某些定制化的Kubernetes平台限制了容器的SYS_PTRACE能力,需酌情处理。

嵌码原理:

利用Preload技术(LD_PRELOAD)由父进程加载预加载模块(libinterceptor.so), 预加载模块会监控父进程启动子进程的动作, 并使用bin/agentinject替换为原来的子进程。bin/agentinject判断子进程是否为go应用,如果不是go应用,则直接启动子进程;如果是go应用,agentinject利用ptrace技术替换Go应用中相关的函数并获取性能数据。

如果go应用没有父进程,并将tingyun-go-entrypoint设置为enabled,探针会修改容器command为 agentinject 原Go应用启动命令 ,agentinject利用ptrace技术替换Go应用中相关的函数并获取性能数据。

使用独立探针

下载Go独立探针,安装完毕后,修改 /安装路径/conf/goagent.conf 中collector地址和license,在应用启动脚本内增加/安装路径/bin/agentinject,重启应用即可。

例如,原Go应用启动脚本如下:

/opt/go-app/go-demo

更改后的应用启动脚本如下:

...
/opt/tingyun-go-v1.0/bin/agentinject /opt/go-app/go-demo

卸载探针:将应用启动脚本恢复为原来的脚本并重启应用。

嵌码原理:使用bin/agentinject启动Go应用, agentinject利用ptrace技术替换Go应用中相关的函数并获取性能数据。

Go SDK 嵌码

安装Go SDK

Go SDK与所有第三方模块的安装方式相同。

  • GOPATH模式下安装:

    $ go get github.com/TingYunGo/goagent
    
  • GOMOD模式下安装:

    在应用文件夹下执行:

    $ go mod tidy
    

使用SDK嵌码

根据应用使用HTTP框架的不同,需要import不同的路径。

我们以一个使用内置HTTP框架的简单例子说明如何嵌码。

源文件: main.go

代码如下:

package main
import (
    "encoding/json"
    "net/http"
)
func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        header := w.Header()
        header.Set("Cache-Control", "no-cache")
        header.Set("Content-Type", "application/json; charset=utf-8")
        w.WriteHeader(http.StatusOK)
        b, _ := json.Marshal(map[string]interface{}{
            "status": "success",
            "URI":    r.URL.RawQuery,
        })
        w.Write(b)
    })
    http.ListenAndServe(":3000", nil)
}

要对如上这个应用嵌码,在源文件同级目录下,创建tingyun.go文件,内容如下:

package main
import (
    _ "github.com/TingYunGo/goagent"
)

至此,全部嵌码工作已完成。

嵌码说明:此例应用使用了内置HTTP框架,对于内置HTTP框架,需要引入github.com/TingYunGo/goagent。

如果您不确定嵌码要使用哪个/哪些 import 模块路径,可通过查看支持列表了解引用依赖。

获取当前应用的依赖模块:

  • GOMOD 方式:查看go.mod文件,或者使用命令:

    $ go mod graph
    
  • GOPATH 方式:编译时使用 -a -v 参数:

    $ go build -a -v
    

嵌码示例

以开源项目 photoprism 为例,项目地址 https://github.com/photoprism/photoprism

  • 步骤1:克隆项目。

    $ git clone https://github.com/photoprism/photoprism.git
    
  • 步骤2:确定项目使用哪些框架和库。

    进入项目文件夹,查看 go.mod文件。

    $ cd photoprism
    $ cat go.mod
    

    我们会看到,此项目使用了gin框架,数据库支持:

    postgresql:(github.com/lib/pq)
    postgresql:(github.com/lib/pq)
    sqlite:(github.com/mattn/go-sqlite3)
    
  • 步骤3:查询支持列表确定引用路径,添加源码。

    查看框架支持列表,我们的嵌码操作需要引用两个路径:

    github.com/TingYunGo/goagent/frameworks/gin
    github.com/TingYunGo/goagent/database
    

    在代码目录 internal/photoprism下创建 tingyun.go文件,内容如下:

    package photoprism
    import (
        _ "github.com/TingYunGo/goagent/database"
        _ "github.com/TingYunGo/goagent/frameworks/gin"
    )
    
  • 步骤4:在项目go.mod所在目录下执行 go mod tidy命令,然后编译。

    $ go mod tidy
    $ make
    

以上4个步骤完成后,项目编译和SDK嵌码工作就已全部完成。更多SDK嵌码示例,请参见使用Go SDK嵌码示例

results matching ""

    No results matching ""