ETCD的监控

随着EKS集群的增长,ETCD 中存储的资源数量也会增加。在极端情况下可能会超出 ETCD 数据库大小限制。

我们将介绍为什么监视 ETCD数据库大小很重要,以及当接近或超过数据库大小限制时可以做什么

为什么应该监控 ETCD 数据库大小

etcd 是一个开源、分布式、一致的键值数据存储, Kubernetes 集群的所有对象都在 etcd 中持久存储和跟踪。etcd 旨在用作配置管理、服务发现和协调分布式工作的一致键值存储。当用户创建集群时,EKS 会为etcd 提供 8GB,虽然 8 GB 的 etcd 数据库足以满足大多数客户使用案例,但在意外情况下,可能会超出允许的最大数据库大小。

etcd 使用多版本并发控制(MVCC)机制 来对存储在其中的对象的键进行并发更新,对象的每次更新都会创建密钥的一个版本,每个版本都是该对象的完整副本,版本号提供了一种确定更改顺序的方法。由于MVCC机制,多个旧版本的密钥可以在任何时间点存在于etcd中。因此,值得注意的是,etcd 消耗的空间增长不仅是由于添加新对象,而且还由于更新现有对象。例如,当工作节点上没有足够的内存时,kube-scheduler可能无法调度 pod,每次调度程序对 pod 进行调度评估时,都会使用最新资源约束的来patch pod,这种patching可能会导致 etcd 中的 pod 多次产生新的版本。

