集群资源管理
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

浙公网安备 33010602011771号