NPD 入門
簡介
節(jié)點(diǎn)問題檢測器(Node Problem Detector) 是一個守護(hù)程序喊巍,用于監(jiān)視和報(bào)告節(jié)點(diǎn)的健康狀況(包括內(nèi)核死鎖、OOM办陷、系統(tǒng)線程數(shù)壓力琅轧、系統(tǒng)文件描述符壓力等指標(biāo))。 你可以將節(jié)點(diǎn)問題探測器以 DaemonSet 或獨(dú)立守護(hù)程序運(yùn)行搭伤。 節(jié)點(diǎn)問題檢測器從各種守護(hù)進(jìn)程收集節(jié)點(diǎn)問題只怎,并以 NodeCondition 和 Event 的形式報(bào)告給 API Server。
您可以通過檢測相應(yīng)的指標(biāo)怜俐,提前預(yù)知節(jié)點(diǎn)的資源壓力身堡,可以在節(jié)點(diǎn)開始驅(qū)逐 Pod 之前手動釋放或擴(kuò)容節(jié)點(diǎn)資源壓力,防止 Kubenetes 進(jìn)行資源回收或節(jié)點(diǎn)不可用可能帶來的損失拍鲤。
Git 倉庫地址:https://github.com/kubernetes/node-problem-detector
Kubernetes 目前問題
基礎(chǔ)架構(gòu)守護(hù)程序問題:ntp服務(wù)關(guān)閉贴谎;
硬件問題:CPU,內(nèi)存或磁盤損壞季稳;
內(nèi)核問題:內(nèi)核死鎖擅这,文件系統(tǒng)損壞;
容器運(yùn)行時問題:運(yùn)行時守護(hù)程序無響應(yīng)
...
當(dāng)kubernetes中節(jié)點(diǎn)發(fā)生上述問題景鼠,在整個集群中仲翎,k8s服務(wù)組件并不會感知以上問題,就會導(dǎo)致pod仍會調(diào)度至問題節(jié)點(diǎn)铛漓。
為了解決這個問題溯香,我們引入了這個新的守護(hù)進(jìn)程node-problem-detector,從各個守護(hù)進(jìn)程收集節(jié)點(diǎn)問題浓恶,并使它們對上游層可見玫坛。一旦上游層面發(fā)現(xiàn)了這些問題,我們就可以討論補(bǔ)救措施包晰。
NPD 使用
構(gòu)建
NPD使用Go modules管理依賴昂秃,因此構(gòu)建它需要Go SDK 1.11+:
cd $GOPATH/src/k8s.io
go get k8s.io/node-problem-detector
cd node-problem-detector
export GO111MODULE=on
go mod vendor
# 設(shè)置構(gòu)建標(biāo)記
export BUILD_TAGS="disable_custom_plugin_monitor disable_system_stats_monitor"
# 在Ubuntu 14.04上需要安裝
sudo apt install libsystemd-journal-dev
make all
安裝
# add repo
helm repo add feisky https://feisky.xyz/kubernetes-charts
helm update
# install packages
helm install feisky/node-problem-detector --namespace kube-system --name npd
啟動參數(shù)
--version: 在控制臺打印 NPD 的版本號.
--hostname-override: 供 NPD 使用的自定義的節(jié)點(diǎn)名稱,NPD 會優(yōu)先獲取該參數(shù)設(shè)置的節(jié)點(diǎn)名稱杜窄,其次是從 NODE_NAME 環(huán)境變量中獲取肠骆,最后從 os.Hostname() 方法獲取。
system-log-monitor 相關(guān)參數(shù)
- --config.system-log-monitor: system log monitor 配置文件路徑塞耕,多個文件用逗號分隔, 如 config/kernel-monitor.json. NPD 會為每一個配置文件生成單獨(dú)的 log monitor蚀腿。你可以使用不同的 log monitors 來監(jiān)控不同的系統(tǒng)日志。
system-stats-monitor 相關(guān)參數(shù)
- --config.system-stats-monitor: system status monitor 配置文件路徑,多個文件用逗號分隔, 如 config/system-stats-monitor.json. NPD 會為每一個配置文件生成單獨(dú)的 status monitor莉钙。你可以使用不同的 status monitors 來監(jiān)控系統(tǒng)的不同狀態(tài)廓脆。
custom-plugin-monitor 相關(guān)參數(shù)
- --config.custom-plugin-monitor: 用戶自定義插件配置文件路徑,多個文件用逗號分隔, 如 config/custom-plugin-monitor.json. NPD 會為每一個配置文件生成單獨(dú)的自定義插件監(jiān)視器磁玉。你可以使用不同的自定義插件監(jiān)視器來監(jiān)控不同的系統(tǒng)問題停忿。
K8s exporter 相關(guān)參數(shù)
--enable-k8s-exporter: 是否開啟上報(bào)信息到 API Server,默認(rèn)為 true.
-
--apiserver-override: 一個URI參數(shù)蚊伞,用于自定義node-problem-detector連接apiserver的地址席赂。 如果--enable-k8s-exporter為false,則忽略此內(nèi)容时迫。 格式與Heapster的源標(biāo)志相同颅停。 例如,要在沒有身份驗(yàn)證的情況下運(yùn)行掠拳,請使用以下配置: http://APISERVER_IP:APISERVER_PORT?inClusterConfig=false
請參閱 heapster 文檔以獲取可用選項(xiàng)的完整列表癞揉。
--address: 綁定 NPD 服務(wù)器的地址。
--port: NPD 服務(wù)端口溺欧,如果為0喊熟,表示禁用 NPD 服務(wù)。
Prometheus exporter 相關(guān)參數(shù)
--prometheus-address: 綁定Prometheus抓取端點(diǎn)的地址姐刁,默認(rèn)為127.0.0.1芥牌。
--prometheus-port: 綁定Prometheus抓取端點(diǎn)的端口,默認(rèn)為20257龙填。使用0禁用胳泉。
Stackdriver exporter 相關(guān)參數(shù)
- --exporter.stackdriver: Stackdriver exporter程序配置文件的路徑拐叉,例如 config/exporter/stackdriver-exporter.json岩遗,默認(rèn)為空字符串。 設(shè)置為空字符串以禁用凤瘦。
過期參數(shù)
--system-log-monitors: system log monitor 配置文件路徑宿礁,多個文件用逗號分隔。該選項(xiàng)已過期, 被 --config.system-log-monitor 取代, 即將被移除. 如果在啟動NPD時同時設(shè)置了 --system-log-monitors 和 --config.system-log-monitor蔬芥,會引發(fā)panic梆靖。
--custom-plugin-monitors: 用戶自定義插件配置文件路徑,多個文件用逗號分隔笔诵。該選項(xiàng)已過期, 被 --config.custom-plugin-monitor 取代, 即將被移除. 如果在啟動NPD時同時設(shè)置了 --custom-plugin-monitors 和 --config.custom-plugin-monitor返吻,會引發(fā)panic。
覆蓋配置文件
構(gòu)建節(jié)點(diǎn)問題檢測器的 docker 鏡像時乎婿,會嵌入 默認(rèn)配置测僵。
不過,你可以像下面這樣使用 ConfigMap 將其覆蓋:
1、更改 config/ 中的配置文件
2捍靠、創(chuàng)建 ConfigMap node-strick-detector-config:
kubectl create configmap node-problem-detector-config --from-file=config/
3沐旨、更改 node-problem-detector.yaml 以使用 ConfigMap:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-problem-detector-v0.1
namespace: kube-system
labels:
k8s-app: node-problem-detector
version: v0.1
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
k8s-app: node-problem-detector
version: v0.1
kubernetes.io/cluster-service: "true"
template:
metadata:
labels:
k8s-app: node-problem-detector
version: v0.1
kubernetes.io/cluster-service: "true"
spec:
hostNetwork: true
containers:
- name: node-problem-detector
image: k8s.gcr.io/node-problem-detector:v0.1
securityContext:
privileged: true
resources:
limits:
cpu: "200m"
memory: "100Mi"
requests:
cpu: "20m"
memory: "20Mi"
volumeMounts:
- name: log
mountPath: /log
readOnly: true
- name: config # Overwrite the config/ directory with ConfigMap volume
mountPath: /config
readOnly: true
volumes:
- name: log
hostPath:
path: /var/log/
- name: config # Define ConfigMap volume
configMap:
name: node-problem-detector-config
4、使用新的配置文件重新創(chuàng)建節(jié)點(diǎn)問題檢測器:
說明: 此方法僅適用于通過 kubectl 啟動的節(jié)點(diǎn)問題檢測器榨婆。
如果節(jié)點(diǎn)問題檢測器作為集群插件運(yùn)行磁携,則不支持覆蓋配置。 插件管理器不支持 ConfigMap良风。
如何驗(yàn)證NPD捕獲信息
通常這些錯誤是比較難真實(shí)測試谊迄,只能通過發(fā)送消息到j(luò)ournal來模擬。
- 發(fā)送一個kernel deadlock類型的condition:在對應(yīng)的node節(jié)點(diǎn)上執(zhí)行以下操作
echo "task docker:7 blocked for more than 300 seconds." |systemd-cat -t kernel
然后通過k8s控制臺拖吼,你可以看到對應(yīng)的信息:
- 發(fā)送一個event
echo "Error trying v2 registry: failed to register layer: rename /var/lib/docker/image/test /var/lib/docker/image/ddd: directory not empty.*" |systemd-cat -t docker
然后通過以下命令來對應(yīng)的event
kubectl describe node/xxxx
實(shí)現(xiàn)原理
核心組件
Problem Daemon(Monitor)
Problem Daemon 是監(jiān)控任務(wù)子守護(hù)進(jìn)程鳞上,NPD 會為每一個 Problem Daemon 配置文件創(chuàng)建一個守護(hù)進(jìn)程,這些配置文件通過 --config.custom-plugin-monitor吊档、--config.system-log-monitor篙议、--config.system-stats-monitor 參數(shù)指定。每個 Problem Daemon監(jiān)控一個特定類型的節(jié)點(diǎn)故障怠硼,并報(bào)告給NPD鬼贱。目前 Problem Daemon 以 Goroutine 的形式運(yùn)行在NPD中,未來會支持在獨(dú)立進(jìn)程(容器)中運(yùn)行并編排為一個Pod香璃。在編譯期間这难,可以通過相應(yīng)的標(biāo)記禁用每一類 Problem Daemon。
custom-plugin-monitor:用戶自定義的 Problem Daemon
system-log-monitor:系統(tǒng)日志監(jiān)控
system-stats-monitor:系統(tǒng)狀態(tài)監(jiān)控
ProblemDaemonHandler
ProblemDaemonHandler 定義了 Problem Daemon 的初始化方法
type ProblemDaemonHandler struct {
// 初始化 Problem Daemon實(shí)例葡秒,如果初始化過程中出錯姻乓,則拋出 panic
CreateProblemDaemonOrDie func(string) Monitor
// 說明了從命令行參數(shù)配置 Problem Daemon 的方式
CmdOptionDescription string
}
在NPD啟動時,init()方法中完成了 ProblemDaemonHandler 的注冊:
var (
// 在 NPD 啟動過程中眯牧,通過 init() 方法注冊
handlers = make(map[types.ProblemDaemonType]types.ProblemDaemonHandler)
)
// 注冊 problem daemon 工廠方法蹋岩,將會用于創(chuàng)建 problem daemon
func Register(problemDaemonType types.ProblemDaemonType, handler types.ProblemDaemonHandler) {
handlers[problemDaemonType] = handler
}
Exporter
Exporter 用于上報(bào)節(jié)點(diǎn)健康信息到某種控制面。在 NPD 啟動時学少,會根據(jù)需求初始化并啟動各種 Exporter剪个。Exporter 分為三類:
K8s Exporter:會將節(jié)點(diǎn)健康信息上報(bào)到 API Server。
Prometheus Exporter:負(fù)責(zé)上報(bào)節(jié)點(diǎn)指標(biāo)信息到 Prometheus版确。
Plugable Exporters:可插拔的 Exporter(如 Stackdriver Exporter)扣囊,我們也可以自定義 Exporter,并在 init() 方法中注冊绒疗,這樣在 NPD 啟動時就會自動初始化并啟動侵歇。
ExporterHandler
ExporterHandler 和 ProblemDaemonHandler 功能類似,其定義了 Exporter 的初始化方法吓蘑。也是在NPD啟動時惕虑,init()方法中完成了 ExporterHandler 的注冊
type ExporterHandler struct {
// CreateExporterOrDie initializes an exporter, panic if error occurs.
CreateExporterOrDie func(CommandLineOptions) Exporter
// CmdOptionDescription explains how to configure the exporter from command line arguments.
Options CommandLineOptions
}
Condition Manager
K8s Exporter 獲取到的異常 Condition 信息會上報(bào)給 Condition Manager, Condition Manager 每秒檢查 Condition 的變化,并同步到 API Server 的 Node 對象中枷遂。
Problem Client
Problem Client 負(fù)責(zé)與 API Server 交互樱衷,并將巡檢過程中生成的 Events 和 Conditions 上報(bào)給 API Server。
type Client interface {
// 從 API Server 獲取當(dāng)前節(jié)點(diǎn)所有指定類型的 Conditions
GetConditions(conditionTypes []v1.NodeConditionType) ([]*v1.NodeCondition, error)
// 調(diào)用 API Server 接口更新當(dāng)前節(jié)點(diǎn)的 Condition 列表
SetConditions(conditions []v1.NodeCondition) error
// 上報(bào) Event 信息到 API Server
Eventf(eventType string, source, reason, messageFmt string, args ...interface{})
// 從 API Server 獲取當(dāng)前 node-problem-detector 實(shí)例所在的節(jié)點(diǎn)信息
GetNode() (*v1.Node, error)
}
Problem Detector
Problem Detector 是 NPD 的核心對象酒唉,它負(fù)責(zé)啟動所有的 Problem Daemon(也可以叫做 Monitor)矩桂,并利用 channel 收集 Problem Daemon中發(fā)現(xiàn)的異常信息,然后將異常信息提交給 Exporter痪伦,Exporter 負(fù)責(zé)將這些異常信息上報(bào)到指定的控制面(如 API Server侄榴、Prometheus、Stackdriver等)网沾。
Status
Status 是 Problem Daemon 向 Exporter 上報(bào)的異常信息對象癞蚕。
type Status struct {
// problem daemon 的名稱
Source string `json:"source"`
// 臨時的節(jié)點(diǎn)問題 —— 事件對象,如果此Status用于Condition更新則此字段可以為空
// 從老到新排列在數(shù)組中
Events []Event `json:"events"`
// 永久的節(jié)點(diǎn)問題 —— NodeCondition辉哥。PD必須總是在此字段報(bào)告最新的Condition
Conditions []Condition `json:"conditions"`
}
Tomb
用于從外部控制協(xié)程的生命周期桦山, 它的邏輯很簡單,準(zhǔn)備結(jié)束生命周期時:
外部協(xié)作者發(fā)起一個通知
協(xié)作線程接收到通知醋旦,進(jìn)行清理
清理完成后恒水,協(xié)程反向通知外部協(xié)作者
外部協(xié)作者退出阻塞
啟動過程
NPD 啟動過程完成的工作有:
打印 NPD 版本號
設(shè)置節(jié)點(diǎn)名稱,優(yōu)先使用命令行中設(shè)置的節(jié)點(diǎn)名稱饲齐,其次是環(huán)境變量 NODE_NAME 中的節(jié)點(diǎn)名稱钉凌,最次是 os.Hostname()
校驗(yàn)命令行參數(shù)的合法性
初始化 problem daemons
初始化默認(rèn) Exporters(包含 K8s Exporter、Prometheus Exporter)和可插拔 Exporters(如 Stackdriver Exporter)
使用 problem daemons 和 Exporters 構(gòu)建 Problem Detector捂人,并啟動
檢測流程
節(jié)點(diǎn)自愈
采集節(jié)點(diǎn)的健康狀態(tài)是為了能夠在業(yè)務(wù)Pod不可用之前提前發(fā)現(xiàn)節(jié)點(diǎn)異常御雕,從而運(yùn)維或開發(fā)人員可以對Docker、Kubelet或節(jié)點(diǎn)進(jìn)行修復(fù)滥搭。在NPDPlus中酸纲,為了減輕運(yùn)維人員的負(fù)擔(dān),提供了根據(jù)采集到的節(jié)點(diǎn)狀態(tài)從而進(jìn)行不同自愈動作的能力论熙。集群管理員可以根據(jù)節(jié)點(diǎn)不同的狀態(tài)配置相應(yīng)的自愈能力福青,如重啟Docker摄狱、重啟Kubelet或重啟CVM節(jié)點(diǎn)等脓诡。同時為了防止集群中的節(jié)點(diǎn)雪崩,在執(zhí)行自愈動作之前做了嚴(yán)格的限流媒役,防止節(jié)點(diǎn)大規(guī)模重啟祝谚。同時為了防止集群中的節(jié)點(diǎn)雪崩,在執(zhí)行自愈動作之前做了嚴(yán)格的限流酣衷。具體策略為:
在同一時刻只允許集群中的一個節(jié)點(diǎn)進(jìn)行自愈行為交惯,并且兩個自愈行為之間至少間隔1分鐘
當(dāng)有新節(jié)點(diǎn)添加到集群中時,會給節(jié)點(diǎn)2分鐘的容忍時間,防止由于節(jié)點(diǎn)剛剛添加到集群的不穩(wěn)定性導(dǎo)致錯誤自愈
custom-plugin-monitor
此Problem Daemon為NPD提供了一種插件化機(jī)制席爽,允許基于任何語言來編寫監(jiān)控腳本意荤,只需要這些腳本遵循NPD關(guān)于退出碼和標(biāo)準(zhǔn)輸出的規(guī)范。通過調(diào)用用戶配置的腳本來檢測各種節(jié)點(diǎn)問題
腳本退出碼:
0:對于Evnet來說表示Normal只锻,對于NodeCondition表示False
1:對于Evnet來說表示W(wǎng)arning玖像,對于NodeCondition表示True
腳本輸出應(yīng)該小于80字節(jié),避免給Etcd的存儲造成壓力
使用標(biāo)記禁用:disable_custom_plugin_monitor
示例
{
"plugin": "custom", // 插件類型
"pluginConfig": { // 插件配置
"invoke_interval": "10s", // 執(zhí)行時間間隔
"timeout": "3m", // 健康檢查超時時間
"max_output_length": 80,
"concurrency": 1 // 并行度
},
"source": "health-checker", // 事件源
"metricsReporting": true, // 是否上報(bào)指標(biāo)信息
"conditions": [ // 發(fā)現(xiàn)異常后在 Node 中設(shè)置的 Condition 信息
{
"type": "KubeletUnhealthy",
"reason": "KubeletIsHealthy",
"message": "kubelet on the node is functioning properly"
}
],
"rules": [ // 巡檢規(guī)則
{
"type": "permanent",
"condition": "KubeletUnhealthy",
"reason": "KubeletUnhealthy",
"path": "/home/kubernetes/bin/health-checker", // 二進(jìn)制文件路徑
"args": [ // 二進(jìn)制文件啟動參數(shù)
"--component=kubelet",
"--enable-repair=true",// 是否啟用自愈齐饮,自愈會嘗試重啟組件
"--cooldown-time=1m", // 冷卻時間捐寥,組件啟動后的一段時間為冷卻時間,冷卻時間能如果發(fā)現(xiàn)異常祖驱,不會嘗試自愈
"--loopback-time=0",// 要回溯的 journal 日志的時間握恳,如果為0,則從組件啟動時間開始回溯
"--health-check-timeout=10s" // 健康檢查超時時間
],
"timeout": "3m" // 巡檢超時時間
}
]
}
plugin
plugin 是NPD或用戶自定義的一些異常檢查程序捺僻,可以用任意語言編寫乡洼。custom-plugin-monitor 在執(zhí)行過程中會執(zhí)行這些異常檢測程序,并根據(jù)返回結(jié)果來判斷是否存在異常匕坯。NPD提供了三個 plugin就珠,分別是:
health-check:檢查kubelet、docker醒颖、kube-proxy妻怎、cri等進(jìn)程是否健康。
log-counter:依賴的插件是 journald泞歉,其作用是統(tǒng)計(jì)指定的 journal 日志中近一段時間滿足正則匹配的歷史日志條數(shù)逼侦。
network_problem.sh:檢查 conntrack table 的使用率是否超過 90%。
health-checker
命令行參數(shù)
參數(shù)名稱 | 參數(shù)說明 | 默認(rèn)值 |
---|---|---|
systemd-service | 與 --service 相同腰耙,已被 --service 取代 | |
service | The underlying service responsible for the component. Set to the corresponding component for docker and kubelet, containerd for cri. | |
loopback-time | The duration to loop back, if it is 0, health-check will check from start time. | 0min |
log-pattern | The log pattern to look for in service journald logs. The format for flag value <failureThresholdCount>:<logPattern> | |
health-check-timeout | The time to wait before marking the component as unhealthy. | 10s |
enable-repair | Flag to enable/disable repair attempt for the component. | true |
crictl-path | The path to the crictl binary. This is used to check health of cri component. | Linux:/usr/bin/crictl Windows:C:/etc/kubernetes/node/bin/crictl.exe |
cri-socket-path | The path to the cri socket. Used with crictl to specify the socket path. | Linux:unix:///var/run/containerd/containerd.sock Windows:npipe:////./pipe/containerd-containerd |
cooldown-time | The duration to wait for the service to be up before attempting repair. | 2min |
component | The component to check health for. Supports kubelet, docker, kube-proxy, and cri. |
結(jié)構(gòu)定義
type healthChecker struct {
component string // 要進(jìn)行健康檢查的組件名稱榛丢,支持 kubelet、docker挺庞、kube-proxy 和 cri
service string // 組件的服務(wù)名稱晰赞,需要通過 service 讀取 journal 日志,并檢查日志是否存在異常
enableRepair bool // 是否啟動自動修復(fù)选侨,如果啟動自動修復(fù)掖鱼,當(dāng)發(fā)現(xiàn)異常時會調(diào)用 repairFunc 嘗試自動修復(fù)
healthCheckFunc func() (bool, error) // 組件健康檢查方法
repairFunc func() // 組件自愈方法,這是一種”best-effort“形式的自愈援制,會嘗試 kill 掉組件的進(jìn)程戏挡,但可能失敗
uptimeFunc func() (time.Duration, error) // 獲取組件的啟動時間(啟動后經(jīng)過的時間)
crictlPath string // crictl 二進(jìn)制文件路徑,用于對 CRI(Container Runtime Interface) 組件執(zhí)行健康檢查
healthCheckTimeout time.Duration // 健康檢查超時時間
coolDownTime time.Duration // 服務(wù)啟動后晨仑,在冷卻時間內(nèi)如果發(fā)現(xiàn)異常褐墅,不會嘗試自動修復(fù)拆檬。超出冷卻時間后才會嘗試自動修復(fù)
loopBackTime time.Duration // 待檢 journal 查日志的起始時間間隔,如果該值為0妥凳,則從組件啟動的日志開始檢查
logPatternsToCheck map[string]int // 要檢查的 journal 日志的正則表達(dá)式
}
執(zhí)行流程
health-checker 的執(zhí)行流程可以分為三個步驟:
調(diào)用 healthCheckFunc() 方法判斷組件進(jìn)程是否健康
獲取組件近一段時間的 journal 日志竟贯,判斷異常日志數(shù)量是否達(dá)到上限
如果前兩步檢查都未發(fā)現(xiàn)異常,則返回 true逝钥。否則澄耍,如果啟動了自動修復(fù)機(jī)制,則調(diào)用 repairFunc() 嘗試自愈
健康檢查
func getHealthCheckFunc(hco *options.HealthCheckerOptions) func() (bool, error) {
switch hco.Component {
case types.KubeletComponent:
// 訪問 http://127.0.0.1:10248/healthz晌缘,判斷 kubelet 是否健康
return healthCheckEndpointOKFunc(types.KubeletHealthCheckEndpoint, hco.HealthCheckTimeout)
case types.KubeProxyComponent:
// 訪問 http://127.0.0.1:10256/healthz齐莲,判斷 kube-proxy 是否健康
return healthCheckEndpointOKFunc(types.KubeProxyHealthCheckEndpoint, hco.HealthCheckTimeout)
case types.DockerComponent:
return func() (bool, error) { // 執(zhí)行 docker ps 命令判斷 Docker 是否健康
if _, err := execCommand(hco.HealthCheckTimeout, getDockerPath(), "ps"); err != nil {
return false, nil
}
return true, nil
}
case types.CRIComponent:
return func() (bool, error) {// 執(zhí)行 circtl --runtime-endpoint=unix:///var/run/containerd/containerd.sock --image-endpoint=unix:///var/run/containerd/containerd.sock
if _, err := execCommand(hco.HealthCheckTimeout, hco.CriCtlPath, "--runtime-endpoint="+hco.CriSocketPath, "--image-endpoint="+hco.CriSocketPath, "pods"); err != nil {
return false, nil
}
return true, nil
}
default:
glog.Warningf("Unsupported component: %v", hco.Component)
}
return nil
}
組件自愈
func getRepairFunc(hco *options.HealthCheckerOptions) func() {
switch hco.Component {
case types.DockerComponent:
// Use "docker ps" for docker health check. Not using crictl for docker to remove
// dependency on the kubelet.
return func() {
execCommand(types.CmdTimeout, "pkill", "-SIGUSR1", "dockerd")
execCommand(types.CmdTimeout, "systemctl", "kill", "--kill-who=main", hco.Service)
}
default:
// Just kill the service for all other components
return func() {
execCommand(types.CmdTimeout, "systemctl", "kill", "--kill-who=main", hco.Service)
}
}
}
log-counter
依賴的插件是 journald,其作用是統(tǒng)計(jì)指定的 journal 日志中近一段時間滿足正則匹配的歷史日志條數(shù)磷箕。
命令行參數(shù)
參數(shù)名稱 | 參數(shù)說明 | 默認(rèn)值 |
---|---|---|
journald-source | The source configuration of journald, e.g., kernel, kubelet, dockerd, etc | |
log-path | The log path that log watcher looks up | |
lookback | The log path that log watcher looks up | |
delay | The time duration log watcher delays after node boot time. This is useful when log watcher needs to wait for some time until the node is stable. | |
pattern | The regular expression to match the problem in log. The pattern must match to the end of the line. | |
count | The number of times the pattern must be found to trigger the condition | 1 |
執(zhí)行流程
Count()
func (e *logCounter) Count() (count int, err error) {
start := e.clock.Now()
for {
select {
case log, ok := <-e.logCh:
if !ok {
err = fmt.Errorf("log channel closed unexpectedly")
return
}
// 只統(tǒng)計(jì) logCounter 啟動之前的日志
if start.Before(log.Timestamp) {
return
}
e.buffer.Push(log)
if len(e.buffer.Match(e.pattern)) != 0 {
count++
}
case <-e.clock.After(timeout):
// 如果超過一定時間沒有新日志生成选酗,則退出
return
}
}
}
journal日志檢查
func checkForPattern(service, logStartTime, logPattern string, logCountThreshold int) (bool, error) {
// 從 journal 日志中匹配符合規(guī)則的錯誤日志
out, err := execCommand(types.CmdTimeout, "/bin/sh", "-c",
// Query service logs since the logStartTime
`journalctl --unit "`+service+`" --since "`+logStartTime+
// 正則匹配
`" | grep -i "`+logPattern+
// 計(jì)算錯誤發(fā)生次數(shù)
`" | wc -l`)
if err != nil {
return true, err
}
occurrences, err := strconv.Atoi(out)
if err != nil {
return true, err
}
// 如果錯誤日志數(shù)量超過閾值,則返回 false
if occurrences >= logCountThreshold {
glog.Infof("%s failed log pattern check, %s occurrences: %v", service, logPattern, occurrences)
return false, nil
}
return true, nil
}
network_problem.sh
檢查 conntrack table 的使用率是否超過 90%
#!/bin/bash
# This plugin checks for common network issues.
# Currently only checks if conntrack table is more than 90% used.
readonly OK=0
readonly NONOK=1
readonly UNKNOWN=2
# "nf_conntrack" replaces "ip_conntrack" - support both
readonly NF_CT_COUNT_PATH='/proc/sys/net/netfilter/nf_conntrack_count'
readonly NF_CT_MAX_PATH='/proc/sys/net/netfilter/nf_conntrack_max'
readonly IP_CT_COUNT_PATH='/proc/sys/net/ipv4/netfilter/ip_conntrack_count'
readonly IP_CT_MAX_PATH='/proc/sys/net/ipv4/netfilter/ip_conntrack_max'
if [[ -f $NF_CT_COUNT_PATH ]] && [[ -f $NF_CT_MAX_PATH ]]; then
readonly CT_COUNT_PATH=$NF_CT_COUNT_PATH
readonly CT_MAX_PATH=$NF_CT_MAX_PATH
elif [[ -f $IP_CT_COUNT_PATH ]] && [[ -f $IP_CT_MAX_PATH ]]; then
readonly CT_COUNT_PATH=$IP_CT_COUNT_PATH
readonly CT_MAX_PATH=$IP_CT_MAX_PATH
else
exit $UNKNOWN
fi
readonly conntrack_count=$(< $CT_COUNT_PATH) || exit $UNKNOWN
readonly conntrack_max=$(< $CT_MAX_PATH) || exit $UNKNOWN
readonly conntrack_usage_msg="${conntrack_count} out of ${conntrack_max}"
if (( conntrack_count > conntrack_max * 9 /10 )); then
echo "Conntrack table usage over 90%: ${conntrack_usage_msg}"
exit $NONOK
else
echo "Conntrack table usage: ${conntrack_usage_msg}"
exit $OK
fi
system-log-monitor
system-log-monitor 用于監(jiān)控系統(tǒng)和內(nèi)核日志岳枷,根據(jù)預(yù)定義規(guī)則來報(bào)告問題芒填、指標(biāo)。它支持基于文件的日志空繁、Journald殿衰、kmsg。要監(jiān)控其它日志盛泡,需要實(shí)現(xiàn)LogWatcher接口
LogMonitor
type logMonitor struct {
// 配置文件路徑
configPath string
// 讀取日志的邏輯委托給LogWatcher闷祥,這里解耦的目的是支持多種類型的日志
watcher watchertypes.LogWatcher
// 日志緩沖,讀取的日志在此等待處理
buffer LogBuffer
// 對應(yīng)配置文件中的字段
config MonitorConfig
// 對應(yīng)配置文件中的conditions字段
conditions []types.Condition
// 輸入日志條目的通道
logCh <-chan *logtypes.Log
// 輸出狀態(tài)的通道
output chan *types.Status
// 用于控制此Monitor的生命周期
tomb *tomb.Tomb
}
LogWatcher
LogWatcher 的主要作用的監(jiān)聽文件更新傲诵,并將追加的文件內(nèi)容寫入 LogBuffer 中供 LogMonitor 處理凯砍。NPD 中提供了三種 LogWatcher 的實(shí)現(xiàn):
filelog:監(jiān)聽任意文本類型日志。
journald:監(jiān)聽 journald 日志拴竹。
kmsg:監(jiān)聽內(nèi)核日志設(shè)備悟衩,如 /dev/kmsg。
LogWatcher 也需要在 init() 方法中完成注冊栓拜。
type LogWatcher interface {
// 開始監(jiān)控日志座泳,并通過通道輸出日志
Watch() (<-chan *types.Log, error)
// 停止,注意釋放打開的資源
Stop()
}
filelog
filelog 通過監(jiān)控指定的文件更新幕与,并對日志內(nèi)容進(jìn)行正則匹配挑势,以發(fā)現(xiàn)異常日志,從而判斷組件是否正常纽门。
{
"plugin": "filelog",
"pluginConfig": {
"timestamp": "^time=\"(\\S*)\"",// 時間戳解析表達(dá)式
"message": "msg=\"([^\n]*)\"", // 日志解析表達(dá)式
"timestampFormat": "2006-01-02T15:04:05.999999999-07:00" // 時間戳格式
},
"logPath": "/var/log/docker.log", // 日志路徑
"lookback": "5m", // 日志回溯時長
"bufferSize": 10, // 緩沖大醒Τ堋(日志條數(shù))
"source": "docker-monitor",
"conditions": [],
"rules": [ // 健康檢查規(guī)則
{
"type": "temporary",
"reason": "CorruptDockerImage",
"pattern": "Error trying v2 registry: failed to register layer: rename /var/lib/docker/image/(.+) /var/lib/docker/image/(.+): directory not empty.*"
}
]
}
journald
journald 底層依賴 sdjournal 包营罢,監(jiān)控系統(tǒng)日志的更新赏陵,并且可以從指定的歷史時間點(diǎn)開始讀取饼齿。如果未指定 journal 日志路徑,則從系統(tǒng)默認(rèn)路徑讀取蝙搔。讀取到的日志會轉(zhuǎn)換成 logtypes.Log 對象缕溉,并寫入 logCh 通道中。journal 通過監(jiān)控 journal 文件更新吃型,并對日志內(nèi)容進(jìn)行正則匹配证鸥,以發(fā)現(xiàn)異常日志,從而判斷組件是否正常勤晚。
{
"plugin": "journald",
"pluginConfig": {
"source": "abrt-notification"
},
"logPath": "/var/log/journal", // journal 日志路徑
"lookback": "5m", // 日志回溯時長
"bufferSize": 10, // log 緩存大型鞑恪(日志條數(shù))
"source": "abrt-adaptor",
"conditions": [],
"rules": [ // 健康檢查規(guī)則
{
"type": "temporary",
"reason": "CCPPCrash",
"pattern": "Process \\d+ \\(\\S+\\) crashed in .*"
},
{
"type": "temporary",
"reason": "UncaughtException",
"pattern": "Process \\d+ \\(\\S+\\) of user \\d+ encountered an uncaught \\S+ exception"
},
{
"type": "temporary",
"reason": "XorgCrash",
"pattern": "Display server \\S+ crash in \\S+"
},
{
"type": "temporary",
"reason": "VMcore",
"pattern": "System encountered a fatal error in \\S+"
},
{
"type": "temporary",
"reason": "Kerneloops",
"pattern": "System encountered a non-fatal error in \\S+"
}
]
}
kmsg
kmsg 和 journald 的實(shí)現(xiàn)原理類似,它底層依賴 kmsgparser 包赐写,實(shí)現(xiàn)內(nèi)核日志的監(jiān)控更新和回溯鸟蜡。默認(rèn)的文件路徑是 /dev/kmsg。kmsg 通過監(jiān)控系統(tǒng)日志文件更新挺邀,并對日志內(nèi)容進(jìn)行正則匹配揉忘,以發(fā)現(xiàn)異常日志,從而判斷組件是否正常端铛。
{
"plugin": "kmsg",
"logPath": "/dev/kmsg", // 內(nèi)核日志路徑
"lookback": "5m", // 日志回溯時長
"bufferSize": 10, // 緩存大衅(日志條數(shù))
"source": "kernel-monitor",
"metricsReporting": true,
"conditions": [
{
"type": "KernelDeadlock",
"reason": "KernelHasNoDeadlock",
"message": "kernel has no deadlock"
},
{
"type": "ReadonlyFilesystem",
"reason": "FilesystemIsNotReadOnly",
"message": "Filesystem is not read-only"
}
],
"rules": [
{
"type": "temporary",
"reason": "OOMKilling",
"pattern": "Killed process \\d+ (.+) total-vm:\\d+kB, anon-rss:\\d+kB, file-rss:\\d+kB.*"
},
{
"type": "temporary",
"reason": "TaskHung",
"pattern": "task [\\S ]+:\\w+ blocked for more than \\w+ seconds\\."
},
{
"type": "temporary",
"reason": "UnregisterNetDevice",
"pattern": "unregister_netdevice: waiting for \\w+ to become free. Usage count = \\d+"
},
{
"type": "temporary",
"reason": "KernelOops",
"pattern": "BUG: unable to handle kernel NULL pointer dereference at .*"
},
{
"type": "temporary",
"reason": "KernelOops",
"pattern": "divide error: 0000 \\[#\\d+\\] SMP"
},
{
"type": "temporary",
"reason": "Ext4Error",
"pattern": "EXT4-fs error .*"
},
{
"type": "temporary",
"reason": "Ext4Warning",
"pattern": "EXT4-fs warning .*"
},
{
"type": "temporary",
"reason": "IOError",
"pattern": "Buffer I/O error .*"
},
{
"type": "temporary",
"reason": "MemoryReadError",
"pattern": "CE memory read error .*"
},
{
"type": "permanent",
"condition": "KernelDeadlock",
"reason": "AUFSUmountHung",
"pattern": "task umount\\.aufs:\\w+ blocked for more than \\w+ seconds\\."
},
{
"type": "permanent",
"condition": "KernelDeadlock",
"reason": "DockerHung",
"pattern": "task docker:\\w+ blocked for more than \\w+ seconds\\."
},
{
"type": "permanent",
"condition": "ReadonlyFilesystem",
"reason": "FilesystemIsReadOnly",
"pattern": "Remounting filesystem read-only"
}
]
}
LogBuffer
LogBuffer 是一個可循環(huán)寫入的日志隊(duì)列,max 字段控制可記錄日志的最大條數(shù)禾蚕,當(dāng)日志條數(shù)超過 max 時您朽,就會從頭覆蓋寫入。LogBuffer 也支持正則匹配 buffer 中的日志內(nèi)容换淆。
type LogBuffer interface {
// 把日志寫入 log buffer 中
Push(*types.Log)
// 對 buffer 中的日志進(jìn)行正則匹配
Match(string) []*types.Log
// 把 log buffer 中的日志按時間由遠(yuǎn)到近連接成一個字符串
String() string
}
實(shí)現(xiàn)原理
啟動過程
執(zhí)行過程
system-stats-monitor
將各種健康相關(guān)的統(tǒng)計(jì)信息報(bào)告為Metrics
目前支持的組件僅僅有主機(jī)信息虚倒、磁盤:
disk/io_time 設(shè)備隊(duì)列非空時間,毫秒
disk/weighted_io 設(shè)備隊(duì)列非空時間加權(quán)产舞,毫秒
disk/avg_queue_len 上次調(diào)用插件以來魂奥,平均排隊(duì)請求數(shù)
使用標(biāo)記禁用:disable_system_stats_monitor
cpuCollector:采集 CPU 相關(guān)指標(biāo)信息。
diskCollector:采集磁盤相關(guān)指標(biāo)信息易猫。
hostCollector:采集宿主機(jī)相關(guān)指標(biāo)信息耻煤。
memoryCollector:采集內(nèi)存相關(guān)指標(biāo)信息。
osFeatureCollector:采集系統(tǒng)屬性相關(guān)指標(biāo)准颓。
netCollector:采集網(wǎng)絡(luò)相關(guān)指標(biāo)信息哈蝇。
檢查規(guī)則
自定義插件規(guī)則
CustomRule
// 自定義規(guī)則(插件),描述CPM如何調(diào)用插件攘已,分析調(diào)用結(jié)果
type CustomRule struct {
// 報(bào)告永久還是臨時問題
Type types.Type `json:"type"`
// 此問題觸發(fā)哪種NodeCondition炮赦,僅當(dāng)永久問題才設(shè)置此字段
Condition string `json:"condition"`
// 問題的簡短原因,對于永久問題样勃,通常描述NodeCondition的一個子類型
Reason string `json:"reason"`
// 自定義插件(腳本)的文件路徑
Path string `json:"path"`
// 傳遞給自定義插件的參數(shù)
Args []string `json:"args"`
// 自定義插件執(zhí)行超時
TimeoutString *string `json:"timeout"`
Timeout *time.Duration `json:"-"`
}
示例
系統(tǒng)日志監(jiān)控規(guī)則
systemlogtypes.Rule
type Rule struct {
// 報(bào)告永久還是臨時問題
Type types.Type `json:"type"`
// 此問題觸發(fā)哪種NodeCondition吠勘,僅當(dāng)永久問題才設(shè)置此字段
Condition string `json:"condition"`
// 問題的簡短原因性芬,對于永久問題,通常描述NodeCondition的一個子類型
Reason string `json:"reason"`
// Pattern is the regular expression to match the problem in log.
// Notice that the pattern must match to the end of the line.
Pattern string `json:"pattern"`
}
示例
異常上報(bào)
node-problem-detector使用 Event 和 NodeCondition 將問題報(bào)告給apiserver剧防。
NodeCondition:導(dǎo)致節(jié)點(diǎn)無法處理于Pod生命周期的的永久性問題應(yīng)報(bào)告為NodeCondition植锉。
Event:對pod影響有限的臨時問題應(yīng)作為event報(bào)告。
異常類型
temporary:致節(jié)點(diǎn)無法處理于Pod生命周期的的永久性問題
permanent:對pod影響有限的臨時問題
指標(biāo)上報(bào)
通過配置 metricsReporting 可以選擇是否開啟 System Log Monitor 的指標(biāo)上報(bào)功能峭拘。該字段默認(rèn)為 true俊庇。
臨時異常只會上報(bào) counter 指標(biāo),如下:
# HELP problem_counter Number of times a specific type of problem have occurred.
# TYPE problem_counter counter
problem_counter{reason="TaskHung"} 2
永久異常會上報(bào) gauge 指標(biāo)和 counter 指標(biāo)鸡挠,如下:
# HELP problem_counter Number of times a specific type of problem have occurred.
# TYPE problem_counter counter
problem_counter{reason="DockerHung"} 1
# HELP problem_gauge Whether a specific type of problem is affecting the node or not.
# TYPE problem_gauge gauge
problem_gauge{condition="KernelDeadlock",reason="DockerHung"} 1
Counter是一個累計(jì)類型的數(shù)據(jù)指標(biāo)辉饱,它代表單調(diào)遞增的計(jì)數(shù)器。
Gauge是可以任意上下波動數(shù)值的指標(biāo)類型拣展。
指標(biāo)
NPD對指標(biāo)這一概念也進(jìn)行了封裝鞋囊,它依賴OpenCensus而不是Prometheus這樣具體的實(shí)現(xiàn)的API。
所有指標(biāo)如下:
const (
CPURunnableTaskCountID MetricID = "cpu/runnable_task_count"
CPUUsageTimeID MetricID = "cpu/usage_time"
CPULoad1m MetricID = "cpu/load_1m"
CPULoad5m MetricID = "cpu/load_5m"
CPULoad15m MetricID = "cpu/load_15m"
ProblemCounterID MetricID = "problem_counter"
ProblemGaugeID MetricID = "problem_gauge"
DiskIOTimeID MetricID = "disk/io_time"
DiskWeightedIOID MetricID = "disk/weighted_io"
DiskAvgQueueLenID MetricID = "disk/avg_queue_len"
DiskOpsCountID MetricID = "disk/operation_count"
DiskMergedOpsCountID MetricID = "disk/merged_operation_count"
DiskOpsBytesID MetricID = "disk/operation_bytes_count"
DiskOpsTimeID MetricID = "disk/operation_time"
DiskBytesUsedID MetricID = "disk/bytes_used"
HostUptimeID MetricID = "host/uptime"
MemoryBytesUsedID MetricID = "memory/bytes_used"
MemoryAnonymousUsedID MetricID = "memory/anonymous_used"
MemoryPageCacheUsedID MetricID = "memory/page_cache_used"
MemoryUnevictableUsedID MetricID = "memory/unevictable_used"
MemoryDirtyUsedID MetricID = "memory/dirty_used"
OSFeatureID MetricID = "system/os_feature"
SystemProcessesTotal MetricID = "system/processes_total"
SystemProcsRunning MetricID = "system/procs_running"
SystemProcsBlocked MetricID = "system/procs_blocked"
SystemInterruptsTotal MetricID = "system/interrupts_total"
SystemCPUStat MetricID = "system/cpu_stat"
NetDevRxBytes MetricID = "net/rx_bytes"
NetDevRxPackets MetricID = "net/rx_packets"
NetDevRxErrors MetricID = "net/rx_errors"
NetDevRxDropped MetricID = "net/rx_dropped"
NetDevRxFifo MetricID = "net/rx_fifo"
NetDevRxFrame MetricID = "net/rx_frame"
NetDevRxCompressed MetricID = "net/rx_compressed"
NetDevRxMulticast MetricID = "net/rx_multicast"
NetDevTxBytes MetricID = "net/tx_bytes"
NetDevTxPackets MetricID = "net/tx_packets"
NetDevTxErrors MetricID = "net/tx_errors"
NetDevTxDropped MetricID = "net/tx_dropped"
NetDevTxFifo MetricID = "net/tx_fifo"
NetDevTxCollisions MetricID = "net/tx_collisions"
NetDevTxCarrier MetricID = "net/tx_carrier"
NetDevTxCompressed MetricID = "net/tx_compressed"
)
其中ProblemCounterID 和 ProblemGaugeID 是針對所有Problem的Counter/Gauge瞎惫,其他都是SystemStatsMonitor暴露的指標(biāo)溜腐。
治愈系統(tǒng)
在NPD的術(shù)語中,治愈系統(tǒng)(Remedy System)是一個或一組進(jìn)程瓜喇,負(fù)責(zé)分析NPD檢測出的問題挺益,并且采取補(bǔ)救措施,讓K8S集群恢復(fù)健康狀態(tài)乘寒。
目前官方提及的治愈系統(tǒng)有只有Draino望众。NPD項(xiàng)目并沒有提供對Draino的集成,你需要手工部署和配置Draino伞辛。
Draino
Draino是Planet開源的小項(xiàng)目烂翰,最初在Planet用于解決GCE上運(yùn)行的K8S集群的持久卷相關(guān)進(jìn)程(mkfs.ext4、mount等)永久卡死在不可中斷睡眠狀態(tài)的問題蚤氏。Draino的工作方式簡單粗暴甘耿,只是檢測到NodeCondition并Cordon、Drain節(jié)點(diǎn)竿滨。
基于Label和NodeCondition自動的Drain掉故障K8S節(jié)點(diǎn):
具有匹配標(biāo)簽的的K8S節(jié)點(diǎn)佳恬,只要進(jìn)入指定的NodeCondition之一,立即禁止調(diào)度(Cordoned)
在禁止調(diào)度之后一段時間于游,節(jié)點(diǎn)被Drain掉
Draino可以聯(lián)用Cluster Autoscaler毁葱,自動的終結(jié)掉Drained的節(jié)點(diǎn)。
在Descheduler項(xiàng)目成熟以后贰剥,可以代替Draino倾剿。