#!/usr/bin/env bash # 验证入口(以 ansible/playbooks/verify/.yml 为唯一执行真源): # - run :执行单篇验证 playbook # - run-all:按 verify 目录中存在的 .yml 顺序执行(仅执行域:XX>0 && YY>0) # - full:preflight + run-all # # 说明: # - 本脚本不再解析任何“矩阵/状态板”文档;验证清单从 verify playbook 自动得出。 # - 步骤 1~3(接入、环境/轻量清理、部署)由操作者或 scripts/deploy-lab.sh 完成;本脚本不执行 k3s-uninstall。 # - 推荐在 Linux 工作机或控制节点仓库根执行。 set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck disable=SC1091 source "${ROOT}/scripts/lib-ansible-lab.sh" ansible_lab_export_config # 默认与 §2 一致:验证后清理临时资源 export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}" load_env() { if [[ -f "${ROOT}/scripts/.env.verify" ]]; then set -a # shellcheck disable=SC1091 source "${ROOT}/scripts/.env.verify" set +a echo "[OK] 已加载 scripts/.env.verify" fi export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}" } DOC_ID_EXEC_RE='^(0[1-9]|[1-9][0-9])-(0[1-9]|[1-9][0-9])$' is_exec_doc_id() { local doc_id="$1" [[ "$doc_id" =~ $DOC_ID_EXEC_RE ]] } list_doc_ids_from_verify_dir() { # 只列出执行域(XX>0 && YY>0)的 verify 清单;支持筛选 local series="${1:-}" local id_regex="${2:-}" local exclude_noop="${3:-0}" local require_teardown="${4:-0}" ROOT="${ROOT}" SERIES="${series}" ID_REGEX="${id_regex}" EXCLUDE_NOOP="${exclude_noop}" REQUIRE_TEARDOWN="${require_teardown}" python3 - <<'PY' import os import re from pathlib import Path root = Path(os.environ["ROOT"]) verify_dir = root / "ansible" / "playbooks" / "verify" series = os.environ.get("SERIES", "").strip() id_regex = os.environ.get("ID_REGEX", "").strip() exclude_noop = os.environ.get("EXCLUDE_NOOP", "0") == "1" require_teardown = os.environ.get("REQUIRE_TEARDOWN", "0") == "1" pat = re.compile(r"^(?P(0[1-9]|[1-9][0-9])-(0[1-9]|[1-9][0-9]))\.yml$") id_pat = re.compile(id_regex) if id_regex else None ids = [] for p in verify_dir.iterdir(): m = pat.match(p.name) if not m: continue doc_id = m.group("id") if series and not doc_id.startswith(f"{series}-"): continue if id_pat and not id_pat.search(doc_id): continue if exclude_noop or require_teardown: content = p.read_text(encoding="utf-8", errors="ignore") if exclude_noop and "noop verify" in content: continue if require_teardown and ("VERIFY_TEARDOWN" not in content and "verify_teardown" not in content): continue ids.append(doc_id) for x in sorted(set(ids)): print(x) PY } print_flow() { cat < / run-all → ansible/playbooks/verify/.yml 5 收尾与记录 VERIFY_TEARDOWN;验证结论建议写回对应实验篇文档(或单独记录日志) 6 一键串联 $0 full(推荐)或 $0 run-all 相关脚本:deploy-lab.sh(安装/铺栈) EOF } run_preflight() { local inv="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}" if ! command -v ansible-playbook >/dev/null 2>&1; then echo "[ERR] 未找到 ansible-playbook" >&2 exit 1 fi [[ -f "$inv" ]] || { echo "[ERR] inventory 不存在:$inv" >&2; exit 1; } ansible_lab_check_inventory_keys "$inv" || exit 1 echo "[RUN] ansible k3s_server -m ping" ansible k3s_server -i "$inv" -m ping if [[ "${VERIFY_PREFLIGHT_CLUSTER:-0}" == "1" ]]; then echo "[RUN] kubectl get nodes(控制节点,需已安装 K3s)" ansible k3s_server -i "$inv" -b -m ansible.builtin.shell -a \ 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get nodes' \ || { echo "[WARN] 集群侧检查失败:若尚未 deploy k3s,可忽略;装好后设 VERIFY_PREFLIGHT_CLUSTER=1 再测" >&2 exit 1 } else echo "[TIP] 跳过 kubectl 检查。已装 K3s 时可执行:VERIFY_PREFLIGHT_CLUSTER=1 $0 preflight" fi echo "[OK] preflight 通过" } run_all_verify() { local series="${1:-}" local id_regex="${2:-}" local exclude_noop="${3:-0}" local require_teardown="${4:-0}" local id while IFS= read -r id; do echo "" echo "########################################## $id" ansible_verify "$id" done < <(list_doc_ids_from_verify_dir "$series" "$id_regex" "$exclude_noop" "$require_teardown") } usage() { cat <<'EOF' 用法:scripts/verify.sh <命令> [...] 命令: flow 打印与 docs/00-05 §2 对齐的「验证流程」说明(不接 Ansible) preflight 检查 ansible-playbook 与 inventory;对 k3s_server 做 ping 若 VERIFY_PREFLIGHT_CLUSTER=1,额外 kubectl get nodes(未装集群会失败) full 先 preflight,再按 doc_id 顺序运行全部 verify(= preflight + run-all,推荐) list [筛选参数] 列出可执行 doc_id(仅执行域) run 运行指定 doc_id(ansible/playbooks/verify/.yml) run-all [筛选参数] 按 doc_id 顺序运行 verify playbook(fail-fast),不做 preflight 筛选参数(可用于 list / run-all / full): --series 只运行某个主序列(例如 04) --id-regex 仅保留匹配 doc_id 的条目(例如 '^04-(0[2-9]|1[0-4])$') --exclude-noop 排除 noop verify --require-teardown 仅保留包含 teardown gate 的条目 环境变量: VERIFY_TEARDOWN=1 验证后清理本篇资源(默认 1,对应 §2 轻量 teardown) VERIFY_PREFLIGHT_CLUSTER 为 1 时 preflight 额外执行 kubectl get nodes ANSIBLE_INVENTORY 默认 <仓库>/ansible/inventory.ini(其中 ansible_ssh_private_key_file 须在本机存在) nginx_entry_base 例如 http://192.168.2.61(02-xx / 03-02 等 HTTP 校验) nodejs_entry_base 例如 http://192.168.2.61(04-01) SKIP_ARMV7 默认 1;为 0 时 01-03/01-05 若未配 ARMV7_SSH(01-05 可用 ARMV7_NFS_SSH)会失败 ARMV7_SSH / ARMV7_NFS_SSH 一行 ssh 命令;与 SKIP_ARMV7=0 配合时 01-03/01-05 经 SSH 在 arm 上 dnf 安装(见 docs/00-07 §E) 与「部署」分工:安装 K3s / Longhorn / nginx 铺栈请用 ./scripts/deploy-lab.sh;验收请用本脚本。 示例: ./scripts/verify.sh flow ./scripts/verify.sh full ./scripts/verify.sh preflight export nginx_entry_base=http://192.168.2.61 ./scripts/verify.sh run 02-05 EOF } ansible_verify() { local doc_id="$1" if ! is_exec_doc_id "$doc_id"; then echo "[ERR] 非执行域 doc_id:$doc_id(仅允许 XX>0 且 YY>0)" >&2 exit 1 fi local inv="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}" local pb_single="${ROOT}/ansible/playbooks/verify/${doc_id}.yml" if [[ ! -f "$pb_single" ]]; then echo "[ERR] verify playbook 不存在:$pb_single" >&2 echo "[TIP] 可用 '$0 list' 查看可执行 doc_id" >&2 exit 1 fi if [[ ! -f "$inv" ]]; then echo "[ERR] inventory 不存在:$inv" >&2 exit 1 fi local td="${VERIFY_TEARDOWN:-1}" echo "[RUN] ansible-playbook -i $inv -e VERIFY_TEARDOWN=$td $pb_single" ansible-playbook -i "$inv" -e "VERIFY_TEARDOWN=$td" "$pb_single" } main() { load_env local cmd="${1:-}" shift || true local series="" local id_regex="" local exclude_noop=0 local require_teardown=0 parse_filter_args() { while [[ $# -gt 0 ]]; do case "$1" in --series) series="${2:-}" [[ -n "$series" ]] || { echo "[ERR] --series 需要参数" >&2; exit 1; } [[ "$series" =~ ^(0[1-9]|[1-9][0-9])$ ]] || { echo "[ERR] --series 仅允许 01..99" >&2; exit 1; } shift 2 ;; --id-regex) id_regex="${2:-}" [[ -n "$id_regex" ]] || { echo "[ERR] --id-regex 需要参数" >&2; exit 1; } shift 2 ;; --exclude-noop) exclude_noop=1 shift ;; --require-teardown) require_teardown=1 shift ;; *) echo "[ERR] 未知参数:$1" >&2 exit 1 ;; esac done } case "$cmd" in ""|-h|--help) usage ;; flow) print_flow ;; preflight) run_preflight ;; full) parse_filter_args "$@" run_preflight echo "" echo "########################################## run-all" run_all_verify "$series" "$id_regex" "$exclude_noop" "$require_teardown" ;; list) parse_filter_args "$@" list_doc_ids_from_verify_dir "$series" "$id_regex" "$exclude_noop" "$require_teardown" ;; run) local doc_id="${1:?need doc_id like 02-05}" ansible_verify "$doc_id" ;; run-all) parse_filter_args "$@" run_all_verify "$series" "$id_regex" "$exclude_noop" "$require_teardown" ;; *) echo "[ERR] unknown cmd: $cmd" >&2 usage exit 1 ;; esac } main "$@"