跳转至

08 k8s 维护脚本

k8s 维护脚本

1.0 内存使用率维护

实现: 当内存使用率超过95% 时,则将节点禁止调度。当节点重新恢复至85% 时,则取消禁止调度。

监控数据来源: kubectl top node

禁止调度和取消禁止调度命令: kubectl cordon 和 kubectl uncordon

#!/bin/bash
# k8s-node-auto-cordon.sh
# 根据 kubectl top node 的内存使用率自动 cordon/uncordon 节点
#
# 规则:
#   - 内存使用率 > CORDON_THRESHOLD 时,对节点执行 cordon
#   - 内存使用率 < UNCORDON_THRESHOLD 时,对由本脚本 cordon 的节点执行 uncordon
#
# 用法:
#   chmod +x k8s-node-auto-cordon.sh
#   ./k8s-node-auto-cordon.sh
#
# 本脚本为一次性检查,建议通过 crontab 定时执行,例如每分钟执行一次:
#   * * * * * /path/to/k8s-node-auto-cordon.sh

set -u

# ---------- 可配置参数 ----------
readonly CORDON_THRESHOLD=${CORDON_THRESHOLD:-95}       # 触发 cordon 的内存使用率阈值
readonly UNCORDON_THRESHOLD=${UNCORDON_THRESHOLD:-85}   # 触发 uncordon 的内存使用率阈值
readonly LOG_FILE=${LOG_FILE:-/var/log/k8s-node-auto-cordon.log}
readonly LABEL_KEY="node-autocordon-memory-pressure"    # 用于标记由本脚本 cordon 的节点

# ---------- 日志 ----------
function log() {
    local level="$1"
    shift
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "$LOG_FILE"
}

function log_info() {
    log "INFO" "$@"
}

function log_warn() {
    log "WARN" "$@"
}

function log_error() {
    log "ERROR" "$@"
}

# ---------- 前置检查 ----------
function check_prerequisites() {
    if ! command -v kubectl &>/dev/null; then
        log_error "未找到 kubectl,请确保 kubectl 已安装并在 PATH 中"
        exit 1
    fi

    if ! kubectl version &>/dev/null; then
        log_error "无法连接 Kubernetes 集群,请检查 kubeconfig"
        exit 1
    fi
}

# ---------- 数值比较工具(支持小数) ----------
function num_gt() {
    awk -v a="$1" -v b="$2" 'BEGIN { exit !(a > b) }'
}

function num_lt() {
    awk -v a="$1" -v b="$2" 'BEGIN { exit !(a < b) }'
}

# ---------- kubectl 状态查询 ----------
function get_node_memory_usage() {
    local node="$1"
    kubectl top node "$node" --no-headers 2>/dev/null | awk '{print $5}' | sed 's/%//'
}

function is_node_cordoned() {
    local node="$1"
    local unschedulable

    unschedulable=$(kubectl get node "$node" -o jsonpath='{.spec.unschedulable}' 2>/dev/null)
    [ "$unschedulable" = "true" ]
}

function is_cordoned_by_us() {
    local node="$1"
    local label_value

    label_value=$(kubectl get node "$node" -o jsonpath="{.metadata.labels['$LABEL_KEY']}" 2>/dev/null)
    [ "$label_value" = "true" ]
}

# ---------- cordon / uncordon 操作 ----------
function cordon_node() {
    local node="$1"
    local usage="$2"

    log_warn "节点 $node 内存使用率为 ${usage}%,超过 ${CORDON_THRESHOLD}%,执行 cordon"

    if ! kubectl label node "$node" "$LABEL_KEY=true" --overwrite &>/dev/null; then
        log_error "无法为节点 $node 打标记标签"
        return 1
    fi

    if ! kubectl cordon "$node" &>/dev/null; then
        log_error "无法 cordon 节点 $node"
        return 1
    fi

    log_info "节点 $node 已成功 cordon"
}

function uncordon_node() {
    local node="$1"
    local usage="$2"

    log_info "节点 $node 内存使用率已降至 ${usage}%,低于 ${UNCORDON_THRESHOLD}%,执行 uncordon"

    if ! kubectl uncordon "$node" &>/dev/null; then
        log_error "无法 uncordon 节点 $node"
        return 1
    fi

    if ! kubectl label node "$node" "$LABEL_KEY-" &>/dev/null; then
        log_warn "无法移除节点 $node 的标记标签"
    fi

    log_info "节点 $node 已成功 uncordon"
}

# ---------- 节点检查 ----------
function check_all_nodes() {
    local nodes
    nodes=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}' 2>/dev/null)

    if [ -z "$nodes" ]; then
        log_error "获取节点列表失败"
        return 1
    fi

    for node in $nodes; do
        local usage
        usage=$(get_node_memory_usage "$node")

        if [ -z "$usage" ]; then
            log_warn "无法获取节点 $node 的内存使用率(metrics-server 可能不可用)"
            continue
        fi

        if ! [[ "$usage" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
            log_warn "节点 $node 的内存使用率格式异常: '$usage'"
            continue
        fi

        if num_gt "$usage" "$CORDON_THRESHOLD"; then
            if ! is_node_cordoned "$node"; then
                cordon_node "$node" "$usage"
            fi
        elif num_lt "$usage" "$UNCORDON_THRESHOLD"; then
            if is_node_cordoned "$node" && is_cordoned_by_us "$node"; then
                uncordon_node "$node" "$usage"
            fi
        fi
    done
}

# ---------- 脚本入口 ----------
function main() {
    check_prerequisites
    check_all_nodes
}

main "$@"