对齐文件规范

This commit is contained in:
2026-03-27 16:58:41 +08:00
parent 231b6713c4
commit 31709425e2
235 changed files with 5433 additions and 2850 deletions

View File

@@ -1,5 +1,6 @@
# 验证矩阵 / 编排脚本用环境变量模板
# 验证 / 编排脚本用环境变量模板
# ---------------------------------------------------------------------------
# 各变量与「未验证 / 部分验证」文档的对应关系、缺省时的行为,见 docs/00-07-待验证项-验证前准备.md
# 使用:复制为本目录下的 .env.verify勿提交 Git在仓库根执行
# set -a && source scripts/.env.verify && set +a
# 或在 bash 中source scripts/.env.verify
@@ -23,10 +24,9 @@ export K3S_DATA_DIR="${K3S_DATA_DIR:-/storage}"
# --- Ansible安装/复验 k3s对应 docs/00-05 §2 步骤 3---
export ANSIBLE_INVENTORY="${ANSIBLE_INVENTORY:-$(pwd)/ansible/inventory.ini}"
# deploy-lab.sh k3s / ssh/run-phase2-k3s-on-ylc61-as-jack.sh
# 为 true 时先跑 k3s-prepare-storage.yml传 -e k3s_prepare_storage=true磁盘变量见 group_vars
# deploy-lab.sh k3s:为 true 时先在 01-06.yml 内启用准备数据盘(传 -e k3s_do_prepare_storage=true -e k3s_prepare_storage=true磁盘变量见 group_vars
export K3S_PREPARE_STORAGE="${K3S_PREPARE_STORAGE:-false}"
# 建议在控制节点或 Linux 工作机执行 deploy-lab.sh;办公机可用 run-phase2-k3s-on-ylc61-as-jack.sh 触发远端
# 建议在控制节点或 Linux 工作机仓库根执行 deploy-lab.sh
# --- SSH 密钥命名(与 scripts/ssh/test-ssh.sh 默认一致;脚本内尚为硬编码路径)---
# test-ssh 使用:$K3S_SSH_KEY_DIR/${K3S_SSH_KEY_PREFIX}<inventory主机名>
@@ -39,8 +39,12 @@ export SSH_USER="${SSH_USER:-jack}"
export TIMEOUT_SEC="${TIMEOUT_SEC:-5}"
# --- 矩阵验证docs/00-05 §2 步骤 46---
# ./scripts/verify.sh run | run-all | preflight | flow
# VERIFY_TEARDOWN(默认 1、VERIFY_PREFLIGHT_CLUSTER为 1 时 preflight 额外 kubectl get nodes
# ./scripts/verify.sh run | run-all | preflight | full | flow
# verify.sh 默认 VERIFY_TEARDOWN=1每篇 playbook 末尾 teardown再跑下一 doc_id勿设为 0资源残留会干扰后续用例
export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}"
# 02-xx / 03-02 等经 Ingress 校验 HTTP填控制节点或入口 URL与 inventory 中 k3s_server IP 一致)
export nginx_entry_base="${nginx_entry_base:-http://192.168.2.61}"
# VERIFY_PREFLIGHT_CLUSTER为 1 时 preflight 额外 kubectl get nodes
# --- SSH第三方验证机 onecloud不忽略矩阵里多处依赖「集群外」curl/探测)---
# 用途示例02-xx nginx 矩阵从第三方访问 Ingress01-07 经 onecloud 对 OpenWrt:18080/18443 发 curl
@@ -76,18 +80,21 @@ export TRAEFIK_NAMESPACE="${TRAEFIK_NAMESPACE:-kube-system}"
# 逗号分隔,与 ZONE_NAME 下实际 DNS 记录一致;勿提交敏感子域若需可只写本机
export VERIFY_TLS_HOSTS="${VERIFY_TLS_HOSTS:-test01.jackadam.top,test02.jackadam.top,test03.jackadam.top,test04.jackadam.top}"
# --- Longhorn03-07 / ansible longhorn-install---
# --- Longhorn03-07 / ansible verify/stack-longhorn-install---
export LONGHORN_NAMESPACE="${LONGHORN_NAMESPACE:-longhorn-system}"
# --- 可选跳过(仅下列项ONECLOUD 与 ARMV7 实机路径不在此列——见下方---
# --- 可选跳过(编排占位;当前 verify.sh 未实现 HA/GitOps 门控时可忽略---
export SKIP_HA="${SKIP_HA:-1}"
# 跑 01-03 / 01-05 时设为 0并填写 ARMV7_*;编排脚本不得在无设备时假装通过
export SKIP_ARMV7="${SKIP_ARMV7:-1}"
export SKIP_GITOPS="${SKIP_GITOPS:-1}"
# --- armv701-03 Docker、01-05 NFS):不忽略;与 ONECLOUD 一样,编排须显式走 ARMV7_*,不得跳过 ---
# 跑 01-03 / 01-05 时SKIP_ARMV7=0并填写 ARMV7_SSH可与 onecloud 同主机、或直连 arm按你环境二选一
# ARMV7_NFS_SSH 默认同 ARMV7_SSH若 NFS 在另一台 arm 上再单独覆盖
# --- armv7 / arm3201-03 Docker、01-05 NFS、05-02 部分)---
# 默认 SKIP_ARMV7=1verify/01-03、01-05 仅跑矩阵基线(文档/文件检查),不经 SSH 改 arm 机
# 设 SKIP_ARMV7=0 且 ARMV7_SSH 非空run 01-03 时经该 SSH 在 arm 上 dnf 装 docker 并校验(假定 Fedora/RHEL 系,见 docs/01-03
# 01-05同上但 NFS 所在主机可用 ARMV7_NFS_SSH未设则回退为 ARMV7_SSH会写 /etc/exports、exportfs见 docs/01-05
# export ARMV7_NFS_EXPORT_PATH="/sdcard"
# export ARMV7_NFS_CLIENT_SUBNET="192.168.2.0/24"
# verify.sh 在 source .env.verify 后执行 playbook子进程会继承下列变量无需 verify.sh 单独传参)。
export SKIP_ARMV7="${SKIP_ARMV7:-1}"
export ARMV7_SSH="${ARMV7_SSH:-}"
export ARMV7_NFS_SSH="${ARMV7_NFS_SSH:-$ARMV7_SSH}"
@@ -99,10 +106,8 @@ export ARMV7_NFS_SSH="${ARMV7_NFS_SSH:-$ARMV7_SSH}"
# export OPENWRT_VERIFY_HTTPS_HOSTS="test01.jackadam.top,..."
# --- 与 scripts/*.sh 对照 ---
# verify.sh → VERIFY_TEARDOWN, VERIFY_PREFLIGHT_CLUSTER, nginx_entry_base, ANSIBLE_INVENTORY
# verify.sh → VERIFY_TEARDOWN, VERIFY_PREFLIGHT_CLUSTER, nginx_entry_base, ANSIBLE_INVENTORY01-03/01-05 另读 SKIP_ARMV7、ARMV7_SSH、ARMV7_NFS_SSH 等(见上文 armv7 段)
# deploy-lab.sh → ANSIBLE_INVENTORY, K3S_PREPARE_STORAGE
# ssh/run-phase2-k3s-on-ylc61-as-jack.sh → LAB_REPO_ROOT, K3S_PREPARE_STORAGE传远端
# ssh/smoke-verify-matrix-on-ylc61.sh → VERIFY_REPO_ROOT, VERIFY_TEARDOWN, nginx_entry_base
# cloudflare-delete-acme-challenge-dns.sh → CF_API_TOKEN, ZONE_NAME, ZONE_ID
# k3s-delete-lab-stacks.sh → KUBECONFIG
# ssh/test-ssh.sh → TIMEOUT_SEC密钥路径当前固定为 $HOME/.ssh/id_ed25519_k3s_<host>

View File

