日常更新

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

@@ -3,3 +3,5 @@
host_key_checking = False
# 使用 inventory 同目录
inventory = inventory.ini
# 允许 include_role 解析仓库内角色(例如 verify_common
roles_path = roles

56
ansible/bin/deploy-lab.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck disable=SC1091
source "${ROOT}/ansible/lib/lib-ansible-lab.sh"
ansible_lab_export_config
load_env() {
if [[ -f "${ROOT}/ansible/env/.env.verify" ]]; then
set -a
# shellcheck disable=SC1091
source "${ROOT}/ansible/env/.env.verify"
set +a
echo "[OK] 已加载 ansible/env/.env.verify"
fi
}
usage() {
cat <<'EOF'
用法ansible/bin/deploy-lab.sh <子命令>
子命令k3s | longhorn | nginx-matrix | nginx-matrix-tls
EOF
}
ansible_wrap() {
local inv="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}"
[[ -f "$inv" ]] || { echo "[ERR] inventory 不存在:$inv" >&2; exit 1; }
command -v ansible-playbook >/dev/null 2>&1 || { echo "[ERR] 未找到 ansible-playbook" >&2; exit 1; }
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-05.yml" -e 'k3s_do_prepare_storage=true' -e 'k3s_prepare_storage=true'
fi
ansible_wrap "${ROOT}/ansible/playbooks/verify/01-05.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 "$@"

6
ansible/bin/scaffold-doc-id.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# 生成执行域 doc_id 最小骨架docs + ansible/files + verify playbook。参见 ansible/tools/scaffold_doc_id.py
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
exec python3 "$ROOT/ansible/tools/scaffold_doc_id.py" "$@"

304
ansible/bin/verify.sh Executable file
View File

@@ -0,0 +1,304 @@
#!/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
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# shellcheck disable=SC1091
source "${ROOT}/ansible/lib/lib-ansible-lab.sh"
ansible_lab_export_config
export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}"
STATUS_DIR="${ROOT}/.status"
TEARDOWN_STATE_JSON="${STATUS_DIR}/verify-teardown-state.json"
load_env() {
export ANSIBLE_CONFIG="${ANSIBLE_CONFIG:-${ROOT}/ansible/ansible.cfg}"
local td_override="${VERIFY_TEARDOWN-__unset__}"
if [[ -f "${ROOT}/ansible/env/.env.verify" ]]; then
set -a
# shellcheck disable=SC1091
source "${ROOT}/ansible/env/.env.verify"
set +a
echo "[OK] 已加载 ansible/env/.env.verify"
else
echo "[TIP] 未发现 ansible/env/.env.verify按默认变量继续"
fi
if [[ "${td_override}" != "__unset__" ]]; then
export VERIFY_TEARDOWN="${td_override}"
else
export VERIFY_TEARDOWN="${VERIFY_TEARDOWN:-1}"
fi
echo "[INFO] ANSIBLE_CONFIG=${ANSIBLE_CONFIG}"
}
record_teardown_state() {
mkdir -p "${STATUS_DIR}"
local td="${VERIFY_TEARDOWN:-1}"
local ts
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
cat > "${TEARDOWN_STATE_JSON}" <<EOF
{"updated_at":"${ts}","verify_teardown":${td}}
EOF
}
warn_teardown_mode() {
local td="${VERIFY_TEARDOWN:-1}"
if [[ "${td}" == "0" ]]; then
echo "[WARN] VERIFY_TEARDOWN=0保留现场模式已启用可能污染后续 full/run-all" >&2
echo "[TIP] 恢复建议:切回 VERIFY_TEARDOWN=1 并复跑主线;必要时手工清理残留命名空间/资源" >&2
echo "[OC] doc_id=preflight result=verified phase=preflight assertion=teardown_mode verify_teardown=0"
fi
if [[ -f "${TEARDOWN_STATE_JSON}" && "${td}" == "1" ]]; then
local last_td=""
last_td="$(TEARDOWN_STATE_JSON="${TEARDOWN_STATE_JSON}" python3 - <<'PY' 2>/dev/null || true
import json, os, pathlib
p = pathlib.Path(os.environ["TEARDOWN_STATE_JSON"])
try:
d = json.loads(p.read_text(encoding="utf-8"))
print(d.get("verify_teardown", ""))
except Exception:
pass
PY
)"
if [[ "${last_td}" == "0" ]]; then
echo "[WARN] 检测到上次验证使用 VERIFY_TEARDOWN=0当前虽为 1但可能存在残留污染" >&2
echo "[TIP] 建议VERIFY_TEARDOWN=1 ./ansible/bin/verify.sh full或 run-all以清理并回归" >&2
echo "[OC] doc_id=preflight result=verified phase=preflight assertion=teardown_state last_verify_teardown=0 current_verify_teardown=1"
fi
fi
}
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() {
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
}
run_preflight() {
local inv="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}"
oc_failed() {
# OC-like preflight line for humans/tools (minimal; stdout is source of truth).
local assertion="$1"
shift || true
echo "[OC] doc_id=preflight result=failed phase=preflight assertion=${assertion} $*"
}
oc_gated() {
local missing="$1"
local scope="$2"
echo "[OC] doc_id=preflight result=gated phase=preflight assertion=dependency_check missing_dependency=${missing} skip_scope=\"${scope}\""
}
need_cmd_or_fail() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "[ERR] 未找到命令:$cmd" >&2
oc_failed "missing_cmd" "missing_cmd=${cmd}"
exit 2
fi
}
need_cmd_or_fail ansible-playbook
need_cmd_or_fail ansible
warn_teardown_mode
record_teardown_state
[[ -f "$inv" ]] || { echo "[ERR] inventory 不存在:$inv" >&2; oc_failed "missing_inventory" "inventory=${inv}"; exit 2; }
ansible_lab_check_inventory_keys "$inv" || { oc_failed "inventory_keys" "inventory=${inv}"; exit 2; }
echo "[INFO] 变量边界inventory=$inv | group_vars=ansible/group_vars/all.yml | env=ansible/env/.env.verify"
echo "[INFO] 关键变量VERIFY_TEARDOWN=${VERIFY_TEARDOWN:-1} nginx_entry_base=${nginx_entry_base:-<unset>} nodejs_entry_base=${nodejs_entry_base:-<unset>}"
echo "[RUN] ansible k3s_server -m ping"
if ! ansible k3s_server -i "$inv" -m ping; then
echo "[ERR] ansible ping 失败k3s_server 不可达" >&2
oc_failed "ansible_ping" "target_group=k3s_server"
exit 2
fi
# Optional cluster-side check (may still fail-fast: control-side hard failure).
if [[ "${VERIFY_PREFLIGHT_CLUSTER:-0}" == "1" ]]; then
if ! ansible k3s_server -i "$inv" -b -m ansible.builtin.shell -a \
'KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get nodes'; then
echo "[ERR] kubectl 集群检查失败VERIFY_PREFLIGHT_CLUSTER=1" >&2
oc_failed "kubectl_get_nodes"
exit 2
fi
fi
# External dependencies: missing deps should not fail preflight (EC2) but must be explicit gated.
# We gate only the dependent scopes; runtime verify can still proceed for non-dependent doc_ids.
local gated=0
local missing_list=()
local scope_list=()
if [[ -z "${ACME_EMAIL:-}" ]]; then
gated=1; missing_list+=("acme"); scope_list+=("acme/tls issuance")
fi
# Epic 4Traefik ACME DNS-01 仅需 CF_API_TOKEN见 03-02 ensure secretZONE_* 不由 preflight 强门禁。
if [[ -z "${CF_API_TOKEN:-}" ]]; then
gated=1; missing_list+=("cloudflare"); scope_list+=("cloudflare api token / acme dns01")
fi
if [[ -z "${NFS_SERVER_IP:-}" || -z "${NFS_EXPORT_PATH:-}" ]]; then
gated=1; missing_list+=("nfs"); scope_list+=("nfs pv/pvc")
fi
if [[ -z "${WORKSTATION_SSH:-}" ]]; then
gated=1; missing_list+=("third_party_probe"); scope_list+=("third-party probe (WORKSTATION_SSH e.g. jack@ylc65)")
fi
if [[ "$gated" == "1" ]]; then
# Join arrays into readable strings.
local missing joined_scope
missing="$(IFS=,; echo "${missing_list[*]}")"
joined_scope="$(IFS='; '; echo "${scope_list[*]}")"
echo "[GATE] preflight external deps missing: ${missing} (scopes: ${joined_scope})"
oc_gated "${missing}" "${joined_scope}"
echo "[OK] preflight 通过带门控gated"
return 0
fi
echo "[OC] doc_id=preflight result=verified phase=preflight assertion=connectivity"
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'
用法ansible/bin/verify.sh <命令> [...]
命令flow | preflight | full | list | run <XX-YY> | run-all
筛选参数:--series <XX> | --id-regex <regex> | --exclude-noop | --require-teardown
EOF
}
print_flow() {
cat <<EOF
1 接入目标环境 inventory + 仓库同步;可选 source ansible/env/.env.verify
2 环境与前置清理 轻量:各 verify playbook 的 teardown
3 部署 ./ansible/bin/deploy-lab.sh k3s|longhorn|nginx-matrix*
4 断言 ./ansible/bin/verify.sh run <XX-YY> / run-all
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
echo "[OC] doc_id=${doc_id} result=failed phase=verify assertion=invalid_doc_id"
exit 1
fi
local inv="${ANSIBLE_INVENTORY:-${ROOT}/ansible/inventory.ini}"
local pb_single="${ROOT}/ansible/playbooks/verify/${doc_id}.yml"
[[ -f "$pb_single" ]] || { echo "[ERR] verify playbook 不存在:$pb_single" >&2; echo "[OC] doc_id=${doc_id} result=failed phase=verify assertion=missing_playbook"; exit 1; }
[[ -f "$inv" ]] || { echo "[ERR] inventory 不存在:$inv" >&2; echo "[OC] doc_id=${doc_id} result=failed phase=verify assertion=missing_inventory"; exit 1; }
local td="${VERIFY_TEARDOWN:-1}"
local run_log
run_log="$(mktemp)"
echo "[RUN] ansible-playbook -i $inv -e VERIFY_TEARDOWN=$td $pb_single"
if ansible-playbook -i "$inv" -e "VERIFY_TEARDOWN=$td" "$pb_single" 2>&1 | tee "$run_log"; then
if grep -q '\[GATE\]' "$run_log"; then
echo "[OC] doc_id=${doc_id} result=gated phase=verify assertion=playbook_gated"
else
# OC1: stable parse fields. OC3 evidence points to playbook output sections.
echo "[OC] doc_id=${doc_id} result=verified phase=verify assertion=playbook_success"
echo "[OC-EVIDENCE] doc_id=${doc_id} kind=cluster summary=\"see kubectl/assert output in playbook logs\""
echo "[OC-EVIDENCE] doc_id=${doc_id} kind=entry summary=\"see http/tls/assert output in playbook logs\""
fi
else
echo "[OC] doc_id=${doc_id} result=failed phase=verify assertion=playbook_failed"
rm -f "$run_log"
return 1
fi
rm -f "$run_log"
}
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:-}"; shift 2 ;;
--id-regex) id_regex="${2:-}"; 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; 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 "$@"

128
ansible/env/.env.verify.example vendored Normal file
View File

@@ -0,0 +1,128 @@
# 验证矩阵 / 编排脚本用环境变量模板example
# ---------------------------------------------------------------------------
# 约定本文件只写“值”KEY=VALUE不写默认展开/命令替换等执行逻辑。
#
# 使用(必须在仓库根目录执行):
# cp ansible/env/.env.verify.example ansible/env/.env.verify
# set -a && source ansible/env/.env.verify && set +a
# ---------------------------------------------------------------------------
# =========================
# 1) 你只需要改这一段(值)
# =========================
# --- SSH ---
# K3S_SSH_KEY_DIR存放 ssh key 的目录
K3S_SSH_KEY_DIR=$HOME/.ssh
# K3S_SSH_KEY_PREFIXkey 前缀(脚本会按此前缀拼接具体节点 key
K3S_SSH_KEY_PREFIX=id_ed25519_k3s_
# SSH_USERssh 登录用户名
SSH_USER=jack
# TIMEOUT_SEC常用探测/连接超时时间(秒)
TIMEOUT_SEC=5
# --- Ansible控制端---
# ANSIBLE_INVENTORYinventory 路径(相对仓库根);一般无需改
ANSIBLE_INVENTORY=ansible/inventory.ini
# ANSIBLE_LOCAL_TMP可选控制端无法写 ~/.ansible 时设为仓库内路径,如 $PWD/.ansible-tmp见 docs/00-03
# --- 01-01 / 01-02集群、kubectl 与 data-dirk3s共用---
# docs/01-01控制面与 docs/01-02工作节点加入共用此二项K3S_SERVER_HOSTNAME = 控制面短主机名inventory 中 k3s_server01-02 手工 K3S_URL 指向该节点 6443K3S_DATA_DIR = 各节点一致的 k3s --data-dir与 group_vars k3s_data_dir 一致。verify/01-01 以节点 kubeconfig 为准playbook 不强制从 env 读此二项。
K3S_SERVER_HOSTNAME=ylc61
K3S_DATA_DIR=/storage
# --- 01-03 / 01-04 armv7docs/01-03、01-04docs/00-03 §10.E---
# 可选SKIP_ARMV7默认 1 时 verify/01-03、01-04 仅矩阵基线0 时须配 ARMV7_SSH 等并由 playbook lookup
SKIP_ARMV7=1
# 01-03SKIP_ARMV7=0 时需 ARMV7_SSHverify 调用 ansible/tools/armv7-docker-verify-install.sh先 docker info失败再 get.docker.com
# 01-04SKIP_ARMV7=0 时需 ARMV7_NFS_SSH或同机ARMV7_NFS_EXPORT_PATH、ARMV7_NFS_CLIENT_SUBNET 见 verify/01-04.yml
ARMV7_SSH=YOUR_ARMV7_SSH
ARMV7_NFS_SSH=YOUR_ARMV7_NFS_SSH
ARMV7_NFS_EXPORT_PATH=/sdcard
ARMV7_NFS_CLIENT_SUBNET=192.168.2.0/24
# --- 01-05docs/01-05节点初始化与 k3s 安装)---
# K3S_PREPARE_STORAGEdeploy-lab.sh k3s 是否在首段跑「准备数据盘」ansible/bin/deploy-lab.sh 读此变量)
# K3S_DO_PREPARE_STORAGE / K3S_DO_INSTALLverify/01-05.yml 从环境读取(与 ansible-playbook -e 等价)。真正分区还须 group_vars k3s_prepare_storage: true 且 k3s_data_disk_device=/dev/xxx
K3S_PREPARE_STORAGE=false
K3S_DO_PREPARE_STORAGE=false
K3S_DO_INSTALL=false
# k3s 安装脚本镜像/超时:在 ansible/group_vars/all.yml 设 k3s_install_mirror: cn、k3s_install_curl_max_time 等(非本 env 变量);或 ansible-playbook … -e k3s_install_mirror=cn
# --- deploy-lab.sh铺栈docs/00-03 / project-context---
# DEPLOY_VERIFY_TEARDOWNdeploy-lab 传入 playbook 的 VERIFY_TEARDOWN默认 0保留已部署资源
DEPLOY_VERIFY_TEARDOWN=0
# --- 01-06 集群外探测 SSHdocs/01-06、00-03---
# WORKSTATION_SSH在 **Linux 工作机**上执行集群外 curl 等的一行 ssh示例 ylc65见 docs/00-02可改为 user@<你的工作机主机名>。含空格必须加引号。(旧名 ONECLOUD_SSH 已弃用)
WORKSTATION_SSH="ssh -o BatchMode=yes jack@ylc65"
# --- 验证入口与 preflight02-xx / 04-xx 共用)---
# nginx_entry_base、nodejs_entry_baseHTTP 验证入口基址(按 ingress/负载均衡填写。nginx 矩阵02-0102-04为集群内临时 Pod 直连 nginx-mX.default.svc。
# VERIFY_TEARDOWNverify.sh 默认 1清理现场调试可设 0保留现场可能污染后续用例
VERIFY_TEARDOWN=1
# VERIFY_PREFLIGHT_CLUSTER默认 0离线/轻量 preflight设 1 时 preflight 在控制节点执行 kubectl get nodes见 docs/00-03
VERIFY_PREFLIGHT_CLUSTER=0
nginx_entry_base=http://192.168.2.61
nodejs_entry_base=http://192.168.2.61
# --- 03-08 k3s HA可选·预留---
# 可选SKIP_HA当前 verify playbook 未 lookup仅与 docs/03-08 及 HA 类备忘对齐
SKIP_HA=1
# --- 03-09 GitOps可选·预留---
# 可选SKIP_GITOPS当前 verify playbook 未 lookup仅与 docs/03-09 备忘对齐
SKIP_GITOPS=1
# --- CloudflareCF API / Zone---
# 03-02 / 03-03ACME DNS-01 需 CF_API_TOKENZONE_* 为手工/Token 校验辅助preflight 不强门禁verify.sh 注释)
# CF_API_TOKENCloudflare API Token敏感信息
CF_API_TOKEN=YOUR_CF_API_TOKEN
# ZONE_NAME / ZONE_IDCloudflare Zone 信息可选预留playbook 未 lookup
ZONE_NAME=jackadam.top
ZONE_ID=YOUR_ZONE_ID
# --- 03-02 / 03-03 ACME 与 Traefik ---
# 03-02 / 03-03ACME_EMAIL 为 Let's Encrypt 注册邮箱verify 必填。ACME_CA_STAGING=1 使用 staging CA。
# TRAEFIK_NAMESPACE预留playbook 中 Traefik 固定 kube-system未读此 env
ACME_EMAIL=you@example.com
ACME_CA_STAGING=0
TRAEFIK_NAMESPACE=kube-system
# TRAEFIK_DASHBOARD_VERIFY_URL03-03 验收 Dashboard HTTP 探针完整 URL可选未设则按 k3s_server_ip/dashboard/
# TRAEFIK_DASHBOARD_VERIFY_URL=http://192.168.2.61/dashboard/
# --- TLS 域名(预留)---
# VERIFY_TLS_HOSTS手工 openssl/curl 对照用03-02 TLS 矩阵域名为清单内嵌playbook 未读此变量
VERIFY_TLS_HOSTS=test01.jackadam.top,test02.jackadam.top,test03.jackadam.top,test04.jackadam.top
# --- 03-02nginx 矩阵 TLS 可选)---
# 03-02NGINX_MATRIX_TLS_ENABLE=true 时部署 TLS+HTTP nginx 矩阵(改 default与 deploy-lab.sh nginx-matrix-tls 一致)。
NGINX_MATRIX_TLS_ENABLE=false
# --- 03-04 Cloudflare Tunneldocs/03-04---
# HTTPS 探针需 CF_TUNNEL_TEST_URL 或 CF_TUNNEL_TEST_HOST二选一皆缺则 [GATE]。TUNNEL_TOKEN 与集群 kube-system Secret cloudflared-credentials 二选一。
TUNNEL_TOKEN=YOUR_TUNNEL_TOKEN
# CF_TUNNEL_TEST_URL=https://your-tunnel-host.example.com/
CF_TUNNEL_TEST_HOST=traefik.jackadam.top
# CF_TUNNEL_CURL_INSECURE=1 — 03-04 探针 curl 使用 -k排障用
# --- 03-05 ---
# 03-05LOCAL_PATH_APPLY_LAB_CONFIG=true 时注入 local-path lab ConfigMap 并 rollout restart provisioner。
LOCAL_PATH_APPLY_LAB_CONFIG=false
# --- NFSdocs/03-06---
# verify/03-06.ymlNFS_SERVER_IP + NFS_EXPORT_PATH 均非空才跑 PV/PVC + Job任一为空则 [GATE]。NFS_SERVER_HOST 仅与文档/运维备注对齐playbook 不读取。
NFS_SERVER_HOST=onecloud
NFS_SERVER_IP=onecloud
NFS_EXPORT_PATH=/export/k3s
# --- Longhorn预留---
# LONGHORN_NAMESPACE与 docs/03-07 叙述对齐;命名空间以 playbook/group_vars 为准,当前 verify 未 lookup 此键
LONGHORN_NAMESPACE=longhorn-system
# --- 04-07 / 04-12 NodeJS TLSdocs/04-12、00-03---
# 04-12NODEJS_TLS_ENTRY_BASE + NODEJS_TLS_HOST 时跑 tls-openssl-sni + HTTPS 断言;无 Secret 时可 CREATE_NODEJS_DEMO_TLS_SECRET=1 自签。
# NODEJS_TLS_ENTRY_BASE=https://192.168.2.61:443
# NODEJS_TLS_HOST=app.example.local
# CREATE_NODEJS_DEMO_TLS_SECRET=1
# NODEJS_TLS_CURL_INSECURE=1

View File

@@ -0,0 +1,9 @@
# 01-01单控制节点安装
| 文件 | 说明 |
|------|------|
| `k3s-server-install.example.sh` | 默认或 `--data-dir=/storage` 安装片段备忘 |
- **手动**:在控制节点按 [docs/01-01-k3s-控制节点含traefik.md](../../../docs/01-01-k3s-控制节点含traefik.md) 执行;可与本目录示例对照。
- **自动**`./ansible/bin/verify.sh run 01-01`(专用 playbook`kubectl` 断言;与本目录 shell 示例共用真源路径)。
- 本篇**无** Kubernetes 应用清单;扩展名 `.sh` 不会进入 `kubectl apply --dry-run` 列表。

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# 控制节点安装 k3s server示例— 真源目录 ansible/files/01-01/
# 详见docs/01-01-k3s-控制节点含traefik.md
# 方案一:默认数据目录
# curl -sfL https://get.k3s.io | sh -
# 方案二:数据盘
# curl -sfL https://get.k3s.io | sh -s - server --data-dir=/storage
echo "[INFO] 取消注释其一并在节点上执行token 路径随方案在文档中说明。"

View File

