Kubernetes 生产环境巡检笔记

Kubernetes 生产环境巡检笔记

1. 巡检目标

Kubernetes 生产巡检的核心不是只确认资源处于 Running,而是同时发现当前故障、冗余失效、容量风险、安全隐患,以及备份无法恢复等潜在问题。

本文附带的脚本以只读方式采集集群信息,不会创建、修改、删除、重启或驱逐任何资源。脚本会生成 Markdown 报告、风险清单和原始证据,适合日常巡检、变更前后核对及故障排查留档。

2. 巡检范围

类别 主要检查内容
集群整体 Kubernetes 版本、节点和工作负载规模、API 就绪状态、Warning 事件
节点 Ready 状态、MemoryPressure、DiskPressure、PIDPressure、NetworkUnavailable
工作负载 Deployment 副本、异常 Pod、容器等待原因、重启次数、单副本部署
资源容量 节点 CPU、内存、资源分配情况;支持 Metrics Server 时自动采集实时指标
存储 PV、PVC、StorageClass、VolumeAttachment,以及非 Bound PVC
网络 Service、EndpointSlice、Ingress、NetworkPolicy,以及无后端地址的 Service
高可用 单副本 Deployment、PDB、HPA、etcd 实例数量
安全 高权限容器、HostNetwork、HostPID、latest 镜像、当前身份权限和 ClusterRoleBinding
调度与任务 ResourceQuota、LimitRange、CronJob、异常事件
人工复核 etcd 性能与恢复、磁盘 inode、证书、备份恢复、SLO 和容灾演练

3. 风险等级

  • CRITICAL:节点不可用、节点压力、容器启动异常、Deployment 可用副本不足等,需要立即处理。
  • HIGH:异常阶段 Pod、频繁重启、高资源使用率、PVC 或 Service 后端异常等,应尽快处理。
  • MEDIUM:单副本、高权限配置、latest 镜像、Warning 事件或 HA 配置风险。
  • INFO:当前环境无法自动采集,需要通过云平台、Prometheus 或人工方式补充确认。

4. 环境要求

  • Bash 3.2 或更高版本。
  • 已安装 kubectl,并配置可连接目标集群的 kubeconfig。
  • 巡检账号至少应具备目标资源的 getlist 权限。
  • 建议部署 Metrics Server,以便采集节点和容器的 CPU、内存数据。
  • 脚本默认单次请求超时为 15 秒,可通过参数调整。

5. 使用方法

先为脚本添加执行权限:

chmod +x k8s-prod-inspection.sh

巡检当前 kubeconfig 所选集群的所有命名空间:

./k8s-prod-inspection.sh

指定生产集群 Context:

./k8s-prod-inspection.sh --context production

只巡检指定命名空间:

./k8s-prod-inspection.sh --context production --namespace payment

指定报告目录及请求超时:

./k8s-prod-inspection.sh \
  --context production \
  --output-dir ./inspection-report \
  --timeout 30s

查看全部参数:

./k8s-prod-inspection.sh --help

建议先确认当前 Context,避免误查其他集群:

kubectl config current-context
kubectl cluster-info

6. 输出说明

未指定输出目录时,脚本在当前目录生成带时间戳的目录:

k8s-inspection-20260916-120000/
├── report.md       # 汇总报告及风险建议
├── findings.tsv    # 便于导入表格的风险清单
└── raw/            # 各项 kubectl 原始输出

建议首先查看 report.md,再根据其中的证据路径核对 raw/。巡检报告包含敏感的集群拓扑、资源名称和权限信息,应按内部安全规范保存与传输。

7. 完整脚本

将以下内容保存为 k8s-prod-inspection.sh

#!/usr/bin/env bash
# Kubernetes production inspection (read-only)
# Usage: ./k8s-prod-inspection.sh [-c context] [-n namespace|all] [-o output-dir]

set -u
set -o pipefail

CONTEXT=""
NAMESPACE="all"
OUTPUT_DIR=""
TIMEOUT="15s"
WARN_DISK=75
CRIT_DISK=85
WARN_CPU=70
WARN_MEM=80
CERT_WARN_DAYS=60

