Files
Deploy-Laboratory/scripts/k3s-delete-lab-stacks.sh
jack 231b6713c4 chore: 对齐 00-05 §2 的部署与验证脚本
- 新增 deploy-lab.sh(k3s/longhorn/nginx 铺栈)与 ssh/run-phase2-k3s-on-ylc61-as-jack.sh
- verify.sh:flow/preflight、VERIFY_TEARDOWN 默认、注释与 §2 对应
- 更新 smoke-verify、README、.env.verify.example、根 README 与主要 playbook 头注释
- k3s-delete-lab-stacks 标明重度清场语义

Made-with: Cursor
2026-03-26 07:32:08 +08:00

289 lines
9.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 按「集群里实际存在的资源」遍历删除(全部由 kubectl 发现,不读仓库 YAML 目录)
# 对应 docs/00-05 §2 步骤 2「重度清理」方向的实验室内清场非 verify.sh 默认 teardown亦非 k3s-uninstall
# 在任意目录执行均可;建议在仓库根:./scripts/k3s-delete-lab-stacks.sh [选项]
#
# 默认跳过系统命名空间kube-system、kube-public、kube-node-lease
# 每个命名空间内会跳过 Service/kubernetesAPI 内置 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] 完成"