@@ -0,0 +1,8 @@
# 01-02工作节点加入 + Traefik 基线)
| 文件 | 说明 |
|------|------|
| `k3s-agent-join.example.sh` | worker 使用环境变量或 `agent --data-dir` 加入集群的片段 |
- **手动**:按 [docs/01-02-k3s-工作节点.md](../../../docs/01-02-k3s-工作节点.md) 在 worker 执行;替换 `K3S_URL``TOKEN` 与 IP。
- **自动**`./ansible/bin/verify.sh run 01-02`(与手工步骤共用本目录作为命令真源备忘)。

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# 工作节点加入 k3s示例— ansible/files/01-02/
# 详见docs/01-02-k3s-工作节点.md
# 方案一:默认数据目录
# curl -sfL https://get.k3s.io | \
# K3S_URL=https://192.168.2.61:6443 \
# K3S_TOKEN=<TOKEN> \
# sh -
# 方案二:数据盘
# curl -sfL https://get.k3s.io | sh -s - agent \
# --data-dir=/storage \
# --server https://192.168.2.61:6443 \
# --token <TOKEN>
echo "[INFO] 将占位符替换为控制面地址与 token 后执行。"

View File

@@ -0,0 +1,6 @@
# 01-03armv7 独立 Docker
- **手动**:在 armv7 主机按 [docs/01-03-armv7-standalone-docker.md](../../../docs/01-03-armv7-standalone-docker.md)**get.docker.com** 官方脚本 + 先 `docker info` 再决定是否安装)。
- **远程脚本**:仓库根执行 [ansible/tools/armv7-docker-verify-install.sh](../../tools/armv7-docker-verify-install.sh)(与文档流程一致;`ARMV7_SSH='ssh …' ./ansible/tools/...`)。
- **自动**`./ansible/bin/verify.sh run 01-03``SKIP_ARMV7=0` 时调用上述脚本)。
- 本篇无通用 K8s 清单;若后续补充 compose 或单元脚本,请用 `.example.` 命名或放在非 `.yml`/`.yaml` 扩展名以避免误 dry-run。

View File

View File

@@ -0,0 +1,6 @@
# 01-05Ansible 一键装集群)
- **真源 playbook**`ansible/playbooks/verify/01-05.yml`(与 `deploy-lab.sh` 调用一致)。
- **文档**[docs/01-05-节点初始化-ansible-实践.md](../../../docs/01-05-节点初始化-ansible-实践.md)。
- **自动**`./ansible/bin/verify.sh run 01-05``./ansible/bin/deploy-lab.sh k3s`
- 本目录用于与 `doc_id` 三元契约对齐;集群对象由 playbook / 其它 `ansible/files` 篇生成,此处可不放置额外 YAML。

View File

@@ -0,0 +1,12 @@
# 01-06OpenWrt HAProxy
- **说明与选用**[`docs/01-06-openwrt-haproxy.md`](../../../docs/01-06-openwrt-haproxy.md)(文首有各 `*.cfg` 对照表)。
- **本目录**HAProxy 示例配置(非 K8s YAML复制到 OpenWrt 后改 IP/端口并 `haproxy -c -f …` 校验。
| 文件 | 用途摘要 |
|------|----------|
| `haproxy-no-check.cfg` | 最简,无 check |
| `haproxy-http.cfg` | 80 明文 httpchk |
| `haproxy-tls.cfg` | 443 TCP + ssl-hello-chk |
| `haproxy-https.cfg` | 443 应用层 HTTPS 检查HAProxy 终结 TLS |
| `haproxy-proxy-http-tls.cfg` | 检查 + PROXY v2 |

View File

@@ -1,6 +1,6 @@
# 01-07 HAProxy - 3.2 HTTP 健康检查80 明文)
# 01-06 HAProxy - 3.2 HTTP 健康检查80 明文)
# backend k3s_http 增加 option httpchk GET /
# 文档docs/01-07-openwrt-haproxy.md 第 3.2 节
# 文档docs/01-06-openwrt-haproxy.md 第 3.2 节
global
log /dev/log local0
maxconn 4096

View File

@@ -1,8 +1,8 @@
# 01-07 HAProxy - 3.4 HTTPS 健康检查443 应用层HAProxy 终结 TLS由 HAProxy 提供证书)
# 01-06 HAProxy - 3.4 HTTPS 健康检查443 应用层HAProxy 终结 TLS由 HAProxy 提供证书)
# frontend 需 bind *:443 sslbackend mode http 连 K3s:443 做 HTTP over TLS 检查
# 将 your-ingress.example.com 改为实际 Host将 /etc/ssl/haproxy.pem 改为实际证书路径
# 自签/内网 CA 用 verify none生产建议 ca-file
# 文档docs/01-07-openwrt-haproxy.md 第 3.4 节
# 文档docs/01-06-openwrt-haproxy.md 第 3.4 节
global
log /dev/log local0
maxconn 4096

View File

@@ -1,5 +1,5 @@
# 01-07 OpenWrt HAProxy 负载均衡 - 原生最简(无健康检查)
# 文档docs/01-07-openwrt-haproxy.md 第 2 节
# 01-06 OpenWrt HAProxy 负载均衡 - 原生最简(无健康检查)
# 文档docs/01-06-openwrt-haproxy.md 第 2 节
# 将 192.168.2.6164 按实际 K3s 节点 IP 修改
# 如需健康检查,见第 3 节对应 cfg
global

View File

@@ -1,6 +1,6 @@
# 01-07 HAProxy - 健康检查升级HTTP+TLS+ PROXY Protocol
# 01-06 HAProxy - 健康检查升级HTTP+TLS+ PROXY Protocol
# 组合k3s_http 用 option httpchkk3s_https 用 ssl-hello-chk均带 send-proxy-v2
# 文档docs/01-07-openwrt-haproxy.md 第 5 节「健康检查与 PROXY 组合」
# 文档docs/01-06-openwrt-haproxy.md 第 5 节「健康检查与 PROXY 组合」
global
log /dev/log local0
maxconn 4096

View File

@@ -1,6 +1,6 @@
# 01-07 HAProxy - 3.3 TLS 健康检查443 握手mode tcp
# 01-06 HAProxy - 3.3 TLS 健康检查443 握手mode tcp
# backend k3s_https 增加 option ssl-hello-chk
# 文档docs/01-07-openwrt-haproxy.md 第 3.3 节
# 文档docs/01-06-openwrt-haproxy.md 第 3.3 节
global
log /dev/log local0
maxconn 4096

View File

@@ -0,0 +1,9 @@
# 01-07 双控制节点 HA手工演练为主
本目录用于满足 `doc_id=01-07` 的真源目录一致性约束。
当前 `01-07` 主要是手工 runbook切换/演练类),自动验证入口为:
- `ansible/playbooks/verify/01-07.yml`(文档存在性与说明提示)
如后续将 01-07 演练步骤自动化,可在本目录新增对应清单与配置文件。

View File

@@ -0,0 +1,101 @@
# 02-05: Nginx + 控制节点 + IngressM1
# 路径 /demo-m1随机一台控制节点nodeSelector + toleration控制节点常有 NoSchedule 污点)
# ConfigMap首页 + default.conf单文件 subPath 挂载,与 M2M4 一致,便于 nginx 后续扩展)
---
apiVersion: v1 # ConfigMap 使用的 API 版本
kind: ConfigMap # 配置资源类型ConfigMap
metadata: # 对该 ConfigMap 的标识信息
name: nginx-m1-html # ConfigMap 名称
namespace: default # 命名空间
data: # ConfigMap 数据键值区
index.html: | # HTML 内容:会挂载到 nginx 的网页目录
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M1</title></head>
<body><h1>M1</h1><p>控制节点 + Ingress</p><p><strong>Backend: M1</strong></p></body></html>
default.conf: | # nginx 配置:通过 subPath 单文件挂载到 conf.d/default.conf
server { listen 80 default_server; server_name _; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M1"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1 # Deployment 使用的 API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 标识信息
name: nginx-m1 # Deployment 名称
namespace: default # 部署命名空间
labels: # 额外标签(用于检索/筛选)
app: nginx-m1 # 应用标签
matrix: "02-05-m1" # 矩阵编号标签(用于你后续调试/统计)
spec: # Deployment 期望状态
replicas: 1 # 副本数:本例为 1便于对应路径验证
selector: # Deployment 用于选择 Pod 的条件
matchLabels: # 标签匹配集合(用于选中模板 Pod
app: nginx-m1 # 必须与 template.metadata.labels 对上
template: # Pod 模板
metadata: # Pod 的元信息
labels: # Pod 标签
app: nginx-m1 # Pod 标签
spec: # Pod 规范
nodeSelector: # 节点选择:固定跑在 control-plane 上
node-role.kubernetes.io/control-plane: "" # 选择带 control-plane 角色标签的节点
tolerations: # 容忍污点:让 Pod 能调度到 control-plane
- key: node-role.kubernetes.io/control-plane # 污点 key
operator: Exists # 存在即匹配
effect: NoSchedule # 匹配 NoSchedule 污点效果
volumes: # Pod 内卷定义
- name: html # 卷名:给 volumeMounts 引用
configMap: # 卷来源ConfigMap
name: nginx-m1-html # 引用的 ConfigMap 名称
containers: # 容器列表
- name: nginx # 容器名
image: nginx:alpine # nginx 镜像
ports: # 容器端口列表
- containerPort: 80 # nginx HTTP 端口
volumeMounts: # 容器内挂载点列表
- name: html # 对应 volumes[].name
mountPath: /usr/share/nginx/html/index.html # 挂载到网页文件路径
subPath: index.html # 从 ConfigMap 里选取单个 key
readOnly: true # 只读挂载(配置文件更安全)
- name: html # 第二处也使用同一个卷
mountPath: /etc/nginx/conf.d/default.conf # nginx 配置文件路径
subPath: default.conf # 从 ConfigMap 里选取对应 key
readOnly: true # 只读挂载
---
apiVersion: v1 # Service 使用的 API 版本
kind: Service # 网络抽象:把 Pod 暴露成稳定访问入口
metadata: # Service 标识
name: nginx-m1 # Service 名称
namespace: default # Service 所在命名空间
spec: # Service 期望状态
selector: # Service 按标签选择后端 Pod
app: nginx-m1 # 选择 nginx-m1 Pod
ports: # Service 端口映射
- port: 80 # Service 端口
targetPort: 80 # 转发到 Pod 的端口
---
apiVersion: traefik.io/v1alpha1 # Traefik Middleware 使用的 API 版本
kind: Middleware # 路由中间件stripPrefix
metadata: # Middleware 标识
name: stripprefix-m1 # Middleware 名称
namespace: default # 命名空间
spec: # Middleware 配置
stripPrefix: # 去掉前缀
prefixes: # 要剔除的前缀列表
- /demo-m1 # 本矩阵的路径前缀
---
apiVersion: networking.k8s.io/v1 # Ingress 使用的 API 版本
kind: Ingress # 入口资源:把路径转发到 Service
metadata: # Ingress 标识
name: nginx-m1 # Ingress 名称
namespace: default # 命名空间
annotations: # Ingress 注解Traefik 用来绑定中间件
traefik.ingress.kubernetes.io/router.middlewares: default-stripprefix-m1@kubernetescrd # 绑定 stripprefix-m1
spec: # Ingress 规则
rules: # 规则列表
- http: # HTTP 规则
paths: # 路径匹配列表
- path: /demo-m1 # 匹配路径
pathType: Prefix # 前缀匹配类型
backend: # 后端目标
service: # 后端 Service
name: nginx-m1 # Service 名
port: # Service 端口
number: 80 # 端口号

View File

@@ -0,0 +1,6 @@
# 02-01nginx 分课)
本目录 YAML 与 `ansible/files/02-05/` 中对应课节清单为**同构副本**,专供本 `doc_id` 解耦学习与手工复制。
- **手动**:将清单拷到目标机路径,按文档改字段后执行 `kubectl` / bash不必使用 verify
- **自动**`./ansible/bin/verify.sh run 02-01`

View File

@@ -0,0 +1,95 @@
# 03-02: Nginx + 控制节点 + IngressRouteM2
# 路径 /demo-m2指定一台控制节点按实际 FQDN 修改 kubernetes.io/hostname
# ConfigMap首页 + default.confX-Backend: M2 便于区分
---
apiVersion: v1 # ConfigMap 使用的 API 版本
kind: ConfigMap # 配置资源类型ConfigMap
metadata: # ConfigMap 标识信息
name: nginx-m2-html # ConfigMap 名称
namespace: default # 命名空间
data: # ConfigMap 数据区
index.html: | # HTML 内容:会挂载到 nginx 的网页目录
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M2</title></head>
<body><h1>M2</h1><p>控制节点 + IngressRoute</p></body></html>
default.conf: | # nginx 配置:通过 subPath 单文件挂载到 conf.d/default.conf
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M2"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1 # Deployment 使用的 API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 标识信息
name: nginx-m2 # Deployment 名称
namespace: default # 部署命名空间
labels: # 标签集合
app: nginx-m2 # 应用标签
matrix: "02-05-m2" # 矩阵编号标签
spec: # Deployment 期望状态
replicas: 1 # 副本数:单副本便于验证
selector: # Deployment 选择 Pod
matchLabels: # 标签匹配集合(用于选中模板 Pod
app: nginx-m2 # 必须与 template.metadata.labels 对上
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nginx-m2 # Pod 标签
spec: # Pod 规范
nodeSelector: # 固定调度节点(按实际修改)
kubernetes.io/hostname: ylc61 # 目标节点主机名
volumes: # 卷定义
- name: html # 卷名
configMap: # 卷来源为 ConfigMap
name: nginx-m2-html # 引用的 ConfigMap 名称
containers: # 容器列表
- name: nginx # 容器名
image: nginx:alpine # nginx 镜像
ports: # 容器端口声明
- containerPort: 80 # nginx 监听端口
volumeMounts: # 容器内挂载点
- name: html # 对应 volumes[].name
mountPath: /usr/share/nginx/html/index.html # 挂到网页文件
subPath: index.html # 使用 ConfigMap 的 index.html key
readOnly: true # 配置只读挂载
- name: html # 第二处配置仍复用该卷
mountPath: /etc/nginx/conf.d/default.conf # 挂到 nginx 配置文件
subPath: default.conf # 使用 ConfigMap 的 default.conf key
readOnly: true # 只读挂载
---
apiVersion: v1 # Service 使用的 API 版本
kind: Service # 网络抽象:为 Pod 提供稳定访问地址
metadata: # Service 标识
name: nginx-m2 # Service 名称
namespace: default # 命名空间
spec: # Service 期望状态
selector: # 通过标签选择后端 Pod
app: nginx-m2 # 选择 app 标签为 nginx-m2 的 Pod
ports: # Service 端口映射
- port: 80 # Service 暴露端口
targetPort: 80 # 转发到 Pod 容器端口
---
apiVersion: traefik.io/v1alpha1 # Traefik Middleware 使用的 API 版本
kind: Middleware # 中间件类型stripPrefix
metadata: # Middleware 标识
name: stripprefix-m2 # Middleware 名称
namespace: default # 命名空间
spec: # 中间件配置
stripPrefix: # 去掉路径前缀
prefixes: # 需要剔除的前缀列表
- /demo-m2 # 本矩阵的路径前缀
---
apiVersion: traefik.io/v1alpha1 # IngressRoute 的 API 版本
kind: IngressRoute # 路由资源类型
metadata: # IngressRoute 标识
name: nginx-m2 # 路由名称
namespace: default # 命名空间
spec: # 路由规则
entryPoints: # Traefik 入口点列表
- web # 使用 web entrypoint
routes: # 路由列表
- match: PathPrefix(`/demo-m2`) # 匹配 /demo-m2 前缀
kind: Rule # 规则类型Rule
middlewares: # 绑定中间件(去前缀)
- name: stripprefix-m2 # 使用 stripprefix-m2
services: # 匹配后转发的服务
- name: nginx-m2 # 后端 Service 名称
port: 80 # 后端 Service 端口

View File

@@ -0,0 +1,6 @@
# 02-02nginx 分课)
本目录 YAML 与 `ansible/files/02-05/` 中对应课节清单为**同构副本**,专供本 `doc_id` 解耦学习与手工复制。
- **手动**:将清单拷到目标机路径,按文档改字段后执行 `kubectl` / bash不必使用 verify
- **自动**`./ansible/bin/verify.sh run 02-02`

View File

@@ -0,0 +1,97 @@
# 03-03: Nginx + 工作节点 + IngressM3
# 路径 /demo-m3随机一台工作节点nodeSelector: node-role.kubernetes.io/worker
# ConfigMap首页 + default.confX-Backend: M3 便于区分
---
apiVersion: v1 # ConfigMap 使用的 API 版本
kind: ConfigMap # 配置资源类型ConfigMap
metadata: # 对该 ConfigMap 的标识信息
name: nginx-m3-html # ConfigMap 名称
namespace: default # 命名空间
data: # ConfigMap 数据键值区
index.html: | # HTML 内容:会挂载到 nginx 网页目录(内部内容行不改动)
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M3</title></head>
<body><h1>M3</h1><p>工作节点 + Ingress</p></body></html>
default.conf: | # nginx 配置:通过 subPath 单文件挂载到 conf.d/default.conf内部内容行不改动
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M3"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1 # Deployment 使用的 API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 标识信息
name: nginx-m3 # Deployment 名称
namespace: default # 部署命名空间
labels: # 额外标签(用于筛选/统计)
app: nginx-m3 # 应用标签
matrix: "02-05-m3" # 矩阵编号标签
spec: # Deployment 期望状态
replicas: 1 # 副本数:这里为 1
selector: # Deployment 用于选择 Pod 的条件
matchLabels: # 标签匹配集合(用于选中模板 Pod
app: nginx-m3 # 必须与 template.metadata.labels 对上
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nginx-m3 # Pod 标签
spec: # Pod 规范
nodeSelector: # 固定跑到 worker 节点
node-role.kubernetes.io/worker: "" # worker 节点 selector
volumes: # 卷定义
- name: html # 卷名(供 volumeMounts 引用)
configMap: # 卷来源ConfigMap
name: nginx-m3-html # 引用的 ConfigMap 名称
containers: # 容器列表
- name: nginx # 容器名
image: nginx:alpine # nginx 镜像
ports: # 容器端口声明
- containerPort: 80 # nginx HTTP 端口
volumeMounts: # 容器内挂载点
- name: html # 对应 volumes[].name
mountPath: /usr/share/nginx/html/index.html # 挂到网页首页
subPath: index.html # 从 ConfigMap 取该 key
readOnly: true # 配置只读
- name: html # 第二处仍引用同一个卷
mountPath: /etc/nginx/conf.d/default.conf # 挂到 nginx 配置文件路径
subPath: default.conf # 从 ConfigMap 取该 key
readOnly: true # 配置只读
---
apiVersion: v1 # Service 使用的 API 版本
kind: Service # 网络抽象:把 Pod 暴露成稳定访问入口
metadata: # Service 标识
name: nginx-m3 # Service 名称
namespace: default # 命名空间
spec: # Service 期望状态
selector: # Service 通过标签选中后端 Pod
app: nginx-m3 # 选择 app 标签
ports: # Service 端口映射列表
- port: 80 # Service 暴露端口
targetPort: 80 # 转发到 Pod 的容器端口
---
apiVersion: traefik.io/v1alpha1 # Traefik Middleware API 版本
kind: Middleware # 中间件类型stripPrefix
metadata: # Middleware 标识
name: stripprefix-m3 # 名称
namespace: default # 命名空间
spec: # 中间件配置
stripPrefix: # 去掉指定路径前缀
prefixes: # 前缀列表
- /demo-m3 # 本矩阵路径前缀
---
apiVersion: networking.k8s.io/v1 # Ingress 使用的 API 版本
kind: Ingress # 入口资源:把路径转发到 Service
metadata: # Ingress 标识
name: nginx-m3 # Ingress 名称
namespace: default # 命名空间
annotations: # Traefik 注解:绑定中间件
traefik.ingress.kubernetes.io/router.middlewares: default-stripprefix-m3@kubernetescrd # 绑定 stripprefix-m3 中间件
spec: # Ingress 规则
rules: # 规则列表
- http: # HTTP 规则
paths: # 路径匹配列表
- path: /demo-m3 # 匹配路径
pathType: Prefix # 前缀匹配类型
backend: # 后端目标
service: # 后端是 Service
name: nginx-m3 # Service 名称
port: # 后端端口
number: 80 # 端口号

View File

@@ -0,0 +1,6 @@
# 02-03nginx 分课)
本目录 YAML 与 `ansible/files/02-05/` 中对应课节清单为**同构副本**,专供本 `doc_id` 解耦学习与手工复制。
- **手动**:将清单拷到目标机路径,按文档改字段后执行 `kubectl` / bash不必使用 verify
- **自动**`./ansible/bin/verify.sh run 02-03`

View File

@@ -0,0 +1,95 @@
# 03-04: Nginx + 工作节点 + IngressRouteM4
# 路径 /demo-m4指定一台工作节点按实际 FQDN 修改 kubernetes.io/hostname
# ConfigMap首页 + default.confX-Backend: M4 便于区分
---
apiVersion: v1 # ConfigMap 使用的 API 版本
kind: ConfigMap # 配置资源类型ConfigMap
metadata: # ConfigMap 标识信息
name: nginx-m4-html # ConfigMap 名称
namespace: default # 命名空间
data: # ConfigMap 数据区
index.html: | # HTML 内容:挂载到 nginx 网页目录(内部内容行不改动)
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>M4</title></head>
<body><h1>M4</h1><p>工作节点 + IngressRoute</p></body></html>
default.conf: | # nginx 配置:通过 subPath 挂载到 conf.d/default.conf内部内容行不改动
server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { add_header X-Backend "M4"; try_files $uri $uri/ /index.html; } }
---
apiVersion: apps/v1 # Deployment 使用的 API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 标识信息
name: nginx-m4 # Deployment 名称
namespace: default # 部署命名空间
labels: # 应用标签/矩阵标签
app: nginx-m4 # 应用标签
matrix: "02-05-m4" # 矩阵编号
spec: # Deployment 期望状态
replicas: 1 # 副本数
selector: # Deployment 选择器
matchLabels: # 标签匹配集合(用于选中模板 Pod
app: nginx-m4 # 必须与 template.metadata.labels 对上
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nginx-m4 # Pod 标签
spec: # Pod 规范
nodeSelector: # 固定运行的工作节点
kubernetes.io/hostname: ylc64 # worker 节点主机名
volumes: # 卷定义
- name: html # 卷名
configMap: # 卷来源
name: nginx-m4-html # 引用的 ConfigMap 名称
containers: # 容器列表
- name: nginx # 容器名
image: nginx:alpine # nginx 镜像
ports: # 容器端口
- containerPort: 80 # HTTP 端口
volumeMounts: # 容器内挂载
- name: html # 引用 volumes[].name
mountPath: /usr/share/nginx/html/index.html # 挂到首页文件
subPath: index.html # 取 ConfigMap 的 index.html key
readOnly: true # 只读
- name: html # 仍复用同一个卷
mountPath: /etc/nginx/conf.d/default.conf # 挂到 nginx 配置文件
subPath: default.conf # 取 ConfigMap 的 default.conf key
readOnly: true # 只读
---
apiVersion: v1 # Service 使用的 API 版本
kind: Service # 网络抽象:把 Pod 暴露为稳定入口
metadata: # Service 标识
name: nginx-m4 # Service 名称
namespace: default # 命名空间
spec: # Service 期望状态
selector: # Service 选择器
app: nginx-m4 # 选中后端 Pod
ports: # 端口映射列表
- port: 80 # Service 端口
targetPort: 80 # 转发到 Pod 容器端口
---
apiVersion: traefik.io/v1alpha1 # Traefik Middleware API 版本
kind: Middleware # 中间件stripPrefix
metadata: # Middleware 标识
name: stripprefix-m4 # 名称
namespace: default # 命名空间
spec: # 中间件配置
stripPrefix: # 去除路径前缀
prefixes: # 前缀列表
- /demo-m4 # 本矩阵路径前缀
---
apiVersion: traefik.io/v1alpha1 # IngressRoute API 版本
kind: IngressRoute # Traefik 路由 CRD
metadata: # IngressRoute 标识
name: nginx-m4 # 路由名称
namespace: default # 命名空间
spec: # IngressRoute 规则
entryPoints: # 入口点列表
- web # webHTTP
routes: # 路由列表
- match: PathPrefix(`/demo-m4`) # 匹配 /demo-m4 前缀
kind: Rule # 规则类型
middlewares: # 绑定中间件
- name: stripprefix-m4 # 需要去前缀
services: # 后端服务列表
- name: nginx-m4 # Service 名称
port: 80 # Service 端口

