使用dotnet-monitor sidecar模式 dump docker運行的dotnet程序.

前情概要

隨着容器和雲技術的發展, 大量的應用運行在雲上的容器中, 它們的好處是毋庸置疑的, 例如極大的提高了我們的研發部署速度, 快速的擴縮容等等, 但是也存在一些小小的問題, 例如難以調試.
基於VM的部署我們可以通過安全的方式登錄到主機上做一些你想做的事情, 但是雲上的容器那就是不太方便了(目前AWS的ECS已經有類似docker exec的方式直接進入容器中了, 其他的雲未作了解).
但是就算能進入容器也不意味着調試就好做了, 通常來說使用的鏡像都是經過優化和精簡的(如果要調式可能需要安裝大量的組件).

所以, 接下來介紹一下使用dotnet-monitor 來內存轉儲(memory dump)運行在容器中的 dotnet 程序.

需要提前知曉的一些知識點

什麼是 dotnet-monitor?

Announcing dotnet monitor in .NET 6 官方博客的原文:

Running a .NET application in diverse environments can make collecting diagnostics artifacts (e.g., logs, traces, process dumps) challenging. dotnet monitor is a tool that provides an unified way to collect these diagnostic artifacts regardless of whether running you』re running on your desktop machine or in a kubernetes cluster.

There are two different mechanisms for collection of these diagnostic artifacts:

An HTTP API for on demand collection of artifacts. You can call these API endpoints when you already know your application is experiencing an issue and you are interested in gathering more information.
Triggers for rule-based configuration for always-on collection of artifacts. You may configure rules to collect diagnostic artifacts when a desired condition is met, for example, collect a process dump when you have sustained high CPU.

google翻譯:

在不同的環境中運行 .NET 應用程序會使收集診斷工件(例如,日誌、跟蹤、進程轉儲)具有挑戰性。dotnet monitor是一個工具,它提供了一種統一的方式來收集這些診斷工件,無論您是在台式機上運行還是在 kubernetes 集群中運行。

收集這些診斷工件有兩種不同的機制:

用於按需收集工件的HTTP API 。當您已經知道您的應用程序遇到問題並且您有興趣收集更多信息時,您可以調用這些 API 端點。
基於規則的配置觸發器,用於始終在線收集工件。您可以配置規則以在滿足所需條件時收集診斷工件,例如,當您持續使用高 CPU 時收集進程轉儲。

dotnet-monitor工作在什麼位置?

借用官方博客中的一張圖說明一下dotnet-monitor工作在什麼地方

dotnet-monitor是如何能對我們的目標程序進行操作的?

dotnet-monitor 可以連接到dotnet運行時公開的一個診斷端口(diagnostic port)(3.0新提供的新功能), 並通過自定義協議(ipc protocol)與運行時交互,

更多調試知識和工具例如ETW, eventpipe, lldb, dotnet-trace, dotent-counters 等可以查看 dotnet diagnostics.

目標應用程序容器準備

首先, 我們得讓我們被調試的目標程序公開這個診斷端口, 因為默認情況下這個診斷端口只能由運行這個程序的用戶或者root用戶來訪問, 顯然sidecar 模式啟動的dotnet-monitor是不可能和目標程序用的是同一個用戶的.

未作特別聲明的話, 後文給出的實驗都是基於AWS FargateLinux 配置.

#添加環境變量
DOTNET_DiagnosticPorts=/my_diagnostic_volume/diag.sock,suspend,connect

/my_diagnostic_volume/diag.sockUnix Domain Socket 文件路徑, my_diagnostic_volume 是掛載的一個volume.
suspend 意思是讓運行時等待dotnet-monitor 連接進來之後在執行託管代碼.
connect 接受dotnet-monitor連接, 詳細解釋看這裡diagnostic ports
上述配置的完整語法結構是 address[,(listen|connect)][,(suspend|nosuspend)]
詳情請查看文檔configure additional diagnostic ports