usage() {
  cat <<'EOF'
Usage: k8s-prod-inspection.sh [options]
  -c, --context NAME       kubeconfig context (default: current context)
  -n, --namespace NAME     namespace to inspect (default: all)
  -o, --output-dir DIR     report directory (default: ./k8s-inspection-TIMESTAMP)
  -t, --timeout DURATION   kubectl request timeout (default: 15s)
  -h, --help               show help

This script is read-only. It does not create, update, delete, restart, or drain resources.
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    -c|--context) CONTEXT="${2:?missing context}"; shift 2 ;;
    -n|--namespace) NAMESPACE="${2:?missing namespace}"; shift 2 ;;
    -o|--output-dir) OUTPUT_DIR="${2:?missing output directory}"; shift 2 ;;
    -t|--timeout) TIMEOUT="${2:?missing timeout}"; shift 2 ;;
    -h|--help) usage; exit 0 ;;
    *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
  esac
done

command -v kubectl >/dev/null 2>&1 || { echo "ERROR: kubectl is required" >&2; exit 1; }

STAMP="$(date '+%Y%m%d-%H%M%S')"
OUTPUT_DIR="${OUTPUT_DIR:-./k8s-inspection-${STAMP}}"
mkdir -p "$OUTPUT_DIR/raw"
REPORT="$OUTPUT_DIR/report.md"
FINDINGS="$OUTPUT_DIR/findings.tsv"
: >"$FINDINGS"

K=(kubectl --request-timeout="$TIMEOUT")
[[ -n "$CONTEXT" ]] && K+=(--context "$CONTEXT")
NS_ARGS=(-A)
[[ "$NAMESPACE" != "all" ]] && NS_ARGS=(-n "$NAMESPACE")

run() { "${K[@]}" "$@"; }
capture() {
  local name="$1"; shift
  run "$@" >"$OUTPUT_DIR/raw/$name.txt" 2>&1
}
finding() {
  # severity, item, evidence, recommendation
  printf '%s\t%s\t%s\t%s\n' "$1" "$2" "${3//$'\t'/ }" "${4//$'\t'/ }" >>"$FINDINGS"
}
section() { printf '\n## %s\n\n' "$1" >>"$REPORT"; }
code_file() {
  local title="$1" file="$2"
  printf '### %s\n\n```text\n' "$title" >>"$REPORT"
  sed -n '1,200p' "$file" >>"$REPORT" 2>/dev/null || true
  printf '\n```\n\n' >>"$REPORT"
}

CURRENT_CONTEXT="${CONTEXT:-$(kubectl config current-context 2>/dev/null || true)}"
if ! run version -o yaml >"$OUTPUT_DIR/raw/version.txt" 2>&1; then
  echo "ERROR: cannot connect to context '${CURRENT_CONTEXT:-unknown}'. See $OUTPUT_DIR/raw/version.txt" >&2
  exit 1
fi

cat >"$REPORT" <<EOF
# Kubernetes 生产环境巡检报告

- 时间:$(date '+%Y-%m-%d %H:%M:%S %z')
- Context:${CURRENT_CONTEXT:-unknown}
- 巡检范围:${NAMESPACE}
- 模式:只读
- 原始证据目录:raw/
EOF

# Inventory and component health
capture nodes get nodes -o wide || finding CRITICAL "无法读取节点" "kubectl get nodes 失败" "检查连接与 RBAC 权限"
capture namespaces get namespaces || true
capture pods get pods "${NS_ARGS[@]}" -o wide || finding HIGH "无法读取 Pod" "kubectl get pods 失败" "检查 RBAC 权限"
capture workloads get deploy,statefulset,daemonset "${NS_ARGS[@]}" || true
capture events get events "${NS_ARGS[@]}" --field-selector type=Warning --sort-by=.lastTimestamp || true
capture component_health get --raw=/readyz?verbose || true
capture apiservices get apiservices || true

NOT_READY="$(run get nodes --no-headers 2>/dev/null | awk '$2 !~ /^Ready/ {print $1":"$2}' | paste -sd, -)"
[[ -n "$NOT_READY" ]] && finding CRITICAL "节点非 Ready" "$NOT_READY" "检查 kubelet、容器运行时、网络和节点压力"

PRESSURE="$(run get nodes -o jsonpath='{range .items[*]}{.metadata.name}{" "}{range .status.conditions[?(@.status=="True")]}{.type}{" "}{end}{"\n"}{end}' 2>/dev/null | awk '$0 ~ /(MemoryPressure|DiskPressure|PIDPressure|NetworkUnavailable)/')"
[[ -n "$PRESSURE" ]] && finding CRITICAL "节点存在压力状态" "$(echo "$PRESSURE" | paste -sd';' -)" "立即检查节点容量、磁盘、PID 和网络"