View File

@@ -0,0 +1,6 @@
# 02-04nginx 分课)
本目录 YAML 与 `ansible/files/02-05/` 中对应课节清单为**同构副本**,专供本 `doc_id` 解耦学习与手工复制。
- **手动**:将清单拷到目标机路径,按文档改字段后执行 `kubectl` / bash不必使用 verify
- **自动**`./ansible/bin/verify.sh run 02-04`

View File

@@ -39,3 +39,8 @@ spec: # chart 注入配置的具体内容
nodeSelector: # 把 Traefik Pod 固定到指定节点(配合 RWO 本地存储更安全)
kubernetes.io/hostname: ylc61 # 固定节点主机名(按你的实际节点修改)
# ping 绑定 websecure 时chart 默认对 8080 做 HTTP /ping须与 03-03 一致改为 HTTPS:8443
deployment:
healthchecksPort: 8443
healthchecksScheme: HTTPS

View File

@@ -9,13 +9,17 @@ metadata:
namespace: kube-system
spec:
valuesContent: |-
# chart 39.xexpose 须为表,布尔会与默认 values 合并冲突并导致 helm upgrade 模板失败
ports:
web:
expose: true
expose:
default: true
websecure:
expose: true
expose:
default: true
traefik:
expose: true
expose:
default: true
additionalArguments:
# Dashboard
@@ -48,13 +52,20 @@ spec:
nodeSelector:
kubernetes.io/hostname: ylc61
# persistence将 /data 持久化local-path PVC保证 acme.json 落盘
# ping 绑定 websecure 时chart 默认仍对 traefik(8080) 做 HTTP /ping → 404与 chart 39 对齐探针
deployment:
healthchecksPort: 8443
healthchecksScheme: HTTPS
# persistence将 /data 持久化,保证 acme.json 落盘
# 显式 local-path避免集群默认 StorageClass 为 longhorn 等未就绪时 Pod 长期 Pending
persistence:
enabled: true
name: data
accessMode: ReadWriteOnce
size: 128Mi
path: /data
storageClass: local-path
---
apiVersion: traefik.io/v1alpha1

View File

@@ -1,38 +1,31 @@
# docs/03-04-k3s-cloudflare-tunnel-配置接入.md — 替换 TUNNEL_TOKEN 后应用
apiVersion: v1 # Secret 使用的 Kubernetes API 版本
kind: Secret # 资源类型Secret用于保存 Cloudflare Tunnel token
metadata: # 元信息(名称/命名空间等)
name: cloudflared-credentials # Secret 名称Deployment 中会引用)
namespace: kube-system # Secret 所在命名空间
type: Opaque # Secret 类型(普通自定义键值)
stringData: # 以字符串方式提供 Secret 数据(便于直接写明文)
TUNNEL_TOKEN: "<YOUR_TUNNEL_TOKEN>" # Cloudflare Tunnel Token用你真实的 token 替换)
# docs/03-04-k3s-cloudflare-tunnel-配置接入.md
# Secret `cloudflared-credentials`key=TUNNEL_TOKEN由 verify playbook / 手工 kubectl create secret 创建,勿与此 Deployment 同 apply避免覆盖 token。
# 参考ansible/playbooks/verify/03-04.yml → ensure-cloudflared-tunnel-secret
---
apiVersion: apps/v1 # Deployment 使用的 API 版本
kind: Deployment # 工作负载Deployment管理 Pod 副本)
metadata: # Deployment 元信息
name: cloudflared # Deployment 名称
namespace: kube-system # 部署到的命名空间
spec: # Deployment 期望状态
replicas: 1 # 副本数Tunnel 通常只跑一个副本即可)
selector: # Deployment 选择器:匹配 template 的 Pod
matchLabels: # 必须与 template.metadata.labels 对齐
app: cloudflared # 应用标签
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: cloudflared # 与 selector.matchLabels 相同
spec: # Pod 规范
containers: # 容器列表
- name: cloudflared # 容器名
image: cloudflare/cloudflared:latest # cloudflared 镜像
args: # 容器启动参数
- tunnel # 命令子参数tunnel
- run # 命令子参数run
env: # 环境变量
- name: TUNNEL_TOKEN # 容器内使用的环境变量名
valueFrom: # 从某个来源取值
secretKeyRef: # 从 Secret 的 key 取值
name: cloudflared-credentials # Secret 名称
key: TUNNEL_TOKEN # Secret 中的 key
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: cloudflared
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args:
- tunnel
- run
env:
- name: TUNNEL_TOKEN
valueFrom:
secretKeyRef:
name: cloudflared-credentials
key: TUNNEL_TOKEN

View File

@@ -4,6 +4,7 @@ kind: PersistentVolume # 资源类型:持久卷(集群级)
metadata: # PV 元信息
name: nfs-pv-demo # PV 名称
spec: # PV 规格
storageClassName: "" # 显式禁用默认 StorageClass供静态绑定 PVC 使用
capacity: # 容量声明
storage: 20Gi # PV 总容量
accessModes: # 访问模式列表
@@ -19,6 +20,7 @@ metadata: # PVC 元信息
name: nfs-pvc-demo # PVC 名称
namespace: default # PVC 所在命名空间
spec: # PVC 规格
storageClassName: "" # 与 PV 对齐,避免被默认 longhorn class 注入导致绑定失败
accessModes: # 访问模式要求
- ReadWriteMany # 申请 RWX 访问模式
resources: # 资源请求

View File

@@ -0,0 +1,27 @@
# docs/03-06-k3s-使用nfs存储.md — 自动化验收用:挂载 nfs-pvc-demo 并写文件OC3 证据)
# 与 nfs-pv-pvc-demo.yaml 配合;手动学习 PV/PVC 时可不应用本文件。
apiVersion: batch/v1
kind: Job
metadata:
name: nfs-pvc-verify-demo
namespace: default
spec:
backoffLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: verify-write
image: busybox:1.36
command:
- /bin/sh
- -c
- echo "ok-$(date +%s)" > /data/.verify-nfs && sync && test -f /data/.verify-nfs
volumeMounts:
- name: data
mountPath: /data
readOnly: false
volumes:
- name: data
persistentVolumeClaim:
claimName: nfs-pvc-demo

View File

@@ -0,0 +1,9 @@
# 03-08K3s HA 配置与切换)
| 文件 | 说明 |
|------|------|
| `k3s-server-ha-env.example.sh` | 外部 datastore、`tls-san`、第二节点 `K3S_URL`/`TOKEN` 等环境变量示例(**非** Kubernetes 清单) |
- **手动**将示例中的连接串、LB IP、token 替换为真实值;与 systemd/k3s 安装参数对照 [docs/03-08-k3s-ha-集群配置与切换.md](../../../docs/03-08-k3s-ha-集群配置与切换.md)。
- **自动**`./ansible/bin/verify.sh run 03-08`noop + 基线HA 步骤仍以文档与手工为准)。
- 本篇**不提供**可 `kubectl apply` 的 HA 安装真源(控制平面由 k3s 与 datastore 决定)。

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# k3s 多 server + 外部 datastore 参数备忘(示例,勿直接 source 到生产)
# 完整步骤见docs/03-08-k3s-ha-集群配置与切换.md
# 真源目录ansible/files/03-08/
# --- 首个 server示例 IP 请替换)---
# export K3S_DATASTORE_ENDPOINT='postgres://k3s:CHANGE_ME@192.168.2.50:5432/k3s?sslmode=disable'
# sudo k3s server --datastore-endpoint="$K3S_DATASTORE_ENDPOINT" --tls-san 192.168.2.60
# --- 第二个 server经 LB 加入token 与控制端一致)---
# export K3S_URL='https://192.168.2.60:6443'
# export K3S_TOKEN='<SAME_TOKEN_AS_SERVER1>'
# export K3S_DATASTORE_ENDPOINT='postgres://k3s:CHANGE_ME@192.168.2.50:5432/k3s?sslmode=disable'
# sudo k3s server --server "$K3S_URL" --token "$K3S_TOKEN" \
# --datastore-endpoint="$K3S_DATASTORE_ENDPOINT" --tls-san 192.168.2.60
echo "[INFO] 编辑本文件中的占位符后,将命令复制到节点上执行;或写入 systemd ExecStart。"

View File

@@ -0,0 +1,9 @@
# 03-09GitOps 框架)
| 文件 | 说明 |
|------|------|
| `argocd-namespace.example.yaml` | 仅创建 `argocd` 命名空间的极简示例(正式安装请用官方 `install.yaml` 或 Helm |
- **手动**:集群声明性配置的**主真源**建议在独立 GitOps 仓库(见 [docs/03-09-k3s-gitops-集群配置管理.md](../../../docs/03-09-k3s-gitops-集群配置管理.md));本目录只放与本仓库文档对齐的**示意**文件。
- **自动**`./ansible/bin/verify.sh run 03-09`noop + 基线)。
- 同一套 YAML 路径既可供你 `kubectl apply -f` 试跑,也与自动化验收引用同一目录,避免文档与仓库脱节。

View File

@@ -0,0 +1,8 @@
# 极简示例:为后续 Argo CD 安装预留命名空间
# 生产请改用官方清单: https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
apiVersion: v1
kind: Namespace
metadata:
name: argocd
labels:
app.kubernetes.io/part-of: argocd

View File

@@ -0,0 +1,59 @@
# 对应文档docs/04-02-nodejs-镜像与运行命令.md
# 累积04-01 + 固定镜像 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(3000)" # Node.js 内联服务逻辑
ports: # 容器端口
- containerPort: 3000 # 应用监听端口
---
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: 3000 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-02nodejs 分课)
本目录 `04-02-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-02/04-02-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-02`

View File

@@ -0,0 +1,76 @@
# 对应文档docs/04-03-nodejs-环境变量与配置注入.md
# 累积04-02 + ConfigMap + 通过 env 注入 APP_MSG镜像仍用 18.20-alpine 与 04-02 一致)
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
---
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(3000);
ports: # 容器端口
- containerPort: 3000 # 应用监听端口
---
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: 3000 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-03nodejs 分课)
本目录 `04-03-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-03/04-03-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-03`

View File

@@ -0,0 +1,76 @@
# 对应文档docs/04-04-nodejs-端口与Service.md
# 累积04-03 + 容器与进程改监听 8080Service targetPort 对齐
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
---
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 # 应用监听端口
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-04nodejs 分课)
本目录 `04-04-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-04/04-04-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-04`

View File

@@ -0,0 +1,83 @@
# 对应文档docs/04-05-nodejs-资源请求与限制.md
# 累积04-04 + resources.requests/limits
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
---
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 # 应用监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-05nodejs 分课)
本目录 `04-05-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-05/04-05-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-05`

View File

@@ -0,0 +1,95 @@
# 对应文档docs/04-06-nodejs-探针与健康检查.md
# 累积04-05 + livenessProbe/readinessProbe端口 8080路径 /
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
---
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 # 应用监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针(判断容器是否需要重启)
httpGet: # 通过 HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 启动后首次探测延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针(判断是否接收流量)
httpGet: # 通过 HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 启动后首次探测延迟
periodSeconds: 5 # 探测周期
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-06nodejs 分课)
本目录 `04-06-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-06/04-06-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-06`

View File

@@ -0,0 +1,97 @@
# 对应文档docs/04-07-nodejs-调度与亲和.md
# 累积04-06 + nodeSelector默认 ylc62请改为本集群节点短主机名
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
---
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 规格
nodeSelector: # 调度到指定节点
kubernetes.io/hostname: ylc62 # 节点主机名(按实际修改)
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 # 应用监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 启动后首次探测延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 启动后首次探测延迟
periodSeconds: 5 # 探测周期
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-07nodejs 分课)
本目录 `04-07-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-07/04-07-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-07`

View File

@@ -0,0 +1,110 @@
# 对应文档docs/04-08-nodejs-安全上下文.md
# 累积04-07 + pod securityContext.fsGroup、容器 securityContext、只读根、/tmp emptyDir
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源ConfigMap
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 注入给应用的消息内容
---
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 规格
nodeSelector: # 调度到指定节点
kubernetes.io/hostname: ylc62 # 节点主机名(按实际修改)
securityContext: # Pod 级安全上下文
fsGroup: 1000 # 挂载卷文件组 ID
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
securityContext: # 容器级安全上下文
allowPrivilegeEscalation: false # 禁止提权
runAsNonRoot: true # 强制非 root 运行
runAsUser: 1000 # 运行用户 UID
readOnlyRootFilesystem: true # 根文件系统只读
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 # 应用监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 启动后首次探测延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 启动后首次探测延迟
periodSeconds: 5 # 探测周期
volumeMounts: # 卷挂载
- name: tmp # 引用临时卷
mountPath: /tmp # 容器内临时目录
volumes: # 卷定义
- name: tmp # 临时卷名称
emptyDir: {} # 空目录卷Pod 生命周期内)
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-08nodejs 分课)
本目录 `04-08-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-08/04-08-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-08`

View File

@@ -0,0 +1,128 @@
# 对应文档docs/04-09-nodejs-存储与卷.md
# 累积04-08 + PVC nodejs-demo-data默认 storageClassName: local-path可按集群改为 longhorn 等)+ 挂载 /data
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 # 申请容量
---
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 示例消息内容
---
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 1 # 副本数
selector: # Pod 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
nodeSelector: # 节点选择
kubernetes.io/hostname: ylc62 # 固定到指定节点(按实际修改)
securityContext: # Pod 级安全上下文
fsGroup: 1000 # 挂载卷文件组 ID
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
securityContext: # 容器级安全上下文
allowPrivilegeEscalation: false # 禁止提权
runAsNonRoot: true # 非 root 运行
runAsUser: 1000 # 运行用户 UID
readOnlyRootFilesystem: true # 根文件系统只读
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 # 监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 初始延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 初始延迟
periodSeconds: 5 # 探测周期
volumeMounts: # 卷挂载
- name: tmp # 临时卷名称
mountPath: /tmp # 容器内临时目录
- name: data # 数据卷名称
mountPath: /data # 容器内数据目录
volumes: # 卷定义
- name: tmp # 临时卷
emptyDir: {} # 空目录卷
- name: data # 数据卷
persistentVolumeClaim: # 卷来源为 PVC
claimName: nodejs-demo-data # 绑定 PVC 名称
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-09nodejs 分课)
本目录 `04-09-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-09/04-09-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-09`

View File

@@ -0,0 +1,129 @@
# 对应文档docs/04-10-nodejs-Ingress与Traefik.md
# 累积04-09 + Ingress 增加 host、path 改为 /api访问需 Host: app.example.local
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 # 申请容量
---
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 示例消息内容
---
apiVersion: apps/v1 # Deployment API 版本
kind: Deployment # 工作负载Deployment
metadata: # Deployment 元信息
name: nodejs-demo # Deployment 名称
namespace: default # 命名空间
spec: # Deployment 规格
replicas: 1 # 副本数
selector: # Pod 选择器
matchLabels: # 标签匹配集合
app: nodejs-demo # 匹配 app=nodejs-demo 的 Pod
template: # Pod 模板
metadata: # Pod 元信息
labels: # Pod 标签
app: nodejs-demo # 与 selector.matchLabels 对齐
spec: # Pod 规格
nodeSelector: # 节点选择
kubernetes.io/hostname: ylc62 # 固定到指定节点(按实际修改)
securityContext: # Pod 级安全上下文
fsGroup: 1000 # 挂载卷文件组 ID
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
securityContext: # 容器级安全上下文
allowPrivilegeEscalation: false # 禁止提权
runAsNonRoot: true # 非 root 运行
runAsUser: 1000 # 运行用户 UID
readOnlyRootFilesystem: true # 根文件系统只读
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 # 监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 初始延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 初始延迟
periodSeconds: 5 # 探测周期
volumeMounts: # 卷挂载
- name: tmp # 临时卷名称
mountPath: /tmp # 容器内临时目录
- name: data # 数据卷名称
mountPath: /data # 容器内数据目录
volumes: # 卷定义
- name: tmp # 临时卷
emptyDir: {} # 空目录卷
- name: data # 数据卷
persistentVolumeClaim: # 卷来源为 PVC
claimName: nodejs-demo-data # 绑定 PVC 名称
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-10nodejs 分课)
本目录 `04-10-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-10/04-10-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-10`

View File

@@ -0,0 +1,134 @@
# 对应文档docs/04-11-nodejs-副本与滚动发布.md
# 累积04-10 + replicas: 3 + RollingUpdatemaxSurge:1 maxUnavailable:0
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 # 申请容量
---
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 示例消息内容
---
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 规格
nodeSelector: # 节点选择
kubernetes.io/hostname: ylc62 # 固定到指定节点(按实际修改)
securityContext: # Pod 级安全上下文
fsGroup: 1000 # 挂载卷文件组 ID
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
securityContext: # 容器级安全上下文
allowPrivilegeEscalation: false # 禁止提权
runAsNonRoot: true # 非 root 运行
runAsUser: 1000 # 运行用户 UID
readOnlyRootFilesystem: true # 根文件系统只读
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 # 监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 初始延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 初始延迟
periodSeconds: 5 # 探测周期
volumeMounts: # 卷挂载
- name: tmp # 临时卷名称
mountPath: /tmp # 容器内临时目录
- name: data # 数据卷名称
mountPath: /data # 容器内数据目录
volumes: # 卷定义
- name: tmp # 临时卷
emptyDir: {} # 空目录卷
- name: data # 数据卷
persistentVolumeClaim: # 卷来源为 PVC
claimName: nodejs-demo-data # 绑定 PVC 名称
---
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 # 转发到容器端口
---
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 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-11nodejs 分课)
本目录 `04-11-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-11/04-11-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-11`

View File

@@ -0,0 +1,141 @@
# 对应文档docs/04-12-nodejs-TLS与证书.md
# 累积04-11 + Ingress TLSwebsecure、secretName: nodejs-demo-tls
# 应用前请先创建 TLS Secret例如
# kubectl create secret tls nodejs-demo-tls --cert=fullchain.pem --key=privkey.pem -n default
# 证书 SAN 须覆盖 app.example.local与 rules.host / tls.hosts 一致)
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 # 申请容量
---
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 示例消息内容
---
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 规格
nodeSelector: # 节点选择
kubernetes.io/hostname: ylc62 # 固定到指定节点(按实际修改)
securityContext: # Pod 级安全上下文
fsGroup: 1000 # 挂载卷文件组 ID
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
securityContext: # 容器级安全上下文
allowPrivilegeEscalation: false # 禁止提权
runAsNonRoot: true # 非 root 运行
runAsUser: 1000 # 运行用户 UID
readOnlyRootFilesystem: true # 根文件系统只读
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 # 监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 初始延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 初始延迟
periodSeconds: 5 # 探测周期
volumeMounts: # 卷挂载
- name: tmp # 临时卷名称
mountPath: /tmp # 容器内临时目录
- name: data # 数据卷名称
mountPath: /data # 容器内数据目录
volumes: # 卷定义
- name: tmp # 临时卷
emptyDir: {} # 空目录卷
- name: data # 数据卷
persistentVolumeClaim: # 卷来源为 PVC
claimName: nodejs-demo-data # 绑定 PVC 名称
---
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 # 转发到容器端口
---
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: websecure # 使用 websecure(HTTPS) 入口
spec: # Ingress 规则
tls: # TLS 配置
- hosts: # 证书覆盖域名
- app.example.local # 域名
secretName: nodejs-demo-tls # 引用的 TLS Secret 名称
rules: # 路由规则列表
- host: app.example.local # 主机名匹配
http: # HTTP 路由
paths: # 路径列表
- path: /api # 匹配 API 路径前缀
pathType: Prefix # 前缀匹配
backend: # 后端目标
service: # 后端 Service
name: nodejs-demo # Service 名称
port: # Service 端口
number: 80 # 端口号

