一、当Kubernetes节点开始"喊饿"时

最近在维护一个生产环境的Kubernetes集群时,突然收到了一连串告警。几个工作节点纷纷报告磁盘空间不足,有些甚至已经使用了超过90%的空间。这就像家里的冰箱突然塞满了食物,新买的食材怎么也放不进去了。节点上的Pod开始被驱逐,服务出现了中断,运维群里的消息瞬间炸开了锅。

这种情况在Kubernetes环境中其实很常见。随着容器不断创建和销毁,各种日志、临时文件和未清理的镜像会慢慢蚕食节点的磁盘空间。就像我们电脑的C盘,不知不觉就变红了。不同的是,生产环境的磁盘问题会直接影响业务连续性。

二、诊断磁盘空间问题的"听诊器"

在开始治疗之前,我们需要先诊断清楚问题所在。Kubernetes提供了很多好用的工具来帮我们找出磁盘空间的"罪魁祸首"。

首先,我们可以使用kubectl describe node命令查看节点的整体状况:

# 查看节点磁盘压力情况
kubectl describe node <node-name> | grep -A 10 "Conditions"

# 示例输出:
Conditions:
  Type              Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----              ------  -----------------                 ------------------                ------                       -------
  DiskPressure      True    Mon, 12 Jun 2023 14:23:45 +0800   Mon, 12 Jun 2023 13:45:32 +0800   KubeletHasDiskPressure       kubelet has disk pressure

这个命令会显示节点的各种状态条件,其中DiskPressure就是磁盘压力的指示器。如果看到Status为True,就说明该节点确实存在磁盘压力。

接下来,我们需要登录到问题节点上,使用传统的Linux命令来找出具体是哪些文件占用了大量空间:

# 查看磁盘整体使用情况
df -h

# 找出占用空间最大的目录
du -h --max-depth=1 / | sort -hr

# 查看/var/lib/docker目录大小(容器运行时存储位置)
du -sh /var/lib/docker/*

通过这些命令,我们通常能快速定位到问题的根源。常见的情况包括:

  1. 容器日志文件过大
  2. 未清理的容器镜像
  3. Kubernetes的临时文件堆积
  4. 应用程序生成的临时数据

三、清理磁盘空间的"组合拳"

找到了问题所在,接下来就是制定清理策略了。根据不同的情况,我们需要采取不同的清理方法。

3.1 清理无用的容器镜像

就像我们手机里不再使用的APP,节点上可能堆积了很多不再需要的容器镜像。我们可以使用docker命令来清理:

# 列出所有镜像
docker images

# 删除特定镜像
docker rmi <image-id>

# 自动清理未被任何容器使用的镜像
docker image prune -a

# 更激进的清理方式(慎用)
docker system prune -a --volumes

对于Kubernetes环境,我们还可以设置镜像回收策略。编辑kubelet的配置文件(通常在/var/lib/kubelet/config.yaml):

imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80

这样当磁盘使用率达到85%时,kubelet会自动清理镜像,直到使用率降到80%以下。

3.2 管理容器日志

容器日志是另一个常见的磁盘空间"杀手"。我们可以从几个方面入手:

首先,为容器设置日志轮转策略。在Docker中,可以修改/etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

对于Kubernetes Pod,我们可以在部署时配置日志策略:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: my-container
        image: my-image
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        volumeMounts:
        - name: varlog
          mountPath: /var/log
      volumes:
      - name: varlog
        emptyDir:
          sizeLimit: 100Mi

3.3 使用EmptyDir的正确姿势

EmptyDir是Kubernetes中常用的临时存储方式,但如果不加限制,它可能会占用大量磁盘空间。我们可以设置sizeLimit来限制其大小:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: busybox
    command: ["sh", "-c", "while true; do echo hello >> /scratch/hello.txt; sleep 10; done"]
    volumeMounts:
    - name: scratch
      mountPath: /scratch
  volumes:
  - name: scratch
    emptyDir:
      sizeLimit: 100Mi

当EmptyDir使用量超过限制时,Pod会被驱逐并重新调度。

四、预防胜于治疗的"养生之道"

解决了眼前的危机后,我们需要建立长效机制来预防问题再次发生。以下是几个实用的预防措施:

4.1 设置资源配额

我们可以使用ResourceQuota来限制命名空间的资源使用:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: storage-quota
spec:
  hard:
    requests.storage: "100Gi"
    persistentvolumeclaims: "10"

4.2 监控和告警系统

部署Prometheus和Grafana来监控磁盘使用情况,并设置适当的告警规则:

# Prometheus告警规则示例
groups:
- name: node.rules
  rules:
  - alert: NodeDiskUsageHigh
    expr: 100 - (node_filesystem_avail_bytes{mountpoint="/"} * 100 / node_filesystem_size_bytes{mountpoint="/"}) > 85
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "High disk usage on {{ $labels.instance }}"
      description: "{{ $labels.instance }} has disk usage above 85% for 10 minutes"

4.3 定期维护脚本

编写定期执行的维护脚本,自动清理旧日志和临时文件:

#!/bin/bash
# 清理30天前的日志文件
find /var/log -name "*.log" -type f -mtime +30 -delete

# 清理kubelet的旧检查点文件
find /var/lib/kubelet/checkpoints -type f -mtime +7 -delete

# 清理Docker构建缓存
docker builder prune -f

五、特殊情况的处理技巧

有时候我们会遇到一些特殊的情况,需要特别的处理方式。

5.1 处理被驱逐的Pod

当节点磁盘空间不足时,kubelet会开始驱逐Pod。我们可以通过以下命令查看被驱逐的Pod:

kubectl get pods --all-namespaces --field-selector=status.phase=Failed

对于重要的Pod,我们可以通过设置优先级来避免被驱逐:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for important pods only."

然后在Pod模板中引用:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: important-app
spec:
  template:
    spec:
      priorityClassName: high-priority
      containers:
      - name: important-container
        image: important-image

5.2 处理持久卷(PV)的空间问题

如果使用的是持久卷,当PV空间不足时,我们可以尝试扩展PV:

# 首先编辑PVC,增加请求的大小
kubectl edit pvc my-pvc
# 找到spec.resources.requests.storage字段并增加大小

注意:不是所有的存储类型都支持在线扩容,有些可能需要先删除并重建PVC。

六、经验总结与最佳实践

经过这次事件和处理过程,我总结了一些Kubernetes磁盘空间管理的最佳实践:

  1. 监控先行:建立完善的磁盘监控系统,在问题发生前就能收到预警。
  2. 限制为王:对所有可能占用磁盘的资源都设置合理的限制,包括容器日志、EmptyDir、镜像缓存等。
  3. 定期清理:建立定期维护机制,自动清理不再需要的文件和数据。
  4. 容量规划:根据业务需求合理规划节点磁盘容量,预留足够的缓冲空间。
  5. 文档记录:记录每次磁盘问题的处理过程和解决方案,形成知识库。

记住,在Kubernetes环境中,磁盘空间管理不是一次性的工作,而是需要持续关注的日常运维任务。只有建立完善的预防、监控和处理机制,才能确保集群的稳定运行。