集群资源管理

Kubernetes 资源类型

  • CPU:可压缩资源,按毫核(m)分配(1核=1000m)
  • 内存:不可压缩资源,单位字节(GiB/MiB/KiB)
  • 临时存储(ephemeral-storage):节点本地临时存储
  • GPU(nvidia.com/gpu)

资源配置方式

  • K8s 通过 Pod 的resources字段配置资源需求和限制,主要包含两类参数:requests(资源请求)、limits(资源限制)

requests(资源请求)

  • 定义 Pod 启动时所需的最小资源量,作为调度依据。
  • 调度器会将 Pod 分配到满足requests资源的节点上。

limits(资源限制)

  • 定义 Pod 允许使用的最大资源量,防止资源滥用。
  • CPU:超出限制会被 “限流”(throttled),但不会被终止。
  • 内存:超出限制会触发 OOM killer,容器被终止并重启(受重启策略控制)。

示例

resources:
  requests:
    cpu: "500m"     # 0.5核
    memory: "1Gi"   # 1GB内存
  limits:
    cpu: "1"        # 1核
    memory: "2Gi"   # 2GB内存

服务质量等级(QoS)

  • K8s 根据requests和limits的配置,将 Pod 分为 3 类 QoS 等级
  • 资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳定运行。

Guaranteed

  • 优先级最高,最先保障。节点资源不足时最后被驱逐
  • 如果所有容器的requests 达到 limits,节点资源不足时,POD会被销毁
  • 用于生产关键应用

Burstable

  • requests < limits
  • 用于常规应用

BestEffort

  • 未设置requests和limits
  • 最先被杀
  • 用于测试/非关键应用

资源限制与隔离

命名空间资源配额(ResourceQuota)

  • 在命名空间级别限制资源总量
apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
spec:
  hard:
    requests.cpu: "10"
    requests.memory: 20Gi
    limits.cpu: "20"
    limits.memory: 40Gi
    pods: "50"

限制范围(LimitRange)

  • 为命名空间内的 Pod / 容器设置默认requests和limits
  • Requests和Limits只能被设置到容器上,Kubernetes的计算资源单位是大小写敏感的
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
spec:
  limits:
  - default:        # 默认limits
      cpu: "1"
      memory: "1Gi"
    defaultRequest: # 默认requests
      cpu: "500m"
      memory: "512Mi"
    type: "Container"

高级资源管理功能

水平 Pod 自动扩缩(HPA)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50

垂直 Pod 自动扩缩(VPA)

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: my-app-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: "Auto"

监控与优化

  • kubectl top:查看实时资源使用
kubectl top nodes
kubectl top pods
  • Metrics Server:集群资源指标聚合
  • Prometheus + Grafana:历史数据监控与分析
  • cAdvisor:容器级监控

最佳实践

  • 生产环境建议配置:

    • 所有容器设置 requests 和 limits
    • 关键应用使用 Guaranteed QoS
    • 实施命名空间资源配额
  • 节点资源预留:

    • CPU:至少预留 0.5 核
    • 内存:至少预留 10%
    • 单节点容器数量:不超过 100 个

清理k8s中无用的资源

#!/bin/bash
set -eo pipefail

# 配置区域 - 根据实际环境调整
# 保留的成功Job历史数量
JOB_HISTORY_LIMIT=5
# 保留的失败Pod历史时间(小时)
FAILED_POD_RETENTION_HOURS=24
# 保留的已完成Pod历史时间(小时)
COMPLETED_POD_RETENTION_HOURS=48
# 未被引用的ConfigMap/Secret保留时间(小时)
UNREFERENCED_CONFIG_RETENTION_HOURS=720  # 30天
# 不清理的命名空间列表,用空格分隔
PROTECTED_NAMESPACES="kube-system kube-public kube-node-lease monitoring"
# 日志文件路径
LOG_FILE="/var/log/k8s_cleanup.log"
# 干运行模式(1:开启,0:关闭),开启时只显示操作不实际执行
DRY_RUN=0

# 初始化日志
log() {
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[$timestamp] $1" | tee -a "$LOG_FILE"
}