View File

@@ -0,0 +1,6 @@
# 04-12nodejs 分课)
本目录 `04-12-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-12/04-12-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-12`

View File

@@ -0,0 +1,158 @@
# 对应文档docs/04-13-nodejs-HPA.md
# 累积04-12 + HorizontalPodAutoscalerCPU 50%min 1 max 5
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 # 申请容量
---
apiVersion: v1 # ConfigMap API 版本
kind: ConfigMap # 配置资源
metadata: # ConfigMap 元信息
name: nodejs-demo-config # ConfigMap 名称
namespace: default # 命名空间
data: # 配置键值
APP_MSG: "Hello from ConfigMap" # 示例消息内容
---
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 规格
nodeSelector: # 节点选择
kubernetes.io/hostname: ylc62 # 固定到指定节点(按实际修改)
securityContext: # Pod 级安全上下文
fsGroup: 1000 # 挂载卷文件组 ID
containers: # 容器列表
- name: nodejs-demo # 容器名
image: node:18.20-alpine # Node.js 镜像
imagePullPolicy: IfNotPresent # 拉取策略
securityContext: # 容器级安全上下文
allowPrivilegeEscalation: false # 禁止提权
runAsNonRoot: true # 非 root 运行
runAsUser: 1000 # 运行用户 UID
readOnlyRootFilesystem: true # 根文件系统只读
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 # 监听端口
resources: # 资源请求与限制
requests: # 最小资源请求
cpu: "50m" # 请求 CPU
memory: "64Mi" # 请求内存
limits: # 资源上限
cpu: "500m" # CPU 限制
memory: "256Mi" # 内存限制
livenessProbe: # 存活探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 3 # 初始延迟
periodSeconds: 10 # 探测周期
readinessProbe: # 就绪探针
httpGet: # HTTP 探测
path: / # 探测路径
port: 8080 # 探测端口
initialDelaySeconds: 2 # 初始延迟
periodSeconds: 5 # 探测周期
volumeMounts: # 卷挂载
- name: tmp # 临时卷名称
mountPath: /tmp # 容器内临时目录
- name: data # 数据卷名称
mountPath: /data # 容器内数据目录
volumes: # 卷定义
- name: tmp # 临时卷
emptyDir: {} # 空目录卷
- name: data # 数据卷
persistentVolumeClaim: # 卷来源为 PVC
claimName: nodejs-demo-data # 绑定 PVC 名称
---
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 # 转发到容器端口
---
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: websecure # 使用 websecure(HTTPS) 入口
spec: # Ingress 规则
tls: # TLS 配置
- hosts: # 证书覆盖域名
- app.example.local # 域名
secretName: nodejs-demo-tls # 引用的 TLS Secret 名称
rules: # 路由规则列表
- host: app.example.local # 主机名匹配
http: # HTTP 路由
paths: # 路径列表
- path: /api # 匹配 API 路径前缀
pathType: Prefix # 前缀匹配
backend: # 后端目标
service: # 后端 Service
name: nodejs-demo # Service 名称
port: # Service 端口
number: 80 # 端口号
---
apiVersion: autoscaling/v2 # HPA API 版本
kind: HorizontalPodAutoscaler # 水平自动扩缩容资源
metadata: # HPA 元信息
name: nodejs-demo # HPA 名称
namespace: default # 命名空间
spec: # HPA 规格
scaleTargetRef: # 伸缩目标引用
apiVersion: apps/v1 # 目标 API 版本
kind: Deployment # 目标资源类型
name: nodejs-demo # 目标 Deployment 名称
minReplicas: 1 # 最小副本数
maxReplicas: 5 # 最大副本数
metrics: # 伸缩指标
- type: Resource # 资源指标类型
resource: # 资源指标配置
name: cpu # 指标资源CPU
target: # 目标值
type: Utilization # 目标类型:利用率
averageUtilization: 50 # 目标平均 CPU 利用率(%

View File

@@ -0,0 +1,6 @@
# 04-13nodejs 分课)
本目录 `04-13-nodejs-demo.yaml``ansible/files/04-01/` 同名文件复制,与总集目录内文件**同构**,专供本课独立阅读;改本课清单时以本目录为准即可最小化影响其他篇。
- **手动**`kubectl apply -f ansible/files/04-13/04-13-nodejs-demo.yaml`(按需改字段)。
- **自动**`./ansible/bin/verify.sh run 04-13`

View File

@@ -0,0 +1,5 @@
# 04-14 truth source placeholder
`04-14` 当前以流程说明GitOps/CI 流水线)为主,暂无独立可直接 `kubectl apply` 的单一清单。
本目录用于满足 `doc_id -> ansible/files/<doc_id>/` 一致性约束。

View File

@@ -0,0 +1,69 @@
# GitLab Helm Chart 示例 values实验室用
#
# 使用方式:
# cp ansible/files/05-03/values-gitlab.example.yaml values-gitlab.yaml
# 按需修改域名、资源、Ingress完整键名以当前 Chart 为准:
# helm show values gitlab/gitlab
#
# Chart 文档https://docs.gitlab.com/charts/
global:
# 实验室固定调度到指定节点PVClocal-path与 Pod 同节点 → 数据落在该节点本地盘
# 本仓库 inventory 中 192.168.2.63 对应节点名 ylc63若你的集群节点名不同请 kubectl get nodes 后改写
nodeSelector:
kubernetes.io/hostname: ylc63
hosts:
# 根域占位;请改为你的域名(内网实验可配合 hosts / split-horizon DNS
domain: example.com
# GitLab Web 主机名片段:最终为 <name>.<domain>,例如 git.example.com
gitlab:
name: git
registry:
name: registry
ingress:
# 集群已用 Traefik 或在外层终止 TLS 时,通常不启用 Chart 内置 cert-manager
configureCertmanager: false
tls:
enabled: true
# 若使用已有 Secret例如 Traefik / 手工证书),取消注释并填写:
# secretName: gitlab-wildcard-tls
# 实验室缩小副本(可选;生产请按官方 sizing 与监控数据调整)
gitlab:
webservice:
minReplicas: 1
sidekiq:
minReplicas: 1
# Gitaly 仓库数据盘:与 global.nodeSelector 同节点时local-path 卷落在该节点本地
gitaly:
persistence:
storageClass: local-path
size: 50Gi
# Bitnami PostgreSQL / Redis、MinIO 子 chart **不**继承 global.nodeSelector 时需单独写(见 GitLab Chart 文档 Node Selector 节)
postgresql:
primary:
nodeSelector:
kubernetes.io/hostname: ylc63
persistence:
storageClass: local-path
size: 8Gi
redis:
master:
nodeSelector:
kubernetes.io/hostname: ylc63
persistence:
storageClass: local-path
size: 5Gi
minio:
nodeSelector:
kubernetes.io/hostname: ylc63
persistence:
storageClass: local-path
size: 10Gi
# 资源紧张时可考虑关闭捆绑 Prometheus按需取消注释
# prometheus:
# install: false

View File

@@ -0,0 +1,9 @@
# 05-05Prometheus + Grafana
| 文件 | 说明 |
|------|------|
| `kube-prometheus-stack-values.example.yaml` | Helm `kube-prometheus-stack` 的实验室向 values 示例(**非** `kubectl apply` 对象) |
- **手动**:按 [docs/05-05-prometheus与grafana.md](../../../docs/05-05-prometheus与grafana.md) 执行 `helm repo add` / `helm upgrade --install`,可附加 `-f ansible/files/05-05/kube-prometheus-stack-values.example.yaml`
- **自动**`./ansible/bin/verify.sh run 05-05`noop + 集群基线;与本目录示例共用真源,便于对照文档改 values
- 文件名含 `example.``verify_common` 不会对其实施 `kubectl apply --dry-run`

View File

@@ -0,0 +1,22 @@
# Helm values for prometheus-community/kube-prometheus-stack
# 用法(仓库根):
# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# helm repo update
# kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f -
# helm upgrade --install monitoring prometheus-community/kube-prometheus-stack -n monitoring \
# -f ansible/files/05-05/kube-prometheus-stack-values.example.yaml
#
# 注意:本文件是 Helm values不要用 kubectl apply。
prometheus:
prometheusSpec:
retention: 7d
resources:
requests:
cpu: 100m
memory: 256Mi
grafana:
enabled: true
persistence:
enabled: false

View File

@@ -0,0 +1,10 @@
# 06-01NetworkPolicy 与连通性排障)
| 文件 | 说明 |
|------|------|
| `networkpolicy-traefik-egress.example.yaml` | Traefik 出站示例后端命名空间、Service CIDR、DNS |
| `networkpolicy-backend-ingress.example.yaml` | 后端仅允许来自 `kube-system`Traefik的入站示例 |
- **手动**:复制为正式名后 `kubectl apply -f ...`,并按集群实际 **namespace / 标签 / CIDR** 修改(见 [docs/06-01-k3s-networkpolicy-故障排查.md](../../../docs/06-01-k3s-networkpolicy-故障排查.md))。
- **自动**`./ansible/bin/verify.sh run 06-01`noop + 基线;策略真源以本目录为准,与手工 `kubectl` 一致)。
- 示例文件名含 `example.`,默认验证流程跳过对其的 `kubectl dry-run`

View File

@@ -0,0 +1,21 @@
# 示例:后端 Namespace 仅允许来自 kube-systemTraefik的入站
# 将 namespace、podSelector、端口改为你的应用标签与 Service 端口。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-from-traefik-example
namespace: default
spec:
podSelector:
matchLabels:
app: nginx
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 80

View File

@@ -0,0 +1,32 @@
# 示例:为 Traefik 放行出站(按实际 namespace 与标签调整)
# 适用场景:后端在其它命名空间、需访问集群 DNS 与 Service VIP。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: traefik-egress-lab-example
namespace: kube-system
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
policyTypes:
- Egress
egress:
# 访问任意命名空间内 Pod可按需收窄为 namespaceSelector + podSelector
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 8000
# Service CIDRk3s 默认常为 10.43.0.0/16请与集群一致
- to:
- ipBlock:
cidr: 10.43.0.0/16
# 集群 DNS
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53

View File

@@ -0,0 +1,5 @@
# 06-02运维小结
- **文档**[docs/06-02-运维小结.md](../../../docs/06-02-运维小结.md)(检查项、命令速查、变更记录建议)。
- **自动**`./ansible/bin/verify.sh run 06-02`noop + 集群基线)。
- 本篇无独立 Kubernetes 清单;运维动作以 `kubectl`/脚本为主。

View File

@@ -0,0 +1,5 @@
# 07-01Calico 双栈实验)
- **文档**[docs/07-01-k3s-calico-dualstack.md](../../../docs/07-01-k3s-calico-dualstack.md)。
- **自动**`./ansible/bin/verify.sh run 07-01`(实验环境与主线 Flannel 可能冲突,仅在隔离环境操作)。
- 清单与安装步骤以文档为准;本目录占位满足契约,避免与生产集群误用。

View File

@@ -0,0 +1,5 @@
# 07-02Cilium 双栈 / eBPF 实验)
- **文档**[docs/07-02-k3s-cilium-dualstack-ebpf.md](../../../docs/07-02-k3s-cilium-dualstack-ebpf.md)。
- **自动**`./ansible/bin/verify.sh run 07-02`
- 实验性 CNI 切换风险高YAML 真源随文档演进时再纳入本目录。

View File

@@ -1,4 +1,8 @@
---
# 变量边界约定:
# - inventory.ini主机拓扑/SSH
# - group_vars/all.yml长期基线参数
# - ansible/env/.env.verify运行时上下文与外部依赖ACME/NFS/Cloudflare 等)
# 使用 root SSH 连接setup-k3s-workers-ssh.sh 已将同一公钥写入各节点 root
ansible_user: root
@@ -8,11 +12,17 @@ timezone: "Asia/Shanghai"
k3s_version: "" # 为空表示用 get.k3s.io 默认最新
k3s_data_dir: "/storage"
k3s_server_ip: "192.168.2.61"
# 安装脚本网络GitHub 慢/不可达时设 cn等价于安装脚本环境变量 INSTALL_K3S_MIRROR也可用 ansible-playbook -e k3s_install_mirror=cn
k3s_install_mirror: ""
# curl 连接/整体超时(秒),避免 get.k3s.io 卡住时 Ansible 长时间无反馈;-e k3s_install_curl_max_time=900 可调大
k3s_install_curl_max_time: 600
# 安装 shell 在控制面上的硬超时(秒),应略大于 k3s_install_curl_max_time
k3s_install_task_timeout: 720
# 安装 k3s 前校验:/storage 为挂载点且与 / 不同设备(实验室 10G+32G 建议 true「目录式假 /storage」旧环境可 false
k3s_verify_storage_mount: true
# 可选:由 playbooks/verify/01-06.yml-e k3s_do_prepare_storage=true对第二块整盘分区、格式化并挂载到 k3s_data_dir会清空该盘见 01-06
# 可选:由 playbooks/verify/01-05.yml-e k3s_do_prepare_storage=true对第二块整盘分区、格式化并挂载到 k3s_data_dir会清空该盘见 01-05
k3s_prepare_storage: false
# k3s_data_disk_device: "/dev/vdb"
# NVMe 整盘一般为 /dev/nvme0n1首分区为 /dev/nvme0n1p1playbook 会按设备名自动加 1 或 p1

View File

@@ -0,0 +1,37 @@
# shellcheck shell=bash
# 仓库根 Ansible从任意 cwd 调用时仍使用 ansible/ansible.cfg如 host_key_checking=False
ansible_lab_export_config() {
export ANSIBLE_CONFIG="${ROOT}/ansible/ansible.cfg"
}
# 若 inventory 为各主机声明了 ansible_ssh_private_key_file则在本机检查文件存在避免 ssh 报 no such identity
ansible_lab_check_inventory_keys() {
local inv="$1"
local line path exp
[[ -f "$inv" ]] || return 0
while IFS= read -r line || [[ -n "$line" ]]; do
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ansible_ssh_private_key_file=([^[:space:]]+) ]] || continue
path="${BASH_REMATCH[1]}"
exp="${path/#\~/$HOME}"
if [[ ! -f "$exp" ]]; then
echo "[ERR] SSH 私钥不存在:$expinventory 中为 $path" >&2
echo " 将密钥放到该路径并 chmod 600或改 ansible/inventory.ini 中的 ansible_ssh_private_key_file。" >&2
echo " 生成/分发可参考scripts/ssh/setup-k3s-workers-ssh.sh、docs/01-05-节点初始化-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

@@ -1,5 +1,5 @@
# SKIP_ARMV7=1默认仅 noop文档 + ansible/files
# SKIP_ARMV7=0 且设置 ARMV7_SSH经 SSH 在 armv7/arm32 主机上 dnf 安装 docker 并校验Fedora/RHEL 系,见 docs/01-03
# SKIP_ARMV7=0 且设置 ARMV7_SSH经 SSH 调用 ansible/tools/armv7-docker-verify-install.sh先 docker info失败再 get.docker.com,见 docs/01-03
- name: 01-03 armv7 Docker矩阵 + 可选远程安装)
hosts: localhost
gather_facts: false
@@ -102,7 +102,7 @@
- name: Fail when SKIP_ARMV7=0 but ARMV7_SSH empty
ansible.builtin.fail:
msg: "SKIP_ARMV7=0 但未设置 ARMV7_SSHscripts/.env.verify.example"
msg: "SKIP_ARMV7=0 但未设置 ARMV7_SSHansible/env/.env.verify.example"
when: skip_armv7 == '0' and armv7_ssh | length == 0
- name: Note skipping remote arm install
@@ -110,19 +110,14 @@
msg: "SKIP_ARMV7={{ skip_armv7 }}:跳过 arm 远程安装。若需安装SKIP_ARMV7=0 且 export ARMV7_SSH='ssh -o BatchMode=yes user@arm-host'"
when: skip_armv7 != '0' or armv7_ssh | length == 0
- name: Remote Docker install (dnf on arm)
- name: Remote Docker verify-or-install (get.docker.com)
when: skip_armv7 == '0' and armv7_ssh | length > 0
block:
- name: Check docker on armv7 host
ansible.builtin.shell: "{{ armv7_ssh }} docker version"
register: armv7_docker_check
changed_when: false
failed_when: false
- name: Install Docker and enable service (dnf)
ansible.builtin.shell: "{{ armv7_ssh }} 'sudo dnf install -y docker && sudo systemctl enable --now docker'"
when: armv7_docker_check.rc != 0
- name: Verify docker version and ps
ansible.builtin.shell: "{{ armv7_ssh }} docker version && {{ armv7_ssh }} docker ps -a"
changed_when: false
ansible.builtin.shell: |
set -euo pipefail
exec bash "{{ repo_root }}/ansible/tools/armv7-docker-verify-install.sh"
environment:
ARMV7_SSH: "{{ armv7_ssh }}"
args:
executable: /bin/bash
register: armv7_docker_verify_install
changed_when: "'skip_install=0' in (armv7_docker_verify_install.stdout | default(''))"

View File

@@ -0,0 +1,145 @@
# SKIP_ARMV7=1默认仅 noop。
# SKIP_ARMV7=0 且 ARMV7_NFS_SSH 或 ARMV7_SSH经 SSH 在 arm 上 dnf 装 nfs-utils、写 /etc/exports、exportfs见 docs/01-04
# 导出路径/网段ARMV7_NFS_EXPORT_PATH默认 /sdcard、ARMV7_NFS_CLIENT_SUBNET默认 192.168.2.0/24
- name: 01-04 armv7 NFS矩阵 + 可选远程安装)
hosts: localhost
gather_facts: false
vars:
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "01-04"
doc_filename: "01-04-armv7-nfs服务安装.md"
skip_armv7: "{{ lookup('env', 'SKIP_ARMV7') | default('1', true) | trim }}"
armv7_ssh: "{{ lookup('env', 'ARMV7_SSH') | default('', true) | trim }}"
armv7_nfs_export_path: "{{ lookup('env', 'ARMV7_NFS_EXPORT_PATH') | default('/sdcard', true) | trim }}"
armv7_nfs_client_subnet: "{{ lookup('env', 'ARMV7_NFS_CLIENT_SUBNET') | default('192.168.2.0/24', true) | trim }}"
tasks:
- name: Resolve ARMV7_NFS_SSH from env
ansible.builtin.set_fact:
armv7_nfs_ssh: >-
{% set n = lookup('env', 'ARMV7_NFS_SSH') | default('', true) | trim %}
{% set b = lookup('env', 'ARMV7_SSH') | default('', true) | trim %}
{{ n if n | length > 0 else b }}
- name: Baseline docs/files checks
block:
- name: Assert docs file exists
ansible.builtin.stat:
path: "{{ repo_root }}/docs/{{ doc_filename }}"
register: _doc_stat
- name: Fail when docs file missing
ansible.builtin.assert:
that:
- _doc_stat.stat.exists
fail_msg: "docs file missing: docs/{{ doc_filename }}"
- name: Find matching ansible/files doc_id directory
ansible.builtin.find:
paths: "{{ repo_root }}/ansible/files"
file_type: directory
patterns: "{{ doc_id }}"
use_regex: false
register: _files_dirs
- name: Fail when ansible/files doc_id directory missing
ansible.builtin.assert:
that:
- _files_dirs.matched | int >= 1
fail_msg: "ansible/files missing doc_id directory: ansible/files/{{ doc_id }}"
- name: Show noop verification summary
ansible.builtin.debug:
msg:
- "doc_id={{ doc_id }}"
- "doc={{ doc_filename }}"
- "files_dirs={{ _files_dirs.files | map(attribute='path') | list }}"
- name: Verify cluster reachable (kubectl get nodes) [runbook baseline]
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl get nodes
args:
executable: /bin/bash
delegate_to: "{{ groups['k3s_server'][0] }}"
become: true
run_once: true
changed_when: false
- name: Verify core namespace exists (kube-system) [runbook baseline]
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl get ns kube-system
args:
executable: /bin/bash
delegate_to: "{{ groups['k3s_server'][0] }}"
become: true
run_once: true
changed_when: false
- name: Find YAML manifests under ansible/files doc_id dirs
ansible.builtin.find:
paths: "{{ _files_dirs.files | map(attribute='path') | list }}"
file_type: file
patterns:
- "*.yml"
- "*.yaml"
recurse: true
use_regex: false
register: _files_manifests
- name: Show manifest count summary
ansible.builtin.debug:
msg:
- "doc_id={{ doc_id }}"
- "manifest_files={{ _files_manifests.matched | default(0) }}"
- "manifest_paths={{ (_files_manifests.files | map(attribute='path') | list)[:12] }}"
- name: Server-side dry-run apply (kubectl apply --dry-run=server) [doc assertion]
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} \
kubectl apply --dry-run=server -f "{{ item.path }}"
args:
executable: /bin/bash
loop: "{{ _files_manifests.files }}"
loop_control:
label: "{{ item.path }}"
delegate_to: "{{ groups['k3s_server'][0] }}"
become: true
run_once: true
changed_when: false
when: (_files_manifests.matched | default(0) | int) > 0
- name: Fail when SKIP_ARMV7=0 but no ARMV7_SSH / ARMV7_NFS_SSH
ansible.builtin.fail:
msg: "SKIP_ARMV7=0 但未设置 ARMV7_SSH或 ARMV7_NFS_SSH 指向 NFS 所在 arm 主机)"
when: skip_armv7 == '0' and armv7_nfs_ssh | length == 0
- name: Note skipping remote NFS setup
ansible.builtin.debug:
msg: "SKIP_ARMV7={{ skip_armv7 }}:跳过 arm NFS 远程配置。"
when: skip_armv7 != '0' or armv7_nfs_ssh | length == 0
- name: Remote NFS install (dnf on arm)
when: skip_armv7 == '0' and armv7_nfs_ssh | length > 0
block:
- name: Install nfs-utils and enable nfs-server
ansible.builtin.shell: "{{ armv7_nfs_ssh }} 'sudo dnf install -y nfs-utils && sudo systemctl enable --now nfs-server'"
- name: Check if export path already in /etc/exports
ansible.builtin.shell: "{{ armv7_nfs_ssh }} sudo grep -qF {{ armv7_nfs_export_path | quote }} /etc/exports"
register: armv7_exports_grep
failed_when: false
changed_when: false
- name: Append NFS export line
ansible.builtin.shell: "{{ armv7_nfs_ssh }} bash -c 'echo \"{{ armv7_nfs_export_path }} {{ armv7_nfs_client_subnet }}(rw,sync,no_subtree_check,no_root_squash)\" | sudo tee -a /etc/exports'"
when: armv7_exports_grep.rc != 0
- name: Apply exportfs
ansible.builtin.shell: "{{ armv7_nfs_ssh }} sudo exportfs -rav"
changed_when: true
- name: Verify showmount
ansible.builtin.shell: "{{ armv7_nfs_ssh }} showmount -e localhost"
changed_when: false

