前言
近期發現業務高峰期時刻會出現CPU繁忙導致的timeout異常,通過監控來看是因為Node上面的一些Pod突發搶占了大量CPU導致的。
問: 沒有限制CPU嗎?是不是限制的CPU使用值就可以解決了呢?
 解: 其實不能根本解決這個問題,因為使用的容器引擎是Docker,而Docker是使用了cgroups技術,這就引入了一個老大難的問題,cgroup的隔離性。當問題發生時並沒有辦法把異常CPU進程直接摁住,而會有短暫的高峰,現象為:限制了CPU為2核,突發時CPU可能是4、5、6等,然后容器會被kill掉,K8S會嘗試重建容器。
那么該如何解決?
- 使用隔離性更好的容器引擎,如 kata(VM級別)。
 - 優化程序
 
方案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使用率線程的堆棧抓取。
 整體流程如下:
- 為 Grafana 添加 webhook 類型的告警通知渠道,地址為該程序的 url(默認的hooks路徑為 /hooks)。
 - 配置Grafana圖表,並設置告警閾值
 - 當 webhook 觸發時,程序會自動將 
craw.sh腳本拷貝到對應 Pod 的容器中並執行。 - 程序將 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
為 Grafana 新建一個通知頻道

注意點
- 需要打開 Send reminders, 不然 Grafana 默認在觸發告警后一直沒有解決不會重復發送告警
 - 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
 
       