巧用Grafana和Arthas自動抓取K8S中異常Java程式的執行緒堆疊

KAnts發表於2020-04-28

前言

近期發現業務高峰期時刻會出現CPU繁忙導致的timeout異常,通過監控來看是因為Node上面的一些Pod突發搶佔了大量CPU導致的。

問: 沒有限制CPU嗎?是不是限制的CPU使用值就可以解決了呢?
解: 其實不能根本解決這個問題,因為使用的容器引擎是Docker,而Docker是使用了cgroups技術,這就引入了一個老大難的問題,cgroup的隔離性。當問題發生時並沒有辦法把異常CPU程式直接摁住,而會有短暫的高峰,現象為:限制了CPU為2核,突發時CPU可能是4、5、6等,然後容器會被kill掉,K8S會嘗試重建容器。

那麼該如何解決?

  1. 使用隔離性更好的容器引擎,如 kata(VM級別)。
  2. 優化程式

方案1

我們可以知道方案1解決的比較徹底,而且只需要全域性處理一次即可,但技術比較新穎,不知道會不會帶來其它問題,我們之後準備拿出部分Node嘗試kata container。

方案2

對應用開發者要求比較高,需要對應的開發者針對性介入,短期收益很高,我們先部署了這種。

如何實施?

我們知道程式在執行中,除非特別嚴重的BUG,CPU高峰一般非常短暫,這時候靠人肉抓包基本上是來不及的,也很耗費精力,我們就希望有一個程式能在CPU達到一定閾值的時候自動抓取執行緒堆疊來事後針對性優化,並且一定時間內只允許執行一次防止迴圈抓包導致程式不可用。

根據要實現的最終效果我們發現與Grafana、Prometheus的告警機制十分接近,我們要做的就是接收告警的webhook,去對應的容器中獲取執行緒堆疊就行。

於是我們利用了 Grafana ,寫了一個程式來完成這個功能。

專案資訊

開發語言: Go、Shell
專案地址: https://github.com/majian159/k8s-java-debug-daemon

k8s-java-debug-daemon

利用了 Grafana 的告警機制,配合阿里的 arthas,來完成高CPU使用率執行緒的堆疊抓取。
整體流程如下:

  1. 為 Grafana 新增 webhook 型別的告警通知渠道,地址為該程式的 url(預設的hooks路徑為 /hooks)。
  2. 配置Grafana圖表,並設定告警閾值
  3. 當 webhook 觸發時,程式會自動將 craw.sh 指令碼拷貝到對應 Pod 的容器中並執行。
  4. 程式將 stdout 儲存到本地檔案。

效果預覽


預設行為

  • 每 node 同時執行執行數為10
    可以在 ./internal/defaultvalue.go 中更改
    var defaultNodeLockManager = nodelock.NewLockManager(10)
    
  • 預設使用叢集內的Master配置
    可以在 ./internal/defaultvalue.go 中更改
    func DefaultKubernetesClient(){}
    
    // default
    func getConfigByInCluster(){}
    
    func getConfigByOutOfCluster(){}
    
  • 預設使用並實現了一個基於本地檔案的堆疊儲存器, 路徑位於工作路徑下的 stacks
    可以在 ./internal/defaultvalue.go 中更改
    func GetDefaultNodeLockManager(){}
    
  • 預設取最繁忙的前50個執行緒的堆疊資訊 (可在 craw.sh 中修改)
  • 採集樣本時間為2秒 (可在 craw.sh 中修改)

如何使用

Docker Image

majian159/java-debug-daemon

為 Grafana 新建一個通知頻道

注意點

  1. 需要開啟 Send reminders, 不然 Grafana 預設在觸發告警後一直沒有解決不會重複傳送告警
  2. Send reminder every 可以控制最快多久告警一次

為 Grafana 新建一個告警圖表

如果嫌麻煩可以直接匯入以下配置, 在自行更改

{
  "datasource": "prometheus",
  "alert": {
    "alertRuleTags": {},
    "conditions": [
      {
        "evaluator": {
          "params": [
            1
          ],
          "type": "gt"
        },
        "operator": {
          "type": "and"
        },
        "query": {
          "params": [
            "A",
            "5m",
            "now"
          ]
        },
        "reducer": {
          "params": [],
          "type": "last"
        },
        "type": "query"
      }
    ],
    "executionErrorState": "keep_state",
    "for": "10s",
    "frequency": "30s",
    "handler": 1,
    "name": "Pod 高CPU堆疊抓取",
    "noDataState": "no_data",
    "notifications": [
      {
        "uid": "AGOJRCqWz"
      }
    ]
  },
  "aliasColors": {},
  "bars": false,
  "dashLength": 10,
  "dashes": false,
  "fill": 1,
  "fillGradient": 0,
  "gridPos": {
    "h": 9,
    "w": 24,
    "x": 0,
    "y": 2
  },
  "hiddenSeries": false,
  "id": 14,
  "legend": {
    "alignAsTable": true,
    "avg": true,
    "current": true,
    "max": true,
    "min": false,
    "rightSide": true,
    "show": true,
    "total": false,
    "values": true
  },
  "lines": true,
  "linewidth": 1,
  "nullPointMode": "null",
  "options": {
    "dataLinks": []
  },
  "percentage": false,
  "pointradius": 2,
  "points": false,
  "renderer": "flot",
  "seriesOverrides": [],
  "spaceLength": 10,
  "stack": false,
  "steppedLine": false,
  "targets": [
    {
      "expr": "container_memory_working_set_bytes{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", image!=\"\", container!=\"POD\"}* on (namespace, pod) group_left(node) max by(namespace, pod, node, container) (kube_pod_info)",
      "legendFormat": "{{node}} - {{namespace}} - {{pod}} - {{container}}",
      "refId": "A"
    }
  ],
  "thresholds": [
    {
      "colorMode": "critical",
      "fill": true,
      "line": true,
      "op": "gt",
      "value": 1
    }
  ],
  "timeFrom": null,
  "timeRegions": [],
  "timeShift": null,
  "title": "Pod CPU",
  "tooltip": {
    "shared": true,
    "sort": 0,
    "value_type": "individual"
  },
  "type": "graph",
  "xaxis": {
    "buckets": null,
    "mode": "time",
    "name": null,
    "show": true,
    "values": []
  },
  "yaxes": [
    {
      "format": "short",
      "label": null,
      "logBase": 1,
      "max": null,
      "min": null,
      "show": true
    },
    {
      "format": "short",
      "label": null,
      "logBase": 1,
      "max": null,
      "min": null,
      "show": true
    }
  ],
  "yaxis": {
    "align": false,
    "alignLevel": null
  }
}

Queries配置

Metrics 中填寫

container_memory_working_set_bytes{job="kubelet", metrics_path="/metrics/cadvisor", image!="", container!="POD"} * on (namespace, pod) group_left(node) max by(namespace, pod, node, container) (kube_pod_info)

Legend 中填寫

{{node}} - {{namespace}} - {{pod}} - {{container}}

配置完如下:

Alert配置

IS ABOVE
CPU使用值,這邊配置的是超過1核CPU就報警, 可以根據需要自己調節
Evaluate every
每多久計算一次
For
Pedding時間

配置完應該如下:

構建

二進位制

# 為當前系統平臺構建
make

# 指定目標系統, GOOS: linux darwin window freebsd
make GOOS=linux

Docker映象

make docker

# 自定義映象tag
make docker IMAGE=test

相關文章