View File

@@ -1,145 +1,347 @@
# SKIP_ARMV7=1默认仅 noop。
# SKIP_ARMV7=0 且 ARMV7_NFS_SSH 或 ARMV7_SSH经 SSH 在 arm 上 dnf 装 nfs-utils、写 /etc/exports、exportfs见 docs/01-05
# 导出路径/网段ARMV7_NFS_EXPORT_PATH默认 /sdcard、ARMV7_NFS_CLIENT_SUBNET默认 192.168.2.0/24
- name: 01-05 armv7 NFS矩阵 + 可选远程安装)
hosts: localhost
gather_facts: false
---
# 单文件化说明:
# - 01-05.yml 默认仍做“最小 verify”kube-system pods
# - 如需“准备数据盘/安装 K3s”必须显式开启开关
# -e k3s_do_prepare_storage=true # 内联原 01-05-prepare-storage.yml
# -e k3s_do_install=true # 内联原 01-05-install.yml
# 或 source ansible/env/.env.verify 后由环境变量 K3S_DO_PREPARE_STORAGE / K3S_DO_INSTALLtrue/false开启
- name: Prepare data disk and mount to k3s_data_dir (opt-in)
hosts: k3s_nodes
become: true
vars:
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "01-05"
doc_filename: "01-05-armv7-nfs服务安装.md"
skip_armv7: "{{ lookup('env', 'SKIP_ARMV7') | default('1', true) | trim }}"
armv7_ssh: "{{ lookup('env', 'ARMV7_SSH') | default('', true) | trim }}"
armv7_nfs_export_path: "{{ lookup('env', 'ARMV7_NFS_EXPORT_PATH') | default('/sdcard', true) | trim }}"
armv7_nfs_client_subnet: "{{ lookup('env', 'ARMV7_NFS_CLIENT_SUBNET') | default('192.168.2.0/24', true) | trim }}"
_k3s_do_prepare_storage: "{{ k3s_do_prepare_storage | default((lookup('env', 'K3S_DO_PREPARE_STORAGE') | default('', true) | trim | lower in ['true', '1', 'yes']) | bool) }}"
k3s_do_prepare_storage_enabled: "{{ _k3s_do_prepare_storage | bool }}"
pre_tasks:
- name: Gate - skip prepare storage when k3s_do_prepare_storage=false
when: not k3s_do_prepare_storage_enabled
block:
- ansible.builtin.debug:
msg: "[SKIP] optional doc_id=01-05 action=prepare-storage var=k3s_do_prepare_storage"
- meta: end_play
tasks:
- name: Resolve ARMV7_NFS_SSH from env
ansible.builtin.set_fact:
armv7_nfs_ssh: >-
{% set n = lookup('env', 'ARMV7_NFS_SSH') | default('', true) | trim %}
{% set b = lookup('env', 'ARMV7_SSH') | default('', true) | trim %}
{{ n if n | length > 0 else b }}
- name: Baseline docs/files checks
block:
- name: Assert docs file exists
ansible.builtin.stat:
path: "{{ repo_root }}/docs/{{ doc_filename }}"
register: _doc_stat
- name: Fail when docs file missing
ansible.builtin.assert:
that:
- _doc_stat.stat.exists
fail_msg: "docs file missing: docs/{{ doc_filename }}"
- name: Find matching ansible/files doc_id directory
ansible.builtin.find:
paths: "{{ repo_root }}/ansible/files"
file_type: directory
patterns: "{{ doc_id }}"
use_regex: false
register: _files_dirs
- name: Fail when ansible/files doc_id directory missing
ansible.builtin.assert:
that:
- _files_dirs.matched | int >= 1
fail_msg: "ansible/files missing doc_id directory: ansible/files/{{ doc_id }}"
- name: Show noop verification summary
ansible.builtin.debug:
msg:
- "doc_id={{ doc_id }}"
- "doc={{ doc_filename }}"
- "files_dirs={{ _files_dirs.files | map(attribute='path') | list }}"
- name: Verify cluster reachable (kubectl get nodes) [runbook baseline]
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl get nodes
args:
executable: /bin/bash
delegate_to: "{{ groups['k3s_server'][0] }}"
become: true
run_once: true
changed_when: false
- name: Verify core namespace exists (kube-system) [runbook baseline]
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} kubectl get ns kube-system
args:
executable: /bin/bash
delegate_to: "{{ groups['k3s_server'][0] }}"
become: true
run_once: true
changed_when: false
- name: Find YAML manifests under ansible/files doc_id dirs
ansible.builtin.find:
paths: "{{ _files_dirs.files | map(attribute='path') | list }}"
file_type: file
patterns:
- "*.yml"
- "*.yaml"
recurse: true
use_regex: false
register: _files_manifests
- name: Show manifest count summary
ansible.builtin.debug:
msg:
- "doc_id={{ doc_id }}"
- "manifest_files={{ _files_manifests.matched | default(0) }}"
- "manifest_paths={{ (_files_manifests.files | map(attribute='path') | list)[:12] }}"
- name: Server-side dry-run apply (kubectl apply --dry-run=server) [doc assertion]
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig | default('/etc/rancher/k3s/k3s.yaml') }} \
kubectl apply --dry-run=server -f "{{ item.path }}"
args:
executable: /bin/bash
loop: "{{ _files_manifests.files }}"
loop_control:
label: "{{ item.path }}"
delegate_to: "{{ groups['k3s_server'][0] }}"
become: true
run_once: true
changed_when: false
when: (_files_manifests.matched | default(0) | int) > 0
- name: Fail when SKIP_ARMV7=0 but no ARMV7_SSH / ARMV7_NFS_SSH
ansible.builtin.fail:
msg: "SKIP_ARMV7=0 但未设置 ARMV7_SSH或 ARMV7_NFS_SSH 指向 NFS 所在 arm 主机)"
when: skip_armv7 == '0' and armv7_nfs_ssh | length == 0
- name: Note skipping remote NFS setup
- name: Skip notice when storage prep disabled
ansible.builtin.debug:
msg: "SKIP_ARMV7={{ skip_armv7 }}:跳过 arm NFS 远程配置。"
when: skip_armv7 != '0' or armv7_nfs_ssh | length == 0
msg: "k3s_prepare_storage is false — skipping (see group_vars/all.yml)"
when: not (k3s_prepare_storage | default(false) | bool)
- name: Remote NFS install (dnf on arm)
when: skip_armv7 == '0' and armv7_nfs_ssh | length > 0
- name: Prepare block storage for k3s_data_dir
when: k3s_prepare_storage | default(false) | bool
block:
- name: Install nfs-utils and enable nfs-server
ansible.builtin.shell: "{{ armv7_nfs_ssh }} 'sudo dnf install -y nfs-utils && sudo systemctl enable --now nfs-server'"
- name: Check if export path already in /etc/exports
ansible.builtin.shell: "{{ armv7_nfs_ssh }} sudo grep -qF {{ armv7_nfs_export_path | quote }} /etc/exports"
register: armv7_exports_grep
# 先判挂载:已挂载则不再要求 k3s_data_disk_device避免「目录已就绪仍 assert 磁盘」)
- name: Check whether k3s_data_dir is already a mountpoint
ansible.builtin.command: mountpoint -q {{ k3s_data_dir }}
register: mp_k3s
changed_when: false
failed_when: false
- name: Skip when k3s_data_dir already mounted
ansible.builtin.debug:
msg: "{{ k3s_data_dir }} already mounted — skipping partitioning on {{ inventory_hostname }}"
when: mp_k3s.rc == 0
- name: Require k3s_data_disk_device only when partition work is needed
ansible.builtin.assert:
that:
- k3s_data_disk_device is defined
- (k3s_data_disk_device | string | length) > 0
fail_msg: "Set k3s_data_disk_device (e.g. /dev/vdb) in group_vars or host_vars"
when: mp_k3s.rc != 0
- name: Verify k3s_data_disk_device is a block device
ansible.builtin.command: test -b {{ k3s_data_disk_device }}
changed_when: false
when: mp_k3s.rc != 0
- name: Append NFS export line
ansible.builtin.shell: "{{ armv7_nfs_ssh }} bash -c 'echo \"{{ armv7_nfs_export_path }} {{ armv7_nfs_client_subnet }}(rw,sync,no_subtree_check,no_root_squash)\" | sudo tee -a /etc/exports'"
when: armv7_exports_grep.rc != 0
- name: Install partitioning and filesystem tools
ansible.builtin.package:
name:
- parted
- e2fsprogs
state: present
when: mp_k3s.rc != 0
- name: Apply exportfs
ansible.builtin.shell: "{{ armv7_nfs_ssh }} sudo exportfs -rav"
- name: Compute first partition path (nvme*n* -> p1, else 1)
ansible.builtin.set_fact:
k3s_data_partition: >-
{{ k3s_data_disk_device }}{{ 'p1' if (k3s_data_disk_device | regex_search('nvme[0-9]+n[0-9]+$')) else '1' }}
when: mp_k3s.rc != 0
- name: Create GPT and single ext4 partition
ansible.builtin.command: >-
parted -s {{ k3s_data_disk_device }} mklabel gpt mkpart primary ext4 0% 100%
args:
creates: "{{ k3s_data_partition }}"
when: mp_k3s.rc != 0
- name: Wait for partition node in /dev
ansible.builtin.wait_for:
path: "{{ k3s_data_partition }}"
state: present
timeout: 60
when: mp_k3s.rc != 0
- name: Detect existing filesystem on partition
ansible.builtin.command: blkid -s TYPE -o value {{ k3s_data_partition }}
register: fs_type
changed_when: false
failed_when: false
when: mp_k3s.rc != 0
- name: Create ext4 on partition
ansible.builtin.command: mkfs.ext4 -F {{ k3s_data_partition }}
when:
- mp_k3s.rc != 0
- (fs_type.stdout | default('') | trim | length) == 0
- name: Read UUID of partition
ansible.builtin.command: blkid -s UUID -o value {{ k3s_data_partition }}
register: blk_uuid
changed_when: false
when: mp_k3s.rc != 0
- name: Ensure mount directory exists
ansible.builtin.file:
path: "{{ k3s_data_dir }}"
state: directory
mode: "0755"
when: mp_k3s.rc != 0
- name: Add fstab entry for k3s_data_dir
ansible.builtin.lineinfile:
path: /etc/fstab
regexp: "^UUID={{ blk_uuid.stdout | trim }}\\s"
line: "UUID={{ blk_uuid.stdout | trim }} {{ k3s_data_dir }} ext4 defaults,nofail 0 2"
create: true
mode: "0644"
when: mp_k3s.rc != 0
- name: Mount all from fstab
ansible.builtin.command: mount -a
changed_when: true
when: mp_k3s.rc != 0
- name: Verify showmount
ansible.builtin.shell: "{{ armv7_nfs_ssh }} showmount -e localhost"
- name: Install K3s (opt-in)
hosts: k3s_server
become: true
run_once: true
vars:
_k3s_do_install: "{{ k3s_do_install | default((lookup('env', 'K3S_DO_INSTALL') | default('', true) | trim | lower in ['true', '1', 'yes']) | bool) }}"
k3s_do_install_enabled: "{{ _k3s_do_install | bool }}"
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
k3s_verify_storage_mount_enabled: "{{ k3s_verify_storage_mount | default(true) | bool }}"
pre_tasks:
- name: Gate - skip install when k3s_do_install=false
when: not k3s_do_install_enabled
block:
- ansible.builtin.debug:
msg: "[SKIP] optional doc_id=01-05 action=install var=k3s_do_install"
- meta: end_play
tasks:
- name: Require k3s_server_ip
ansible.builtin.assert:
that:
- k3s_server_ip is defined
- (k3s_server_ip | string | length) > 0
fail_msg: "k3s_server_ip 未配置,请在 ansible/group_vars/all.yml 设置"
- name: Verify /storage mountpoint when enabled
when: k3s_verify_storage_mount_enabled
block:
- name: Ensure k3s_data_dir is mountpoint
ansible.builtin.command: mountpoint -q {{ k3s_data_dir }}
changed_when: false
- name: Read root and k3s_data_dir mount sources
ansible.builtin.shell: |
set -euo pipefail
root_src=$(findmnt -n -o SOURCE /)
data_src=$(findmnt -n -o SOURCE {{ k3s_data_dir }})
echo "root=${root_src}"
echo "data=${data_src}"
test "${root_src}" != "${data_src}"
args:
executable: /bin/bash
changed_when: false
- name: Install required packages for k3s install
ansible.builtin.package:
name:
- curl
- tar
- iproute
state: present
- name: Check k3s binary
ansible.builtin.stat:
path: /usr/local/bin/k3s
register: _k3s_bin
- name: Note k3s server install network expectations
when: not _k3s_bin.stat.exists
ansible.builtin.debug:
msg: "正在下载安装 k3s serverget.k3s.io最长约 {{ k3s_install_curl_max_time | default(600) }}s久无输出多为网络问题可在 group_vars 设 k3s_install_mirror: cn 或调大 k3s_install_curl_max_time"
- name: Install k3s server when binary absent
when: not _k3s_bin.stat.exists
ansible.builtin.shell: |
set -euo pipefail
curl --connect-timeout 30 --max-time {{ k3s_install_curl_max_time | default(600) | int }} -sfL https://get.k3s.io | \
{{ ('INSTALL_K3S_MIRROR=' ~ (k3s_install_mirror | default('') | trim) ~ ' ') if (k3s_install_mirror | default('') | trim | length > 0) else '' }}{{ ('INSTALL_K3S_VERSION=' ~ k3s_version ~ ' ') if (k3s_version | default('') | trim | length > 0) else '' }}INSTALL_K3S_EXEC="server --data-dir {{ k3s_data_dir }} --write-kubeconfig-mode 644" sh -
args:
executable: /bin/bash
timeout: "{{ k3s_install_task_timeout | default(720) | int }}"
- name: Ensure k3s service enabled and started
ansible.builtin.service:
name: k3s
enabled: true
state: started
- name: Wait k3s kubeconfig ready
ansible.builtin.wait_for:
path: "{{ k3s_kubeconfig }}"
state: present
timeout: 300
- name: Wait server node Ready
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl get node "{{ inventory_hostname }}" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'
args:
executable: /bin/bash
register: _server_ready
changed_when: false
until: _server_ready.stdout | trim == "True"
retries: 60
delay: 5
- name: Read k3s server token
ansible.builtin.slurp:
path: "{{ k3s_data_dir }}/server/token"
register: _server_token_raw
- name: Save k3s token for workers
ansible.builtin.set_fact:
k3s_server_token: "{{ _server_token_raw.content | b64decode | trim }}"
- name: Install K3s workers (opt-in)
hosts: k3s_worker
become: true
serial: 1
vars:
_k3s_do_install: "{{ k3s_do_install | default((lookup('env', 'K3S_DO_INSTALL') | default('', true) | trim | lower in ['true', '1', 'yes']) | bool) }}"
k3s_do_install_enabled: "{{ _k3s_do_install | bool }}"
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
k3s_verify_storage_mount_enabled: "{{ k3s_verify_storage_mount | default(true) | bool }}"
k3s_server_host: "{{ groups['k3s_server'][0] }}"
k3s_join_token: "{{ hostvars[k3s_server_host].k3s_server_token | default('') }}"
pre_tasks:
- name: Gate - skip worker install when k3s_do_install=false
when: not k3s_do_install_enabled
block:
- ansible.builtin.debug:
msg: "[SKIP] optional doc_id=01-05 action=worker-install var=k3s_do_install"
- meta: end_play
tasks:
- name: Require k3s join token
ansible.builtin.assert:
that:
- (k3s_join_token | trim | length) > 0
fail_msg: "k3s join token 为空,请先确保 server 安装成功"
- name: Verify /storage mountpoint on worker when enabled
when: k3s_verify_storage_mount_enabled
block:
- name: Ensure k3s_data_dir is mountpoint
ansible.builtin.command: mountpoint -q {{ k3s_data_dir }}
changed_when: false
- name: Read root and k3s_data_dir mount sources
ansible.builtin.shell: |
set -euo pipefail
root_src=$(findmnt -n -o SOURCE /)
data_src=$(findmnt -n -o SOURCE {{ k3s_data_dir }})
test "${root_src}" != "${data_src}"
args:
executable: /bin/bash
changed_when: false
- name: Install required packages for worker install
ansible.builtin.package:
name:
- curl
- tar
- iproute
state: present
- name: Check k3s-agent binary
ansible.builtin.stat:
path: /usr/local/bin/k3s-agent
register: _k3s_agent_bin
- name: Note k3s agent install network expectations
when: not _k3s_agent_bin.stat.exists
ansible.builtin.debug:
msg: "正在本节点下载安装 k3s-agentget.k3s.io → GitHub最长约 {{ k3s_install_curl_max_time | default(600) }}s卡住时请检查 worker 出网或设 k3s_install_mirror: cn"
- name: Install k3s worker when binary absent
when: not _k3s_agent_bin.stat.exists
ansible.builtin.shell: |
set -euo pipefail
curl --connect-timeout 30 --max-time {{ k3s_install_curl_max_time | default(600) | int }} -sfL https://get.k3s.io | \
{{ ('INSTALL_K3S_MIRROR=' ~ (k3s_install_mirror | default('') | trim) ~ ' ') if (k3s_install_mirror | default('') | trim | length > 0) else '' }}{{ ('INSTALL_K3S_VERSION=' ~ k3s_version ~ ' ') if (k3s_version | default('') | trim | length > 0) else '' }}K3S_URL="https://{{ k3s_server_ip }}:6443" K3S_TOKEN={{ k3s_join_token | quote }} INSTALL_K3S_EXEC="agent --data-dir {{ k3s_data_dir }}" sh -
args:
executable: /bin/bash
timeout: "{{ k3s_install_task_timeout | default(720) | int }}"
- name: Ensure k3s-agent service enabled and started
ansible.builtin.service:
name: k3s-agent
enabled: true
state: started
# 不在 worker 上 delegate_to server部分环境下会从 worker 上下文连控制机 SSH 失败(如 192.168.2.61:22 timeout
# 改为独立 play仅由控制端 SSH → k3s_server 执行 kubectl与「Install K3s server」连接路径一致。
- name: Wait k3s workers Ready from server (post-install)
hosts: k3s_server
become: true
run_once: true
vars:
_k3s_do_install: "{{ k3s_do_install | default((lookup('env', 'K3S_DO_INSTALL') | default('', true) | trim | lower in ['true', '1', 'yes']) | bool) }}"
k3s_do_install_enabled: "{{ _k3s_do_install | bool }}"
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
pre_tasks:
- name: Gate - skip when k3s_do_install=false
when: not k3s_do_install_enabled
block:
- ansible.builtin.debug:
msg: "[SKIP] optional doc_id=01-05 action=wait-workers-ready var=k3s_do_install"
- meta: end_play
tasks:
- name: Wait each worker node Ready
when: (groups['k3s_worker'] | default([])) | length > 0
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG={{ k3s_kubeconfig | quote }}
kubectl wait --for=condition=Ready "node/{{ item }}" --timeout=320s
args:
executable: /bin/bash
loop: "{{ groups['k3s_worker'] }}"
changed_when: false
- name: "01-05 k3s baseline verify (kube-system pods)"
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
tasks:
- name: kubectl get nodes
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get nodes -o wide
changed_when: false
- name: kube-system pods summary
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get pods -n kube-system -o wide
changed_when: false

View File