# Pod failures/restarts
capture bad_pods get pods "${NS_ARGS[@]}" --field-selector=status.phase!=Running,status.phase!=Succeeded -o wide || true
BAD_PODS="$(run get pods "${NS_ARGS[@]}" --no-headers --field-selector=status.phase!=Running,status.phase!=Succeeded 2>/dev/null | wc -l | tr -d ' ')"
[[ "${BAD_PODS:-0}" -gt 0 ]] && finding HIGH "异常阶段 Pod" "${BAD_PODS} 个,详见 raw/bad_pods.txt" "检查调度、镜像、存储和容器日志"

RESTARTS="$(run get pods "${NS_ARGS[@]}" -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{range .status.containerStatuses[*]}{.name}{":"}{.restartCount}{" "}{end}{"\n"}{end}' 2>/dev/null | awk '{bad=0; for(i=2;i<=NF;i++){split($i,a,":"); if(a[2]+0>=5) bad=1} if(bad) print}')"
if [[ -n "$RESTARTS" ]]; then
  printf '%s\n' "$RESTARTS" >"$OUTPUT_DIR/raw/high_restarts.txt"
  finding HIGH "容器重启次数较高" "存在 restartCount >= 5 的 Pod" "检查退出码、OOM、探针和应用日志"
fi

WAITING="$(run get pods "${NS_ARGS[@]}" -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{range .status.containerStatuses[*]}{.state.waiting.reason}{" "}{end}{"\n"}{end}' 2>/dev/null | grep -E 'CrashLoopBackOff|ImagePullBackOff|ErrImagePull|CreateContainerConfigError' || true)"
if [[ -n "$WAITING" ]]; then
  printf '%s\n' "$WAITING" >"$OUTPUT_DIR/raw/waiting_errors.txt"
  finding CRITICAL "容器等待异常" "CrashLoop/ImagePull/配置错误,详见 raw/waiting_errors.txt" "检查镜像、Secret、配置和应用启动日志"
fi

# Workload availability
UNAVAILABLE="$(run get deploy "${NS_ARGS[@]}" -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" desired="}{.spec.replicas}{" available="}{.status.availableReplicas}{"\n"}{end}' 2>/dev/null | awk '{split($2,a,"="); split($3,b,"="); if((b[2]+0)<(a[2]+0)) print}')"
if [[ -n "$UNAVAILABLE" ]]; then
  printf '%s\n' "$UNAVAILABLE" >"$OUTPUT_DIR/raw/unavailable_deployments.txt"
  finding CRITICAL "Deployment 副本不足" "详见 raw/unavailable_deployments.txt" "检查 Pod、调度、探针和滚动发布状态"
fi

# Resource metrics (optional)
if capture node_metrics top nodes; then
  awk -v wc="$WARN_CPU" -v wm="$WARN_MEM" 'NR>1 {gsub("%","",$3); gsub("%","",$5); if($3+0>=wc || $5+0>=wm) print}' "$OUTPUT_DIR/raw/node_metrics.txt" >"$OUTPUT_DIR/raw/high_node_usage.txt"
  [[ -s "$OUTPUT_DIR/raw/high_node_usage.txt" ]] && finding HIGH "节点资源使用率偏高" "CPU >= ${WARN_CPU}% 或内存 >= ${WARN_MEM}%" "扩容或调整请求值,并分析容量趋势"
  capture pod_metrics top pods "${NS_ARGS[@]}" --containers || true
else
  finding INFO "资源指标不可用" "kubectl top nodes 失败" "安装或修复 metrics-server;生产监控建议接入 Prometheus"
fi

# Capacity, storage, networking and policy
capture node_capacity describe nodes || true
capture pvc get pvc "${NS_ARGS[@]}" -o wide || true
capture pv get pv -o wide || true
capture storageclasses get storageclass || true
capture volumeattachments get volumeattachments || true
capture ingress get ingress "${NS_ARGS[@]}" -o wide || true
capture services get services "${NS_ARGS[@]}" -o wide || true
capture endpoint_slices get endpointslices.discovery.k8s.io "${NS_ARGS[@]}" || true
capture network_policies get networkpolicies "${NS_ARGS[@]}" || true

BAD_PVC="$(run get pvc "${NS_ARGS[@]}" --no-headers 2>/dev/null | awk '$2 !~ /^Bound$/ {print $1"/"$2":"$3}' | paste -sd, -)"
[[ -n "$BAD_PVC" ]] && finding HIGH "PVC 非 Bound" "$BAD_PVC" "检查 StorageClass、CSI、容量和拓扑约束"

