Featured image of post Kubernetes ConfigMap 过大导致 kubectl apply 失败

Kubernetes ConfigMap 过大导致 kubectl apply 失败

ConfigMap 过大导致 kubectl apply 失败问题

整改 Dashboard.json ConfigMap 过大导致 kubectl apply 失败问题

在实际部署 Grafana Dashboard 过程中,因 kubectl apply 会将完整配置写入 kubectl.kubernetes.io/last-applied-configuration 注释,导致 ConfigMap 的 metadata.annotations 长度超限制,出现部署报错。本文围绕「拆分 ConfigMap + 挂载到指定目录」的核心思路,提供 subPath 和 Projected 两种落地方案,同时结合 Kubernetes 官方源码解析限制本质。

一、问题核心回顾

1. 报错现象

执行 kubectl apply 创建 Grafana 相关 ConfigMap(如 grafana-dashboards-general)时,终端提示如下错误,配置创建失败:

1
ConfigMap "grafana-dashboards-general" is invalid: metadata.annotations: Too long: must have at most 262144 characters

2. 根源分析

  • kubectl apply** 的注释机制**:kubectl apply 作为 Kubernetes 声明式部署的核心命令,会自动在 ConfigMap 的 metadata.annotations 中添加 kubectl.kubernetes.io/last-applied-configuration 注释 —— 该注释会完整记录上一次 apply 时的配置内容,用于后续对比配置差异、实现增量更新。当 dashboard.json 包含大量可视化配置(如多指标面板、复杂筛选规则)时,注释携带的完整配置会让 metadata.annotations 总长度骤增。

  • Kubernetes 官方的注释长度限制逻辑:这一限制的底层校验逻辑来自 Kubernetes 源码文件 staging/src/k8s.io/apimachinery/pkg/api/validation/objectmeta.go(具体代码地址:具体代码地址【点击】

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package validation

func ValidateAnnotationsSize(annotations map[string]string) error {
	var totalSize int64
	for k, v := range annotations {
		totalSize += (int64)(len(k)) + (int64)(len(v))
	}
	if totalSize > (int64)(TotalAnnotationSizeLimitB) {
		return fmt.Errorf("annotations size %d is larger than limit %d", totalSize, TotalAnnotationSizeLimitB)
	}
	return nil
}

从代码可见,Kubernetes 会严格计算所有注释的 “键 + 值” 总字节数,若超过 TotalAnnotationSizeLimitB 定义的 262144 字节(256KB),则直接返回错误,这也是本次 ConfigMap 部署失败的根本原因。

二、整改思路

核心逻辑:将单个超大 ConfigMap 按功能拆分为多个小 ConfigMap,再通过 Kubernetes 卷挂载能力,将所有小 ConfigMap 的配置文件合并挂载到 Grafana 所需的 /home/data/grafana/dashboards 目录。这种方式既规避了单 ConfigMap 注释过长的问题(拆分后单个 ConfigMap 配置 + 注释总长度低于 256KB),又能保证 Grafana 正常加载所有 Dashboard 配置,不影响业务使用。

三、具体实现方案

方案 1:基于 subPath 挂载多 ConfigMap

利用 Kubernetes 的 subPath 特性,可将单个 ConfigMap 中的多个配置文件(或多个 ConfigMap 的配置文件),分别挂载到目标目录的指定文件路径,实现多 ConfigMap 配置的集中存储,且不相互干扰。

1. 前置操作:拆分 ConfigMap

首先将原超大 dashboard.json 按业务功能(如 “系统监控面板”“业务指标面板”“日志分析面板”)拆分为多个独立 JSON 文件,例如拆分为 sys-monitor.jsonbiz-metric.jsonlog-analysis.json(归为 grafana-dashboards-group1 ConfigMap)和 user-active.jsondata-stat.jsonalert-rule.json(归为 grafana-dashboards-group2 ConfigMap)。

执行以下命令创建拆分后的 ConfigMap(需替换文件路径为实际路径):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建 ConfigMap group1(包含 sys-monitor.json、biz-metric.json、log-analysis.json)

kubectl create configmap grafana-dashboards-group1 \

 --from-file=sys-monitor.json=./sys-monitor.json \

 --from-file=biz-metric.json=./biz-metric.json \

 --from-file=log-analysis.json=./log-analysis.json \

 -n grafana-namespace  # 替换为实际 Grafana 所在命名空间

# 创建 ConfigMap group2(包含 user-active.json、data-stat.json、alert-rule.json)

kubectl create configmap grafana-dashboards-group2 \

 --from-file=user-active.json=./user-active.json \

 --from-file=data-stat.json=./data-stat.json \

 --from-file=alert-rule.json=./alert-rule.json \

 -n grafana-namespace

2. Deployment 配置(含 subPath 挂载)

在 Grafana 的 Deployment 配置中,通过 subPath 明确每个配置文件的挂载路径,确保所有拆分后的 JSON 文件最终都集中在 /home/data/grafana/dashboards 目录下,配置示例如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
apiVersion: apps/v1

kind: Deployment

metadata:

 name: grafana-deployment  # Grafana 实际 Deployment 名称

 namespace: grafana-namespace  # 替换为实际命名空间

spec:

 replicas: 1

 selector:

   matchLabels:

     app: grafana  # Grafana 对应的标签,需与 Pod 标签一致

 template:

   metadata:

     labels:

       app: grafana

   spec:

     containers:

       - name: grafana-container  # Grafana 容器名称

         image: grafana/grafana:latest  # 实际使用的 Grafana 镜像版本

         command: ["/run.sh"]  # Grafana 启动命令(按实际镜像配置调整)

         volumeMounts:

           # 从 group1 挂载 sys-monitor.json 到目标目录

           - name: grafana-dashboards-group1

             mountPath: /home/data/grafana/dashboards/sys-monitor.json  # 目标文件路径

             subPath: sys-monitor.json  # 对应 ConfigMap 中的文件键名

           # 从 group1 挂载 biz-metric.json

           - name: grafana-dashboards-group1

             mountPath: /home/data/grafana/dashboards/biz-metric.json

             subPath: biz-metric.json

           # 从 group1 挂载 log-analysis.json

           - name: grafana-dashboards-group1

             mountPath: /home/data/grafana/dashboards/log-analysis.json

             subPath: log-analysis.json

           # 从 group2 挂载 user-active.json

           - name: grafana-dashboards-group2

             mountPath: /home/data/grafana/dashboards/user-active.json

             subPath: user-active.json

           # 从 group2 挂载 data-stat.json

           - name: grafana-dashboards-group2

             mountPath: /home/data/grafana/dashboards/data-stat.json

             subPath: data-stat.json

           # 从 group2 挂载 alert-rule.json

           - name: grafana-dashboards-group2

             mountPath: /home/data/grafana/dashboards/alert-rule.json

             subPath: alert-rule.json

     volumes:

       # 定义 group1 卷,关联同名 ConfigMap

       - name: grafana-dashboards-group1

         configMap:

           name: grafana-dashboards-group1

       # 定义 group2 卷,关联同名 ConfigMap

       - name: grafana-dashboards-group2

         configMap:

           name: grafana-dashboards-group2

3. 方案特点

  • 优势:配置逻辑直观,可精确控制每个文件的挂载路径,避免文件覆盖风险,适合配置文件数量固定、变更频率低的场景;

  • 注意:若后续新增或删除 Dashboard 配置文件,需手动在 volumeMounts 中添加或删除对应条目,扩展性较弱。

方案 2:基于 Projected Volume 挂载多 ConfigMap

利用 Kubernetes 的 Projected Volume(投射卷)特性,可将多个 ConfigMap 的所有配置文件自动合并挂载到目标目录,无需手动指定单个文件路径,大幅简化配置,同时支持灵活扩展。

1. 前置操作:拆分 ConfigMap

与方案 1 一致,已将原超大 ConfigMap 拆分为 grafana-dashboards-group1grafana-dashboards-group2 两个小 ConfigMap,确保单个 ConfigMap 配置 + 注释总长度低于 256KB(符合 ValidateAnnotationsSize 函数的限制)。

2. Deployment 配置(含 Projected 挂载)

在 Deployment 中定义 projected 类型卷,关联所有拆分后的 ConfigMap,即可实现所有配置文件自动合并到 /home/data/grafana/dashboards 目录,配置示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
apiVersion: apps/v1

kind: Deployment

metadata:

 name: grafana-deployment  # Grafana 实际 Deployment 名称

 namespace: grafana-namespace  # 替换为实际命名空间

spec:

 replicas: 1

 selector:

   matchLabels:

     app: grafana  # 与 Pod 标签一致

 template:

   metadata:

     labels:

       app: grafana

   spec:

     containers:

       - name: grafana-container  # Grafana 容器名称

         image: grafana/grafana:latest  # 实际使用的 Grafana 镜像版本

         command: ["/run.sh"]  # Grafana 启动命令(按实际调整)

         volumeMounts:

           # 将 Projected 卷挂载到目标目录,所有 ConfigMap 文件自动合并

           - name: grafana-dashboards-proj

             mountPath: /home/data/grafana/dashboards

             readOnly: true  # 配置文件仅需读权限,提升安全性

     volumes:

       # 定义 Projected 卷,关联所有拆分后的 ConfigMap

       - name: grafana-dashboards-proj

         projected:

           sources:

             # 引入 group1 ConfigMap,自动包含其所有文件

             - configMap:

                 name: grafana-dashboards-group1

             # 引入 group2 ConfigMap,自动包含其所有文件

             - configMap:

                 name: grafana-dashboards-group2

           defaultMode: 0644  # 挂载文件默认权限(读权限,符合 Grafana 需求)

3. 方案特点

  • 优势:扩展性强,新增 ConfigMap 时仅需在 projected.sources 中添加对应条目,无需修改 volumeMounts;配置简洁,无需手动指定单个文件路径,减少维护成本;

  • 注意:需确保不同 ConfigMap 中无同名配置文件(否则后加载的 ConfigMap 会覆盖先加载的文件),建议在拆分时统一文件命名规范(如前缀区分功能)。

四、验证与效果

1. 部署验证

执行 kubectl apply -f grafana-deployment.yaml -n grafana-namespace 部署后,通过以下命令验证挂载效果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 1. 查看 Grafana Pod 是否正常启动

kubectl get pods -n grafana-namespace | grep grafana

# 2. 进入 Grafana 容器(替换 <pod-name> 为实际 Pod 名称)

kubectl exec -it <pod-name> -n grafana-namespace -- sh

# 3. 查看目标目录下的文件(应包含所有拆分的 JSON 配置文件)

ls /home/data/grafana/dashboards

# 4. 验证文件内容完整性(以 sys-monitor.json 为例)

cat /home/data/grafana/dashboards/sys-monitor.json

若目标目录下能正常显示所有拆分的配置文件,且文件内容与本地一致,说明挂载成功,同时单个 ConfigMap 未触发 ValidateAnnotationsSize 函数的长度限制。

2. 功能验证

启动 Grafana 服务后,通过浏览器访问 Grafana 界面(按实际部署的访问地址),进入 “Dashboard” 页面:

  • 若能看到所有拆分后的 Dashboard 列表(如 “系统监控”“业务指标” 等);

  • 点击任一 Dashboard 能正常加载面板、显示指标数据,无配置缺失或报错。

则说明方案生效,ConfigMap 过大问题已解决,且 Grafana 功能正常。

五、总结

两种方案均通过 “拆分 ConfigMap” 的核心思路,规避了 kubectl.kubernetes.io/last-applied-configuration 注释触发 ValidateAnnotationsSize 函数限制的问题,适用于不同场景需求:

  • subPath 方案:适合配置文件数量固定、需精确控制路径的场景,例如 Dashboard 配置长期稳定、变更极少的情况;

  • Projected 方案:适合配置文件动态增减、追求简化维护的场景,例如需频繁新增或调整 Dashboard 的情况,也是实际部署中更推荐的方案 —— 兼顾扩展性与配置简洁性,符合 Kubernetes 原生设计理念。

此外,在拆分 ConfigMap 时,建议按 “业务域” 或 “功能模块” 划分,同时规范文件命名(如 sys-xxx.json“biz-xxx.json”),既能严格控制单个 ConfigMap 体量(避免触发 256KB 注释限制),也能提升配置的可维护性,为后续 Dashboard 管理提供便利。

本文采用 CC BY-NC-SA 4.0 协议,转载请注明出处。
记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计