@@ -1,146 +1,12 @@
---
# 单文件化说明:
# - 01-06.yml 默认仍做“最小 verify”kube-system pods
# - 如需“准备数据盘/安装 K3s”必须显式开启开关
# -e k3s_do_prepare_storage=true # 内联原 01-06-prepare-storage.yml
# -e k3s_do_install=true # 内联原 01-06-install.yml
- name: Prepare data disk and mount to k3s_data_dir (opt-in)
hosts: k3s_nodes
become: true
- name: "01-06 noop verify"
hosts: localhost
gather_facts: false
vars:
k3s_do_prepare_storage: "{{ k3s_do_prepare_storage | default(false) | bool }}"
pre_tasks:
- name: Gate - skip prepare storage when k3s_do_prepare_storage=false
when: not k3s_do_prepare_storage
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=01-06 action=prepare-storage var=k3s_do_prepare_storage"
- meta: end_play
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "01-06"
doc_filename: "01-06-openwrt-haproxy.md"
tasks:
- name: Skip notice when storage prep disabled
ansible.builtin.debug:
msg: "k3s_prepare_storage is false — skipping (see group_vars/all.yml)"
when: not (k3s_prepare_storage | default(false) | bool)
- name: Prepare block storage for k3s_data_dir
when: k3s_prepare_storage | default(false) | bool
block:
- name: Require k3s_data_disk_device when k3s_prepare_storage is true
ansible.builtin.assert:
that:
- k3s_data_disk_device is defined
- (k3s_data_disk_device | string | length) > 0
fail_msg: "Set k3s_data_disk_device (e.g. /dev/vdb) in group_vars or host_vars"
- name: Verify k3s_data_disk_device is a block device
ansible.builtin.command: test -b {{ k3s_data_disk_device }}
changed_when: false
- name: Check whether k3s_data_dir is already a mountpoint
ansible.builtin.command: mountpoint -q {{ k3s_data_dir }}
register: mp_k3s
changed_when: false
failed_when: false
- name: Skip when k3s_data_dir already mounted
ansible.builtin.debug:
msg: "{{ k3s_data_dir }} already mounted — skipping partitioning on {{ inventory_hostname }}"
when: mp_k3s.rc == 0
- name: Install partitioning and filesystem tools
ansible.builtin.package:
name:
- parted
- e2fsprogs
state: present
when: mp_k3s.rc != 0
- name: Compute first partition path (nvme*n* -> p1, else 1)
ansible.builtin.set_fact:
k3s_data_partition: >-
{{ k3s_data_disk_device }}{{ 'p1' if (k3s_data_disk_device | regex_search('nvme[0-9]+n[0-9]+$')) else '1' }}
when: mp_k3s.rc != 0
- name: Create GPT and single ext4 partition
ansible.builtin.command: >-
parted -s {{ k3s_data_disk_device }} mklabel gpt mkpart primary ext4 0% 100%
args:
creates: "{{ k3s_data_partition }}"
when: mp_k3s.rc != 0
- name: Wait for partition node in /dev
ansible.builtin.wait_for:
path: "{{ k3s_data_partition }}"
state: present
timeout: 60
when: mp_k3s.rc != 0
- name: Detect existing filesystem on partition
ansible.builtin.command: blkid -s TYPE -o value {{ k3s_data_partition }}
register: fs_type
changed_when: false
failed_when: false
when: mp_k3s.rc != 0
- name: Create ext4 on partition
ansible.builtin.command: mkfs.ext4 -F {{ k3s_data_partition }}
when:
- mp_k3s.rc != 0
- (fs_type.stdout | default('') | trim | length) == 0
- name: Read UUID of partition
ansible.builtin.command: blkid -s UUID -o value {{ k3s_data_partition }}
register: blk_uuid
changed_when: false
when: mp_k3s.rc != 0
- name: Ensure mount directory exists
ansible.builtin.file:
path: "{{ k3s_data_dir }}"
state: directory
mode: "0755"
when: mp_k3s.rc != 0
- name: Add fstab entry for k3s_data_dir
ansible.builtin.lineinfile:
path: /etc/fstab
regexp: "^UUID={{ blk_uuid.stdout | trim }}\\s"
line: "UUID={{ blk_uuid.stdout | trim }} {{ k3s_data_dir }} ext4 defaults,nofail 0 2"
create: true
mode: "0644"
when: mp_k3s.rc != 0
- name: Mount all from fstab
ansible.builtin.command: mount -a
changed_when: true
when: mp_k3s.rc != 0
- name: Install K3s (opt-in)
hosts: k3s_nodes
become: true
vars:
k3s_do_install: "{{ k3s_do_install | default(false) | bool }}"
pre_tasks:
- name: Gate - skip install when k3s_do_install=false
when: not k3s_do_install
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=01-06 action=install var=k3s_do_install"
- meta: end_play
tasks:
- name: Placeholder (install content inlined in following plays)
ansible.builtin.debug:
msg: "[RUN] doc_id=01-06 action=install-start"
- name: "01-06 k3s baseline verify (kube-system pods)"
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
tasks:
- name: kube-system pods summary
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl get pods -n kube-system -o wide
changed_when: false
- name: Include noop doc verify role tasks
ansible.builtin.include_role:
name: verify_common
tasks_from: noop-doc-verify.yml

View File

@@ -4,7 +4,23 @@
vars:
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "01-07"
doc_filename: "01-07-openwrt-haproxy.md"
doc_filename: "01-07-双控制节点ha.md"
tasks:
- name: Include noop doc verify tasks
ansible.builtin.include_tasks: tasks/noop-doc-verify.yml
- name: Assert docs file exists
ansible.builtin.stat:
path: "{{ repo_root }}/docs/{{ doc_filename }}"
register: _doc
- name: Fail when docs file missing
ansible.builtin.assert:
that:
- _doc.stat.exists
fail_msg: "docs missing: docs/{{ doc_filename }}"
- name: Summary
ansible.builtin.debug:
msg:
- "doc_id={{ doc_id }} (manual runbook / HA exercise)"
- "This verify case only asserts docs file exists."
- "HA join/switch must be exercised manually per docs/{{ doc_filename }}."

View File

@@ -1,26 +0,0 @@
- name: "01-08 noop verify"
hosts: localhost
gather_facts: false
vars:
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "01-08"
doc_filename: "01-08-双控制节点ha.md"
tasks:
- name: Assert docs file exists
ansible.builtin.stat:
path: "{{ repo_root }}/docs/{{ doc_filename }}"
register: _doc
- name: Fail when docs file missing
ansible.builtin.assert:
that:
- _doc.stat.exists
fail_msg: "docs missing: docs/{{ doc_filename }}"
- name: Summary
ansible.builtin.debug:
msg:
- "doc_id={{ doc_id }} (manual runbook / HA exercise)"
- "This verify case only asserts docs file exists."
- "HA join/switch must be exercised manually per docs/{{ doc_filename }}."

View File

@@ -4,7 +4,7 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/02-05/01-control-ingress.yaml"
manifest_src: "{{ playbook_dir }}/../../files/02-01/01-control-ingress.yaml"
manifest_dest: /tmp/nginx-m1.yaml
tasks:
- name: Copy manifest
@@ -27,35 +27,24 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_entry_base: "{{ nginx_entry_base | default('http://' ~ k3s_server_ip) }}"
tasks:
- name: Rollout status nginx-m1
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nginx-m1 -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/nginx-m1
- name: HTTP check /demo-m1 (retry 503 for convergence)
ansible.builtin.shell: |
set -e
base="{{ verify_entry_base | trim | regex_replace('/+$','') }}"
url="$base/demo-m1/"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
echo "try $i: $url -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
backend=$(curl -sS -D - -o /dev/null --connect-timeout 3 --max-time 8 "$url" 2>/dev/null | awk -F': ' '/^X-Backend:/{print $2; exit}' | tr -d '\r')
echo "X-Backend=$backend"
test "$backend" = "M1"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check /demo-m1 (in-cluster via Service ClusterIP)
ansible.builtin.include_role:
name: verify_common
tasks_from: http-curl-traefik-incluster.yml
vars:
verify_traefik_kubeconfig: "{{ k3s_kubeconfig }}"
verify_incluster_http_url: "http://nginx-m1.default.svc.cluster.local/"
verify_traefik_assertion: nginx_matrix_m1_entry_http
verify_traefik_header_name: X-Backend
verify_traefik_header_value: M1
- name: Teardown 02-01 nginx control + Ingress (M1)
hosts: k3s_server

View File

@@ -8,7 +8,7 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/02-05/02-control-ingressroute.yaml"
manifest_src: "{{ playbook_dir }}/../../files/02-02/02-control-ingressroute.yaml"
manifest_dest: /tmp/nginx-m2.yaml
tasks:
- name: Copy manifest
@@ -31,35 +31,24 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_entry_base: "{{ nginx_entry_base | default('http://' ~ k3s_server_ip) }}"
tasks:
- name: Rollout status nginx-m2
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nginx-m2 -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/nginx-m2
- name: HTTP check /demo-m2 (retry 503 for convergence)
ansible.builtin.shell: |
set -e
base="{{ verify_entry_base | trim | regex_replace('/+$','') }}"
url="$base/demo-m2/"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
echo "try $i: $url -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
backend=$(curl -sS -D - -o /dev/null --connect-timeout 3 --max-time 8 "$url" 2>/dev/null | awk -F': ' '/^X-Backend:/{print $2; exit}' | tr -d '\r')
echo "X-Backend=$backend"
test "$backend" = "M2"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check /demo-m2 (in-cluster via Service ClusterIP)
ansible.builtin.include_role:
name: verify_common
tasks_from: http-curl-traefik-incluster.yml
vars:
verify_traefik_kubeconfig: "{{ k3s_kubeconfig }}"
verify_incluster_http_url: "http://nginx-m2.default.svc.cluster.local/"
verify_traefik_assertion: nginx_matrix_m2_entry_http
verify_traefik_header_name: X-Backend
verify_traefik_header_value: M2
- name: Teardown 02-02 nginx control + IngressRoute (M2)
hosts: k3s_server

View File

@@ -4,7 +4,7 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/02-05/03-worker-ingress.yaml"
manifest_src: "{{ playbook_dir }}/../../files/02-03/03-worker-ingress.yaml"
manifest_dest: /tmp/nginx-m3.yaml
tasks:
- name: Copy manifest
@@ -27,35 +27,24 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_entry_base: "{{ nginx_entry_base | default('http://' ~ k3s_server_ip) }}"
tasks:
- name: Rollout status nginx-m3
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nginx-m3 -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/nginx-m3
- name: HTTP check /demo-m3 (retry 503 for convergence)
ansible.builtin.shell: |
set -e
base="{{ verify_entry_base | trim | regex_replace('/+$','') }}"
url="$base/demo-m3/"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
echo "try $i: $url -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
backend=$(curl -sS -D - -o /dev/null --connect-timeout 3 --max-time 8 "$url" 2>/dev/null | awk -F': ' '/^X-Backend:/{print $2; exit}' | tr -d '\r')
echo "X-Backend=$backend"
test "$backend" = "M3"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check /demo-m3 (in-cluster via Service ClusterIP)
ansible.builtin.include_role:
name: verify_common
tasks_from: http-curl-traefik-incluster.yml
vars:
verify_traefik_kubeconfig: "{{ k3s_kubeconfig }}"
verify_incluster_http_url: "http://nginx-m3.default.svc.cluster.local/"
verify_traefik_assertion: nginx_matrix_m3_entry_http
verify_traefik_header_name: X-Backend
verify_traefik_header_value: M3
- name: Teardown 02-03 nginx worker + Ingress (M3)
hosts: k3s_server

View File

@@ -4,7 +4,7 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/02-05/04-worker-ingressroute.yaml"
manifest_src: "{{ playbook_dir }}/../../files/02-04/04-worker-ingressroute.yaml"
manifest_dest: /tmp/nginx-m4.yaml
tasks:
- name: Copy manifest
@@ -27,35 +27,24 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_entry_base: "{{ nginx_entry_base | default('http://' ~ k3s_server_ip) }}"
tasks:
- name: Rollout status nginx-m4
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nginx-m4 -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/nginx-m4
- name: HTTP check /demo-m4 (retry 503 for convergence)
ansible.builtin.shell: |
set -e
base="{{ verify_entry_base | trim | regex_replace('/+$','') }}"
url="$base/demo-m4/"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
echo "try $i: $url -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
backend=$(curl -sS -D - -o /dev/null --connect-timeout 3 --max-time 8 "$url" 2>/dev/null | awk -F': ' '/^X-Backend:/{print $2; exit}' | tr -d '\r')
echo "X-Backend=$backend"
test "$backend" = "M4"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check /demo-m4 (in-cluster via Service ClusterIP)
ansible.builtin.include_role:
name: verify_common
tasks_from: http-curl-traefik-incluster.yml
vars:
verify_traefik_kubeconfig: "{{ k3s_kubeconfig }}"
verify_incluster_http_url: "http://nginx-m4.default.svc.cluster.local/"
verify_traefik_assertion: nginx_matrix_m4_entry_http
verify_traefik_header_name: X-Backend
verify_traefik_header_value: M4
- name: Teardown 02-04 nginx worker + IngressRoute (M4)
hosts: k3s_server

View File

@@ -1,7 +1,7 @@
---
# 合并说明:
# - 原 02-05.yml 仅 import 02-05-deploy.yml + 02-01..02-04
# - 现已把 02-05-deploy.yml 内联到本文件,保持 scripts/verify.sh run 02-05 的语义不变
# - 现已把 02-05-deploy.yml 内联到本文件,保持 ansible/bin/verify.sh run 02-05 的语义不变
- name: Deploy nginx matrix (M1~M4)
hosts: k3s_server
@@ -56,17 +56,22 @@
register: restart_out
changed_when: true
- name: Wait for nginx pods to be ready
# rollout restart 后 Pod 名频繁更替kubectl wait pod -l 可能竞态 NotFound改用 rollout status
- name: Wait for nginx rollouts stable after ConfigMap restart
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m1 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m2 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m3 --timeout=120s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod \
-l app=nginx-m4 --timeout=120s
register: wait_result
set -euo pipefail
KCFG={{ k3s_kubeconfig | quote }}
export KUBECONFIG="$KCFG"
for dep in nginx-m1 nginx-m2; do
echo "[OC-ASSERT] assertion=nginx_matrix_rollout deployment=${dep} timeout=60s"
kubectl rollout status "deployment/$dep" -n default --timeout=60s
done
for dep in nginx-m3 nginx-m4; do
echo "[OC-ASSERT] assertion=nginx_matrix_rollout deployment=${dep} timeout=120s"
kubectl rollout status "deployment/$dep" -n default --timeout=120s
done
args:
executable: /bin/bash
changed_when: false
- name: Verify nginx matrix

View File

@@ -30,12 +30,13 @@
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
tasks:
- name: Rollout status traefik (kube-system)
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/traefik -n kube-system --timeout=240s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/traefik
verify_rollout_namespace: kube-system
verify_rollout_timeout_s: 240
- name: Assert HelmChartConfig exists
ansible.builtin.shell: |

View File

@@ -8,13 +8,15 @@
manifest_src: "{{ playbook_dir }}/../../files/03-02/traefik-acme.yaml"
manifest_dest: /tmp/traefik-acme.yaml
acme_email: "{{ lookup('env', 'ACME_EMAIL') | default('', true) }}"
cf_api_token: "{{ lookup('env', 'CF_API_TOKEN') | default('', true) }}"
tasks:
- name: "Gate - skip apply when ACME_EMAIL missing"
when: acme_email | trim == ""
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=03-02 reason=missing_env missing=ACME_EMAIL"
- meta: end_play
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-02 reason=missing_env missing=ACME_EMAIL"
- name: Copy manifest
ansible.builtin.copy:
@@ -28,6 +30,21 @@
regexp: "<YOUR_REAL_EMAIL>"
replace: "{{ acme_email | trim }}"
- name: Enable ACME staging CA when ACME_CA_STAGING=1
when: (lookup('env', 'ACME_CA_STAGING') | default('0', true) | trim) == "1"
ansible.builtin.replace:
path: "{{ manifest_dest }}"
regexp: '^\s*# - "--certificatesresolvers\.cloudflare\.acme\.caserver=https://acme-staging-v02\.api\.letsencrypt\.org/directory".*$'
replace: ' - "--certificatesresolvers.cloudflare.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"'
- name: Ensure Cloudflare API token Secret before Traefik ACME apply
when: (cf_api_token | trim | length) > 0
ansible.builtin.include_role:
name: verify_common
tasks_from: ensure-cloudflare-api-token-secret.yml
vars:
verify_cf_api_token: "{{ cf_api_token | trim }}"
- name: Apply manifest
ansible.builtin.shell: |
set -e
@@ -42,7 +59,8 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
nginx_matrix_tls_enable: "{{ nginx_matrix_tls_enable | default(false) | bool }}"
_nginx_matrix_tls_enable: "{{ nginx_matrix_tls_enable | default((lookup('env', 'NGINX_MATRIX_TLS_ENABLE') | default('', true) | trim | lower in ['true', '1', 'yes']) | bool) }}"
nginx_matrix_tls_enabled: "{{ _nginx_matrix_tls_enable | bool }}"
manifests_path: "{{ playbook_dir }}/../../files/03-02"
tls_domains:
- test01.jackadam.top
@@ -51,24 +69,30 @@
- test04.jackadam.top
pre_tasks:
- name: Gate - skip nginx matrix TLS when nginx_matrix_tls_enable=false
when: not nginx_matrix_tls_enable
when: not nginx_matrix_tls_enabled
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=03-02 action=nginx-matrix-tls var=nginx_matrix_tls_enable"
msg: "[SKIP] optional doc_id=03-02 action=nginx-matrix-tls var=nginx_matrix_tls_enable"
- meta: end_play
tasks:
- name: Deploy nginx matrix TLS (mode=deploy)
when: (mode | default('deploy')) == 'deploy'
block:
- name: Ensure manifests path exists
- name: Ensure manifests path exists (controller repo path)
ansible.builtin.stat:
path: "{{ manifests_path }}"
register: manifests_stat
delegate_to: localhost
become: false
run_once: true
- name: Fail if manifests not found
ansible.builtin.fail:
msg: "manifests 未找到: {{ manifests_path }},请从仓库根目录或 ansible 同级执行"
when: not manifests_stat.stat.exists
delegate_to: localhost
become: false
run_once: true
- name: Ensure control-plane label on k3s_server nodes (for M1)
ansible.builtin.shell: |
@@ -105,12 +129,21 @@
ansible.builtin.shell: KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout restart deployment nginx-m1 nginx-m2 nginx-m3 nginx-m4 -n default
changed_when: true
- name: Wait for nginx pods to be ready
- name: Wait for nginx rollouts stable after ConfigMap restart
ansible.builtin.shell: |
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod -l app=nginx-m1 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod -l app=nginx-m2 --timeout=60s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod -l app=nginx-m3 --timeout=120s
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=ready pod -l app=nginx-m4 --timeout=120s
set -euo pipefail
KCFG={{ k3s_kubeconfig | quote }}
export KUBECONFIG="$KCFG"
for dep in nginx-m1 nginx-m2; do
echo "[OC-ASSERT] assertion=nginx_matrix_tls_rollout deployment=${dep} timeout=60s"
kubectl rollout status "deployment/$dep" -n default --timeout=60s
done
for dep in nginx-m3 nginx-m4; do
echo "[OC-ASSERT] assertion=nginx_matrix_tls_rollout deployment=${dep} timeout=120s"
kubectl rollout status "deployment/$dep" -n default --timeout=120s
done
args:
executable: /bin/bash
changed_when: false
- name: Verify nginx matrix TLS resources
@@ -187,29 +220,85 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
acme_email: "{{ lookup('env', 'ACME_EMAIL') | default('', true) }}"
cf_api_token: "{{ lookup('env', 'CF_API_TOKEN') | default('', true) }}"
tasks:
- name: "Gate - skip verify when ACME_EMAIL missing"
when: acme_email | trim == ""
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=03-02 reason=missing_env missing=ACME_EMAIL"
- meta: end_play
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-02 reason=missing_env missing=ACME_EMAIL"
- name: Assert Cloudflare token secret exists
- name: Ensure Cloudflare token Secret from CF_API_TOKEN (real-pass)
when: (cf_api_token | trim | length) > 0
ansible.builtin.include_role:
name: verify_common
tasks_from: ensure-cloudflare-api-token-secret.yml
vars:
verify_cf_api_token: "{{ cf_api_token | trim }}"
- name: Check cloudflare-api-token secret exists
ansible.builtin.shell: |
set -e
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl -n kube-system get secret cloudflare-api-token
args:
executable: /bin/bash
changed_when: false
register: cloudflare_secret_check
failed_when: false
- name: Rollout status traefik (kube-system)
- name: Gate - no CF_API_TOKEN and secret missing
when: cloudflare_secret_check.rc != 0 and (cf_api_token | trim | length) == 0
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-02 reason=missing_dependency missing=cloudflare-api-token skip_scope=traefik-acme"
- name: Fail when secret missing but CF_API_TOKEN was set
when: cloudflare_secret_check.rc != 0 and (cf_api_token | trim | length) > 0
ansible.builtin.fail:
msg: "已设置 CF_API_TOKEN 但 cloudflare-api-token Secret 仍不可用,请检查 apiserver 权限与命名空间 kube-system"
# Helm/ACME 换新 RS 时,旧 Pod 可能长期「pending termination」rollout status 永久卡住。
# 实验室验收scale 0 → 清 Pod → scale 1入口短暂不可用可接受
- name: Unstick Traefik deployment via scale down/up (kube-system)
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/traefik -n kube-system --timeout=300s
set -euo pipefail
export KUBECONFIG={{ k3s_kubeconfig }}
echo "[OC-ASSERT] assertion=traefik_rollout_unblock phase=scale_reset"
kubectl scale deployment traefik -n kube-system --replicas=0
for i in $(seq 1 90); do
rep=$(kubectl get deployment traefik -n kube-system -o jsonpath='{.status.replicas}' 2>/dev/null || echo 1)
[ "${rep:-1}" = "0" ] && break
sleep 2
done
for sel in "app.kubernetes.io/name=traefik" "app.kubernetes.io/instance=traefik"; do
kubectl get pods -n kube-system -l "$sel" -o name 2>/dev/null | while read -r p; do
[ -z "$p" ] && continue
kubectl delete "$p" -n kube-system --grace-period=0 --force --ignore-not-found=true || true
done
done
{ kubectl get pods -n kube-system --no-headers -o custom-columns=:metadata.name 2>/dev/null | grep -E '^traefik-[0-9a-f]+-' || true; } | while read -r n; do
[ -z "$n" ] && continue
kubectl delete pod "$n" -n kube-system --grace-period=0 --force --ignore-not-found=true || true
done
kubectl scale deployment traefik -n kube-system --replicas=1
sleep 3
args:
executable: /bin/bash
changed_when: false
changed_when: true
failed_when: false
- name: Rollout status traefik (kube-system)
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/traefik
verify_rollout_namespace: kube-system
verify_rollout_timeout_s: 600
- name: Teardown 03-02 Traefik ACME (optional)
hosts: k3s_server

View File