EMPTY_EP="$(run get endpointslices.discovery.k8s.io "${NS_ARGS[@]}" -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.labels.kubernetes\.io/service-name}{" "}{.endpoints[*].addresses}{"\n"}{end}' 2>/dev/null | awk 'NF==1 {print $1}' | paste -sd, -)"
[[ -n "$EMPTY_EP" ]] && finding HIGH "Service 无后端地址" "$EMPTY_EP" "检查 Service selector、Pod readiness 和端口配置"

# HA/config/security posture
capture pdb get pdb "${NS_ARGS[@]}" -o wide || true
capture hpa get hpa "${NS_ARGS[@]}" || true
capture quotas get resourcequota "${NS_ARGS[@]}" || true
capture limitranges get limitrange "${NS_ARGS[@]}" || true
capture cronjobs get cronjob "${NS_ARGS[@]}" || true
capture cert_requests get certificatesigningrequests || true

SINGLETONS="$(run get deploy "${NS_ARGS[@]}" -o jsonpath='{range .items[?(@.spec.replicas==1)]}{.metadata.namespace}{"/"}{.metadata.name}{"\n"}{end}' 2>/dev/null)"
if [[ -n "$SINGLETONS" ]]; then
  printf '%s\n' "$SINGLETONS" >"$OUTPUT_DIR/raw/single_replica_deployments.txt"
  finding MEDIUM "单副本 Deployment" "详见 raw/single_replica_deployments.txt" "确认是否为关键业务;关键业务建议多副本并跨故障域"
fi

PRIVILEGED="$(run get pods "${NS_ARGS[@]}" -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" hostNetwork="}{.spec.hostNetwork}{" hostPID="}{.spec.hostPID}{" "}{range .spec.containers[*]}{.name}{":privileged="}{.securityContext.privileged}{" "}{end}{"\n"}{end}' 2>/dev/null | grep -E 'hostNetwork=true|hostPID=true|privileged=true' || true)"
if [[ -n "$PRIVILEGED" ]]; then
  printf '%s\n' "$PRIVILEGED" >"$OUTPUT_DIR/raw/privileged_pods.txt"
  finding MEDIUM "高权限 Pod" "详见 raw/privileged_pods.txt" "确认业务必要性并采用最小权限、安全上下文和准入策略"
fi

LATEST="$(run get pods "${NS_ARGS[@]}" -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{.spec.containers[*].image}{"\n"}{end}' 2>/dev/null | grep -E '(^|[[:space:]])[^[:space:]]+:latest([[:space:]]|$)' || true)"
if [[ -n "$LATEST" ]]; then
  printf '%s\n' "$LATEST" >"$OUTPUT_DIR/raw/latest_images.txt"
  finding MEDIUM "使用 latest 镜像标签" "详见 raw/latest_images.txt" "固定不可变版本,关键场景使用镜像 digest"
fi

# Recent warning events
WARNINGS="$(run get events "${NS_ARGS[@]}" --field-selector type=Warning --no-headers 2>/dev/null | wc -l | tr -d ' ')"
[[ "${WARNINGS:-0}" -gt 0 ]] && finding MEDIUM "存在 Warning 事件" "${WARNINGS} 条,详见 raw/events.txt" "按原因聚合,优先处理持续或反复出现的事件"

# Optional etcd visibility; works for common kubeadm static-pod deployments.
capture etcd_pods get pods -n kube-system -l component=etcd -o wide || true
ETCD_COUNT="$(run get pods -n kube-system -l component=etcd --no-headers 2>/dev/null | wc -l | tr -d ' ')"
if [[ "${ETCD_COUNT:-0}" -eq 0 ]]; then
  finding INFO "etcd 无法从集群内直接巡检" "可能为托管控制面或标签不同" "从云平台/控制面监控确认 etcd 健康、容量、延迟和备份恢复"
elif [[ "$ETCD_COUNT" -lt 3 ]]; then
  finding MEDIUM "etcd 成员数量少于 3" "发现 ${ETCD_COUNT} 个带 component=etcd 标签的 Pod" "核实生产控制面的高可用设计"
fi

# RBAC checks are best effort.
if run auth can-i --list -A >"$OUTPUT_DIR/raw/current_identity_permissions.txt" 2>&1; then :; fi
capture clusterrolebindings get clusterrolebindings -o wide || true

# Report summary
section "风险摘要"
if [[ ! -s "$FINDINGS" ]]; then
  echo "未发现脚本可识别的异常;仍需结合业务 SLO、云平台、存储和备份系统检查。" >>"$REPORT"