# 检查依赖工具
check_dependencies() {
    local dependencies=("kubectl" "jq")
    for dep in "${dependencies[@]}"; do
        if ! command -v "$dep" &> /dev/null; then
            log "错误: 未找到必要工具 $dep,请先安装"
            exit 1
        fi
    done
}

# 检查命名空间是否受保护
is_protected_namespace() {
    local ns="$1"
    for protected in $PROTECTED_NAMESPACES; do
        if [ "$ns" == "$protected" ]; then
            return 0
        fi
    done
    return 1
}

# 清理失败的Pod
cleanup_failed_pods() {
    log "开始清理失败的Pod..."
    local cutoff=$(date -d "$FAILED_POD_RETENTION_HOURS hours ago" +"%Y-%m-%dT%H:%M:%SZ")
    
    # 获取所有失败状态的Pod
    local failed_pods=$(kubectl get pods --all-namespaces -o json | \
        jq -r --arg cutoff "$cutoff" '.items[] | 
        select(.status.phase == "Failed" or .status.phase == "Error") |
        select(.metadata.creationTimestamp < $cutoff) |
        [.metadata.namespace, .metadata.name] | @tsv')
    
    if [ -z "$failed_pods" ]; then
        log "没有符合条件的失败Pod需要清理"
        return
    fi
    
    echo "$failed_pods" | while IFS=$'\t' read -r ns pod; do
        if is_protected_namespace "$ns"; then
            log "跳过受保护命名空间 $ns 中的Pod $pod"
            continue
        fi
        
        log "准备清理命名空间 $ns 中的失败Pod: $pod (创建时间: $(kubectl get pod $pod -n $ns -o jsonpath='{.metadata.creationTimestamp}'))"
        
        if [ $DRY_RUN -eq 0 ]; then
            if kubectl delete pod "$pod" -n "$ns" --ignore-not-found; then
                log "成功清理Pod $ns/$pod"
            else
                log "清理Pod $ns/$pod 失败"
            fi
        fi
    done
}

# 清理已完成的Pod(非Job创建)
cleanup_completed_pods() {
    log "开始清理已完成的独立Pod..."
    local cutoff=$(date -d "$COMPLETED_POD_RETENTION_HOURS hours ago" +"%Y-%m-%dT%H:%M:%SZ")
    
    # 获取已完成且不是由Job创建的Pod
    local completed_pods=$(kubectl get pods --all-namespaces -o json | \
        jq -r --arg cutoff "$cutoff" '.items[] | 
        select(.status.phase == "Succeeded") |
        select(.metadata.creationTimestamp < $cutoff) |
        select(.metadata.ownerReferences[]?.kind != "Job") |
        [.metadata.namespace, .metadata.name] | @tsv')
    
    if [ -z "$completed_pods" ]; then
        log "没有符合条件的已完成独立Pod需要清理"
        return
    fi
    
    echo "$completed_pods" | while IFS=$'\t' read -r ns pod; do
        if is_protected_namespace "$ns"; then
            log "跳过受保护命名空间 $ns 中的Pod $pod"
            continue
        fi
        
        log "准备清理命名空间 $ns 中的已完成Pod: $pod (创建时间: $(kubectl get pod $pod -n $ns -o jsonpath='{.metadata.creationTimestamp}'))"
        
        if [ $DRY_RUN -eq 0 ]; then
            if kubectl delete pod "$pod" -n "$ns" --ignore-not-found; then
                log "成功清理Pod $ns/$pod"
            else
                log "清理Pod $ns/$pod 失败"
            fi
        fi
    done
}

# 清理过期的Job
cleanup_jobs() {
    log "开始清理过期的Job..."
    
    # 获取所有命名空间
    local namespaces=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}')
    
    for ns in $namespaces; do
        if is_protected_namespace "$ns"; then
            log "跳过受保护命名空间 $ns 中的Job"
            continue
        fi
        
        # 获取该命名空间下所有已完成的Job,按创建时间排序
        local jobs=$(kubectl get jobs -n "$ns" -o json | \
            jq -r '.items[] | 
            select(.status.succeeded == 1 or .status.failed != null) |
            {name: .metadata.name, creationTimestamp: .metadata.creationTimestamp} |
            @tsv' | sort -k2)
        
        if [ -z "$jobs" ]; then
            continue
        fi
        
        # 计算需要保留的Job数量
        local job_count=$(echo "$jobs" | wc -l)
        local jobs_to_delete=$((job_count - JOB_HISTORY_LIMIT))
        
        if [ $jobs_to_delete -le 0 ]; then
            continue
        fi
        
        # 获取需要删除的Job
        local jobs_to_clean=$(echo "$jobs" | head -n $jobs_to_delete | cut -f1)
        
        for job in $jobs_to_clean; do
            log "准备清理命名空间 $ns 中的过期Job: $job (创建时间: $(kubectl get job $job -n $ns -o jsonpath='{.metadata.creationTimestamp}'))"
            
            if [ $DRY_RUN -eq 0 ]; then
                # 删除Job及其关联的Pod
                if kubectl delete job "$job" -n "$ns" --ignore-not-found; then
                    log "成功清理Job $ns/$job"
                else
                    log "清理Job $ns/$job 失败"
                fi
            fi
        done
    done
}

# 清理未被引用的ConfigMap
cleanup_unreferenced_configmaps() {
    log "开始清理未被引用的ConfigMap..."
    local cutoff=$(date -d "$UNREFERENCED_CONFIG_RETENTION_HOURS hours ago" +"%Y-%m-%dT%H:%M:%SZ")
    
    # 获取所有命名空间
    local namespaces=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}')
    
    for ns in $namespaces; do
        if is_protected_namespace "$ns"; then
            log "跳过受保护命名空间 $ns 中的ConfigMap"
            continue
        fi
        
        # 获取所有ConfigMap
        local configmaps=$(kubectl get configmaps -n "$ns" -o json | \
            jq -r --arg cutoff "$cutoff" '.items[] | 
            select(.metadata.creationTimestamp < $cutoff) |
            .metadata.name')
        
        if [ -z "$configmaps" ]; then
            continue
        fi
        
        # 检查每个ConfigMap是否被引用
        for cm in $configmaps; do
            # 检查是否被Pod引用
            local pod_references=$(kubectl get pods -n "$ns" -o json | \
                jq -r --arg cm "$cm" '.items[] | 
                select(.spec.volumes[]?.configMap.name == $cm) or
                select(.spec.containers[].envFrom[]?.configMapRef.name == $cm) or
                select(.spec.containers[].env[]?.valueFrom.configMapKeyRef.name == $cm) |
                .metadata.name' | wc -l)
            
            # 检查是否被Deployment引用
            local deploy_references=$(kubectl get deployments -n "$ns" -o json | \
                jq -r --arg cm "$cm" '.items[] | 
                select(.spec.template.spec.volumes[]?.configMap.name == $cm) or
                select(.spec.template.spec.containers[].envFrom[]?.configMapRef.name == $cm) or
                select(.spec.template.spec.containers[].env[]?.valueFrom.configMapKeyRef.name == $cm) |
                .metadata.name' | wc -l)
            
            # 检查是否被StatefulSet引用
            local stateful_references=$(kubectl get statefulsets -n "$ns" -o json | \
                jq -r --arg cm "$cm" '.items[] | 
                select(.spec.template.spec.volumes[]?.configMap.name == $cm) or
                select(.spec.template.spec.containers[].envFrom[]?.configMapRef.name == $cm) or
                select(.spec.template.spec.containers[].env[]?.valueFrom.configMapKeyRef.name == $cm) |
                .metadata.name' | wc -l)
            
            total_references=$((pod_references + deploy_references + stateful_references))
            
            if [ $total_references -eq 0 ]; then
                log "准备清理命名空间 $ns 中未被引用的ConfigMap: $cm (创建时间: $(kubectl get configmap $cm -n $ns -o jsonpath='{.metadata.creationTimestamp}'))"
                
                if [ $DRY_RUN -eq 0 ]; then
                    if kubectl delete configmap "$cm" -n "$ns" --ignore-not-found; then
                        log "成功清理ConfigMap $ns/$cm"
                    else
                        log "清理ConfigMap $ns/$cm 失败"
                    fi
                fi
            fi
        done
    done
}

# 清理未被引用的Secret
cleanup_unreferenced_secrets() {
    log "开始清理未被引用的Secret..."
    local cutoff=$(date -d "$UNREFERENCED_CONFIG_RETENTION_HOURS hours ago" +"%Y-%m-%dT%H:%M:%SZ")
    
    # 获取所有命名空间
    local namespaces=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}')
    
    for ns in $namespaces; do
        if is_protected_namespace "$ns"; then
            log "跳过受保护命名空间 $ns 中的Secret"
            continue
        fi
        
        # 获取所有Secret
        local secrets=$(kubectl get secrets -n "$ns" -o json | \
            jq -r --arg cutoff "$cutoff" '.items[] | 
            select(.metadata.creationTimestamp < $cutoff) |
            .metadata.name')
        
        if [ -z "$secrets" ]; then
            continue
        fi
        
        # 检查每个Secret是否被引用
        for secret in $secrets; do
            # 检查是否被Pod引用
            local pod_references=$(kubectl get pods -n "$ns" -o json | \
                jq -r --arg secret "$secret" '.items[] | 
                select(.spec.volumes[]?.secret.secretName == $secret) or
                select(.spec.containers[].envFrom[]?.secretRef.name == $secret) or
                select(.spec.containers[].env[]?.valueFrom.secretKeyRef.name == $secret) |
                .metadata.name' | wc -l)
            
            # 检查是否被Deployment引用
            local deploy_references=$(kubectl get deployments -n "$ns" -o json | \
                jq -r --arg secret "$secret" '.items[] | 
                select(.spec.template.spec.volumes[]?.secret.secretName == $secret) or
                select(.spec.template.spec.containers[].envFrom[]?.secretRef.name == $secret) or
                select(.spec.template.spec.containers[].env[]?.valueFrom.secretKeyRef.name == $secret) |
                .metadata.name' | wc -l)
            
            # 检查是否被StatefulSet引用
            local stateful_references=$(kubectl get statefulsets -n "$ns" -o json | \
                jq -r --arg secret "$secret" '.items[] | 
                select(.spec.template.spec.volumes[]?.secret.secretName == $secret) or
                select(.spec.template.spec.containers[].envFrom[]?.secretRef.name == $secret) or
                select(.spec.template.spec.containers[].env[]?.valueFrom.secretKeyRef.name == $secret) |
                .metadata.name' | wc -l)
            
            total_references=$((pod_references + deploy_references + stateful_references))
            
            if [ $total_references -eq 0 ]; then
                log "准备清理命名空间 $ns 中未被引用的Secret: $secret (创建时间: $(kubectl get secret $secret -n $ns -o jsonpath='{.metadata.creationTimestamp}'))"
                
                if [ $DRY_RUN -eq 0 ]; then
                    if kubectl delete secret "$secret" -n "$ns" --ignore-not-found; then
                        log "成功清理Secret $ns/$secret"
                    else
                        log "清理Secret $ns/$secret 失败"
                    fi
                fi
            fi
        done
    done
}

# 主函数
main() {
    log "===== 开始Kubernetes资源清理任务 ====="
    
    check_dependencies
    
    # 显示当前配置
    log "清理配置: "
    log "  保留成功Job历史数量: $JOB_HISTORY_LIMIT"
    log "  失败Pod保留时间: $FAILED_POD_RETENTION_HOURS 小时"
    log "  已完成Pod保留时间: $COMPLETED_POD_RETENTION_HOURS 小时"
    log "  未被引用配置保留时间: $UNREFERENCED_CONFIG_RETENTION_HOURS 小时"
    log "  受保护命名空间: $PROTECTED_NAMESPACES"
    log "  干运行模式: $(if [ $DRY_RUN -eq 1 ]; then echo "开启"; else echo "关闭"; fi)"
    
    # 执行清理任务
    cleanup_failed_pods
    cleanup_completed_pods
    cleanup_jobs
    cleanup_unreferenced_configmaps
    cleanup_unreferenced_secrets
    
    log "===== Kubernetes资源清理任务完成 ====="
    log ""
}

# 启动主函数
main

  • 手动清理失败的Pod
kubectl delete pods --field-selector=status.phase=Failed
posted @ 2024-05-10 10:06  立勋  阅读(28)  评论(0)    收藏  举报