当超过ETCD数据库大小限制时,etcd 会发出无空间警报并停止接受进一步的写入请求。换句话说,集群变为只读,所有改变对象的请求(例如创建新的 pod、扩展部署等)都将被集群的 API 服务器拒绝。此外,用户将无法删除对象或对象历史版本来回收 ETCD 存储空间。这是因为删除依赖于compaction 操作来清理对象,并且当无空间警报处于活动状态时,不允许compaction(https://github.com/etcd-io/etcd/issues/14595)。要将空间返回给操作系统并减少 ETCD 数据库的大小,需要运行碎片整理操作。

所以监控集群的 ETCD 数据库大小非常重要,以便可以采取缓解措施来避免超过大小限制,从而防止集群进入只读模式。

EKS 会主动监控集群的 etcd 数据库,并在数据库大小接近限制时尝试通知客户。但这种只是尽best effort,还是建议客户主动监控 etcd 数据库大小。

如何监控etcd数据库大小?

Kubernetes API Server公开了许多对监控和分析有用的指标。在EKS 控制平面上可以监控指标 etcd_db_total_size_in_bytes 来跟踪 etcd 数据库大小。要查看原始指标,可以使用 –raw 标志运行 kubectl 命令:

kubectl get --raw /metrics | grep "etcd_db_total_size_in_bytes"

image-20231125092250232

结合 Prometheus 后,可以使用预先创建的警报规则 (例如 etcdBackendQuotaLowSpaceetcdExcessiveDatabaseGrowth)来设置有关 etcd 存储使用情况和趋势的警报,这可以帮助主动监控 etcd 数据库的使用情况并避免超出限制。

如何检测 etcd 空间不足?

EKS 控制平面日志将audit日志直接从集群的控制平面提供到CloudWatch Logs,audit日志提供影响集群的各个用户、管理员或系统组件的记录。当集群超过 etcd 数据库大小的限制时,审核日志会显示错误响应字符database space exceeded

可以使用以下CloudWatch Logs Insight查询来查找首次看到此错误消息的时间戳:

fields @timestamp, @message, @logStream
| filter @logStream like /kube-apiserver-audit/
| filter @message like /mvcc: database space exceeded/
| limit 10

例如下面可以看到集群在 11 月 17 日上午 8:50 左右达到了数据库大小限制。

当接近 etcd 数据库大小限制时该怎么办?

在这里,我们将研究如何识别集群何时快速接近数据库大小限制(但尚未超过限制)。发生这种情况时需要做两件事,首先找出导致etcd数据库空间快速消耗的原因,通常是因为使用模式错误或配置有问题。其次,在确定导致空间消耗的原因后,您可以清理对象以回收可用空间。

如何确定哪些内容正在消耗 etcd 数据库空间?

对象计数

通常,存储在 etcd 中的对象总数会导致存储消耗增加。Kubernetes API 服务器公开了一个按类型显示对象计数的指标。如果您使用的 Kubernetes 版本低于 1.22,则使用 etcd_object_count 指标。如果您使用的是 1.22 或更高版本,请使用 apiserver_storage_object_counts 指标。

1.22 及更高版本的 kubectl 命令:

kubectl get --raw=/metrics | grep apiserver_storage_objects |awk '$2>100' |sort -g -k 2

# 如果看不到输出说明集群没有超过100的对象,可执行 kubectl get --raw=/metrics | grep apiserver_storage_objects  |sort -g -k 2

适用于 1.21 及更早版本的 kubectl 命令如下:

kubectl get --raw=/metrics | grep etcd_object_counts |awk '$2>100' |sort -g -k 2

可以在下面看到该命令的示例输出。在此示例中,其中一种对象类型具有超过 200 万个对象。

[root@ip-1-1-1-1 bin]# kubectl get --raw=/metrics | grep etcd_object_counts |awk '$2>100' |sort -g -k 2
# HELP etcd_object_counts [ALPHA] (Deprecated since 1.22.0) Number of stored objects at the time of last check split by kind. This metric is replaced by apiserver_storage_object_counts.
# TYPE etcd_object_counts gauge
etcd_object_counts{resource="targetgroupbindings.elbv2.k8s.aws"} 140
etcd_object_counts{resource="nodes"} 145
etcd_object_counts{resource="clusterroles.rbac.authorization.k8s.io"} 155
etcd_object_counts{resource="clusterrolebindings.rbac.authorization.k8s.io"} 165
etcd_object_counts{resource="jobs.batch"} 217
etcd_object_counts{resource="statefulsets.apps"} 237
etcd_object_counts{resource="volumeattachments.storage.k8s.io"} 280
etcd_object_counts{resource="roles.rbac.authorization.k8s.io"} 366
etcd_object_counts{resource="ciskubebenchreports.aquasecurity.github.io"} 367
etcd_object_counts{resource="rolebindings.rbac.authorization.k8s.io"} 430
etcd_object_counts{resource="serviceaccounts"} 458
etcd_object_counts{resource="clusterbackgroundscanreports.kyverno.io"} 468
etcd_object_counts{resource="deployments.apps"} 485
etcd_object_counts{resource="csinodes.storage.k8s.io"} 557
etcd_object_counts{resource="leases.coordination.k8s.io"} 585
etcd_object_counts{resource="policyreports.wgpolicyk8s.io"} 661
etcd_object_counts{resource="ingresses.networking.k8s.io"} 678
etcd_object_counts{resource="persistentvolumes"} 799
etcd_object_counts{resource="persistentvolumeclaims"} 903
etcd_object_counts{resource="services"} 1123
etcd_object_counts{resource="endpoints"} 1127
etcd_object_counts{resource="endpointslices.discovery.k8s.io"} 1418
etcd_object_counts{resource="controllerrevisions.apps"} 1528
etcd_object_counts{resource="pods"} 2281
etcd_object_counts{resource="replicasets.apps"} 2442
etcd_object_counts{resource="configmaps"} 3268
etcd_object_counts{resource="secrets"} 3477
etcd_object_counts{resource="backgroundscanreports.kyverno.io"} 5444
etcd_object_counts{resource="events"} 6234
etcd_object_counts{resource="vulnerabilityreports.aquasecurity.github.io"} 11280
etcd_object_counts{resource="configauditreports.aquasecurity.github.io"} 47359
etcd_object_counts{resource="admissionreports.kyverno.io"} 2.01054e+06

Object Size

有时,导致空间消耗增加的并不是etcd中的对象数量,而是少数对象的大小较大。您可以应用某些优化来减小每个对象的大小。例如,您可以使用引用,而不是直接在 Pod 定义中嵌入large Binary large object (BLOB)。这可以通过使用ConfigMapSecret 对象来存储 BLOB,然后从 pod 引用 ConfigMap 或 Secret 来实现。这有助于显着降低 Pod Object Size。

您可以通过运行以下 kubectl 命令以 YAML 格式输出对象来获取对象大小。然后,您可以检查输出文件的大小以计算出对象大小。可以针对每个命名空间运行此命令,以找出哪个命名空间占用了最多的空间。

kubectl get pod test -o yaml > /tmp/l

ls -l /tmp/l 

确定需要优化的 Pod specification后,保存原始specification的副本,然后使用上述命令测试更新后的specification的大小。

请求量

您可以分析特定时间段内对 Kubernetes API 服务器的请求量,以识别请求模式的异常趋势。这些异常可能与 etcd 空间消耗的激增有关。您可以监控 API Server指标 apiserver_request_total 以查看总请求量。如果请求异常急剧增加,可以使用Logs Insights 查询按URL和userAgent筛选请求,这将帮助您更好地了解请求及其调用者。

userAgent的请求:

fields userAgent, requestURI, @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| stats count(*) as count by userAgent
| sort count desc

URI的请求:

filter @logStream like /kube-apiserver-audit/
# Uncomment the below statements to fine tune the query as needed
# Exact match:
# | filter requestURI = "/api/v1/namespaces/batch/pods"
# Substring match:
# | filter requestURI like "/api/v1/namespaces/batch/pods"
# | filter strcontains(requestURI, "/api/v1/namespaces/batch/pods")
# Substring regexp match:
# | filter requestURI like /\/api\/v1\/namespaces\/.*\/pods/
# Set inclusion match:
# | filter verb in ["get", "watch", "list"]
| stats count(*) as count by requestURI, verb, user.username
| sort count desc

对象更新

有时,etcd 数据库大小会因为一些对象经历快速更新而增加。在这种情况下,对象总数或每个对象的大小相对较小,但每个对象有大量修订。

您可以针对 Kubernetes 审核日志运行以下 Amazon CloudWatch Logs Insights 查询,以显示已修补超过八次的对象(本例中为 Pod)。

fields requestURI
| filter @logStream like /kube-apiserver-audit/
| filter requestURI like /pods/
| filter verb like /patch/
| filter count > 8
| stats count(*) as count by requestURI, responseStatus.code
| filter responseStatus.code not like /500/
| sort count desc

例如,查询输出显示名为 task-ssh 的 pod 正在被更新多次。

Amazon CloudWatch Logs 洞察控制台的屏幕截图,显示了多次更新的 Pod 的查询和结果。

现在您已经确定了正在快速更新的对象,您可以针对 audit log运行以下CloudWatch Logs Insights 查询,并检查正在应用的补丁的内容:

fields @timestamp, userAgent, responseStatus.code, requestURI
| filter @logStream like /kube-apiserver-audit/
| filter requestURI like /pods/
| filter verb like /patch/
| filter requestURI like /name_of_the_pod_that_is_updating_fast/
| sort @timestamp

您可以查看上述查询的输出,以深入了解对象快速变化的原因。通常,工作负载上的某些异常行为会导致频繁的对象更新。例如,此行为可能是由于工作节点上内存不足触发的崩溃循环,或者可能是由于启动部署失败而重试。一旦确定了此行为的根本情况,您就可以采取缓解措施以减少每个对象的修订数量。

如何回收etcd数据库空间?

您可以使用 kubectl delete 命令一次性清除所有未使用或孤立的对象。

例如,下面的 shell 脚本显示了如何删除admissionreports.kyverno.io对象。

for i in {1..10}
do
   time kubectl get --raw "/apis/kyverno.io/v1alpha2/namespaces/flux-system/admissionreports?limit=10000" | jq -r '.items | .[].metadata.name' | xargs -n 1 -P 400 kubectl delete -n flux-system admissionreports 2>/dev/null
done

注:-P 400 是控制并行度。值越大,获得的并行性就越高,但这也意味着这些进程之间的竞争会更加激烈。因此,值越大并不总是速度越快。您应该调整阈值以找到最适合您的值。

其他方法

  1. 另一种加快删除速度的方法是使用标签。例如,如果您为对象添加标签,则可以使用标签批量删除所有对象或特定对象类型。
kubectl delete all -l <label-key>=<label-value>
kubectl delete <object> -l <label-key>=<label-value>
  1. 对于像 Jobs这样支持自动清理的对象,您可以设置 TTL 值来限制这些对象的生命周期(https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/)。

  2. 在对象之间建立所有者引用。当删除父拥有对象时,依赖对象将自动删除(https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/)。

  3. 此外,您还可以采取更主动的方法来限制利用 Kubernetes 资源配额创建的对象数量。资源配额提供了一种机制来限制可以按类型在命名空间中创建的对象的数量。例如,您可以使用它来限制创建的 ConfigMap、机密或服务的数量。资源配额的使用方法请参见https://kubernetes.io/docs/concepts/policy/resource-quotas/

2023 年 10 月更新 – EKS auto recovery workflow

EKS 现在拥有针对 etcd 无空间警报的自动恢复工作流程。如果集群没有空间,此工作流程将运行压缩和碎片整理以释放空间。当每个对象有多个修订时(例如,由于崩溃循环导致重复的 Pod 状态更新),它通常是有效的。

如果您的集群处于无空间警报,请等待 15 分钟,查看auto recovery workflow是否解决了报警。如果工作流无法释放空间,例如因为创建了太多对象而没有清理,则您将需要删除不需要的对象。

处于无空间报警时可以删除对象吗?

当etcd处于无空间警报时,删除前需要更新的对象无法删除,此类对象的示例包括正在运行的 Pod 和具有finalizer的对象。即使 etcd 处于无空间报警,删除前不需要更新的对象也可以被删除,此类对象的示例包括configmap和 Kyverno admissionreports

我应该删除多少个对象?

建议在空间满时,使用 kubectl 命令删除至少 20% 不需要的对象。

如果我无法删除对象怎么办?

如果对象的 kubectl delete 命令失败并显示 “mvcc: database space exceeded”,则对象删除正在尝试向 etcd 发出更新。您可以尝试选择不同的对象类型进行删除。如果您无法做到这一点,则需要联系AWS 技术支持 以获得回收空间的帮助,从而使集群再次可读写。这再次表明主动监控 etcd 数据库大小以避免超出限制的重要性。