問題背景
Prometheus在抓取container的CPU/Mem等metric的時候,發(fā)現(xiàn)metric上沒有帶Pod的label,這導(dǎo)致一個問題,無法通過自定義的label查看其下的所有metric資源。
例如下面的一個a-custom-project, 帶了一個project: a-custom-project label
apiVersion: v1
kind: Pod
metadata:
labels:
project: a-custom-project
name: a-custom-project
spec:
containers:
image: nginx
imagePullPolicy: Always
希望project: a-custom-project label過濾出其下的所有metic指標(biāo)屁魏,下面是一個container_cpu_usage_seconds_total的metric
正常情況下是不會出現(xiàn)紅色框中的label,因此在grafana上也無法通過下面的方式顯示
問題分析
導(dǎo)致此問題的出現(xiàn)主要的原因是kubelet在調(diào)用kubeGenericRuntimeManager創(chuàng)建container的時候捉腥,過濾掉所有的自定義的label氓拼,只添加了固定的幾個指標(biāo)。
kubernetes/pkg/kubelet/kuberuntime/labels.go
// newContainerLabels creates container labels from v1.Container and v1.Pod.
func newContainerLabels(container *v1.Container, pod *v1.Pod) map[string]string {
labels := map[string]string{}
labels[types.KubernetesPodNameLabel] = pod.Name
labels[types.KubernetesPodNamespaceLabel] = pod.Namespace
labels[types.KubernetesPodUIDLabel] = string(pod.UID)
labels[types.KubernetesContainerNameLabel] = container.Name
return labels
}
并且kubelet會過濾容器上的label標(biāo)簽但狭,只保留規(guī)定的KubernetesPodNameLabel / KubernetesPodNameSpaceLabel / KubernetesContainerNameLabel 等label
kubernetes/pkg/kubelet/server/server.go
func containerPrometheusLabelsFunc(s stats.Provider) metrics.ContainerLabelsFunc {
// containerPrometheusLabels maps cAdvisor labels to prometheus labels.
return func(c *cadvisorapi.ContainerInfo) map[string]string {
// Prometheus requires that all metrics in the same family have the same labels,
// so we arrange to supply blank strings for missing labels
var name, image, podName, namespace, containerName string
if len(c.Aliases) > 0 {
name = c.Aliases[0]
}
image = c.Spec.Image
if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNameLabel]; ok {
podName = v
}
if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNamespaceLabel]; ok {
namespace = v
}
if v, ok := c.Spec.Labels[kubelettypes.KubernetesContainerNameLabel]; ok {
containerName = v
}
// Associate pod cgroup with pod so we have an accurate accounting of sandbox
if podName == "" && namespace == "" {
if pod, found := s.GetPodByCgroupfs(c.Name); found {
podName = pod.Name
namespace = pod.Namespace
}
}
set := map[string]string{
metrics.LabelID: c.Name,
metrics.LabelName: name,
metrics.LabelImage: image,
"pod": podName,
"namespace": namespace,
"container": containerName,
}
return set
}
}
此問題從2015年披诗,k8s 1.1版本起就在社區(qū)開始有反饋,期間也不斷有人issues立磁,但是一直沒有解決呈队,社區(qū)主要考慮的有2點:
1)abel是一個很易變的數(shù)據(jù),變化后會導(dǎo)致圖標(biāo)不連續(xù)(詳見:https://www.robustperception.io/how-to-have-labels-for-machine-roles)
2)過多的label會導(dǎo)致prometheus存儲壓力的增加唱歧,每個label都會創(chuàng)建一個timeseries宪摧,(詳見:https://github.com/kubernetes/kubernetes/issues/79702)
references:https://github.com/kubernetes/kubernetes/issues/32326
影響范圍
目前只影響cadvisor上指標(biāo),所有的container_*指標(biāo)都存在此問題颅崩,例如fs / cpu / mem / net 等等容器指標(biāo)几于。
解決方案一:聯(lián)表聚合(社區(qū)方案)
社區(qū)的思想是減少容器的label,這塊大大減輕prometheus的存儲壓力沿后,并且認(rèn)為label應(yīng)該是恒定沿彭,在整個業(yè)務(wù)的生命周期中應(yīng)該固定的,并且認(rèn)為label是個易變尖滚,很容易導(dǎo)致業(yè)務(wù)的圖標(biāo)出現(xiàn)中斷:
references:
- https://www.robustperception.io/target-labels-are-for-life-not-just-for-christmas
- https://www.robustperception.io/exposing-the-software-version-to-prometheus
- https://www.robustperception.io/how-to-have-labels-for-machine-roles
基于上面這個思想喉刘,社區(qū)提供一個聯(lián)表聚合的方案,這個方案也是社區(qū)提供的唯一方案(詳見:https://www.weave.works/blog/aggregating-pod-resource-cpu-memory-usage-arbitrary-labels-prometheus/)
大概的解決思路是:kube_state_metrics exporter提供一個kube_pod_labels的metric漆弄,這個metric里面有pod自定義的pod labels睦裳,然后和沒有自定義label的metric進(jìn)行聯(lián)表聚合查詢,從而實現(xiàn)了通過自定義label來過濾沒有自定義label的metric的能力撼唾,由于是聯(lián)表聚合查詢廉邑,因此此能力是運行時的。
sum(irate(container_cpu_usage_seconds_total{image!=""}[1m])) by (namespace,pod)
* on (namespace, pod) group_left(label_yfd_service)
kube_pod_labels{label_yfd_service="tutor-live-subjective-question"}
緩解方案
1)通過sharding方式,但是此方式無法按時間段存儲在不同的prometheus蛛蒙,因此這種方式對單個merric來說是無效的糙箍。在metric固定的情況下,增加prometheus是可以起到一些緩解作用
2)通過recording rule方式牵祟,此方式通過將聚合查詢的結(jié)果記錄為一張新表倍靡,可以實現(xiàn)和原生label相同的查詢速度,但是此方式對計算和存儲的資源消耗很大课舍,但是也可以通過增加prometheus來緩解。
解決方案二:修改kubelet源碼方案
在社區(qū)有人提出通過白名單的給cadvisor添加label他挎,但是社區(qū)沒有下文(詳見:https://github.com/google/cadvisor/issues/2380)筝尾。在社區(qū)搜了一下有很多人都在提這個問題,最近還有一個人提了PR办桨,但是被拒絕了(詳見:https://github.com/kubernetes/kubernetes/pull/95210)筹淫。
Demo實現(xiàn):直接通過硬編碼 project 關(guān)鍵字 label,檢查此 label 并將其傳遞到container 的 label 上呢撞。kubelet 暴露給 prometheus 的接口處也添加了一個檢查损姜,會將此 label 加入到返回的 set 集中,從而實現(xiàn) promethues 能獲取到pod 的 label殊霞。
更優(yōu)方案:通過設(shè)置一個白名單列表摧阅,通過命令參數(shù)傳入,這樣既可以避免過多的 label 影響prometheus的存儲绷蹲,也可以實現(xiàn)靈活的 label 配置棒卷。
kubernetes/pkg/kubelet/kuberuntime/labels.go
// newContainerLabels creates container labels from v1.Container and v1.Pod.
func newContainerLabels(container *v1.Container, pod *v1.Pod) map[string]string {
labels := map[string]string{}
labels[types.KubernetesPodNameLabel] = pod.Name
labels[types.KubernetesPodNamespaceLabel] = pod.Namespace
labels[types.KubernetesPodUIDLabel] = string(pod.UID)
labels[types.KubernetesContainerNameLabel] = container.Name
// patch by zhaixigui
for labelName ,_ := range pod.ObjectMeta.GetLabels() {
if labelName == "project" {
labels[labelName] = pod.ObjectMeta.Labels[labelName]
}
}
return labels
kubernetes/pkg/kubelet/server/server.go
func containerPrometheusLabelsFunc(s stats.Provider) metrics.ContainerLabelsFunc {
// containerPrometheusLabels maps cAdvisor labels to prometheus labels.
return func(c *cadvisorapi.ContainerInfo) map[string]string {
// Prometheus requires that all metrics in the same family have the same labels,
// so we arrange to supply blank strings for missing labels
var name, image, podName, namespace, containerName string
if len(c.Aliases) > 0 {
name = c.Aliases[0]
}
image = c.Spec.Image
if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNameLabel]; ok {
podName = v
}
if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNamespaceLabel]; ok {
namespace = v
}
if v, ok := c.Spec.Labels[kubelettypes.KubernetesContainerNameLabel]; ok {
containerName = v
}
// Associate pod cgroup with pod so we have an accurate accounting of sandbox
if podName == "" && namespace == "" {
if pod, found := s.GetPodByCgroupfs(c.Name); found {
podName = pod.Name
namespace = pod.Namespace
}
}
set := map[string]string{
metrics.LabelID: c.Name,
metrics.LabelName: name,
metrics.LabelImage: image,
"pod": podName,
"namespace": namespace,
"container": containerName,
}
// patch by xigui
for labelName,labelValue := range c.Spec.Labels {
if labelName == "project" {
set[labelName] = labelValue
}
}
return set
}
}
解決方案三:阿里云 - 大數(shù)據(jù)
阿里云將 Promsql 查詢時間小于2秒的用社區(qū)的 kube_pod_label 聯(lián)表聚合的方案,而大于2秒的查詢用大數(shù)據(jù)平臺處理(類似Hbase)祝钢,有大數(shù)據(jù)平臺保證查詢的響應(yīng)時間比规。
解決方案四:獨立安裝cAdvisor
社區(qū)有 member 想將 cAdvisor 獨立出來,為了解決 kubelet 的性能問題拦英,但是最后也不了了之蜒什。(詳解:https://github.com/kubernetes/kubernetes/issues/18770)。
另外有一個人將 cAdvisor 的部分統(tǒng)計信息放到了CRI里疤估,試圖將 kubelet 和 cadvisor 分離灾常,但是此PR并不是解決 Pod 的 label 傳遞到 container 上的問題(詳見:https://github.com/kubernetes/kubernetes/pull/45614),此方案沒有最終的實現(xiàn)做裙。
性能測試
prometheus查詢container_cpu_usage_seconds_total metric岗憋,實例數(shù)5個,最大查詢時間2天
1)聯(lián)表查詢(單位:毫秒)
sum(irate(container_cpu_usage_seconds_total{image!=""}[1m])) by (namespace,pod)
* on (namespace, pod) group_left(label_yfd_service)
kube_pod_labels{label_yfd_service="tutor-live-subjective-question"}
2d: 1273, 1312, 1277, 1258, 1320
1h: 311, 396, 367, 365, 2882)原生查詢(單位:毫秒)
2)原生查詢
sum(irate(container_cpu_usage_seconds_total{image!="", yfd_service="tutor-live-subjective-question"}[1m])) by (namespace,pod)
2d: 71, 73, 78, 76, 85
1h: 39, 43, 45, 50, 40
對比:通過直接過濾label和kube_pod_labels連表查詢的返回響應(yīng)
2d: 平均相差1212毫秒锚贱,相差16倍
1h:平均相差302毫秒仔戈, 相差7倍
方案對比
修改源碼 | 聯(lián)表聚合 | 大數(shù)據(jù) | |
---|---|---|---|
優(yōu)點 | 簡單,性能高 | 簡單,符合社區(qū)方向 | 在不改源碼的情況监徘,解決了「聯(lián)表聚合」性能問題 |
缺點 | 修改了kubelet源碼 | 響應(yīng)時間很慢晋修,數(shù)據(jù)量大了很難處理 | 需要一個大數(shù)據(jù)平臺,對維護(hù)和成本來說都是巨大的挑戰(zhàn) |
響應(yīng)時間 | 快 | 很慢 | 中 |
架構(gòu)復(fù)雜性 | 簡單 | 簡單 | 非常復(fù)雜 |
開發(fā)成本 | 低 | 無 | 高 |
資源要求 | 低 | 低 | 高 |
符合社區(qū)方向 | 不符合 | 符合 | - |
穩(wěn)定性 | 高 | 高 | 低 |
其他公司的實踐
公司 | 規(guī)模 | 選用方案 | 說明 |
---|---|---|---|
快手 | 超大 | 放棄prometheus | 經(jīng)過幾次修改prometheus源碼后均不能解決其大數(shù)據(jù)量的問題 |
瓜子 | 小 | 聯(lián)表聚合 | 規(guī)模較小凰盔,提升性能用rule |
馬蜂窩 | 小 | 聯(lián)表聚合 | 規(guī)模小墓卦,對基于自定義的label查詢需求不大 |
對kubernetes云原生方面的故障排查、解決方案户敬、優(yōu)化調(diào)優(yōu)感興趣的朋友可以關(guān)注喜貴的云原生