else
  printf '| 级别 | 检查项 | 证据 | 建议 |\n|---|---|---|---|\n' >>"$REPORT"
  awk -F '\t' '{gsub(/\|/,"\\|",$2); gsub(/\|/,"\\|",$3); gsub(/\|/,"\\|",$4); printf "| %s | %s | %s | %s |\n",$1,$2,$3,$4}' "$FINDINGS" >>"$REPORT"
fi

section "统计"
NODE_TOTAL="$(run get nodes --no-headers 2>/dev/null | wc -l | tr -d ' ')"
POD_TOTAL="$(run get pods "${NS_ARGS[@]}" --no-headers 2>/dev/null | wc -l | tr -d ' ')"
printf -- '- 节点:%s\n- Pod:%s\n- 异常阶段 Pod:%s\n- Warning 事件:%s\n' "$NODE_TOTAL" "$POD_TOTAL" "${BAD_PODS:-0}" "${WARNINGS:-0}" >>"$REPORT"

section "核心状态(最多显示 200 行)"
code_file "节点" "$OUTPUT_DIR/raw/nodes.txt"
code_file "异常 Pod" "$OUTPUT_DIR/raw/bad_pods.txt"
code_file "工作负载" "$OUTPUT_DIR/raw/workloads.txt"
code_file "节点资源" "$OUTPUT_DIR/raw/node_metrics.txt"

section "人工补充检查"
cat >>"$REPORT" <<EOF
- etcd endpoint health、WAL fsync 延迟、数据库配额/碎片率,以及快照恢复演练。
- API Server、Scheduler、Controller Manager 的 P95/P99 延迟、错误率、限流和队列深度。
- 节点磁盘使用率(预警 ${WARN_DISK}% / 严重 ${CRIT_DISK}%)、inode、I/O 延迟、conntrack、时间同步。
- Ingress/Gateway 的流量、4xx/5xx、P95/P99 延迟、证书剩余天数(建议低于 ${CERT_WARN_DAYS} 天告警)。
- CSI/存储的容量、IOPS、时延、快照,以及真实的数据恢复验证。
- Prometheus、日志和告警链路自身健康;7/30/90 天容量趋势。
- RBAC 通配符和 cluster-admin 授权、镜像漏洞/签名、Secret 管理、审计日志。
- 节点或可用区故障演练,以及业务 RPO/RTO 验证。
EOF

echo "Inspection complete: $REPORT"
echo "Raw evidence: $OUTPUT_DIR/raw"
exit 0

8. 无法完全自动化的检查

通用 kubectl 脚本无法可靠覆盖所有生产指标。下列内容通常需要 Prometheus、云平台、存储平台、证书系统或备份系统配合:

  • etcd 的 endpoint health、Leader 变化、WAL fsync 延迟、数据库配额、碎片率和快照恢复。
  • API Server、Scheduler、Controller Manager 的 P95/P99 延迟、错误率、限流和队列深度。
  • 主机磁盘使用率、inode、I/O 延迟、conntrack、系统负载和时间同步。
  • Ingress/Gateway 的流量、4xx/5xx、延迟、上游失败和 TLS 证书剩余有效期。
  • CSI 和存储系统的容量、IOPS、吞吐、延迟、快照及真实数据恢复。
  • 镜像漏洞、签名和来源可信度,Secret 外部托管及审计日志留存。
  • Prometheus、日志与告警链路自身的健康状态,以及 7/30/90 天容量趋势。
  • 节点、可用区和整个集群级别的故障演练,以及业务 RPO/RTO 验证。

因此,脚本报告应作为自动化基线,而不是生产验收或容灾结论的唯一依据。

9. 建议执行频率

频率 建议内容
实时监控 节点和 Pod 状态、组件存活、错误率、延迟、资源耗尽和证书告警
每日 执行本脚本,检查 Warning 事件、异常 Pod、重启、PVC、备份结果
每周 容量趋势、资源请求与限制、HPA、PDB、权限及公网暴露面
每月 版本、补丁、漏洞、证书、配置漂移、容量预测和恢复验证
每季度 etcd/PV 恢复、节点或可用区故障、完整容灾演练

10. 巡检闭环建议

对每项异常至少记录:发现时间、风险等级、当前值、阈值、趋势、影响范围、证据、整改负责人、计划完成时间和复核结果。只有形成整改与复核闭环,巡检才真正发挥生产保障作用。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容