@@ -2,7 +2,7 @@
本目录集中维护通用运维脚本。约定:**在仓库根目录执行**,使用 `./scripts/...` 路径调用。
流程说明与「部署 / 验证」分工以 [`docs/00-05-测试与验证框架.md`](../docs/00-05-测试与验证框架.md) **§2 自动化验证流程** 为准;下表与之一一对应。
流程说明与「部署 / 验证」分工以 [`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md) **§2 自动化验证流程** 为准;下表与之一一对应。
| §2 步骤 | 含义 | 本仓库入口 |
|--------|------|------------|
@@ -10,18 +10,23 @@
| 2 环境/清理 | 轻量:各 verify 的 teardown重度清实验负载 / 重装 K3s | 轻量:`VERIFY_TEARDOWN`(默认 1重度`k3s-delete-lab-stacks.sh`、文档中的 `k3s-uninstall`**勿**默认进 `run-all` |
| 3 部署 | K3s、Longhorn、nginx 矩阵等铺栈 | **`./scripts/deploy-lab.sh`**`k3s` / `longhorn` / `nginx-matrix` / `nginx-matrix-tls` |
| 45 断言与收尾 | 按 doc 目标 kubectl/curl本篇 teardown | **`./scripts/verify.sh`** `run` / `run-all` |
| 6 一键串联 | 按矩阵顺序跑全部 verify | **`./scripts/verify.sh run-all`** |
| 6 一键串联 | 按 doc_id 顺序跑全部 verify(可先 preflight | **`./scripts/verify.sh full`**(推荐,= preflight + run-all **`./scripts/verify.sh run-all`** |
真机一键验收(可选先铺栈再全量验收):
- `./scripts/acceptance.sh`:可选先执行 `deploy-lab.sh`(由环境变量开关),再执行 `verify.sh full`
辅助命令:
- `./scripts/verify.sh flow` — 打印与 §2 对齐的流程说明(不接 Ansible
- `./scripts/verify.sh preflight` — 检查 `ansible-playbook`、矩阵、`inventory`,并对 `k3s_server` 执行 `ping`;若已装集群可设 `VERIFY_PREFLIGHT_CLUSTER=1` 再执行 `kubectl get nodes`
- `./scripts/verify.sh preflight` — 检查 `ansible-playbook``inventory`,并对 `k3s_server` 执行 `ping`;若已装集群可设 `VERIFY_PREFLIGHT_CLUSTER=1` 再执行 `kubectl get nodes`
- `./scripts/verify.sh list --series 04 --exclude-noop` — 支持按主序列与 noop 过滤查看执行集合。
## 验证编排环境变量(可选)
复制 [`scripts/.env.verify.example`](.env.verify.example) 为 `scripts/.env.verify` 并填写本机值;**勿提交** `scripts/.env.verify`(已在仓库 `.gitignore` 中忽略)。
其中 **`ONECLOUD_SSH`** 用于矩阵里**集群外**第三方 curl 等;**`ARMV7_SSH` / `ARMV7_NFS_SSH`** 用于 `01-03` / `01-05` 实机;**`ACME_EMAIL`** 供 Traefik ACME`03-02` / `03-03`);另有 **`VERIFY_TLS_HOSTS`**、`K3S_SERVER_HOSTNAME``TIMEOUT_SEC``LONGHORN_NAMESPACE` 等,完整列表见 [`.env.verify.example`](.env.verify.example) 文末注释。
其中 **`ONECLOUD_SSH`** 用于**集群外**第三方 curl 等;**`SKIP_ARMV7` / `ARMV7_SSH` / `ARMV7_NFS_SSH`**(及 01-05 的 **`ARMV7_NFS_EXPORT_PATH`**、**`ARMV7_NFS_CLIENT_SUBNET`**)由 playbook 通过环境变量读取:`SKIP_ARMV7=1`(默认)时 `01-03`/`01-05` 仅基线检查;**`SKIP_ARMV7=0` 且 SSH 已配置** 时会对 arm 主机执行 dnf 路径Fedora/RHEL 系,见 `docs/00-05` §E**`ACME_EMAIL`** 供 Traefik ACME`03-02` / `03-03`);另有 **`VERIFY_TLS_HOSTS`**、`K3S_SERVER_HOSTNAME``TIMEOUT_SEC``LONGHORN_NAMESPACE` 等,完整列表见 [`.env.verify.example`](.env.verify.example) 文末注释。
```bash
set -a && source scripts/.env.verify && set +a
@@ -29,19 +34,23 @@ set -a && source scripts/.env.verify && set +a
## 部署 K3s推荐在控制节点或 Linux 工作机)
1. 在仓库根(或 `cd ansible` 后改用相对路径)执行 **`./scripts/deploy-lab.sh k3s`**。若需先准备数据盘,在 **本机或 `.env.verify`** 中设 `K3S_PREPARE_STORAGE=true`(会传 `-e k3s_prepare_storage=true``k3s-prepare-storage.yml`)。
2. 办公机无 Ansible 时,可 SSH 到 ylc61 再执行上述命令,或使用:
在仓库根(或 `cd ansible` 后改用相对路径)执行 **`./scripts/deploy-lab.sh k3s`**。若需先准备数据盘,在 **本机或 `.env.verify`** 中设 `K3S_PREPARE_STORAGE=true`(会传 `-e k3s_prepare_storage=true``ansible/playbooks/verify/01-06.yml`)。
**密钥与执行用户**`inventory.ini` 中私钥路径随执行用户变化;在目标节点上以非 root 用户执行时,注意私钥路径与 `ansible_user` 与文档一致。
## 验证run-all / full
**推荐一行**(在仓库根;需已安装 Ansible、[`ansible/inventory.ini`](../ansible/inventory.ini) 可达、`k3s_server` 可 ping集群与入口变量已按 [`00-04`](../docs/00-04-部署环境说明.md) 与 `.env.verify` 配好):
```bash
export K3S_PREPARE_STORAGE=false # 或 true
./scripts/ssh/run-phase2-k3s-on-ylc61-as-jack.sh
./scripts/verify.sh full
```
环境变量 **`LAB_REPO_ROOT`**(仅 SSH 包装脚本使用)可指定远端仓库路径,默认 `/home/jack/实验室建设`
`full` = `preflight` + `run-all`。若 `nginx_entry_base``nodejs_entry_base` 等未写入 `scripts/.env.verify`,可先 `export nginx_entry_base=http://<入口IP>` 再执行。仅跑用例、跳过 preflight 时用 `./scripts/verify.sh run-all``list/run-all/full` 均支持筛选参数:`--series <XX>``--id-regex <regex>``--exclude-noop``--require-teardown`
**密钥与执行用户**`inventory.ini` 中私钥路径随执行用户变化;在 ylc61 上以 `jack` 执行可避免解析到 `/root/.ssh/`
将准备项NFS、ACME、armv7、noop 文档等)补齐后再推进“已验证”,见 [`docs/00-04-待验证项-验证前准备.md`](../docs/00-04-待验证项-验证前准备.md)
## 验证矩阵run-all / 抽样 smoke
等价多行写法(与 `full` 相同):
```bash
./scripts/verify.sh preflight
@@ -49,20 +58,10 @@ export nginx_entry_base=http://192.168.2.61
./scripts/verify.sh run-all
```
从办公机经 SSH 在远端仓库根跑抽样用例:
```bash
./scripts/ssh/smoke-verify-matrix-on-ylc61.sh
```
可用 **`VERIFY_REPO_ROOT`** 指定远端路径(默认 `/home/jack/实验室建设`)。
## 目录与脚本对照
- **`verify.sh`** — 矩阵验收:`flow` / `preflight` / `list` / `run` / `run-all`
- **`verify.sh`** — doc_id 验收:`flow` / `preflight` / `full` / `list` / `run` / `run-all`
- **`deploy-lab.sh`** — 安装/铺栈:`k3s` / `longhorn` / `nginx-matrix` / `nginx-matrix-tls`
- **`ssh/run-phase2-k3s-on-ylc61-as-jack.sh`** — 办公机触发远端 `deploy-lab.sh k3s`
- **`ssh/smoke-verify-matrix-on-ylc61.sh`** — 办公机触发远端若干 `verify.sh run`
- **`k3s-delete-lab-stacks.sh`** — 按 kubectl 实况清命名空间负载(重度清场,非默认 teardown
- **`cloudflare-delete-acme-challenge-dns.sh`** — 清理 CF 上 `_acme-challenge` DNS
- **`ssh/setup-k3s-workers-ssh.sh`** — 为 K3s 节点预配 SSH配合 `01-06`
@@ -80,6 +79,6 @@ export nginx_entry_base=http://192.168.2.61
## 说明文档
- 验证框架:[`docs/00-05-测试与验证框架.md`](../docs/00-05-测试与验证框架.md)
- 验证矩阵状态[`docs/00-02-验证矩阵.md`](../docs/00-02-验证矩阵.md)
- 主文档入口:`docs/00-00-构建总览.md`(若存在)
- 验证框架:[`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md)
- 验证前准备[`docs/00-04-待验证项-验证前准备.md`](../docs/00-04-待验证项-验证前准备.md)
- 主文档入口:`docs/00-00-构建总览.md`

92
scripts/acceptance.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# 真机一键验收入口:可选铺栈 + 矩阵全量验收verify.sh full
# 默认:只做验收(不强制重装/重铺栈),避免误伤现有环境。
#
# 开关(环境变量):
# ACCEPT_DEPLOY=1 先执行 deploy-lab默认 0
# ACCEPT_DEPLOY_K3S=1 部署/复验 K3s默认 1仅在 ACCEPT_DEPLOY=1 时生效)
# ACCEPT_DEPLOY_LONGHORN=1 部署 Longhorn默认 0
# ACCEPT_DEPLOY_NGINX_MATRIX=1 部署 nginx 矩阵(默认 0
# ACCEPT_DEPLOY_NGINX_MATRIX_TLS=1 部署 TLS nginx 矩阵(默认 0
# VERIFY_TEARDOWN=1 验收后清理临时资源(沿用 verify.sh 默认;可设 0 保留现场)
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
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
}
usage() {
cat <<'EOF'
用法scripts/acceptance.sh
说明:
- 真机「一键验收」可选先铺栈deploy-lab再跑矩阵全量验收verify.sh full
- 默认不铺栈(避免误改现网);只执行 ./scripts/verify.sh full
常用示例:
# 只验收(推荐默认)
./scripts/acceptance.sh
# 先复验/安装 K3s再全量验收
ACCEPT_DEPLOY=1 ./scripts/acceptance.sh
# 铺栈K3s + Longhorn + nginx-matrix然后全量验收
ACCEPT_DEPLOY=1 ACCEPT_DEPLOY_LONGHORN=1 ACCEPT_DEPLOY_NGINX_MATRIX=1 ./scripts/acceptance.sh
# 验收不清理(保留现场排障)
VERIFY_TEARDOWN=0 ./scripts/acceptance.sh
EOF
}
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "[ERR] 未找到命令:$1" >&2
exit 1
fi
}
main() {
load_env
local cmd="${1:-}"
case "$cmd" in
"" ) ;;
-h|--help|help) usage; exit 0 ;;
*) echo "[ERR] unknown arg: $cmd" >&2; usage; exit 1 ;;
esac
need_cmd bash
need_cmd python3
need_cmd ansible-playbook
if [[ "${ACCEPT_DEPLOY:-0}" == "1" ]]; then
echo "########################################## deploy (optional)"
if [[ "${ACCEPT_DEPLOY_K3S:-1}" == "1" ]]; then
./scripts/deploy-lab.sh k3s
fi
if [[ "${ACCEPT_DEPLOY_LONGHORN:-0}" == "1" ]]; then
./scripts/deploy-lab.sh longhorn
fi
if [[ "${ACCEPT_DEPLOY_NGINX_MATRIX:-0}" == "1" ]]; then
./scripts/deploy-lab.sh nginx-matrix
fi
if [[ "${ACCEPT_DEPLOY_NGINX_MATRIX_TLS:-0}" == "1" ]]; then
./scripts/deploy-lab.sh nginx-matrix-tls
fi
fi
echo ""
echo "########################################## verify full (matrix)"
./scripts/verify.sh full
}
main "$@"

View File

@@ -1,14 +1,18 @@
#!/usr/bin/env bash
# 实验室「正式部署」入口(对应 docs/00-05 §2 步骤 13,与 verify.sh 的逐条验收 teardown 不同)。
# 实验室「正式部署」入口(对应 docs/00-04 §2 步骤 13
# 在仓库根执行:./scripts/deploy-lab.sh <子命令>
#
# 步骤对应关系(详见 docs/00-05-测试与验证框架.md §2
# 步骤对应关系(详见 docs/00-03-测试与验证框架.md §2
# 1 接入 — 本机有 ansible-playbook、inventory 可达;可选加载 scripts/.env.verify
# 2 前置 — 可选 k3s-prepare-storage磁盘 → /storage非 k3s-uninstall 类重度清理
# 3 部署 — k3s-init-and-install 或其它子命令所列 playbook
# 2 前置 — 可选 01-06-prepare-storage磁盘 → /storage非 k3s-uninstall 类重度清理
# 3 部署 — 调用 ansible/playbooks/verify/ 下 playbook默认 -e VERIFY_TEARDOWN=0铺栈后保留资源
# 需要验收后自动清理时,用 ./scripts/verify.sh run <doc_id>(默认 VERIFY_TEARDOWN=1
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib-ansible-lab.sh"
ansible_lab_export_config
load_env() {
if [[ -f "${ROOT}/scripts/.env.verify" ]]; then
@@ -25,17 +29,18 @@ usage() {
用法scripts/deploy-lab.sh <子命令>
子命令:
k3s 安装/复验 K3s可选先数据盘准备,见环境变量
longhorn Helm 安装 Longhornansible/playbooks/longhorn-install.yml
nginx-matrix 部署 HTTP nginx 矩阵ansible/playbooks/nginx-matrix-deploy.yml
nginx-matrix-tls 部署 TLS nginx 矩阵(ansible/playbooks/nginx-matrix-tls-deploy.yml
k3s 安装/复验 K3sverify/01-06.yml可选先数据盘准备)
longhorn Helm 安装 Longhorn + 健康检查verify/03-07.ymlVERIFY_TEARDOWN=0
nginx-matrix HTTP nginx 矩阵 + 校验verify/02-05.ymlVERIFY_TEARDOWN=0
nginx-matrix-tls TLS nginx 矩阵(verify/03-02.yml需显式 -e nginx_matrix_tls_enable=true
环境变量(节选,完整见 scripts/.env.verify.example
ANSIBLE_INVENTORY 默认 <仓库>/ansible/inventory.ini
K3S_PREPARE_STORAGE 为 true 时先执行 k3s-prepare-storage.yml并传 -e k3s_prepare_storage=true
ANSIBLE_INVENTORY 默认 <仓库>/ansible/inventory.ini
K3S_PREPARE_STORAGE 为 true 时在 01-06.yml 内启用准备数据盘(传 -e k3s_do_prepare_storage=true -e k3s_prepare_storage=true
DEPLOY_VERIFY_TEARDOWN 默认 0若设为 1则与 verify 子命令一并传入 Ansiblelonghorn/nginx-matrix 会执行卸载类 teardown
说明:
「矩阵级验收请用 ./scripts/verify.sh run / run-all本脚本只做安装/铺栈,不负责按 doc_id 做断言与 teardown
铺栈入口与验收入口共用 verify 下 playbook区别为本脚本固定默认 VERIFY_TEARDOWN=0。验收请用 ./scripts/verify.sh full / run <doc_id>
EOF
}
@@ -49,15 +54,17 @@ ansible_wrap() {
echo "[ERR] 未找到 ansible-playbook请先安装 Ansible" >&2
exit 1
fi
echo "[RUN] ansible-playbook -i $inv $*"
ansible-playbook -i "$inv" "$@"
ansible_lab_check_inventory_keys "$inv" || exit 1
local td="${DEPLOY_VERIFY_TEARDOWN:-0}"
echo "[RUN] ansible-playbook -i $inv -e VERIFY_TEARDOWN=$td $*"
ansible-playbook -i "$inv" -e "VERIFY_TEARDOWN=$td" "$@"
}
cmd_k3s() {
if [[ "${K3S_PREPARE_STORAGE:-false}" == "true" ]]; then
ansible_wrap "${ROOT}/ansible/playbooks/k3s-prepare-storage.yml" -e 'k3s_prepare_storage=true'
ansible_wrap "${ROOT}/ansible/playbooks/verify/01-06.yml" -e 'k3s_do_prepare_storage=true' -e 'k3s_prepare_storage=true'
fi
ansible_wrap "${ROOT}/ansible/playbooks/k3s-init-and-install.yml"
ansible_wrap "${ROOT}/ansible/playbooks/verify/01-06.yml" -e 'k3s_do_install=true'
}
main() {
@@ -66,9 +73,9 @@ main() {
case "$sub" in
""|-h|--help) usage ;;
k3s) cmd_k3s ;;
longhorn) ansible_wrap "${ROOT}/ansible/playbooks/longhorn-install.yml" ;;
nginx-matrix) ansible_wrap "${ROOT}/ansible/playbooks/nginx-matrix-deploy.yml" ;;
nginx-matrix-tls) ansible_wrap "${ROOT}/ansible/playbooks/nginx-matrix-tls-deploy.yml" ;;
longhorn) ansible_wrap "${ROOT}/ansible/playbooks/verify/03-07.yml" ;;
nginx-matrix) ansible_wrap "${ROOT}/ansible/playbooks/verify/02-05.yml" ;;
nginx-matrix-tls) ansible_wrap "${ROOT}/ansible/playbooks/verify/03-02.yml" -e 'nginx_matrix_tls_enable=true' ;;
*)
echo "[ERR] 未知子命令:$sub" >&2
usage

104
scripts/fix-04-doc-refs.py Normal file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""After 04-xx renumbering: fix cross-links and per-doc manifest names across the repo."""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
# Old canonical .md basename -> new basename (apply before per-file yaml fix)
MD_MAP: list[tuple[str, str]] = [
("04-11-nodejs-副本与滚动发布.md", "04-06-nodejs-副本与滚动发布.md"),
("04-10-nodejs-Ingress与Traefik.md", "04-07-nodejs-Ingress与Traefik.md"),
("04-09-nodejs-存储与卷.md", "04-11-nodejs-存储与卷.md"),
("04-08-nodejs-安全上下文.md", "04-10-nodejs-安全上下文.md"),
("04-07-nodejs-调度与亲和.md", "04-09-nodejs-调度与亲和.md"),
("04-06-nodejs-探针与健康检查.md", "04-05-nodejs-探针与健康检查.md"),
("04-05-nodejs-资源请求与限制.md", "04-08-nodejs-资源请求与限制.md"),
("04-04-nodejs-端口与Service.md", "04-02-nodejs-端口与Service.md"),
("04-03-nodejs-环境变量与配置注入.md", "04-04-nodejs-环境变量与配置注入.md"),
("04-02-nodejs-镜像与运行命令.md", "04-03-nodejs-镜像与运行命令.md"),
]
SKIP_DIR_NAMES = {".git", "node_modules", "logs"}
TEXT_SUFFIXES = {".md", ".yml", ".yaml", ".sh", ".txt", ".example"}
def iter_files():
for p in ROOT.rglob("*"):
if not p.is_file():
continue
if any(x in p.parts for x in SKIP_DIR_NAMES):
continue
if p.suffix.lower() not in TEXT_SUFFIXES and p.name not in (
".env.verify.example",
):
continue
yield p
def apply_md_map(content: str) -> str:
for old, new in MD_MAP:
content = content.replace(old, new)
return content
def fix_doc_manifests(content: str, doc_id: str) -> str:
return re.sub(
r"04-\d{2}-nodejs-demo\.yaml",
f"04-{doc_id}-nodejs-demo.yaml",
content,
)
def fix_title(content: str, title_body: str) -> str:
lines = content.splitlines()
if lines and lines[0].startswith("# "):
lines[0] = f"# {title_body}"
return "\n".join(lines) + ("\n" if content.endswith("\n") else "")
return content
def main() -> None:
for path in iter_files():
raw = path.read_text(encoding="utf-8")
new = apply_md_map(raw)
if path.parent.name == "docs" and re.match(r"04-\d{2}-", path.name):
m = re.match(r"04-(\d{2})-", path.name)
if m:
doc_id = m.group(1)
new = fix_doc_manifests(new, doc_id)
base = path.name.removesuffix(".md")
new = fix_title(new, base)
path.write_text(new, encoding="utf-8")
# verify playbooks: doc_filename must match renumbered docs
vf = ROOT / "ansible/playbooks/verify"
for yml in sorted(vf.glob("04-*.yml")):
m = re.match(r"04-(\d{2})\.yml$", yml.name)
if not m:
continue
nid = m.group(1)
text = yml.read_text(encoding="utf-8")
# find docs/04-NN-*.md in file after md_map would already be applied
dm = re.search(r'doc_filename:\s*"([^"]+)"', text)
if not dm:
continue
old_fn = dm.group(1)
if not old_fn.startswith(f"04-{nid}-"):
# pick any docs/04-NN-*.md with this NN
docs_dir = ROOT / "docs"
matches = list(docs_dir.glob(f"04-{nid}-*.md"))
if len(matches) == 1:
text = re.sub(
r'doc_filename:\s*"[^"]+"',
f'doc_filename: "{matches[0].name}"',
text,
count=1,
)
yml.write_text(text, encoding="utf-8")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,478 @@
#!/usr/bin/env python3
"""Generate cumulative 04-02..04-11 nodejs-demo YAML (Core→Plus→Pro doc order)."""
from pathlib import Path
from textwrap import dedent
DIR = Path(__file__).resolve().parents[1] / "labs/nodejs/manifests"
CM = dedent(
"""\
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
"""
).strip()
SVC_8080 = dedent(
"""\
apiVersion: v1 # Service API 版本
kind: Service # Service 资源
metadata: # Service 元信息
name: nodejs-demo # Service 名称
namespace: default # 命名空间
spec: # Service 规格
selector: # 选择后端 Pod
app: nodejs-demo # 选中 app=nodejs-demo
ports: # 端口映射
- port: 80 # Service 暴露端口
targetPort: 8080 # 转发到容器端口
"""
).strip()
ING_NODE = dedent(
"""\
apiVersion: networking.k8s.io/v1 # Ingress API 版本
kind: Ingress # Ingress 资源
metadata: # Ingress 元信息
name: nodejs-demo # Ingress 名称
namespace: default # 命名空间
annotations: # Traefik 注解
traefik.ingress.kubernetes.io/router.entrypoints: web # 使用 web(HTTP) 入口
spec: # Ingress 规则
rules: # 规则列表
- http: # HTTP 路由
paths: # 路径列表
- path: /node # 匹配路径前缀
pathType: Prefix # 前缀匹配
backend: # 后端目标
service: # 后端 Service
name: nodejs-demo # Service 名称
port: # Service 端口
number: 80 # 端口号
"""
).strip()
ING_HOST = dedent(
"""\
apiVersion: networking.k8s.io/v1 # Ingress API 版本
kind: Ingress # Ingress 资源
metadata: # Ingress 元信息
name: nodejs-demo # Ingress 名称
namespace: default # 命名空间
annotations: # Traefik 注解
traefik.ingress.kubernetes.io/router.entrypoints: web # 使用 web(HTTP) 入口
spec: # Ingress 规则
rules: # 规则列表
- host: app.example.local # 主机名匹配
http: # HTTP 路由
paths: # 路径列表
- path: /api # 匹配 API 路径前缀
pathType: Prefix # 前缀匹配
backend: # 后端目标
service: # 后端 Service
name: nodejs-demo # Service 名称
port: # Service 端口
number: 80 # 端口号
"""
).strip()
# 与 Deployment 模板中 ` ports:` 同级(勿对整段 dedent否则会剥掉缩进
PROBES = (
" livenessProbe: # 存活探针\n"
" httpGet: # HTTP 探测\n"
" path: / # 探测路径\n"
" port: 8080 # 探测端口\n"
" initialDelaySeconds: 3 # 初始延迟\n"
" periodSeconds: 10 # 探测周期\n"
" readinessProbe: # 就绪探针\n"
" httpGet: # HTTP 探测\n"
" path: / # 探测路径\n"
" port: 8080 # 探测端口\n"
" initialDelaySeconds: 2 # 初始延迟\n"
" periodSeconds: 5 # 探测周期\n"
)
RES = (
" resources: # 资源请求与限制\n"
" requests: # 最小资源请求\n"
" cpu: \"50m\" # 请求 CPU\n"
" memory: \"64Mi\" # 请求内存\n"
" limits: # 资源上限\n"
" cpu: \"500m\" # CPU 限制\n"
" memory: \"256Mi\" # 内存限制\n"
)
def main() -> None:
# 04-02: 01 + 仅改监听 8080无 ConfigMap
doc2 = dedent(
"""\
# 对应文档docs/04-02-nodejs-端口与Service.md
# 累积04-01 + 容器与 Service 改监听 8080与后续探针一致
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 1 # 副本数
selector: # Deployment 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18-alpine # Node.js 镜像
command: ["node", "-e", "require('http').createServer((req,res)=>res.end('Hello World from Node.js')).listen(8080)"] # 内联 HTTP 服务改监听 8080
ports: # 容器端口
- containerPort: 8080 # 应用监听端口
---
"""
) + SVC_8080 + "\n---\n" + ING_NODE + "\n"
# 04-03: + 固定镜像 tag、command/args与旧 04-02 等价,端口 8080
doc3 = dedent(
"""\
# 对应文档docs/04-03-nodejs-镜像与运行命令.md
# 累积04-02 + 固定镜像 tag、imagePullPolicy、command/args
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 1 # 副本数
selector: # Deployment 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # 固定 tag 的 Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略:本地有则不重复拉取
command: ["node"] # 主命令
args: # 命令参数
- "-e" # 执行内联脚本
- "require('http').createServer((req,res)=>res.end('Hello from pinned image')).listen(8080)" # Node.js 内联服务逻辑
ports: # 容器端口
- containerPort: 8080 # 应用监听端口
---
"""
) + SVC_8080 + "\n---\n" + ING_NODE + "\n"
# 04-04: + ConfigMap等同旧 04-04 主体)
doc4 = (
f"# 对应文档docs/04-04-nodejs-环境变量与配置注入.md\n"
f"# 累积04-03 + ConfigMap + 通过 env 注入 APP_MSG\n---\n{CM}\n---\n"
+ dedent(
"""\
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 1 # 副本数
selector: # Deployment 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
env: # 环境变量注入
- name: APP_MSG # 环境变量名
valueFrom: # 从资源引用取值
configMapKeyRef: # 从 ConfigMap key 读取
name: nodejs-demo-config # ConfigMap 名称
key: APP_MSG # ConfigMap 键名
command: # 启动命令
- node # 运行 node
- "-e" # 执行内联脚本
- | # 多行 JS 脚本(内部内容不改动)
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports: # 容器端口
- containerPort: 8080 # 应用监听端口
---
"""
)
+ SVC_8080
+ "\n---\n"
+ ING_NODE
+ "\n"
)
# 04-05: + 探针(无 resources
doc5 = (
f"# 对应文档docs/04-05-nodejs-探针与健康检查.md\n"
f"# 累积04-04 + livenessProbe/readinessProbe端口 8080路径 /\n---\n{CM}\n---\n"
+ dedent(
"""\
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 1 # 副本数
selector: # Deployment 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
env: # 环境变量注入
- name: APP_MSG # 环境变量名
valueFrom: # 从资源引用取值
configMapKeyRef: # 从 ConfigMap key 读取
name: nodejs-demo-config # ConfigMap 名称
key: APP_MSG # ConfigMap 键名
command: # 启动命令
- node # 运行 node
- "-e" # 执行内联脚本
- | # 多行 JS 脚本(内部内容不改动)
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports: # 容器端口
- containerPort: 8080 # 应用监听端口
"""
).rstrip()
+ "\n"
+ PROBES
+ "\n"
+ dedent(
"""\
---
"""
)
+ SVC_8080
+ "\n---\n"
+ ING_NODE
+ "\n"
)
# 04-06: + replicas:3 + RollingUpdateIngress 仍为 /node
doc6 = (
f"# 对应文档docs/04-06-nodejs-副本与滚动发布.md\n"
f"# 累积04-05 + replicas: 3 + RollingUpdatemaxSurge:1 maxUnavailable:0\n---\n{CM}\n---\n"
+ dedent(
"""\
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 3 # 副本数(高可用)
strategy: # 更新策略
type: RollingUpdate # 滚动更新
rollingUpdate: # 滚动更新参数
maxSurge: 1 # 更新时最多额外增加 1 个 Pod
maxUnavailable: 0 # 更新时不可用 Pod 数为 0
selector: # Pod 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
env: # 环境变量注入
- name: APP_MSG # 环境变量名
valueFrom: # 从资源引用取值
configMapKeyRef: # 从 ConfigMap key 读取
name: nodejs-demo-config # ConfigMap 名称
key: APP_MSG # ConfigMap 键名
command: # 启动命令
- node # 运行 node
- "-e" # 执行内联脚本
- | # 多行 JS 脚本(内部内容不改动)
const http=require('http');
const msg=process.env.APP_MSG||'no env';
http.createServer((q,s)=>s.end(msg)).listen(8080);
ports: # 容器端口
- containerPort: 8080 # 应用监听端口
"""
).rstrip()
+ "\n"
+ PROBES
+ "\n"
+ dedent(
"""\
---
"""
)
+ SVC_8080
+ "\n---\n"
+ ING_NODE
+ "\n"
)
# 04-07: Ingress host + /api
doc7 = doc6.replace(
"# 对应文档docs/04-06-nodejs-副本与滚动发布.md\n"
"# 累积04-05 + replicas: 3 + RollingUpdatemaxSurge:1 maxUnavailable:0\n",
"# 对应文档docs/04-07-nodejs-Ingress与Traefik.md\n"
"# 累积04-06 + Ingress 增加 host、path 改为 /api访问需 Host: app.example.local\n",
)
doc7 = doc7.replace("---\n" + ING_NODE + "\n", "---\n" + ING_HOST + "\n")
# 04-08: + resources
c8 = (
" ports: # 容器端口\n"
" - containerPort: 8080 # 应用监听端口\n"
)
c8r = (
" ports: # 容器端口\n"
" - containerPort: 8080 # 应用监听端口\n" + RES
)
doc8 = doc7.replace(
"# 对应文档docs/04-07-nodejs-Ingress与Traefik.md\n"
"# 累积04-06 + Ingress 增加 host、path 改为 /api访问需 Host: app.example.local\n",
"# 对应文档docs/04-08-nodejs-资源请求与限制.md\n"
"# 累积04-07 + resources.requests/limits\n",
).replace(c8, c8r)
# 04-09: + nodeSelector
doc9 = doc8.replace(
"# 对应文档docs/04-08-nodejs-资源请求与限制.md\n"
"# 累积04-07 + resources.requests/limits\n",
"# 对应文档docs/04-09-nodejs-调度与亲和.md\n"
"# 累积04-08 + nodeSelector默认 ylc62请改为本集群节点短主机名\n",
).replace(
" spec: # Pod 规格\n containers: # 容器列表\n",
" spec: # Pod 规格\n nodeSelector: # 调度到指定节点\n"
" kubernetes.io/hostname: ylc62 # 节点主机名(按实际修改)\n"
" containers: # 容器列表\n",
)
# 04-10: + securityContext + tmp volume
doc10 = doc9.replace(
"# 对应文档docs/04-09-nodejs-调度与亲和.md\n"
"# 累积04-08 + nodeSelector默认 ylc62请改为本集群节点短主机名\n",
"# 对应文档docs/04-10-nodejs-安全上下文.md\n"
"# 累积04-09 + pod securityContext.fsGroup、容器 securityContext、只读根、/tmp emptyDir\n",
).replace(
" spec: # Pod 规格\n nodeSelector: # 调度到指定节点\n"
" kubernetes.io/hostname: ylc62 # 节点主机名(按实际修改)\n"
" containers: # 容器列表\n",
" spec: # Pod 规格\n nodeSelector: # 调度到指定节点\n"
" kubernetes.io/hostname: ylc62 # 节点主机名(按实际修改)\n"
" securityContext: # Pod 级安全上下文\n"
" fsGroup: 1000 # 挂载卷文件组 ID\n"
" containers: # 容器列表\n",
)
doc10 = doc10.replace(
" - name: nodejs-demo # 容器名\n image: node:18.20-alpine # Node.js 镜像\n"
" imagePullPolicy: IfNotPresent # 拉取策略\n env:",
" - name: nodejs-demo # 容器名\n image: node:18.20-alpine # Node.js 镜像\n"
" imagePullPolicy: IfNotPresent # 拉取策略\n"
" securityContext: # 容器级安全上下文\n"
" allowPrivilegeEscalation: false # 禁止提权\n"
" runAsNonRoot: true # 强制非 root 运行\n"
" runAsUser: 1000 # 运行用户 UID\n"
" readOnlyRootFilesystem: true # 根文件系统只读\n"
" env:",
)
doc10 = doc10.replace(
" periodSeconds: 5 # 探测周期\n\n---\n",
" periodSeconds: 5 # 探测周期\n"
" volumeMounts: # 卷挂载\n"
" - name: tmp # 引用临时卷\n"
" mountPath: /tmp # 容器内临时目录\n"
" volumes: # 卷定义\n"
" - name: tmp # 临时卷名称\n"
" emptyDir: {} # 空目录卷Pod 生命周期内)\n\n---\n",
)
pvc = dedent(
"""\
apiVersion: v1 # PVC API 版本
kind: PersistentVolumeClaim # 持久卷声明
metadata: # PVC 元信息
name: nodejs-demo-data # PVC 名称
namespace: default # 命名空间
spec: # PVC 规格
accessModes: # 访问模式
- ReadWriteOnce # RWO同一时间仅单节点挂载读写
storageClassName: local-path # 存储类(按集群可改)
resources: # 资源请求
requests: # 配额请求
storage: 1Gi # 申请容量
---
"""
).strip()
doc11 = doc10.replace(
"# 对应文档docs/04-10-nodejs-安全上下文.md\n"
"# 累积04-09 + pod securityContext.fsGroup、容器 securityContext、只读根、/tmp emptyDir\n",
"# 对应文档docs/04-11-nodejs-存储与卷.md\n"
"# 累积04-10 + PVC nodejs-demo-data默认 storageClassName: local-path+ 挂载 /data\n",
)
doc11 = doc11.replace(
"---\n" + CM + "\n---\n",
"---\n" + pvc + "\n" + CM + "\n---\n",
1,
)
doc11 = doc11.replace(
" volumeMounts: # 卷挂载\n"
" - name: tmp # 引用临时卷\n"
" mountPath: /tmp # 容器内临时目录\n",
" volumeMounts: # 卷挂载\n"
" - name: tmp # 临时卷名称\n"
" mountPath: /tmp # 容器内临时目录\n"
" - name: data # 数据卷名称\n"
" mountPath: /data # 容器内数据目录\n",
)
doc11 = doc11.replace(
" volumes: # 卷定义\n - name: tmp # 临时卷名称\n"
" emptyDir: {} # 空目录卷Pod 生命周期内)\n",
" volumes: # 卷定义\n - name: tmp # 临时卷\n emptyDir: {} # 空目录卷\n"
" - name: data # 数据卷\n persistentVolumeClaim: # 卷来源为 PVC\n"
" claimName: nodejs-demo-data # 绑定 PVC 名称\n",
)
DIR.mkdir(parents=True, exist_ok=True)
(DIR / "04-02-nodejs-demo.yaml").write_text(doc2, encoding="utf-8")
(DIR / "04-03-nodejs-demo.yaml").write_text(doc3, encoding="utf-8")
(DIR / "04-04-nodejs-demo.yaml").write_text(doc4, encoding="utf-8")
(DIR / "04-05-nodejs-demo.yaml").write_text(doc5, encoding="utf-8")
(DIR / "04-06-nodejs-demo.yaml").write_text(doc6, encoding="utf-8")
(DIR / "04-07-nodejs-demo.yaml").write_text(doc7, encoding="utf-8")
(DIR / "04-08-nodejs-demo.yaml").write_text(doc8, encoding="utf-8")
(DIR / "04-09-nodejs-demo.yaml").write_text(doc9, encoding="utf-8")
(DIR / "04-10-nodejs-demo.yaml").write_text(doc10, encoding="utf-8")
(DIR / "04-11-nodejs-demo.yaml").write_text(doc11, encoding="utf-8")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,37 @@
# shellcheck shell=bash
# 仓库根 Ansible从任意 cwd 调用时仍使用 ansible/ansible.cfg如 host_key_checking=False
ansible_lab_export_config() {
export ANSIBLE_CONFIG="${ROOT}/ansible/ansible.cfg"
}
# 若 inventory 为各主机声明了 ansible_ssh_private_key_file则在本机检查文件存在避免 ssh 报 no such identity
ansible_lab_check_inventory_keys() {
local inv="$1"
local line path exp
[[ -f "$inv" ]] || return 0
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ansible_ssh_private_key_file=([^[:space:]]+) ]] || continue
path="${BASH_REMATCH[1]}"
exp="${path/#\~/$HOME}"
if [[ ! -f "$exp" ]]; then
echo "[ERR] SSH 私钥不存在:$expinventory 中为 $path" >&2
echo " 将密钥放到该路径并 chmod 600或改 ansible/inventory.ini 中的 ansible_ssh_private_key_file。" >&2
echo " 生成/分发可参考scripts/ssh/setup-k3s-workers-ssh.sh、docs/01-06-节点初始化-ansible-实践.md" >&2
return 1
fi
# OpenSSH 拒绝 group/other 可读的私钥(常见误为 0644须 600 或 400
local mode
mode=$(stat -c '%a' "$exp" 2>/dev/null) || mode=""
case "$mode" in
600|400) ;;
*)
echo "[ERR] SSH 私钥权限过宽(当前 ${mode:-?},须仅所有者可读):$exp" >&2
echo " 执行chmod 600 $exp" >&2
echo " 若需一次修正本仓库 inventory 中各节点密钥chmod 600 ~/.ssh/id_ed25519_k3s_192.168.2.61 ~/.ssh/id_ed25519_k3s_192.168.2.62 ~/.ssh/id_ed25519_k3s_192.168.2.63 ~/.ssh/id_ed25519_k3s_192.168.2.64" >&2
return 1
;;
esac
done < "$inv"
return 0
}

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""将 doc_id 解析为 verify playbook 绝对路径唯一真源ansible/playbooks/verify/<doc_id>.yml
历史上曾解析 labs/matrix-doc-playbooks.yml“验证矩阵”该概念已废弃。
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
def main() -> None:
if len(sys.argv) != 2:
print("用法: resolve_verify_playbook.py <doc_id>", file=sys.stderr)
sys.exit(2)
doc_id = sys.argv[1].strip()
if not doc_id:
sys.exit(2)
p = ROOT / "ansible" / "playbooks" / "verify" / f"{doc_id}.yml"
if not p.is_file():
print(f"ERR: playbook 不存在:{p}", file=sys.stderr)
sys.exit(2)
print(p.resolve())
if __name__ == "__main__":
main()

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env bash
# 在办公机执行:经 SSH 在控制节点 ylc61 上跑「K3s 安装/复验」部署流水线。
# 对应 docs/00-05 §2 步骤 1接入+ 3部署中的 k3s 路径;与 verify.sh 矩阵验收分离。
#
# 依赖:本机可 ssh ylc61BatchMode远端仓库路径存在且含 scripts/deploy-lab.sh。
set -euo pipefail
REMOTE_ROOT="${LAB_REPO_ROOT:-/home/jack/实验室建设}"
# 将当前 shell 中的 K3S_PREPARE_STORAGE 传到远端(未设置则默认 false
KS_PREP="${K3S_PREPARE_STORAGE:-false}"
exec ssh -o BatchMode=yes ylc61 bash -lc "
set -euo pipefail
cd '${REMOTE_ROOT}'
chmod +x scripts/deploy-lab.sh 2>/dev/null || true
if [[ -f scripts/.env.verify ]]; then
set -a
# shellcheck disable=SC1091
source scripts/.env.verify
set +a
fi
# 办公机传入的 K3S_PREPARE_STORAGE 覆盖远端 .env 中的默认
export K3S_PREPARE_STORAGE='${KS_PREP}'
./scripts/deploy-lab.sh k3s
"

View File

@@ -1,4 +1,9 @@
#!/usr/bin/env bash
# 为 inventory 中 k3s 各节点配置 SSH 密钥jack + root供 Ansible 使用。
#
# 依赖OpenSSHssh、ssh-keygen、scp可选 sshpass一次输入密码模式可选 puttygen仅当在交互中选择生成 .ppk 时)。
# 默认 **不需要** PuTTY在 Linux 工作机(如 ylc65上用 ssh/ansible 即可。只有需要在 **Windows 上用 PuTTY/Pageant** 加载私钥时,
# 才在提示「是否生成 PuTTY 私钥」时选 y此时需安装 puttygenFedora: dnf install puttyDebian: apt install putty-tools
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
@@ -41,11 +46,11 @@ gen_key_if_missing() {
ssh-keygen -t ed25519 -f "$key_path" -C "k3s-cluster" -N ""
chmod 600 "$key_path" 2>/dev/null || true
# 如选择生成 PuTTY 私钥,则调用 puttygen若命令不存在则给出安装提示并退出
# 仅当用户在交互中明确选择生成 .ppkWindows PuTTY时调用 puttygenLinux 默认不生成
if [[ "${GENERATE_PUTTY_PPK:-n}" == "y" ]]; then
if ! command -v puttygen >/dev/null 2>&1; then
echo "[ERR] 已选择生成 PuTTY 私钥,但当前系统未安装 puttygen。" >&2
echo " 请先安装 puttygen 后重新运行本脚本,或在提示时选不生成 PuTTY 私钥。" >&2
echo "[ERR] 已选择生成 PuTTY 私钥.ppk,但当前系统未安装 puttygen。" >&2
echo " 请先安装 putty-tools/putty 包后重试,或在提示时选不生成PuTTY 私钥OpenSSH 默认已够用)。" >&2
echo "" >&2
echo " 常见系统安装示例:" >&2
echo " Fedora / CentOS / RHEL : sudo dnf install putty 或 sudo dnf install putty-tools" >&2
@@ -121,7 +126,6 @@ copy_key_to_host() {
print_title "K3s 节点 SSH 密钥批量配置(控制节点 + 工作节点,每节点一把密钥)"
ensure_cmd ssh-keygen
ensure_cmd ssh-copy-id
ensure_cmd ssh
# 默认显示相对路径(相对于仓库根)
@@ -130,7 +134,7 @@ INVENTORY_PATH="$(ask_default "Ansible inventory 路径(相对仓库根 ${ROOT
[[ "$INVENTORY_PATH" != /* ]] && INVENTORY_PATH="${ROOT_DIR}/${INVENTORY_PATH}"
[[ ! -f "$INVENTORY_PATH" ]] && { echo "[ERR] 找不到 inventory: $INVENTORY_PATH" >&2; exit 1; }
# 交互输入:用户名(有默认值)、密码(可选,用于后续 SSH/sudo、是否生成 PuTTY 私钥
# 交互输入:用户名(有默认值)、密码(可选,用于后续 SSH/sudo、是否额外生成 PuTTY .ppk可选默认否
echo ""
echo "--- 交互输入用户名与密码 ---"
SSH_USER="$(ask_default "SSH 登录用户名(直接回车使用默认 jack" "$SSH_USER_DEFAULT")"
@@ -174,8 +178,8 @@ if [[ -n "${JACK_PASS:-}" ]]; then
fi
fi
# 是否同时为每把新生成的 OpenSSH 密钥生成一份 PuTTY 私钥(.ppk
GENERATE_PUTTY_PPK="$(ask_default "是否为新密钥同时生成 PuTTY 私钥(.ppk(y/N)" "N")"
# 是否同时为每把新生成的 OpenSSH 密钥生成一份 PuTTY 私钥(.ppkLinux/默认回车即可
GENERATE_PUTTY_PPK="$(ask_default "是否额外生成 PuTTY 私钥(.ppk,仅 Windows PuTTY 需要Linux 用 OpenSSH 请选 N[y/N]" "N")"
GENERATE_PUTTY_PPK="${GENERATE_PUTTY_PPK,,}" # 转小写
if [[ "$GENERATE_PUTTY_PPK" == "y" ]]; then
GENERATE_PUTTY_PPK="y"

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env bash
# 从办公机 Git Bash 执行SSH 到 ylc61 上跑若干样板 verify对应 docs/00-05 §2 步骤 46 的抽样)。
# 步骤 13 需已满足:远端仓库路径正确、已 deploy K3s、可选 scripts/.env.verify。
#
# 环境变量:
# VERIFY_REPO_ROOT 远端仓库根目录(默认 /home/jack/实验室建设)
# VERIFY_TEARDOWN / nginx_entry_base / nodejs_entry_base 传给远端 verify.sh
set -euo pipefail
REMOTE_ROOT="${VERIFY_REPO_ROOT:-/home/jack/实验室建设}"
TEARDOWN="${VERIFY_TEARDOWN:-1}"
NGX="${nginx_entry_base:-http://192.168.2.61}"
NODE="${nodejs_entry_base:-http://192.168.2.61}"
exec ssh -o BatchMode=yes ylc61 bash -lc "
set -euo pipefail
cd '${REMOTE_ROOT}'
chmod +x scripts/verify.sh 2>/dev/null || true
export VERIFY_TEARDOWN='${TEARDOWN}'
export nginx_entry_base='${NGX}'
export nodejs_entry_base='${NODE}'
if [[ -f scripts/.env.verify ]]; then
set -a
# shellcheck disable=SC1091
source scripts/.env.verify
set +a
fi
export VERIFY_TEARDOWN='${TEARDOWN}'
export nginx_entry_base='${NGX}'
export nodejs_entry_base='${NODE}'
./scripts/verify.sh run 02-05
./scripts/verify.sh run 03-05
./scripts/verify.sh run 03-07
./scripts/verify.sh run 04-01
"

65
scripts/test-all.sh Executable file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# 离线「全量」自检:与 CI 同源labs 索引、verify 清单校验)+
# 对关键 playbook 执行 ansible-playbook --syntax-check。
# 不连接集群、不执行 kubectl真机验收仍用 ./scripts/verify.sh full。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib-ansible-lab.sh"
ansible_lab_export_config
INV="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}"
need_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "[ERR] 未找到命令:$1" >&2
exit 1
fi
}
echo "########################################## 1/2 verify playbook ↔ docs 文件存在性"
need_cmd python3
python3 "${ROOT}/scripts/validate_matrix_playbooks.py"
echo ""
echo "########################################## 2/2 Ansible syntax-checkverify playbook"
need_cmd ansible-playbook
[[ -f "$INV" ]] || {
echo "[ERR] inventory 不存在:$INVsyntax-check 仍需 -i" >&2
exit 1
}
mapfile -t PBS < <(
ROOT="${ROOT}" python3 -c "
import os
from pathlib import Path
root = Path(os.environ['ROOT'])
pbs = []
for p in sorted((root / 'ansible' / 'playbooks' / 'verify').glob('*.yml')):
if p.name.startswith('_'):
continue
pbs.append(p.relative_to(root).as_posix())
seen = set()
for rel in pbs:
if rel in seen:
continue
seen.add(rel)
print(rel)
"
)
n=${#PBS[@]}
i=0
for rel in "${PBS[@]}"; do
i=$((i + 1))
pb="${ROOT}/${rel}"
echo "[$i/$n] -- $rel"
ansible-playbook -i "$INV" "$pb" --syntax-check
done
echo ""
echo "[OK] 全量离线检查通过(${n} 条 playbook syntax-check"

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""校验 verify playbook 清单(抛弃“验证矩阵”概念后的替代校验)。
规则(最小可用):
- ansible/playbooks/verify/ 目录下所有形如 XX-YY.yml 的文件,都必须存在对应 docs/XX-YY-*.md 文档
- 仅检查“存在性 + 1:1 对齐”,不解析 Markdown 内容
历史上本脚本用于校验 docs/00-03-验证矩阵.md ↔ labs/matrix-doc-playbooks.yml
该概念已废弃,但保留脚本名以减少 CI/用户习惯改动。
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
VERIFY_DIR = ROOT / "ansible" / "playbooks" / "verify"
DOCS_DIR = ROOT / "docs"
EXEC_ID_RE = re.compile(r"^(0[1-9]|[1-9][0-9])-(0[1-9]|[1-9][0-9])$")
def is_exec_domain(doc_id: str) -> bool:
return EXEC_ID_RE.fullmatch(doc_id) is not None
def main() -> None:
if not VERIFY_DIR.is_dir():
print(f"ERR: 缺少目录 {VERIFY_DIR}", file=sys.stderr)
sys.exit(2)
if not DOCS_DIR.is_dir():
print(f"ERR: 缺少目录 {DOCS_DIR}", file=sys.stderr)
sys.exit(2)
doc_ids: list[str] = []
invalid_verify_names: list[str] = []
for p in VERIFY_DIR.iterdir():
if p.is_file() and len(p.name) == len("00-00.yml") and p.name[2:3] == "-" and p.name[5:] == ".yml":
if is_exec_domain(p.stem):
doc_ids.append(p.stem)
else:
invalid_verify_names.append(p.name)
missing_docs: list[str] = []
missing_files_dir: list[str] = []
weak_doc_exec_refs: list[str] = []
for did in sorted(set(doc_ids)):
matches = sorted(DOCS_DIR.glob(f"{did}-*.md"))
if not matches:
missing_docs.append(did)
continue
doc = matches[0]
content = doc.read_text(encoding="utf-8", errors="ignore")
if f"ansible/files/{did}/" not in content and "```yaml" in content:
weak_doc_exec_refs.append(did)
expects_files_dir = (f"ansible/files/{did}/" in content) or ("```yaml" in content)
if expects_files_dir and not (ROOT / "ansible" / "files" / did).is_dir():
missing_files_dir.append(did)
if invalid_verify_names:
print(
f"ERR: verify 仅允许执行域命名XX>0 且 YY>0以下文件不合规: {sorted(invalid_verify_names)}",
file=sys.stderr,
)
sys.exit(2)
if missing_docs:
print(f"ERR: 存在 verify/<doc_id>.yml 但缺少 docs/<doc_id>-*.md: {missing_docs}", file=sys.stderr)
sys.exit(2)
if missing_files_dir:
print(f"ERR: 缺少 ansible/files/<doc_id>/ 目录: {missing_files_dir}", file=sys.stderr)
sys.exit(2)
if weak_doc_exec_refs:
print(
f"ERR: 文档包含 YAML 代码块但未引用 ansible/files/<doc_id>/ 真源: {weak_doc_exec_refs}",
file=sys.stderr,
)
sys.exit(2)
print(f"[OK] 执行域 verify/doc/files 一致性通过({len(sorted(set(doc_ids)))} 条)")
if __name__ == "__main__":
main()

208
scripts/verify.sh Normal file → Executable file
View File

@@ -1,15 +1,19 @@
#!/usr/bin/env bash
# 验证矩阵自动化入口(对应 docs/00-05 §2「自动化验证流程」步骤 46 的一键串联
# 4 断言 — 各 verify/XX-YY.yml 内 kubectl / curl / helm 等
# 5 收尾 — 默认 VERIFY_TEARDOWN=1 做本篇资源清理(非整集群卸载
# 6 串联 — run-all 按 docs/00-02-验证矩阵.md 顺序 fail-fast
# 验证入口(以 ansible/playbooks/verify/<doc_id>.yml 为唯一执行真源
# - run <XX-YY>:执行单篇验证 playbook
# - run-all按 verify 目录中存在的 <doc_id>.yml 顺序执行仅执行域XX>0 && YY>0
# - fullpreflight + run-all
#
# 步骤 13接入、环境/轻量清理、部署)由操作者或 scripts/deploy-lab.sh 完成;本脚本不执行 k3s-uninstall。
# 推荐在 Linux 工作机(如 ylc65或控制节点仓库根执行
# 说明:
# - 本脚本不再解析任何“矩阵/状态板”文档;验证清单从 verify playbook 自动得出
# - 步骤 13接入、环境/轻量清理、部署)由操作者或 scripts/deploy-lab.sh 完成;本脚本不执行 k3s-uninstall。
# - 推荐在 Linux 工作机或控制节点仓库根执行。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MATRIX_MD="${ROOT}/docs/00-02-验证矩阵.md"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib-ansible-lab.sh"
ansible_lab_export_config
# 默认与 §2 一致:验证后清理临时资源
export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}"
@@ -25,44 +29,82 @@ load_env() {
export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}"
}
parse_doc_ids_from_matrix() {
if [[ ! -f "${MATRIX_MD}" ]]; then
echo "[ERR] matrix 不存在:${MATRIX_MD}" >&2
exit 1
fi
# shellcheck disable=SC2016
awk '
match($0, /`[0-9][0-9]-[0-9][0-9]-[^`]+\.md`/) {
s = substr($0, RSTART+1, RLENGTH-2);
id = substr(s, 1, 5);
if (!seen[id]++) print id;
}
' "${MATRIX_MD}"
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<id>(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 <<EOF
与 docs/00-05-测试与验证框架.md §2「自动化验证流程一般步骤」对应:
与 docs/00-04-待验证项-验证前准备.md / docs/00-03-测试与验证框架.md 的「验证流程」对应:
1 接入目标环境 inventory + 仓库同步;可选 source scripts/.env.verify本脚本 run/run-all 会自动加载)
2 环境与前置清理 轻量:各 verify playbook 的 teardown重度 k3s 重装勿混进 run-all
3 部署 ./scripts/deploy-lab.sh k3s | longhorn | nginx-matrix* 或手工 ansible-playbook
4 断言 本脚本 run <XX-YY> / run-all → ansible/playbooks/verify/<XX-YY>.yml
5 收尾与记录 VERIFY_TEARDOWN矩阵状态见 docs/00-02-验证矩阵.md建议手工写回
6 一键串联 $0 run-all
4 断言 本脚本 run <XX-YY> / run-all → ansible/playbooks/verify/<doc_id>.yml
5 收尾与记录 VERIFY_TEARDOWN验证结论建议写回对应实验篇文档(或单独记录日志
6 一键串联 $0 full推荐 $0 run-all
相关脚本deploy-lab.sh安装/铺栈)、ssh/run-phase2-k3s-on-ylc61-as-jack.sh办公机触发远端 deploy k3s
相关脚本deploy-lab.sh安装/铺栈)
EOF
}
preflight() {
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 "${MATRIX_MD}" ]] || { echo "[ERR] 缺少验证矩阵:${MATRIX_MD}" >&2; exit 1; }
[[ -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
@@ -81,29 +123,52 @@ preflight() {
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
preflight 检查 ansible-playbook 与 inventory对 k3s_server 做 ping
若 VERIFY_PREFLIGHT_CLUSTER=1额外 kubectl get nodes未装集群会失败
list 从验证矩阵列出 doc_id顺序 run-all
run <XX-YY> 运行指定 doc_idansible/playbooks/verify/<XX-YY>.yml
run-all 按验证矩阵顺序运行全部 doc_idfail-fast
full 先 preflight再按 doc_id 顺序运行全部 verify= preflight + run-all,推荐
list [筛选参数] 列出可执行 doc_id仅执行域
run <XX-YY> 运行指定 doc_idansible/playbooks/verify/<doc_id>.yml
run-all [筛选参数] 按 doc_id 顺序运行 verify playbookfail-fast不做 preflight
筛选参数(可用于 list / run-all / full
--series <XX> 只运行某个主序列(例如 04
--id-regex <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_INVENTORY 默认 <仓库>/ansible/inventory.ini(其中 ansible_ssh_private_key_file 须在本机存在)
nginx_entry_base 例如 http://192.168.2.6102-xx / 03-02 等 HTTP 校验)
nodejs_entry_base 例如 http://192.168.2.6104-01
SKIP_ARMV7 默认 1为 0 时 01-03/01-05 若未配 ARMV7_SSH01-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矩阵验收请用本脚本。
与「部署」分工:安装 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
@@ -112,41 +177,88 @@ 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
if [[ ! -f "$pb_single" ]]; then
echo "[ERR] verify playbook 不存在fail-fast$pb_single" >&2
exit 1
fi
echo "[RUN] ansible-playbook -i $inv $pb_single"
ansible-playbook -i "$inv" "$pb_single"
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) preflight ;;
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_doc_ids_from_matrix
parse_filter_args "$@"
list_doc_ids_from_verify_dir "$series" "$id_regex" "$exclude_noop" "$require_teardown"
;;
run)
local doc_id="${2:?need doc_id like 02-05}"
local doc_id="${1:?need doc_id like 02-05}"
ansible_verify "$doc_id"
;;
run-all)
local id
while IFS= read -r id; do
echo ""
echo "########################################## $id"
ansible_verify "$id"
done < <(parse_doc_ids_from_matrix)
parse_filter_args "$@"
run_all_verify "$series" "$id_regex" "$exclude_noop" "$require_teardown"
;;
*)
echo "[ERR] unknown cmd: $cmd" >&2