日常更新

This commit is contained in:
2026-03-29 09:08:01 +08:00
parent 31709425e2
commit befdefd222
224 changed files with 7240 additions and 3297 deletions

View File

@@ -1,114 +0,0 @@
# 验证 / 编排脚本用环境变量模板
# ---------------------------------------------------------------------------
# 各变量与「未验证 / 部分验证」文档的对应关系、缺省时的行为,见 docs/00-07-待验证项-验证前准备.md
# 使用:复制为本目录下的 .env.verify勿提交 Git在仓库根执行
# set -a && source scripts/.env.verify && set +a
# 或在 bash 中source scripts/.env.verify
# 仅示例占位,请把 YOUR_* 换成真实值;密钥只留在本机 .env.verify。
# ---------------------------------------------------------------------------
# --- 集群与 kubectl ---
# 在控制节点本机跑 kubectl 时常用:
# export KUBECONFIG="${KUBECONFIG:-/etc/rancher/k3s/k3s.yaml}"
# 若在办公机通过 SSH 在远端执行 kubectl可设为
# export K3S_CONTROL_SSH="ssh -o BatchMode=yes ylc61"
# export REMOTE_KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
# 在控制节点本机执行 kubectl 时,避免再次经 SSH 自环(可按需保留):
# export VERIFY_FORCE_LOCAL_KUBECTL=1
# 经 SSH 在控制节点执行 kubectl 时:若 PATH 中无 kubectl可设 REMOTE_KUBECTL="k3s kubectl"
# export REMOTE_KUBECTL="k3s kubectl"
# 控制节点短主机名(与 inventory 一致;编排里拼 URL/SSH 用)
export K3S_SERVER_HOSTNAME="${K3S_SERVER_HOSTNAME:-ylc61}"
# 与 ansible group_vars 一致,验证磁盘/文档 00-04 时引用
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为 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
# --- SSH 密钥命名(与 scripts/ssh/test-ssh.sh 默认一致;脚本内尚为硬编码路径)---
# test-ssh 使用:$K3S_SSH_KEY_DIR/${K3S_SSH_KEY_PREFIX}<inventory主机名>
# 若你改用其他前缀,需同步改 test-ssh.sh 或仅用手工 ssh -i。
export K3S_SSH_KEY_DIR="${K3S_SSH_KEY_DIR:-$HOME/.ssh}"
export K3S_SSH_KEY_PREFIX="${K3S_SSH_KEY_PREFIX:-id_ed25519_k3s_}"
# setup-k3s-workers-ssh.sh 首次登录的非 root 用户名(交互默认 jack
export SSH_USER="${SSH_USER:-jack}"
# test-ssh.sh连接超时秒数脚本已支持环境变量 TIMEOUT_SEC
export TIMEOUT_SEC="${TIMEOUT_SEC:-5}"
# --- 矩阵验证docs/00-05 §2 步骤 46---
# ./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
# 与 K3s 节点 SSH 无关,但必须能免交互登录(建议 BatchMode + 已知的 IdentityFile
# 编排脚本应始终引用 ONECLOUD_SSH不要用「本机直接 curl」代替除非你明确改成本机。
export ONECLOUD_SSH="${ONECLOUD_SSH:-ssh -o BatchMode=yes onecloud}"
# 若需显式密钥,可写完整一行,例如:
# export ONECLOUD_SSH="ssh -o BatchMode=yes -i ~/.ssh/id_ed25519_onecloud onecloud"
# --- NFS03-06verify playbook 使用 NFS_SERVER_IP / NFS_EXPORT_PATH ---
export NFS_SERVER_HOST="${NFS_SERVER_HOST:-YOUR_NFS_IP_OR_HOSTNAME}"
export NFS_SERVER_IP="${NFS_SERVER_IP:-$NFS_SERVER_HOST}"
export NFS_EXPORT_PATH="${NFS_EXPORT_PATH:-/export/k3s}"
# export NFS_SSH="ssh -o BatchMode=yes root@${NFS_SERVER_HOST}"
# --- CloudflareAPI / Tunnel与 scripts/cloudflare-delete-acme-challenge-dns.sh 等一致)---
# DNS 脚本使用CF_API_TOKEN、ZONE_NAME 或 ZONE_ID
export CF_API_TOKEN="${CF_API_TOKEN:-}"
export ZONE_NAME="${ZONE_NAME:-jackadam.top}"
export ZONE_ID="${ZONE_ID:-}"
# Tunnel / Dashboard 等若需单独 token按需增加勿提交真实值
# export CF_TUNNEL_TOKEN=""
# export CF_ACCOUNT_ID=""
# --- ACME / Traefik03-02、03-03Let's Encrypt 注册邮箱 ---
# 与 HelmChartConfig / traefik-acme.yaml 中 <YOUR_REAL_EMAIL> 一致;编排或 sed 替换时引用 ACME_EMAIL。
export ACME_EMAIL="${ACME_EMAIL:-}"
# 文档中 traefik-acme 曾用 staging CA 调试1=使用测试 CA与 yaml 中 caserver 是否一致自行核对)
export ACME_CA_STAGING="${ACME_CA_STAGING:-0}"
export TRAEFIK_NAMESPACE="${TRAEFIK_NAMESPACE:-kube-system}"
# --- TLS 验证域名02-05 / 03-02 矩阵 curl、openssl s_client---
# 逗号分隔,与 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 verify/stack-longhorn-install---
export LONGHORN_NAMESPACE="${LONGHORN_NAMESPACE:-longhorn-system}"
# --- 可选跳过(编排占位;当前 verify.sh 未实现 HA/GitOps 门控时可忽略)---
export SKIP_HA="${SKIP_HA:-1}"
export SKIP_GITOPS="${SKIP_GITOPS:-1}"
# --- 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}"
# --- OpenWrt / 01-07与 K3s 四节点无关时单独用)---
# export OPENWRT_SSH="ssh -o BatchMode=yes root@192.168.x.x"
# export OPENWRT_HAPROXY_HTTP_PORT="18080"
# export OPENWRT_HAPROXY_HTTPS_PORT="18443"
# 01-07 文档中第三方 curl 用 --https-hosts 时的主机列表(逗号分隔,与 VERIFY_TLS_HOSTS 可相同)
# export OPENWRT_VERIFY_HTTPS_HOSTS="test01.jackadam.top,..."
# --- 与 scripts/*.sh 对照 ---
# 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
# 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>
# ssh/setup-k3s-workers-ssh.sh → 交互 inventory + SSH_USER可选一次性密码勿写入本文件