如果我們的需要dump內存文件, 可能會遇到WriteDumpAsync failed - HRESULT: 0x00000000 issues 1783這樣的錯誤, 是因為權限問題.
比如我在AWS Fargate中遇到的就是 /dump API 返回400錯誤 Write dump failed - HRESULT: 0x00000000, 目標程序輸出日誌 ptrace(ATTACH, 1) FAILED Operation not permitted.
解決這個需要吧SYS_PTRACE權限給到目標程序. AWS Fargate 是編輯任務定義的json文件增加這一部分, docker 啟動是通過增加--cap-add=SYS_PTRACE 參數.

{
    "linuxParameters": {
        "capabilities": {
            "add": [
                "SYS_PTRACE"
            ]
        }
    }
}

最後, 配置目標程序容器依賴dotnet-monitor容器, 這樣可以先讓dotnet-monitor容器啟動後, 在啟動目標程序容器.
到此, 目標程序容器的配置就完成了, 接下來配置dotnet-monitor

dotnet-monitor容器準備

  • 增加 Docker image 作為目標容器的sidecar 容器.
  • 暴露端口52323 #dotnet-monitor映射端口.
  • 增加容器啟動命令參數 --no-auth # 簡單粗暴的讓所有的API都不要鑒權.
  • 添加環境變量
    • DOTNETMONITOR_DiagnosticPort__ConnectionMode=Listen # 必須的.
    • DOTNETMONITOR_DiagnosticPort__EndpointName=/my_diagnostic_volume/diag.sock # 目標容器配置的DOTNET_DiagnosticPorts中的address.
    • DOTNETMONITOR_Storage__DumpTempFolder=/my_diagnostic_volume/dump_files # dump內存是用的目錄.
    • DOTNETMONITOR_Urls=//+:52323 # dotnet-monitor要提供服務在什麼端口上. dotnet-monitor默認用的就是52323.

詳細的文檔解釋看這裡

至此, 所有的配置就都完成了.

使用dotnet-monitor 來dump目標容器的內存文件

Get 請求 /dump endpoint 即可下載內存轉儲文件.

wget ip:52323/dump -O my_target_application_memory_dump.dmp

當前可以用API’s列表, 詳情請看這裡API’s

| Route            | Description                                                        | Version Introduced |
| ---------------- | ------------------------------------------------------------------ | ------------------ |
| /processes       | Gets detailed information about discoverable processes.            | 6.0                |
| /dump            | Captures managed dumps of processes without using a debugger.      | 6.0                |
| /gcdump          | Captures GC dumps of processes.                                    | 6.0                |
| /trace           | Captures traces of processes without using a profiler.             | 6.0                |
| /metrics         | Captures metrics of a process in the Prometheus exposition format. | 6.0                |
| /livemetrics     | Captures live metrics of a process.                                | 6.0                |
| /stacks          | [Experimental] Gets the current callstacks of all .NET threads.    | 7.0                |
| /logs            | Captures logs of processes.                                        | 6.0                |
| /info            | Gets info about dotnet monitor.                                    | 6.0                |
| /operations      | Gets egress operation status or cancels operations.                | 6.0                |
| /collectionrules | Gets the current state of collection rules.                        | 6.3                |

在之後的對內存文件的分析可以使用dotnet-dump, lldb等程序.
更多高級用法請查看, 例如可以配置內存每增加100Mb就觸發dump內存文件.

相關鏈接

//learn.microsoft.com/zh-cn/dotnet/core/diagnostics/dotnet-monitor
//learn.microsoft.com/zh-cn/dotnet/core/diagnostics/dotnet-dump
//learn.microsoft.com/zh-cn/dotnet/core/diagnostics/diagnostic-port
//github.com/dotnet/dotnet-monitor/blob/main/README.md
//devblogs.microsoft.com/dotnet/announcing-dotnet-monitor-in-net-6/