289 lines
9.1 KiB
Bash
289 lines
9.1 KiB
Bash
#!/usr/bin/env bash
|
||
# 按「集群里实际存在的资源」遍历删除(全部由 kubectl 发现,不读仓库 YAML 目录)
|
||
# 对应 docs/00-03 §2 步骤 2「重度清理」方向的实验室内清场(非 verify.sh 默认 teardown,亦非 k3s-uninstall)。
|
||
# 在任意目录执行均可;建议在仓库根:./scripts/k3s-delete-lab-stacks.sh [选项]
|
||
#
|
||
# 默认跳过系统命名空间:kube-system、kube-public、kube-node-lease
|
||
# 每个命名空间内会跳过 Service/kubernetes(API 内置 Service)
|
||
# 可选:PVC / ConfigMap / Secret(默认不删 Secret,避免误伤账号 token)
|
||
#
|
||
# 环境:kubectl 可用;export KUBECONFIG=... 按需设置
|
||
|
||
set -euo pipefail
|
||
|
||
DRY_RUN=false
|
||
YES=false
|
||
PREVIEW_ONLY=false
|
||
INCLUDE_KUBE_SYSTEM=false
|
||
WITH_PVC=false
|
||
WITH_CONFIGMAPS=false
|
||
WITH_SECRETS=false
|
||
# 空 = 自动枚举「非系统」命名空间;非空 = 仅处理列出的 NS(逗号分隔)
|
||
NAMESPACES_ARG=""
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
用法: k3s-delete-lab-stacks.sh [选项]
|
||
|
||
按 kubectl 当前集群中已部署的资源逐项删除(常见工作负载 + Ingress/IngressRoute 等)。
|
||
不依赖本仓库 ansible/files 目录。
|
||
|
||
选项:
|
||
--preview 只列出将参与删除的命名空间及各资源(kubectl get),不执行删除
|
||
--dry-run 删除时使用 kubectl 的 --dry-run=client(不落库,部分环境仍会做校验)
|
||
-y, --yes 跳过确认
|
||
--namespaces NS[,NS...]
|
||
只处理这些命名空间(仍受 --include-kube-system 与系统 NS 规则约束)
|
||
--include-kube-system
|
||
也处理 kube-system(极危险,可能拆掉 Traefik/Coredns 等)
|
||
--with-pvc 删除 PersistentVolumeClaim(数据卷,默认不删)
|
||
--with-configmaps 删除 ConfigMap(会跳过 kube-root-ca.crt)
|
||
--with-secrets 删除 Secret(会跳过 default-token-* 及 type=kubernetes.io/service-account-token)
|
||
-h, --help 帮助
|
||
|
||
示例:
|
||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||
./scripts/k3s-delete-lab-stacks.sh --preview
|
||
./scripts/k3s-delete-lab-stacks.sh --namespaces default -y
|
||
./scripts/k3s-delete-lab-stacks.sh --dry-run -y
|
||
EOF
|
||
exit 0
|
||
}
|
||
|
||
ARGS=("$@")
|
||
i=0
|
||
while [[ $i -lt ${#ARGS[@]} ]]; do
|
||
case "${ARGS[$i]}" in
|
||
--dry-run) DRY_RUN=true ;;
|
||
-y|--yes) YES=true ;;
|
||
--preview) PREVIEW_ONLY=true ;;
|
||
--include-kube-system) INCLUDE_KUBE_SYSTEM=true ;;
|
||
--with-pvc) WITH_PVC=true ;;
|
||
--with-configmaps) WITH_CONFIGMAPS=true ;;
|
||
--with-secrets) WITH_SECRETS=true ;;
|
||
-h|--help) usage ;;
|
||
--namespaces)
|
||
i=$((i + 1))
|
||
if [[ $i -ge ${#ARGS[@]} ]]; then echo "[ERROR] --namespaces 需要参数" >&2; exit 1; fi
|
||
NAMESPACES_ARG="${ARGS[$i]}"
|
||
;;
|
||
*)
|
||
echo "[ERROR] 未知参数: ${ARGS[$i]},使用 -h 查看帮助" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
i=$((i + 1))
|
||
done
|
||
|
||
if ! command -v kubectl &>/dev/null; then
|
||
echo "[ERROR] 未找到 kubectl" >&2
|
||
exit 1
|
||
fi
|
||
|
||
KUBECTL_DELETE=(kubectl delete)
|
||
if [[ "${DRY_RUN}" == true ]]; then
|
||
KUBECTL_DELETE=(kubectl delete --dry-run=client)
|
||
fi
|
||
|
||
# 系统命名空间:默认不扫(除非 --include-kube-system 且用户未用 --namespaces 限制)
|
||
SYSTEM_NS="kube-system kube-public kube-node-lease"
|
||
|
||
is_system_ns() {
|
||
local n="$1"
|
||
for s in ${SYSTEM_NS}; do
|
||
[[ "${n}" == "${s}" ]] && return 0
|
||
done
|
||
return 1
|
||
}
|
||
|
||
collect_namespaces() {
|
||
if [[ -n "${NAMESPACES_ARG}" ]]; then
|
||
IFS=',' read -r -a arr <<< "${NAMESPACES_ARG}"
|
||
for raw in "${arr[@]}"; do
|
||
n="${raw//[[:space:]]/}"
|
||
[[ -z "${n}" ]] && continue
|
||
if ! is_system_ns "${n}" || [[ "${INCLUDE_KUBE_SYSTEM}" == true ]]; then
|
||
echo "${n}"
|
||
else
|
||
echo "[WARN] 已忽略系统命名空间(加 --include-kube-system 可处理): ${n}" >&2
|
||
fi
|
||
done
|
||
return
|
||
fi
|
||
|
||
local all
|
||
all=$(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
|
||
while IFS= read -r n; do
|
||
[[ -z "${n}" ]] && continue
|
||
if is_system_ns "${n}" && [[ "${INCLUDE_KUBE_SYSTEM}" != true ]]; then
|
||
continue
|
||
fi
|
||
echo "${n}"
|
||
done <<< "${all}"
|
||
}
|
||
|
||
# 若 kind 在当前集群不存在,静默跳过
|
||
list_kind_in_ns() {
|
||
local ns="$1"
|
||
local kind="$2"
|
||
kubectl get "${kind}" -n "${ns}" -o name 2>/dev/null || true
|
||
}
|
||
|
||
preview_kind_in_ns() {
|
||
local ns="$1"
|
||
local kind="$2"
|
||
if kubectl get "${kind}" -n "${ns}" &>/dev/null; then
|
||
echo "===== ${ns} / ${kind} ====="
|
||
kubectl get "${kind}" -n "${ns}" -o wide 2>/dev/null || true
|
||
fi
|
||
}
|
||
|
||
# 删除某 kind 在 ns 下 kubectl -o name 列出的所有资源
|
||
delete_kind_all_by_name() {
|
||
local ns="$1"
|
||
local kind="$2"
|
||
local line name
|
||
while IFS= read -r line; do
|
||
[[ -z "${line}" ]] && continue
|
||
# line 形如 deployment.apps/foo 或 pod/bar
|
||
name="${line##*/}"
|
||
echo "[DEL] ${line} -n ${ns}"
|
||
"${KUBECTL_DELETE[@]}" "${line}" -n "${ns}" --ignore-not-found || true
|
||
done < <(list_kind_in_ns "${ns}" "${kind}")
|
||
}
|
||
|
||
delete_services_safe() {
|
||
local ns="$1"
|
||
local names
|
||
names=$(kubectl get svc -n "${ns}" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
|
||
while IFS= read -r n; do
|
||
[[ -z "${n}" ]] && continue
|
||
[[ "${n}" == "kubernetes" ]] && continue
|
||
echo "[DEL] service/${n} -n ${ns}"
|
||
"${KUBECTL_DELETE[@]}" "svc" "${n}" -n "${ns}" --ignore-not-found || true
|
||
done <<< "${names}"
|
||
}
|
||
|
||
delete_configmaps_safe() {
|
||
local ns="$1"
|
||
local cm
|
||
while IFS= read -r cm; do
|
||
[[ -z "${cm}" ]] && continue
|
||
[[ "${cm}" == "kube-root-ca.crt" ]] && continue
|
||
echo "[DEL] configmap/${cm} -n ${ns}"
|
||
"${KUBECTL_DELETE[@]}" "configmap" "${cm}" -n "${ns}" --ignore-not-found || true
|
||
done < <(kubectl get configmap -n "${ns}" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
|
||
}
|
||
|
||
delete_secrets_safe() {
|
||
local ns="$1"
|
||
if ! command -v jq &>/dev/null; then
|
||
echo "[WARN] 未安装 jq,无法安全过滤 Secret,已跳过 ${ns}" >&2
|
||
return 0
|
||
fi
|
||
local names
|
||
names=$(kubectl get secret -n "${ns}" -o json 2>/dev/null | jq -r '
|
||
.items[]?
|
||
| select(.type != "kubernetes.io/service-account-token")
|
||
| select(.metadata.name | test("^default-token-") | not)
|
||
| .metadata.name' 2>/dev/null || true)
|
||
while IFS= read -r sn; do
|
||
[[ -z "${sn}" ]] && continue
|
||
echo "[DEL] secret/${sn} -n ${ns}"
|
||
"${KUBECTL_DELETE[@]}" "secret" "${sn}" -n "${ns}" --ignore-not-found || true
|
||
done <<< "${names}"
|
||
}
|
||
|
||
# Traefik / 扩展:不存在则 list 为空
|
||
TRAEFIK_KINDS=(ingressroute middleware tlsoption traefikservice serverstransport)
|
||
|
||
process_namespace() {
|
||
local ns="$1"
|
||
|
||
if [[ "${PREVIEW_ONLY}" == true ]]; then
|
||
echo "######## 命名空间: ${ns} ########"
|
||
for k in cronjob job deployment statefulset daemonset ingress networkpolicy horizontalpodautoscaler hpa \
|
||
"${TRAEFIK_KINDS[@]}" service pvc configmap secret; do
|
||
preview_kind_in_ns "${ns}" "${k}" || true
|
||
done
|
||
return
|
||
fi
|
||
|
||
echo "######## 删除: ${ns} ########"
|
||
|
||
# 1) 定时与任务
|
||
delete_kind_all_by_name "${ns}" "cronjob"
|
||
delete_kind_all_by_name "${ns}" "job"
|
||
|
||
# 2) 路由(Traefik CRD 可能未装,list 为空)
|
||
delete_kind_all_by_name "${ns}" "ingress"
|
||
for tk in "${TRAEFIK_KINDS[@]}"; do
|
||
delete_kind_all_by_name "${ns}" "${tk}"
|
||
done
|
||
|
||
# 3) 工作负载
|
||
delete_kind_all_by_name "${ns}" "deployment"
|
||
delete_kind_all_by_name "${ns}" "statefulset"
|
||
delete_kind_all_by_name "${ns}" "daemonset"
|
||
delete_kind_all_by_name "${ns}" "replicaset"
|
||
|
||
# 4) 其它常见附属(不主动删 Pod:由上层负载级联回收,避免误伤系统静态 Pod)
|
||
delete_kind_all_by_name "${ns}" "networkpolicy"
|
||
delete_kind_all_by_name "${ns}" "horizontalpodautoscaler"
|
||
delete_kind_all_by_name "${ns}" "hpa"
|
||
|
||
# 5) Service(保留 kubernetes)
|
||
delete_services_safe "${ns}"
|
||
|
||
# 6) PVC
|
||
if [[ "${WITH_PVC}" == true ]]; then
|
||
delete_kind_all_by_name "${ns}" "persistentvolumeclaim"
|
||
fi
|
||
|
||
if [[ "${WITH_CONFIGMAPS}" == true ]]; then
|
||
delete_configmaps_safe "${ns}"
|
||
fi
|
||
|
||
if [[ "${WITH_SECRETS}" == true ]]; then
|
||
if command -v jq &>/dev/null; then
|
||
delete_secrets_safe "${ns}"
|
||
else
|
||
echo "[WARN] ${ns}: 未安装 jq,已跳过 --with-secrets" >&2
|
||
fi
|
||
fi
|
||
}
|
||
|
||
NS_LIST=()
|
||
while IFS= read -r _ns; do
|
||
[[ -z "${_ns}" ]] && continue
|
||
NS_LIST+=("${_ns}")
|
||
done < <(collect_namespaces | sort -u)
|
||
|
||
if [[ ${#NS_LIST[@]} -eq 0 ]]; then
|
||
echo "[ERROR] 没有可处理的命名空间(检查 --namespaces / 集群连接)" >&2
|
||
exit 1
|
||
fi
|
||
|
||
if [[ -n "${KUBECONFIG:-}" ]]; then
|
||
echo "[INFO] KUBECONFIG=${KUBECONFIG}"
|
||
else
|
||
echo "[INFO] KUBECONFIG 未设置,使用 kubectl 默认配置"
|
||
fi
|
||
echo "[INFO] 命名空间 (${#NS_LIST[@]}): ${NS_LIST[*]}"
|
||
echo "[INFO] include-kube-system=${INCLUDE_KUBE_SYSTEM} with-pvc=${WITH_PVC} with-configmaps=${WITH_CONFIGMAPS} with-secrets=${WITH_SECRETS} preview=${PREVIEW_ONLY} dry-run=${DRY_RUN}"
|
||
|
||
if [[ "${PREVIEW_ONLY}" != true && "${YES}" != true && "${DRY_RUN}" != true ]]; then
|
||
echo "[WARN] 将按上述命名空间删除工作负载与路由等资源(见脚本内顺序)。输入 yes 继续:"
|
||
read -r confirm
|
||
if [[ "${confirm}" != "yes" ]]; then
|
||
echo "已取消"
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
for ns in "${NS_LIST[@]}"; do
|
||
process_namespace "${ns}"
|
||
done
|
||
|
||
echo "[DONE] 完成"
|