View File

@@ -1,16 +1,16 @@
# Scripts 总览
本目录集中维护通用运维脚本。约定:**在仓库根目录执行**,使用 `./scripts/...` 路径调用
本目录仅保留人工运维脚本。Ansible 执行入口已迁移到 `ansible/bin/`
流程说明与「部署 / 验证」分工以 [`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md) **§2 自动化验证流程** 为准;下表与之一一对应
流程说明与「部署 / 验证」分工以 [`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md) **§2 自动化验证流程** 为准;验证前准备清单见同文档 **§10**
| §2 步骤 | 含义 | 本仓库入口 |
|--------|------|------------|
| 1 接入 | inventory、仓库同步、加载 `.env.verify` | 手工;`verify.sh` / `deploy-lab.sh` 会自动 `source scripts/.env.verify`(若存在) |
| 1 接入 | inventory、仓库同步、加载 `.env.verify` | 手工;`ansible/bin/verify.sh` / `ansible/bin/deploy-lab.sh` 会自动 `source ansible/env/.env.verify`(若存在) |
| 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 一键串联 | 按 doc_id 顺序跑全部 verify可先 preflight | **`./scripts/verify.sh full`**(推荐,= preflight + run-all**`./scripts/verify.sh run-all`** |
| 3 部署 | K3s、Longhorn、nginx 矩阵等铺栈 | **`./ansible/bin/deploy-lab.sh`**`k3s` / `longhorn` / `nginx-matrix` / `nginx-matrix-tls` |
| 45 断言与收尾 | 按 doc 目标 kubectl/curl本篇 teardown | **`./ansible/bin/verify.sh`** `run` / `run-all`;简写 **`./scripts/cs <XX-YY>`**(与 `run <XX-YY>` 完全等价,覆盖 `verify/` 下全部执行域 doc_id |
| 6 一键串联 | 按 doc_id 顺序跑全部 verify可先 preflight | **`./ansible/bin/verify.sh full`**(推荐,= preflight + run-all**`./ansible/bin/verify.sh run-all`** |
真机一键验收(可选先铺栈再全量验收):
@@ -18,53 +18,55 @@
辅助命令:
- `./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 list --series 04 --exclude-noop` — 支持按主序列与 noop 过滤查看执行集合
- `./ansible/bin/scaffold-doc-id.sh <XX-YY> <slug> [--title ...]` — 新建执行域最小闭环:`docs/<XX-YY>-<slug>.md``ansible/files/<XX-YY>/``ansible/playbooks/verify/<XX-YY>.yml`(默认含 `verify_common` noop 基线)。可加 `--dry-run` / `--force`
- `./ansible/bin/verify.sh flow` — 打印与 §2 对齐的流程说明(不接 Ansible
- `./ansible/bin/verify.sh preflight` — 检查 `ansible-playbook` `inventory`,并对 `k3s_server` 执行 `ping`;若已装集群可设 `VERIFY_PREFLIGHT_CLUSTER=1` 再执行 `kubectl get nodes`
- `./ansible/bin/verify.sh list --series 04 --exclude-noop` — 支持按主序列与 noop 过滤查看执行集合。
## 验证编排环境变量(可选)
复制 [`scripts/.env.verify.example`](.env.verify.example) 为 `scripts/.env.verify` 并填写本机值;**勿提交** `scripts/.env.verify`(已在仓库 `.gitignore` 中忽略)。
复制 [`ansible/env/.env.verify.example`](../ansible/env/.env.verify.example) 为 `ansible/env/.env.verify` 并填写本机值;**勿提交** `ansible/env/.env.verify`(已在仓库 `.gitignore` 中忽略)。
其中 **`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) 文末注释。
其中 **`WORKSTATION_SSH`** 用于在 **Linux 工作机**(推荐 `ylc65`,见 `docs/00-02`)等 **非 k3s 节点** 上执行集群外 curl**`SKIP_ARMV7` / `ARMV7_SSH` / `ARMV7_NFS_SSH`**(及 01-04**`ARMV7_NFS_EXPORT_PATH`**、**`ARMV7_NFS_CLIENT_SUBNET`**)由 playbook 通过环境变量读取:`SKIP_ARMV7=1`(默认)时 `01-03`/`01-04` 仅基线检查;**`SKIP_ARMV7=0` 且 SSH 已配置** 时会对 arm 主机执行 dnf 路径Fedora/RHEL 系,见 `docs/00-03-测试与验证框架.md` §10.E。**`ACME_EMAIL`** 供 Traefik ACME`03-02` / `03-03`);另有 **`VERIFY_TLS_HOSTS`**、`K3S_SERVER_HOSTNAME``TIMEOUT_SEC``LONGHORN_NAMESPACE` 等,完整列表见 [`ansible/env/.env.verify.example`](../ansible/env/.env.verify.example) 文末注释。
```bash
set -a && source scripts/.env.verify && set +a
set -a && source ansible/env/.env.verify && set +a
```
## 部署 K3s推荐在控制节点或 Linux 工作机)
在仓库根(或 `cd ansible` 后改用相对路径)执行 **`./scripts/deploy-lab.sh k3s`**。若需先准备数据盘,在 **本机或 `.env.verify`** 中设 `K3S_PREPARE_STORAGE=true`(会传 `-e k3s_prepare_storage=true``ansible/playbooks/verify/01-06.yml`)。
在仓库根(或 `cd ansible` 后改用相对路径)执行 **`./ansible/bin/deploy-lab.sh k3s`**。若需先准备数据盘,在 **本机或 `.env.verify`** 中设 `K3S_PREPARE_STORAGE=true`(会传 `-e k3s_prepare_storage=true``ansible/playbooks/verify/01-05.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` 配好):
**推荐一行**(在仓库根;需已安装 Ansible、[`ansible/inventory.ini`](../ansible/inventory.ini) 可达、`k3s_server` 可 ping集群与入口变量已按 [`00-02-部署环境说明.md`](../docs/00-02-部署环境说明.md) 与 `.env.verify` 配好):
```bash
./scripts/verify.sh full
./ansible/bin/verify.sh full
```
`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`
`full` = `preflight` + `run-all`。若 `nginx_entry_base``nodejs_entry_base` 等未写入 `ansible/env/.env.verify`,可先 `export nginx_entry_base=http://<入口IP>` 再执行。仅跑用例、跳过 preflight 时用 `./ansible/bin/verify.sh run-all``list/run-all/full` 均支持筛选参数:`--series <XX>``--id-regex <regex>``--exclude-noop``--require-teardown`
将准备项NFS、ACME、armv7、noop 文档等)补齐后再推进“已验证”,见 [`docs/00-04-待验证项-验证前准备.md`](../docs/00-04-待验证项-验证前准备.md)
将准备项NFS、ACME、armv7、noop 文档等)补齐后再推进“已验证”,见 [`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md) **§10 验证前准备清单**
等价多行写法(与 `full` 相同):
```bash
./scripts/verify.sh preflight
./ansible/bin/verify.sh preflight
export nginx_entry_base=http://192.168.2.61
./scripts/verify.sh run-all
./ansible/bin/verify.sh run-all
```
## 目录与脚本对照
- **`verify.sh`** — doc_id 验收:`flow` / `preflight` / `full` / `list` / `run` / `run-all`
- **`cs`** — 单篇验收简写:`./scripts/cs <XX-YY>``verify.sh run <XX-YY>`(任意执行域 `doc_id` 通用,非 50 个独立脚本)
- **`deploy-lab.sh`** — 安装/铺栈:`k3s` / `longhorn` / `nginx-matrix` / `nginx-matrix-tls`
- **`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`
- **`ssh/setup-k3s-workers-ssh.sh`** — 为 K3s 节点预配 SSH配合 `01-05`
- **`ssh/test-ssh.sh`** — 验证 worker 密钥登录与 sudo
## 从仓库根执行示例
@@ -72,13 +74,15 @@ export nginx_entry_base=http://192.168.2.61
```bash
./scripts/ssh/setup-k3s-workers-ssh.sh
./scripts/ssh/test-ssh.sh
./scripts/deploy-lab.sh k3s
./scripts/verify.sh preflight
./scripts/verify.sh run 02-05
./scripts/offline-check.sh
./ansible/bin/deploy-lab.sh k3s
./ansible/bin/verify.sh preflight
./ansible/bin/verify.sh run 02-05
./scripts/cs 02-05
```
## 说明文档
- 验证框架:[`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md)
- 验证前准备:[`docs/00-04-待验证项-验证前准备.md`](../docs/00-04-待验证项-验证前准备.md)
- 验证前准备:[`docs/00-03-测试与验证框架.md`](../docs/00-03-测试与验证框架.md) **§10 验证前准备清单**
- 主文档入口:`docs/00-00-构建总览.md`

View File

@@ -14,12 +14,12 @@ set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
load_env() {
if [[ -f "${ROOT}/scripts/.env.verify" ]]; then
if [[ -f "${ROOT}/ansible/env/.env.verify" ]]; then
set -a
# shellcheck disable=SC1091
source "${ROOT}/scripts/.env.verify"
source "${ROOT}/ansible/env/.env.verify"
set +a
echo "[OK] 已加载 scripts/.env.verify"
echo "[OK] 已加载 ansible/env/.env.verify"
fi
}
@@ -29,7 +29,7 @@ usage() {
说明:
- 真机「一键验收」可选先铺栈deploy-lab再跑矩阵全量验收verify.sh full
- 默认不铺栈(避免误改现网);只执行 ./scripts/verify.sh full
- 默认不铺栈(避免误改现网);只执行 ./ansible/bin/verify.sh full
常用示例:
# 只验收(推荐默认)
@@ -70,22 +70,22 @@ main() {
if [[ "${ACCEPT_DEPLOY:-0}" == "1" ]]; then
echo "########################################## deploy (optional)"
if [[ "${ACCEPT_DEPLOY_K3S:-1}" == "1" ]]; then
./scripts/deploy-lab.sh k3s
./ansible/bin/deploy-lab.sh k3s
fi
if [[ "${ACCEPT_DEPLOY_LONGHORN:-0}" == "1" ]]; then
./scripts/deploy-lab.sh longhorn
./ansible/bin/deploy-lab.sh longhorn
fi
if [[ "${ACCEPT_DEPLOY_NGINX_MATRIX:-0}" == "1" ]]; then
./scripts/deploy-lab.sh nginx-matrix
./ansible/bin/deploy-lab.sh nginx-matrix
fi
if [[ "${ACCEPT_DEPLOY_NGINX_MATRIX_TLS:-0}" == "1" ]]; then
./scripts/deploy-lab.sh nginx-matrix-tls
./ansible/bin/deploy-lab.sh nginx-matrix-tls
fi
fi
echo ""
echo "########################################## verify full (matrix)"
./scripts/verify.sh full
./ansible/bin/verify.sh full
}
main "$@"

28
scripts/cs Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# 单篇验证简写:对任意执行域 doc_id 等价于 ./ansible/bin/verify.sh run <XX-YY>
# 用法:在仓库根执行 ./scripts/cs 02-05
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DOC_ID="${1:-}"
usage() {
echo "用法:./scripts/cs <XX-YY>" >&2
echo "说明:等价 ./ansible/bin/verify.sh run <XX-YY>,适用于 verify 目录内全部执行域 doc_id。" >&2
echo "列举:./ansible/bin/verify.sh list" >&2
echo "示例:./scripts/cs 02-05" >&2
}
if [[ -z "$DOC_ID" ]]; then
usage
exit 1
fi
if ! [[ "$DOC_ID" =~ ^(0[1-9]|[1-9][0-9])-(0[1-9]|[1-9][0-9])$ ]]; then
echo "[ERR] 非执行域 doc_id${DOC_ID}(须匹配 XX-YY且 XX、YY 为 0199 非零)" >&2
echo "[TIP] 导航页 YY=00 无 verify playbook请用具体分项如 01-05、02-05。" >&2
usage
exit 1
fi
exec "${ROOT}/ansible/bin/verify.sh" run "$DOC_ID"

View File

@@ -1,87 +0,0 @@
#!/usr/bin/env bash
# 实验室「正式部署」入口(对应 docs/00-04 §2 步骤 13
# 在仓库根执行:./scripts/deploy-lab.sh <子命令>
#
# 步骤对应关系(详见 docs/00-03-测试与验证框架.md §2
# 1 接入 — 本机有 ansible-playbook、inventory 可达;可选加载 scripts/.env.verify
# 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
set -a
# shellcheck disable=SC1091
source "${ROOT}/scripts/.env.verify"
set +a
echo "[OK] 已加载 scripts/.env.verify"
fi
}
usage() {
cat <<'EOF'
用法scripts/deploy-lab.sh <子命令>
子命令:
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 时在 01-06.yml 内启用准备数据盘(传 -e k3s_do_prepare_storage=true -e k3s_prepare_storage=true
DEPLOY_VERIFY_TEARDOWN 默认 0若设为 1则与 verify 子命令一并传入 Ansiblelonghorn/nginx-matrix 会执行卸载类 teardown
说明:
铺栈入口与验收入口共用 verify 下 playbook区别为本脚本固定默认 VERIFY_TEARDOWN=0。验收请用 ./scripts/verify.sh full / run <doc_id>。
EOF
}
ansible_wrap() {
local inv="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}"
if [[ ! -f "$inv" ]]; then
echo "[ERR] inventory 不存在:$inv" >&2
exit 1
fi
if ! command -v ansible-playbook >/dev/null 2>&1; then
echo "[ERR] 未找到 ansible-playbook请先安装 Ansible" >&2
exit 1
fi
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/verify/01-06.yml" -e 'k3s_do_prepare_storage=true' -e 'k3s_prepare_storage=true'
fi
ansible_wrap "${ROOT}/ansible/playbooks/verify/01-06.yml" -e 'k3s_do_install=true'
}
main() {
load_env
local sub="${1:-}"
case "$sub" in
""|-h|--help) usage ;;
k3s) cmd_k3s ;;
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
exit 1
;;
esac
}
main "$@"

View File

@@ -1,104 +0,0 @@
#!/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

@@ -1,478 +0,0 @@
#!/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

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# 按「集群里实际存在的资源」遍历删除(全部由 kubectl 发现,不读仓库 YAML 目录)
# 对应 docs/00-05 §2 步骤 2「重度清理」方向的实验室内清场非 verify.sh 默认 teardown亦非 k3s-uninstall
# 对应 docs/00-03 §2 步骤 2「重度清理」方向的实验室内清场非 verify.sh 默认 teardown亦非 k3s-uninstall
# 在任意目录执行均可;建议在仓库根:./scripts/k3s-delete-lab-stacks.sh [选项]
#
# 默认跳过系统命名空间kube-system、kube-public、kube-node-lease

View File

@@ -1,37 +0,0 @@
# 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,213 @@
[
{
"file": "docs/00-00-构建总览.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/00-03-测试与验证框架.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/01-02-k3s-工作节点.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/01-05-节点初始化-ansible-实践.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-01-k3s-traefik-dashboard.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-02-k3s-traefik-acme.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-03-k3s-traefik-dashboard-acme.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-04-k3s-cloudflare-tunnel-配置接入.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-05-k3s-local-path-pvc.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-06-k3s-使用nfs存储.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-07-k3s-longhorn-持久化存储.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/03-10-k3s-traefik-custom-ports.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-01-k3s-nodejs-高级部署.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-02-nodejs-端口与Service.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-03-nodejs-镜像与运行命令.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-04-nodejs-环境变量与配置注入.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-05-nodejs-探针与健康检查.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-06-nodejs-副本与滚动发布.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-07-nodejs-Ingress与Traefik.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-08-nodejs-资源请求与限制.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-09-nodejs-调度与亲和.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-10-nodejs-安全上下文.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-11-nodejs-存储与卷.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-12-nodejs-TLS与证书.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-13-nodejs-HPA.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/04-14-nodejs-GitOps与CI流水线.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-01-k3s-部署homer首页面板.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-02-onenav首页面板.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-03-k3s-安装gitlab-含runner.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-04-k3s-配置gitlab-cicd.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-06-openlist挂载网盘与自动备份.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-07-openclaw应用部署.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-08-openclaw-k3s-实验部署.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/05-09-openclaw-web-小游戏网页平台.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
},
{
"file": "docs/06-03-k3s-自动备份与恢复-openlist-webdav.md",
"rule": "docs_no_parent_links",
"expires": "2026-04-30",
"reason": "legacy ../ links; migrate to root-relative"
}
]

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env bash
# 离线「全量」自检:与 CI 同源(labs 索引、verify 清单校验)+
# 离线「全量」自检:与 CI 同源verify 清单校验)+
# 对关键 playbook 执行 ansible-playbook --syntax-check。
# 不连接集群、不执行 kubectl真机验收仍用 ./scripts/verify.sh full。
# 不连接集群、不执行 kubectl真机验收仍用 ./ansible/bin/verify.sh full。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck disable=SC1091
source "${ROOT}/scripts/lib-ansible-lab.sh"
source "${ROOT}/ansible/lib/lib-ansible-lab.sh"
ansible_lab_export_config
INV="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}"
@@ -18,12 +18,16 @@ need_cmd() {
fi
}
echo "########################################## 1/2 verify playbook ↔ docs 文件存在性"
echo "########################################## 1/3 docs 链接门禁R3禁止 ../,白名单需到期)"
need_cmd python3
python3 "${ROOT}/scripts/validate_matrix_playbooks.py"
python3 "${ROOT}/ansible/tools/check_docs_no_parent_links.py"
echo ""
echo "########################################## 2/2 Ansible syntax-checkverify playbook"
echo "########################################## 2/3 verify playbook ↔ docs 文件存在性"
python3 "${ROOT}/ansible/tools/validate_matrix_playbooks.py"
echo ""
echo "########################################## 3/3 Ansible syntax-checkverify playbook"
need_cmd ansible-playbook
[[ -f "$INV" ]] || {
echo "[ERR] inventory 不存在:$INVsyntax-check 仍需 -i" >&2
@@ -63,3 +67,4 @@ done
echo ""
echo "[OK] 全量离线检查通过(${n} 条 playbook syntax-check"

View File

@@ -1,29 +0,0 @@
#!/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()

0
scripts/ssh/setup-k3s-workers-ssh.sh Normal file → Executable file
View File

0
scripts/ssh/test-ssh.sh Normal file → Executable file
View File

42
scripts/status-board.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
usage() {
cat <<'EOF'
用法scripts/status-board.sh [命令]
命令:
render 仅渲染状态板(基于本地缓存/静态信息)
update 跑全量 verify 并写缓存(真机执行)
refresh update + render默认
说明:
- 状态板文件docs/00-04-验证状态板.md
- 本地缓存:.status/verify-results.json已在 .gitignore 忽略)
EOF
}
cmd="${1:-refresh}"
case "$cmd" in
-h|--help|help) usage; exit 0 ;;
render)
python3 "${ROOT}/ansible/tools/status_board.py" render
;;
update)
python3 "${ROOT}/ansible/tools/status_board.py" update --all
;;
refresh|"")
python3 "${ROOT}/ansible/tools/status_board.py" update --all
python3 "${ROOT}/ansible/tools/status_board.py" render
;;
*)
echo "[ERR] unknown cmd: $cmd" >&2
usage
exit 1
;;
esac
echo "[OK] status board updated: ${ROOT}/docs/00-04-验证状态板.md"

View File

@@ -1,84 +0,0 @@
#!/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()

View File

@@ -1,271 +0,0 @@
#!/usr/bin/env bash
# 验证入口(以 ansible/playbooks/verify/<doc_id>.yml 为唯一执行真源):
# - run <XX-YY>:执行单篇验证 playbook
# - run-all按 verify 目录中存在的 <doc_id>.yml 顺序执行仅执行域XX>0 && YY>0
# - fullpreflight + run-all
#
# 说明:
# - 本脚本不再解析任何“矩阵/状态板”文档;验证清单从 verify playbook 自动得出。
# - 步骤 13接入、环境/轻量清理、部署)由操作者或 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<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-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/<doc_id>.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 <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_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验收请用本脚本。
示例:
./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 "$@"