使用dotnet-monitor收集k8s中.NET应用的诊断数据

平常在用Visual Studio开发.NET应用时,可以在调试时使用性能诊断工具对应用进行诊断,调查其中诸如内存泄露、线程阻塞等问题。

但是对于线上尤其是运行在容器中的应用来说,收集诊断数据是非常麻烦的,这里介绍使用dotnet-monitor来方便地收集.NET应用诊断数据。

只需要按如下方式对应用的deployment/statefulset进行修改:

apiVersion: apps/v1
kind: Deployment
...
spec:
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
	      image: myapp:latest
 	      env:
        - name: DOTNET_DiagnosticPorts
          value: /diag/port
        volumeMounts:
        - mountPath: /diag
          name: diagvol
        - mountPath: /dumps
          name: dumpsvol
      - name: monitor
        image: mcr.microsoft.com/dotnet/monitor
        args: [ "--no-auth" ]
        env:
        - name: DOTNETMONITOR_Urls
          value: http://localhost:52323
        - name: DOTNETMONITOR_DiagnosticPort__ConnectionMode
          value: Listen
        - name: DOTNETMONITOR_DiagnosticPort__EndpointName
          value: /diag/port
        - name: DOTNETMONITOR_Storage__DumpTempFolder
          value: /dumps
        volumeMounts:
        - mountPath: /diag
          name: diagvol
        - mountPath: /dumps
          name: dumpsvol
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 250m
            memory: 256Mi
      volumes:
      - name: diagvol
        emptyDir: {}
      - name: dumpsvol
        emptyDir: {}

上面进行了三个改动:

  1. 添加了名为monitor的容器作为sidecar
  2. 分别添加了两个名为diagvoldumpsvol的空目录volumes,并挂载到被诊断应用和monitor中
  3. 为被诊断应用配置DOTNET_DiagnosticPorts环境变量

Pod启动后,dotnet-monitor会同被诊断应用的.NET运行时进行IPC通讯,并且dotnet-monitor会将控制命令以API的形式对我们开放。

API列表:

这里以两个主要的诊断场景tracegcdump举例演示诊断一个有内存泄露和线程阻塞问题的应用

通过kubectl port-forward命令,转发monitor容器的请求到本机,在浏览器中访问http://localhost:52323/gcdump,即可捕获并下载被诊断应用的gcdump。

下载完成后,通过perfview打开:

可以看到内存主要被一个保存了大量Mail对象的ConcurrentBag所占用。

关于线程阻塞及线程池饥饿问题的诊断

访问http://localhost:52323/trace?profile=cpu,即可捕捉应用的跟踪信息并下载,默认会记录30秒的数据,可以通过durationSeconds参数更改记录时间。 下载完成后,通过perfview打开

dotnet-monitor内部使用的是和dotnet-trace同样的方式记录追踪信息,而它们都是无法获得线程阻塞时间的数据的,想要获得线程阻塞时间数据,需要使用perfcollect并开启-threadtime选项来收集诊断信息(或者在windows上使用perfview开启/threadTime进行收集)

但是我们依然可以通过dotnet-monitor来得知应用当前线程池的大小、线程队列的长度、线程的调用堆栈等信息,从中也一定程度上可以识别线程池饥饿、定位到导致线程阻塞的代码,如图:
从上图可以看到.NET运行时在不断地创建新的线程


从上图可以看到由于Starvation,线程池大小在发生变化(增加)

在调用堆栈中可以看到有导致线程阻塞的代码