@@ -1,10 +1,231 @@
- name: "03-03 noop verify"
hosts: localhost
gather_facts: false
---
# 03-03 Traefik Dashboard + ACMEHelmChartConfig 合并版)
# 与 03-02 共用同一 Traefik HelmChartConfig 资源名traefik勿在无协调下交替 full 验证二者——后 apply 者覆盖前者。
#
- name: Deploy 03-03 Traefik Dashboard + ACME (HelmChartConfig)
hosts: k3s_server
become: true
run_once: true
vars:
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "03-03"
doc_filename: "03-03-k3s-traefik-dashboard-acme.md"
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/03-03/traefik-dashboard-acme.yaml"
manifest_dest: /tmp/traefik-dashboard-acme.yaml
acme_email: "{{ lookup('env', 'ACME_EMAIL') | default('', true) }}"
cf_api_token: "{{ lookup('env', 'CF_API_TOKEN') | default('', true) }}"
tasks:
- name: Include noop doc verify tasks
ansible.builtin.include_tasks: tasks/noop-doc-verify.yml
- name: "Gate - skip apply when ACME_EMAIL missing"
when: acme_email | trim == ""
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-03 reason=missing_env missing=ACME_EMAIL"
- name: Copy manifest
ansible.builtin.copy:
src: "{{ manifest_src }}"
dest: "{{ manifest_dest }}"
mode: "0644"
- name: Replace ACME email placeholder
ansible.builtin.replace:
path: "{{ manifest_dest }}"
regexp: "<YOUR_REAL_EMAIL>"
replace: "{{ acme_email | trim }}"
- name: Enable ACME staging CA when ACME_CA_STAGING=1
when: (lookup('env', 'ACME_CA_STAGING') | default('0', true) | trim) == "1"
ansible.builtin.replace:
path: "{{ manifest_dest }}"
regexp: '^\s*# - "--certificatesresolvers\.cloudflare\.acme\.caserver=https://acme-staging-v02\.api\.letsencrypt\.org/directory".*$'
replace: ' - "--certificatesresolvers.cloudflare.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"'
- name: Ensure Cloudflare API token Secret before Traefik ACME apply
when: (cf_api_token | trim | length) > 0
ansible.builtin.include_role:
name: verify_common
tasks_from: ensure-cloudflare-api-token-secret.yml
vars:
verify_cf_api_token: "{{ cf_api_token | trim }}"
- name: Apply manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Verify 03-03 Traefik Dashboard + ACME (rollout + dashboard http)
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
acme_email: "{{ lookup('env', 'ACME_EMAIL') | default('', true) }}"
cf_api_token: "{{ lookup('env', 'CF_API_TOKEN') | default('', true) }}"
_traefik_dash_url_env: "{{ lookup('env', 'TRAEFIK_DASHBOARD_VERIFY_URL') | default('', true) | trim }}"
traefik_dashboard_probe_url: "{{ _traefik_dash_url_env if (_traefik_dash_url_env | length > 0) else ('http://' ~ k3s_server_ip ~ '/dashboard/') }}"
tasks:
- name: "Gate - skip verify when ACME_EMAIL missing"
when: acme_email | trim == ""
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-03 reason=missing_env missing=ACME_EMAIL"
- name: Ensure Cloudflare token Secret from CF_API_TOKEN (real-pass)
when: (cf_api_token | trim | length) > 0
ansible.builtin.include_role:
name: verify_common
tasks_from: ensure-cloudflare-api-token-secret.yml
vars:
verify_cf_api_token: "{{ cf_api_token | trim }}"
- name: Check cloudflare-api-token secret exists
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl -n kube-system get secret cloudflare-api-token
args:
executable: /bin/bash
changed_when: false
register: cloudflare_secret_check
failed_when: false
- name: Gate - no CF_API_TOKEN and secret missing
when: cloudflare_secret_check.rc != 0 and (cf_api_token | trim | length) == 0
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-03 reason=missing_dependency missing=cloudflare-api-token skip_scope=traefik-dashboard-acme"
- name: Fail when secret missing but CF_API_TOKEN was set
when: cloudflare_secret_check.rc != 0 and (cf_api_token | trim | length) > 0
ansible.builtin.fail:
msg: "已设置 CF_API_TOKEN 但 cloudflare-api-token Secret 仍不可用,请检查 apiserver 权限与命名空间 kube-system"
# 与 03-02 Verify 一致:旧 RS pending termination 时 rollout 可能永久卡住scale 重置会短暂影响入口。
- name: Unstick Traefik deployment via scale down/up (kube-system)
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG={{ k3s_kubeconfig }}
echo "[OC-ASSERT] assertion=traefik_rollout_unblock phase=scale_reset doc_id=03-03"
kubectl scale deployment traefik -n kube-system --replicas=0
for i in $(seq 1 90); do
rep=$(kubectl get deployment traefik -n kube-system -o jsonpath='{.status.replicas}' 2>/dev/null || echo 1)
[ "${rep:-1}" = "0" ] && break
sleep 2
done
for sel in "app.kubernetes.io/name=traefik" "app.kubernetes.io/instance=traefik"; do
kubectl get pods -n kube-system -l "$sel" -o name 2>/dev/null | while read -r p; do
[ -z "$p" ] && continue
kubectl delete "$p" -n kube-system --grace-period=0 --force --ignore-not-found=true || true
done
done
{ kubectl get pods -n kube-system --no-headers -o custom-columns=:metadata.name 2>/dev/null | grep -E '^traefik-[0-9a-f]+-' || true; } | while read -r n; do
[ -z "$n" ] && continue
kubectl delete pod "$n" -n kube-system --grace-period=0 --force --ignore-not-found=true || true
done
kubectl scale deployment traefik -n kube-system --replicas=1
sleep 3
args:
executable: /bin/bash
changed_when: true
failed_when: false
- name: Rollout status traefik (kube-system)
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/traefik
verify_rollout_namespace: kube-system
verify_rollout_timeout_s: 600
# deployment spec.replicas=0 时 kubectl rollout status 也会“成功”,需显式等到 Pod Ready
- name: Wait for traefik Pod Ready (kube-system)
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG={{ k3s_kubeconfig }}
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=traefik,app.kubernetes.io/instance=traefik-kube-system \
-n kube-system --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: HTTP probe Traefik Dashboard via TRAEFIK_DASHBOARD_VERIFY_URL (control 机)
when: _traefik_dash_url_env | length > 0
ansible.builtin.uri:
url: "{{ traefik_dashboard_probe_url }}"
method: GET
follow_redirects: all
status_code: [200]
timeout: 15
register: traefik_03_03_dashboard_http
changed_when: false
delegate_to: localhost
become: false
- name: OC3 summary for Traefik Dashboard HTTP (external URL)
when: _traefik_dash_url_env | length > 0
ansible.builtin.debug:
msg: "[OC-ASSERT] assertion=traefik_03_03_dashboard_http phase=http probe=uri status={{ traefik_03_03_dashboard_http.status | default('') }} url={{ traefik_dashboard_probe_url }}"
- name: HTTP probe Traefik Dashboard (port-forward traefik Pod试 web 容器端口 8000/8080)
when: _traefik_dash_url_env | length == 0
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG={{ k3s_kubeconfig }}
POD=$(kubectl get pods -n kube-system \
-l 'app.kubernetes.io/name=traefik,app.kubernetes.io/instance=traefik-kube-system' \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
test -n "$POD"
local_port=$(shuf -i 32000-32767 -n 1)
ok=0
for cport in 8000 8080 80; do
kubectl port-forward -n kube-system "pod/$POD" "${local_port}:${cport}" >/tmp/traefik-03-03-pf.log 2>&1 &
PF_PID=$!
trap 'kill $PF_PID 2>/dev/null || true' EXIT
for i in $(seq 1 20); do
grep -q "Forwarding from" /tmp/traefik-03-03-pf.log 2>/dev/null && break
sleep 1
done
if curl -sfL --connect-timeout 3 --max-time 12 -o /dev/null "http://127.0.0.1:${local_port}/dashboard/" 2>/dev/null; then
ok=1
echo "[OC-ASSERT] assertion=traefik_03_03_dashboard_http phase=http probe=port_forward status=200 pod_port=${cport} local_port=${local_port}"
break
fi
kill $PF_PID 2>/dev/null || true
trap - EXIT
wait $PF_PID 2>/dev/null || true
done
test "$ok" = "1"
args:
executable: /bin/bash
changed_when: false
- name: Teardown 03-03 Traefik Dashboard + ACME (optional)
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
acme_email: "{{ lookup('env', 'ACME_EMAIL') | default('', true) }}"
manifest_dest: /tmp/traefik-dashboard-acme.yaml
tasks:
- name: Skip teardown when gated
when: acme_email | trim == ""
meta: end_play
- name: Delete resources when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ manifest_dest }} --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true

View File

@@ -1,10 +1,166 @@
- name: "03-04 noop verify"
hosts: localhost
gather_facts: false
---
# 探针 URLCF_TUNNEL_TEST_URL完整 HTTPS与 CF_TUNNEL_TEST_HOST仅主机名 → https://HOST/)二选一
- name: Deploy 03-04 Cloudflare Tunnel (cloudflared)
hosts: k3s_server
become: true
run_once: true
vars:
repo_root: "{{ playbook_dir }}/../../.."
doc_id: "03-04"
doc_filename: "03-04-k3s-cloudflare-tunnel-配置接入.md"
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/03-04/cloudflared.yaml"
manifest_dest: /tmp/cloudflared-deploy.yaml
tunnel_token: "{{ lookup('env', 'TUNNEL_TOKEN') | default('', true) }}"
_cf_tunnel_url_raw: "{{ lookup('env', 'CF_TUNNEL_TEST_URL') | default('', true) | trim }}"
_cf_tunnel_host_raw: "{{ lookup('env', 'CF_TUNNEL_TEST_HOST') | default('', true) | trim }}"
cf_tunnel_probe_url: >-
{{ (_cf_tunnel_url_raw | length > 0) | ternary(_cf_tunnel_url_raw,
((_cf_tunnel_host_raw | length > 0) | ternary('https://' ~ (_cf_tunnel_host_raw | regex_replace('^https?://', '') | regex_replace('/.*$', '') | regex_replace('/+$', '')) ~ '/', ''))) }}
tasks:
- name: Include noop doc verify tasks
ansible.builtin.include_tasks: tasks/noop-doc-verify.yml
- name: "Gate - tunnel probe URL required (CF_TUNNEL_TEST_URL or CF_TUNNEL_TEST_HOST)"
when: cf_tunnel_probe_url | trim == ""
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-04 reason=missing_env missing=CF_TUNNEL_TEST_URL_or_CF_TUNNEL_TEST_HOST skip_scope=03-04 tunnel http probe"
- name: Check cloudflared-credentials secret exists
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl -n kube-system get secret cloudflared-credentials
args:
executable: /bin/bash
register: cloudflared_secret_check
changed_when: false
failed_when: false
- name: "Gate - no TUNNEL_TOKEN and secret missing"
when: cloudflared_secret_check.rc != 0 and (tunnel_token | trim | length) == 0
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-04 reason=missing_dependency missing=cloudflared-credentials/TUNNEL_TOKEN skip_scope=03-04 cloudflared deploy"
- name: Ensure cloudflared tunnel Secret from TUNNEL_TOKEN
when: (tunnel_token | trim | length) > 0
ansible.builtin.include_role:
name: verify_common
tasks_from: ensure-cloudflared-tunnel-secret.yml
vars:
verify_tunnel_token: "{{ tunnel_token | trim }}"
- name: Copy cloudflared Deployment manifest
ansible.builtin.copy:
src: "{{ manifest_src }}"
dest: "{{ manifest_dest }}"
mode: "0644"
- name: Apply cloudflared Deployment
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Verify 03-04 Cloudflare Tunnel (rollout + HTTPS probe)
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
tunnel_token: "{{ lookup('env', 'TUNNEL_TOKEN') | default('', true) }}"
_cf_tunnel_url_raw: "{{ lookup('env', 'CF_TUNNEL_TEST_URL') | default('', true) | trim }}"
_cf_tunnel_host_raw: "{{ lookup('env', 'CF_TUNNEL_TEST_HOST') | default('', true) | trim }}"
cf_tunnel_probe_url: >-
{{ (_cf_tunnel_url_raw | length > 0) | ternary(_cf_tunnel_url_raw,
((_cf_tunnel_host_raw | length > 0) | ternary('https://' ~ (_cf_tunnel_host_raw | regex_replace('^https?://', '') | regex_replace('/.*$', '') | regex_replace('/+$', '')) ~ '/', ''))) }}
tasks:
- name: "Gate - skip verify when tunnel probe URL missing"
when: cf_tunnel_probe_url | trim == ""
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-04 reason=missing_env missing=CF_TUNNEL_TEST_URL_or_CF_TUNNEL_TEST_HOST skip_scope=03-04 tunnel http probe"
- name: Ensure cloudflared tunnel Secret from TUNNEL_TOKEN (idempotent)
when: (tunnel_token | trim | length) > 0
ansible.builtin.include_role:
name: verify_common
tasks_from: ensure-cloudflared-tunnel-secret.yml
vars:
verify_tunnel_token: "{{ tunnel_token | trim }}"
- name: Check cloudflared-credentials secret exists
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl -n kube-system get secret cloudflared-credentials
args:
executable: /bin/bash
register: cloudflared_secret_check
changed_when: false
failed_when: false
- name: "Gate - no TUNNEL_TOKEN and secret missing"
when: cloudflared_secret_check.rc != 0 and (tunnel_token | trim | length) == 0
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-04 reason=missing_dependency missing=cloudflared-credentials/TUNNEL_TOKEN skip_scope=03-04 cloudflared verify"
- name: Fail when secret missing but TUNNEL_TOKEN was set
when: cloudflared_secret_check.rc != 0 and (tunnel_token | trim | length) > 0
ansible.builtin.fail:
msg: "已设置 TUNNEL_TOKEN 但 cloudflared-credentials Secret 仍不可用,请检查 apiserver 与 kube-system 权限"
- name: Rollout status cloudflared (kube-system)
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/cloudflared
verify_rollout_namespace: kube-system
verify_rollout_timeout_s: 240
- name: HTTPS probe via Tunnel (CF_TUNNEL_TEST_URL / CF_TUNNEL_TEST_HOST)
ansible.builtin.include_role:
name: verify_common
tasks_from: http-curl-expect.yml
vars:
verify_http_url: "{{ cf_tunnel_probe_url | trim }}"
verify_http_expected_code: 200
verify_http_connect_timeout: 5
verify_http_max_time: 15
verify_http_retries: 12
verify_http_retry_sleep: 3
verify_http_assertion_label: cf_tunnel_03_04_https
verify_http_tls_insecure: "{{ (lookup('env', 'CF_TUNNEL_CURL_INSECURE') | default('0', true) | trim) == '1' }}"
- name: Teardown 03-04 Cloudflare Tunnel (optional)
hosts: k3s_server
become: true
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
_cf_tunnel_url_raw: "{{ lookup('env', 'CF_TUNNEL_TEST_URL') | default('', true) | trim }}"
_cf_tunnel_host_raw: "{{ lookup('env', 'CF_TUNNEL_TEST_HOST') | default('', true) | trim }}"
cf_tunnel_probe_url: >-
{{ (_cf_tunnel_url_raw | length > 0) | ternary(_cf_tunnel_url_raw,
((_cf_tunnel_host_raw | length > 0) | ternary('https://' ~ (_cf_tunnel_host_raw | regex_replace('^https?://', '') | regex_replace('/.*$', '') | regex_replace('/+$', '')) ~ '/', ''))) }}
tasks:
- name: Skip teardown when 03-04 verify path not engaged
when: cf_tunnel_probe_url | trim == ""
meta: end_play
- name: Delete cloudflared Deployment and credentials when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete deployment cloudflared -n kube-system --ignore-not-found=true
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete secret cloudflared-credentials -n kube-system --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true

View File

@@ -5,7 +5,8 @@
run_once: true
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
local_path_apply_lab_config_enabled: "{{ local_path_apply_lab_config | default(false) | bool }}"
_local_path_apply_lab_cfg: "{{ local_path_apply_lab_config | default((lookup('env', 'LOCAL_PATH_APPLY_LAB_CONFIG') | default('', true) | trim | lower in ['true', '1', 'yes']) | bool) }}"
local_path_apply_lab_config_enabled: "{{ _local_path_apply_lab_cfg | bool }}"
local_path_json_src: "{{ playbook_dir }}/../../files/03-05/local-path-config-lab.json"
local_path_json_dest: /root/local-path-config-lab.json
pre_tasks:
@@ -13,7 +14,7 @@
when: not local_path_apply_lab_config_enabled
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=03-05 action=apply-local-path-config var=local_path_apply_lab_config"
msg: "[SKIP] optional doc_id=03-05 action=apply-local-path-config var=local_path_apply_lab_config"
- meta: end_play
tasks:
- name: Copy local-path lab json
@@ -84,12 +85,12 @@
delay: 2
- name: Rollout status nginx-local-pvc-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nginx-local-pvc-demo -n default --timeout=240s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/nginx-local-pvc-demo
verify_rollout_timeout_s: 240
- name: Teardown 03-05 local-path pvc demo (optional)
hosts: k3s_server

View File

@@ -7,15 +7,18 @@
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
manifest_src: "{{ playbook_dir }}/../../files/03-06/nfs-pv-pvc-demo.yaml"
manifest_dest: /tmp/nfs-pv-pvc-demo.yaml
nfs_job_manifest_src: "{{ playbook_dir }}/../../files/03-06/nfs-pvc-verify-job.yaml"
nfs_job_manifest_dest: /tmp/nfs-pvc-verify-job.yaml
nfs_server_ip: "{{ lookup('env', 'NFS_SERVER_IP') | default('', true) }}"
nfs_export_path: "{{ lookup('env', 'NFS_EXPORT_PATH') | default('', true) }}"
tasks:
- name: "Gate - skip apply when NFS vars missing"
when: (nfs_server_ip | trim == "") or (nfs_export_path | trim == "")
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=03-06 reason=missing_env missing=NFS_SERVER_IP,NFS_EXPORT_PATH"
- meta: end_play
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-06 reason=missing_env missing=NFS_SERVER_IP,NFS_EXPORT_PATH"
- name: Copy manifest
ansible.builtin.copy:
@@ -35,7 +38,33 @@
regexp: "<NFS_EXPORT_PATH>"
replace: "{{ nfs_export_path | trim }}"
- name: Apply manifest
- name: Reset stale nfs demo resources before apply (handle immutable PVC fields)
ansible.builtin.shell: |
set -e
export KUBECONFIG={{ k3s_kubeconfig }}
kubectl -n default delete job nfs-pvc-verify-demo --ignore-not-found=true || true
for i in $(seq 1 60); do
n=$(kubectl -n default get pods -l job-name=nfs-pvc-verify-demo --no-headers 2>/dev/null | wc -l | tr -d ' ')
[ "${n:-99}" -eq 0 ] && break
sleep 1
done || true
kubectl -n default delete pvc nfs-pvc-demo --ignore-not-found=true || true
kubectl delete pv nfs-pv-demo --ignore-not-found=true || true
for i in $(seq 1 40); do
pvc_gone=0
pv_gone=0
kubectl -n default get pvc nfs-pvc-demo >/dev/null 2>&1 || pvc_gone=1
kubectl get pv nfs-pv-demo >/dev/null 2>&1 || pv_gone=1
if [ "$pvc_gone" -eq 1 ] && [ "$pv_gone" -eq 1 ]; then
break
fi
sleep 1
done
args:
executable: /bin/bash
changed_when: true
- name: Apply PV/PVC manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ manifest_dest }}
@@ -43,7 +72,33 @@
executable: /bin/bash
changed_when: true
- name: Verify 03-06 nfs pvc demo (Bound)
- name: Wait pvc Bound before Job
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl get pvc nfs-pvc-demo -n default -o jsonpath='{.status.phase}'
args:
executable: /bin/bash
register: pvc_phase_deploy
changed_when: false
until: pvc_phase_deploy.stdout | trim == "Bound"
retries: 40
delay: 3
- name: Copy nfs verify Job manifest
ansible.builtin.copy:
src: "{{ nfs_job_manifest_src }}"
dest: "{{ nfs_job_manifest_dest }}"
mode: "0644"
- name: Apply nfs verify Job
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nfs_job_manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Verify 03-06 nfs pvc demo (Bound + Job RW)
hosts: k3s_server
become: true
run_once: true
@@ -54,10 +109,11 @@
tasks:
- name: "Gate - skip verify when NFS vars missing"
when: (nfs_server_ip | trim == "") or (nfs_export_path | trim == "")
block:
- ansible.builtin.debug:
msg: "[GATE] skipped doc_id=03-06 reason=missing_env missing=NFS_SERVER_IP,NFS_EXPORT_PATH"
- meta: end_play
ansible.builtin.include_role:
name: verify_common
tasks_from: gate-debug-end-play.yml
vars:
verify_gate_message: "[GATE] skipped doc_id=03-06 reason=missing_env missing=NFS_SERVER_IP,NFS_EXPORT_PATH"
- name: Wait pvc Bound
ansible.builtin.shell: |
@@ -71,6 +127,25 @@
retries: 40
delay: 3
- name: Wait nfs verify Job complete
ansible.builtin.shell: |
set -euo pipefail
KUBECONFIG={{ k3s_kubeconfig }} kubectl wait --for=condition=complete job/nfs-pvc-verify-demo -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: OC3 evidence — nfs verify Job logs
ansible.builtin.shell: |
set -euo pipefail
export KUBECONFIG={{ k3s_kubeconfig }}
echo "[OC-ASSERT] assertion=nfs_pvc_rw phase=cluster probe=job_logs job=nfs-pvc-verify-demo"
kubectl -n default logs job/nfs-pvc-verify-demo --tail=30
echo "[OC-ASSERT] assertion=nfs_pvc_rw phase=verify probe=job_complete result=ok job=nfs-pvc-verify-demo"
args:
executable: /bin/bash
changed_when: false
- name: Teardown 03-06 nfs pv+pvc demo (optional)
hosts: k3s_server
become: true
@@ -86,7 +161,17 @@
when: (nfs_server_ip | trim == "") or (nfs_export_path | trim == "")
meta: end_play
- name: Delete resources when VERIFY_TEARDOWN=1
- name: Delete Job before PVC/PV (teardown order)
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
export KUBECONFIG={{ k3s_kubeconfig }}
kubectl delete job nfs-pvc-verify-demo -n default --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true
- name: Delete PV/PVC manifest when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
@@ -94,4 +179,3 @@
args:
executable: /bin/bash
changed_when: true

