对齐文件规范
This commit is contained in:
@@ -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 步骤 4~6)---
|
||||
# ./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 矩阵从第三方访问 Ingress;01-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}"
|
||||
|
||||
# --- Longhorn(03-07 / ansible longhorn-install)---
|
||||
# --- Longhorn(03-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}"
|
||||
|
||||
# --- armv7(01-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 / arm32(01-03 Docker、01-05 NFS、05-02 部分)---
|
||||
# 默认 SKIP_ARMV7=1:verify/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_INVENTORY;01-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>
|
||||
|
||||
@@ -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`) |
|
||||
| 4~5 断言与收尾 | 按 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
92
scripts/acceptance.sh
Executable 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 "$@"
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# 实验室「正式部署」入口(对应 docs/00-05 §2 步骤 1~3,与 verify.sh 的逐条验收 teardown 不同)。
|
||||
# 实验室「正式部署」入口(对应 docs/00-04 §2 步骤 1~3)。
|
||||
# 在仓库根执行:./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 安装 Longhorn(ansible/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 安装/复验 K3s(verify/01-06.yml;可选先数据盘准备)
|
||||
longhorn Helm 安装 Longhorn + 健康检查(verify/03-07.yml,VERIFY_TEARDOWN=0)
|
||||
nginx-matrix HTTP nginx 矩阵 + 校验(verify/02-05.yml,VERIFY_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 子命令一并传入 Ansible(longhorn/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
104
scripts/fix-04-doc-refs.py
Normal 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()
|
||||
478
scripts/gen-nodejs-demo-yaml.py
Normal file
478
scripts/gen-nodejs-demo-yaml.py
Normal 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 + RollingUpdate,Ingress 仍为 /node
|
||||
doc6 = (
|
||||
f"# 对应文档:docs/04-06-nodejs-副本与滚动发布.md\n"
|
||||
f"# 累积:04-05 + replicas: 3 + RollingUpdate(maxSurge: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 + RollingUpdate(maxSurge: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()
|
||||
37
scripts/lib-ansible-lab.sh
Normal file
37
scripts/lib-ansible-lab.sh
Normal 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 私钥不存在:$exp(inventory 中为 $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
|
||||
}
|
||||
29
scripts/resolve_verify_playbook.py
Normal file
29
scripts/resolve_verify_playbook.py
Normal 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()
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在办公机执行:经 SSH 在控制节点 ylc61 上跑「K3s 安装/复验」部署流水线。
|
||||
# 对应 docs/00-05 §2 步骤 1(接入)+ 3(部署)中的 k3s 路径;与 verify.sh 矩阵验收分离。
|
||||
#
|
||||
# 依赖:本机可 ssh ylc61(BatchMode);远端仓库路径存在且含 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
|
||||
"
|
||||
@@ -1,4 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# 为 inventory 中 k3s 各节点配置 SSH 密钥(jack + root),供 Ansible 使用。
|
||||
#
|
||||
# 依赖:OpenSSH(ssh、ssh-keygen、scp);可选 sshpass(一次输入密码模式);可选 puttygen(仅当在交互中选择生成 .ppk 时)。
|
||||
# 默认 **不需要** PuTTY:在 Linux 工作机(如 ylc65)上用 ssh/ansible 即可。只有需要在 **Windows 上用 PuTTY/Pageant** 加载私钥时,
|
||||
# 才在提示「是否生成 PuTTY 私钥」时选 y,此时需安装 puttygen(Fedora: dnf install putty;Debian: 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;若命令不存在则给出安装提示并退出
|
||||
# 仅当用户在交互中明确选择生成 .ppk(Windows PuTTY)时调用 puttygen;Linux 默认不生成
|
||||
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 私钥(.ppk);Linux/默认回车即可
|
||||
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"
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 从办公机 Git Bash 执行:SSH 到 ylc61 上跑若干样板 verify(对应 docs/00-05 §2 步骤 4~6 的抽样)。
|
||||
# 步骤 1~3 需已满足:远端仓库路径正确、已 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
65
scripts/test-all.sh
Executable 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-check(verify playbook)"
|
||||
need_cmd ansible-playbook
|
||||
[[ -f "$INV" ]] || {
|
||||
echo "[ERR] inventory 不存在:$INV(syntax-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)"
|
||||
84
scripts/validate_matrix_playbooks.py
Normal file
84
scripts/validate_matrix_playbooks.py
Normal 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
208
scripts/verify.sh
Normal file → Executable file
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# 验证矩阵自动化入口(对应 docs/00-05 §2「自动化验证流程」步骤 4~6 的一键串联):
|
||||
# 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)
|
||||
# - full:preflight + run-all
|
||||
#
|
||||
# 步骤 1~3(接入、环境/轻量清理、部署)由操作者或 scripts/deploy-lab.sh 完成;本脚本不执行 k3s-uninstall。
|
||||
# 推荐在 Linux 工作机(如 ylc65)或控制节点仓库根执行。
|
||||
# 说明:
|
||||
# - 本脚本不再解析任何“矩阵/状态板”文档;验证清单从 verify playbook 自动得出。
|
||||
# - 步骤 1~3(接入、环境/轻量清理、部署)由操作者或 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_id(ansible/playbooks/verify/<XX-YY>.yml)
|
||||
run-all 按验证矩阵顺序运行全部 doc_id(fail-fast)
|
||||
full 先 preflight,再按 doc_id 顺序运行全部 verify(= preflight + run-all,推荐)
|
||||
list [筛选参数] 列出可执行 doc_id(仅执行域)
|
||||
run <XX-YY> 运行指定 doc_id(ansible/playbooks/verify/<doc_id>.yml)
|
||||
run-all [筛选参数] 按 doc_id 顺序运行 verify playbook(fail-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.61(02-xx / 03-02 等 HTTP 校验)
|
||||
nodejs_entry_base 例如 http://192.168.2.61(04-01)
|
||||
SKIP_ARMV7 默认 1;为 0 时 01-03/01-05 若未配 ARMV7_SSH(01-05 可用 ARMV7_NFS_SSH)会失败
|
||||
ARMV7_SSH / ARMV7_NFS_SSH 一行 ssh 命令;与 SKIP_ARMV7=0 配合时 01-03/01-05 经 SSH 在 arm 上 dnf 安装(见 docs/00-07 §E)
|
||||
|
||||
与「部署」分工:安装 K3s / Longhorn / nginx 铺栈请用 ./scripts/deploy-lab.sh;矩阵验收请用本脚本。
|
||||
与「部署」分工:安装 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
|
||||
|
||||
Reference in New Issue
Block a user