View File

@@ -93,18 +93,45 @@
dest: "{{ longhorn_values_dest }}"
mode: "0600"
- name: Ensure longhorn-system namespace is not stuck Terminating (force finalize if needed)
- name: Recover longhorn-system namespace from Terminating and recreate cleanly
ansible.builtin.shell: |
set -e
export KUBECONFIG={{ k3s_kubeconfig }}
ns="longhorn-system"
phase="$(kubectl get ns "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || true)"
if [ "$phase" = "Terminating" ]; then
echo "[WARN] namespace $ns is Terminating; force finalize to unblock install"
echo "[WARN] namespace $ns is Terminating; force finalize and wait deletion"
kubectl get ns "$ns" -o json > /tmp/ns.json
python3 -c "import json; obj=json.load(open('/tmp/ns.json')); obj.setdefault('spec',{}); obj['spec']['finalizers']=[]; json.dump(obj, open('/tmp/ns-finalize.json','w'))"
kubectl replace --raw \"/api/v1/namespaces/$ns/finalize\" -f /tmp/ns-finalize.json >/dev/null
kubectl replace --raw \"/api/v1/namespaces/$ns/finalize\" -f /tmp/ns-finalize.json >/dev/null || true
kubectl delete ns "$ns" --ignore-not-found=true --wait=false || true
for i in $(seq 1 60); do
if ! kubectl get ns "$ns" >/dev/null 2>&1; then
break
fi
sleep 2
done
fi
# 保证 Helm 能写 release secret命名空间必须处于 Active 且可创建资源
if ! kubectl get ns "$ns" >/dev/null 2>&1; then
kubectl create ns "$ns"
fi
phase_now="$(kubectl get ns "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || true)"
deleting_now="$(kubectl get ns "$ns" -o jsonpath='{.metadata.deletionTimestamp}' 2>/dev/null || true)"
if [ "$phase_now" = "Terminating" ]; then
echo "[ERR] namespace $ns still Terminating after recovery; abort helm install"
kubectl get ns "$ns" -o yaml || true
exit 1
fi
if [ -n "$deleting_now" ]; then
echo "[ERR] namespace $ns has deletionTimestamp=$deleting_now; abort helm install"
kubectl get ns "$ns" -o yaml || true
exit 1
fi
# 探针:确认命名空间可写,避免 Helm 创建 release secret 时才失败
kubectl -n "$ns" create configmap longhorn-write-probe --from-literal=ok=1 >/dev/null
kubectl -n "$ns" delete configmap longhorn-write-probe --ignore-not-found=true >/dev/null
args:
executable: /bin/bash
changed_when: true
@@ -199,7 +226,51 @@
- name: Helm upgrade/install Longhorn失败兜底install --replace
ansible.builtin.shell: |
set -e
helm upgrade --install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace -f {{ longhorn_values_dest }} --version {{ longhorn_chart_version }} --wait --timeout 15m || helm install --replace longhorn longhorn/longhorn --namespace longhorn-system --create-namespace -f {{ longhorn_values_dest }} --version {{ longhorn_chart_version }} --wait --timeout 15m
ns="longhorn-system"
recover_ns() {
phase="$(kubectl get ns "$ns" -o jsonpath='{.status.phase}' 2>/dev/null || true)"
deleting="$(kubectl get ns "$ns" -o jsonpath='{.metadata.deletionTimestamp}' 2>/dev/null || true)"
if [ "$phase" = "Terminating" ] || [ -n "$deleting" ]; then
kubectl get ns "$ns" -o json > /tmp/ns.json || true
python3 -c "import json; obj=json.load(open('/tmp/ns.json')); obj.setdefault('spec',{}); obj['spec']['finalizers']=[]; json.dump(obj, open('/tmp/ns-finalize.json','w'))" || true
kubectl replace --raw "/api/v1/namespaces/$ns/finalize" -f /tmp/ns-finalize.json >/dev/null || true
kubectl delete ns "$ns" --ignore-not-found=true --wait=false || true
for i in $(seq 1 90); do
if ! kubectl get ns "$ns" >/dev/null 2>&1; then
break
fi
sleep 2
done
fi
kubectl get ns "$ns" >/dev/null 2>&1 || kubectl create ns "$ns"
}
for i in 1 2 3 4 5; do
set +e
out="$(helm upgrade --install longhorn longhorn/longhorn --namespace "$ns" --create-namespace -f {{ longhorn_values_dest }} --version {{ longhorn_chart_version }} --wait --timeout 15m 2>&1)"
rc=$?
set -e
if [ $rc -eq 0 ]; then
echo "$out"
exit 0
fi
echo "$out"
if echo "$out" | grep -q "is being terminated"; then
echo "[WARN] namespace $ns is being terminated, recover and retry ($i/5)"
recover_ns
sleep $((i * 3))
continue
fi
if echo "$out" | grep -q "engineimages.longhorn.io\" not found"; then
echo "[WARN] longhorn CRD propagation not ready, retry ($i/5)"
sleep $((i * 5))
continue
fi
# 非命名空间终止类错误,直接失败
exit $rc
done
# 兜底:仍失败则返回非 0
exit 1
environment:
KUBECONFIG: "{{ k3s_kubeconfig }}"
args:

View File

@@ -6,5 +6,7 @@
doc_id: "03-08"
doc_filename: "03-08-k3s-ha-集群配置与切换.md"
tasks:
- name: Include noop doc verify tasks
ansible.builtin.include_tasks: tasks/noop-doc-verify.yml
- name: Include noop doc verify role tasks
ansible.builtin.include_role:
name: verify_common
tasks_from: noop-doc-verify.yml

View File

@@ -6,5 +6,7 @@
doc_id: "03-09"
doc_filename: "03-09-k3s-gitops-集群配置管理.md"
tasks:
- name: Include noop doc verify tasks
ansible.builtin.include_tasks: tasks/noop-doc-verify.yml
- name: Include noop doc verify role tasks
ansible.builtin.include_role:
name: verify_common
tasks_from: noop-doc-verify.yml

View File

@@ -6,5 +6,7 @@
doc_id: "03-10"
doc_filename: "03-10-k3s-traefik-custom-ports.md"
tasks:
- name: Include noop doc verify tasks
ansible.builtin.include_tasks: tasks/noop-doc-verify.yml
- name: Include noop doc verify role tasks
ansible.builtin.include_role:
name: verify_common
tasks_from: noop-doc-verify.yml

View File

@@ -48,23 +48,22 @@
verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
tasks:
- name: Rollout status nodejs-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nodejs-demo -n default --timeout=240s
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: kubectl-rollout-status.yml
vars:
verify_rollout_ref: deployment/nodejs-demo
verify_rollout_timeout_s: 240
- name: HTTP check /node
ansible.builtin.shell: |
set -e
base="{{ verify_entry_base | trim | regex_replace('/+$','') }}"
url="$base/node"
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 10 "$url" 2>/dev/null || echo "000")
echo "$url -> $code"
test "$code" = "200"
args:
executable: /bin/bash
changed_when: false
ansible.builtin.include_role:
name: verify_common
tasks_from: http-curl-expect.yml
vars:
verify_http_entry_base: "{{ verify_entry_base }}"
verify_http_path: "/node"
verify_http_connect_timeout: 3
verify_http_max_time: 10
verify_http_assertion_label: nodejs_base_04_01_entry_http

View File

@@ -5,12 +5,15 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-01/04-02-nodejs-demo.yaml"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-02/04-02-nodejs-demo.yaml"
nodejs_manifest_dest: /tmp/nodejs-demo-04-02.yaml
nodejs_verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
nodejs_verify_path: "/node"
nodejs_expected_target_port: 3000
nodejs_http_assertion_label: nodejs_04_02_entry_http
tasks:
- name: Include nodejs deploy+verify template
ansible.builtin.include_tasks: tasks/nodejs-demo-deploy-verify.yml
ansible.builtin.include_role:
name: verify_common
tasks_from: nodejs-demo-deploy-verify.yml

View File

@@ -1,3 +1,4 @@
---
- name: Deploy+Verify 04-03 nodejs image + command/args
hosts: k3s_server
become: true
@@ -5,87 +6,14 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-01/04-03-nodejs-demo.yaml"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-03/04-03-nodejs-demo.yaml"
nodejs_manifest_dest: /tmp/nodejs-demo-04-03.yaml
nodejs_verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
nodejs_verify_path: "/node"
nodejs_expected_target_port: 8080
nodejs_expected_target_port: 3000
nodejs_http_assertion_label: nodejs_04_03_entry_http
tasks:
- name: Copy nodejs demo manifest
ansible.builtin.copy:
src: "{{ nodejs_manifest_src }}"
dest: "{{ nodejs_manifest_dest }}"
mode: "0644"
- name: Apply nodejs demo manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nodejs_manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Rollout status nodejs-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nodejs-demo -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: Assert Service targetPort matches expected (optional)
when: nodejs_expected_target_port is defined and (nodejs_expected_target_port | int) > 0
ansible.builtin.shell: |
set -euo pipefail
exp="{{ nodejs_expected_target_port | int }}"
got=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get svc nodejs-demo -n default -o jsonpath='{.spec.ports[0].targetPort}')
echo "svc/nodejs-demo targetPort=$got expected=$exp"
test "$got" = "$exp"
args:
executable: /bin/bash
changed_when: false
- name: Assert Endpoints exist
ansible.builtin.shell: |
set -euo pipefail
eps=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get endpoints nodejs-demo -n default -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)
echo "endpoints.ip=$eps"
test -n "$eps"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check nodejs demo (path/host optional)
when: nodejs_http_check_enabled | default(true)
ansible.builtin.shell: |
set -euo pipefail
base="{{ nodejs_verify_entry_base | trim | regex_replace('/+$','') }}"
path="{{ nodejs_verify_path | default('/node') }}"
url="$base${path}"
host="{{ nodejs_verify_host | default('') | trim }}"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ -n "$host" ]; then
code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: ${host}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
else
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
fi
echo "try $i: $url host=${host:-<none>} -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
args:
executable: /bin/bash
changed_when: false
- name: Teardown when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ nodejs_manifest_dest }} --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true
- name: Include nodejs deploy+verify template
ansible.builtin.include_role:
name: verify_common
tasks_from: nodejs-demo-deploy-verify.yml

View File

@@ -1,3 +1,4 @@
---
- name: Deploy+Verify 04-04 nodejs env + config injection
hosts: k3s_server
become: true
@@ -5,87 +6,14 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-01/04-04-nodejs-demo.yaml"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-04/04-04-nodejs-demo.yaml"
nodejs_manifest_dest: /tmp/nodejs-demo-04-04.yaml
nodejs_verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
nodejs_verify_path: "/node"
nodejs_expected_target_port: 8080
nodejs_http_assertion_label: nodejs_04_04_entry_http
tasks:
- name: Copy nodejs demo manifest
ansible.builtin.copy:
src: "{{ nodejs_manifest_src }}"
dest: "{{ nodejs_manifest_dest }}"
mode: "0644"
- name: Apply nodejs demo manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nodejs_manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Rollout status nodejs-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nodejs-demo -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: Assert Service targetPort matches expected (optional)
when: nodejs_expected_target_port is defined and (nodejs_expected_target_port | int) > 0
ansible.builtin.shell: |
set -euo pipefail
exp="{{ nodejs_expected_target_port | int }}"
got=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get svc nodejs-demo -n default -o jsonpath='{.spec.ports[0].targetPort}')
echo "svc/nodejs-demo targetPort=$got expected=$exp"
test "$got" = "$exp"
args:
executable: /bin/bash
changed_when: false
- name: Assert Endpoints exist
ansible.builtin.shell: |
set -euo pipefail
eps=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get endpoints nodejs-demo -n default -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)
echo "endpoints.ip=$eps"
test -n "$eps"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check nodejs demo (path/host optional)
when: nodejs_http_check_enabled | default(true)
ansible.builtin.shell: |
set -euo pipefail
base="{{ nodejs_verify_entry_base | trim | regex_replace('/+$','') }}"
path="{{ nodejs_verify_path | default('/node') }}"
url="$base${path}"
host="{{ nodejs_verify_host | default('') | trim }}"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ -n "$host" ]; then
code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: ${host}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
else
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
fi
echo "try $i: $url host=${host:-<none>} -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
args:
executable: /bin/bash
changed_when: false
- name: Teardown when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ nodejs_manifest_dest }} --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true
- name: Include nodejs deploy+verify template
ansible.builtin.include_role:
name: verify_common
tasks_from: nodejs-demo-deploy-verify.yml

View File

@@ -1,3 +1,4 @@
---
- name: Deploy+Verify 04-05 nodejs probes
hosts: k3s_server
become: true
@@ -5,87 +6,14 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-01/04-05-nodejs-demo.yaml"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-05/04-05-nodejs-demo.yaml"
nodejs_manifest_dest: /tmp/nodejs-demo-04-05.yaml
nodejs_verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
nodejs_verify_path: "/node"
nodejs_expected_target_port: 8080
nodejs_http_assertion_label: nodejs_04_05_entry_http
tasks:
- name: Copy nodejs demo manifest
ansible.builtin.copy:
src: "{{ nodejs_manifest_src }}"
dest: "{{ nodejs_manifest_dest }}"
mode: "0644"
- name: Apply nodejs demo manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nodejs_manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Rollout status nodejs-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nodejs-demo -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: Assert Service targetPort matches expected (optional)
when: nodejs_expected_target_port is defined and (nodejs_expected_target_port | int) > 0
ansible.builtin.shell: |
set -euo pipefail
exp="{{ nodejs_expected_target_port | int }}"
got=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get svc nodejs-demo -n default -o jsonpath='{.spec.ports[0].targetPort}')
echo "svc/nodejs-demo targetPort=$got expected=$exp"
test "$got" = "$exp"
args:
executable: /bin/bash
changed_when: false
- name: Assert Endpoints exist
ansible.builtin.shell: |
set -euo pipefail
eps=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get endpoints nodejs-demo -n default -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)
echo "endpoints.ip=$eps"
test -n "$eps"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check nodejs demo (path/host optional)
when: nodejs_http_check_enabled | default(true)
ansible.builtin.shell: |
set -euo pipefail
base="{{ nodejs_verify_entry_base | trim | regex_replace('/+$','') }}"
path="{{ nodejs_verify_path | default('/node') }}"
url="$base${path}"
host="{{ nodejs_verify_host | default('') | trim }}"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ -n "$host" ]; then
code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: ${host}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
else
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
fi
echo "try $i: $url host=${host:-<none>} -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
args:
executable: /bin/bash
changed_when: false
- name: Teardown when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ nodejs_manifest_dest }} --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true
- name: Include nodejs deploy+verify template
ansible.builtin.include_role:
name: verify_common
tasks_from: nodejs-demo-deploy-verify.yml

View File

@@ -1,3 +1,4 @@
---
- name: Deploy+Verify 04-06 nodejs replicas + rolling update
hosts: k3s_server
become: true
@@ -5,87 +6,14 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-01/04-06-nodejs-demo.yaml"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-06/04-06-nodejs-demo.yaml"
nodejs_manifest_dest: /tmp/nodejs-demo-04-06.yaml
nodejs_verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
nodejs_verify_path: "/node"
nodejs_expected_target_port: 8080
nodejs_http_assertion_label: nodejs_04_06_entry_http
tasks:
- name: Copy nodejs demo manifest
ansible.builtin.copy:
src: "{{ nodejs_manifest_src }}"
dest: "{{ nodejs_manifest_dest }}"
mode: "0644"
- name: Apply nodejs demo manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nodejs_manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Rollout status nodejs-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nodejs-demo -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: Assert Service targetPort matches expected (optional)
when: nodejs_expected_target_port is defined and (nodejs_expected_target_port | int) > 0
ansible.builtin.shell: |
set -euo pipefail
exp="{{ nodejs_expected_target_port | int }}"
got=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get svc nodejs-demo -n default -o jsonpath='{.spec.ports[0].targetPort}')
echo "svc/nodejs-demo targetPort=$got expected=$exp"
test "$got" = "$exp"
args:
executable: /bin/bash
changed_when: false
- name: Assert Endpoints exist
ansible.builtin.shell: |
set -euo pipefail
eps=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get endpoints nodejs-demo -n default -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)
echo "endpoints.ip=$eps"
test -n "$eps"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check nodejs demo (path/host optional)
when: nodejs_http_check_enabled | default(true)
ansible.builtin.shell: |
set -euo pipefail
base="{{ nodejs_verify_entry_base | trim | regex_replace('/+$','') }}"
path="{{ nodejs_verify_path | default('/node') }}"
url="$base${path}"
host="{{ nodejs_verify_host | default('') | trim }}"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ -n "$host" ]; then
code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: ${host}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
else
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
fi
echo "try $i: $url host=${host:-<none>} -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
args:
executable: /bin/bash
changed_when: false
- name: Teardown when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ nodejs_manifest_dest }} --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true
- name: Include nodejs deploy+verify template
ansible.builtin.include_role:
name: verify_common
tasks_from: nodejs-demo-deploy-verify.yml

View File

@@ -1,3 +1,4 @@
---
- name: Deploy+Verify 04-07 nodejs Ingress + Traefik
hosts: k3s_server
become: true
@@ -5,87 +6,25 @@
vars:
k3s_kubeconfig: /etc/rancher/k3s/k3s.yaml
verify_teardown: "{{ (VERIFY_TEARDOWN | default('1')) | string }}"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-01/04-07-nodejs-demo.yaml"
nodejs_manifest_src: "{{ playbook_dir }}/../../files/04-07/04-07-nodejs-demo.yaml"
nodejs_manifest_dest: /tmp/nodejs-demo-04-07.yaml
nodejs_verify_entry_base: "{{ nodejs_entry_base | default('http://' ~ k3s_server_ip) }}"
nodejs_verify_path: "/api/"
nodejs_verify_host: "{{ nodejs_verify_host | default('app.example.local') }}"
nodejs_verify_path: "/node"
nodejs_verify_host: ""
_nodejs_tls_entry_base: "{{ NODEJS_TLS_ENTRY_BASE | default(lookup('env', 'NODEJS_TLS_ENTRY_BASE') | default('', true), true) | trim }}"
_nodejs_tls_host: "{{ NODEJS_TLS_HOST | default(lookup('env', 'NODEJS_TLS_HOST') | default('', true), true) | trim }}"
nodejs_tls_probe_enabled: "{{ (_nodejs_tls_entry_base | length > 0) and (_nodejs_tls_host | length > 0) and (_nodejs_tls_entry_base is match('^https?://')) }}"
_nodejs_tls_authority: "{{ _nodejs_tls_entry_base | regex_replace('^https?://', '') | regex_replace('/.*$', '') }}"
nodejs_tls_connect_host: "{{ _nodejs_tls_authority | regex_replace(':([0-9]+)$', '') }}"
nodejs_tls_connect_port: "{{ (_nodejs_tls_authority | regex_replace('^[^:]*:', '') | int) if (':' in _nodejs_tls_authority) else 443 }}"
nodejs_tls_sni_probe_enabled: "{{ nodejs_tls_probe_enabled | bool }}"
nodejs_tls_sni_connect_host: "{{ nodejs_tls_connect_host }}"
nodejs_tls_sni_port: "{{ nodejs_tls_connect_port }}"
nodejs_tls_sni_servername: "{{ _nodejs_tls_host }}"
nodejs_tls_sni_assertion_label: nodejs_04_07_tls_sni_handshake
nodejs_http_assertion_label: nodejs_04_07_entry_http
tasks:
- name: Copy nodejs demo manifest
ansible.builtin.copy:
src: "{{ nodejs_manifest_src }}"
dest: "{{ nodejs_manifest_dest }}"
mode: "0644"
- name: Apply nodejs demo manifest
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl apply -f {{ nodejs_manifest_dest }}
args:
executable: /bin/bash
changed_when: true
- name: Rollout status nodejs-demo
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl rollout status deployment/nodejs-demo -n default --timeout=180s
args:
executable: /bin/bash
changed_when: false
- name: Assert Service targetPort matches expected (optional)
when: nodejs_expected_target_port is defined and (nodejs_expected_target_port | int) > 0
ansible.builtin.shell: |
set -euo pipefail
exp="{{ nodejs_expected_target_port | int }}"
got=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get svc nodejs-demo -n default -o jsonpath='{.spec.ports[0].targetPort}')
echo "svc/nodejs-demo targetPort=$got expected=$exp"
test "$got" = "$exp"
args:
executable: /bin/bash
changed_when: false
- name: Assert Endpoints exist
ansible.builtin.shell: |
set -euo pipefail
eps=$(KUBECONFIG={{ k3s_kubeconfig }} kubectl get endpoints nodejs-demo -n default -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)
echo "endpoints.ip=$eps"
test -n "$eps"
args:
executable: /bin/bash
changed_when: false
- name: HTTP check nodejs demo (path/host optional)
when: nodejs_http_check_enabled | default(true)
ansible.builtin.shell: |
set -euo pipefail
base="{{ nodejs_verify_entry_base | trim | regex_replace('/+$','') }}"
path="{{ nodejs_verify_path | default('/node') }}"
url="$base${path}"
host="{{ nodejs_verify_host | default('') | trim }}"
ok=0
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ -n "$host" ]; then
code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: ${host}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
else
code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null || echo "000")
fi
echo "try $i: $url host=${host:-<none>} -> $code"
if [ "$code" = "200" ]; then ok=1; break; fi
sleep 2
done
test "$ok" = "1"
args:
executable: /bin/bash
changed_when: false
- name: Teardown when VERIFY_TEARDOWN=1
when: verify_teardown == "1"
ansible.builtin.shell: |
set -e
KUBECONFIG={{ k3s_kubeconfig }} kubectl delete -f {{ nodejs_manifest_dest }} --ignore-not-found=true
args:
executable: /bin/bash
changed_when: true
- name: Include nodejs deploy+verify template
ansible.builtin.include_role:
name: verify_common
tasks_from: nodejs-demo-deploy-verify.yml

Some files were not shown because too many files have changed in this diff Show More