diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5335bcd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/.idea/
+/.fastRequest/
+/.vscode/
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 57632e9..8ff70f5 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,61 +4,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
-
-
-
-
-
@@ -126,11 +78,29 @@
-
+
+
+
+
+
+
+
+
+ 1782791068556
+
+
+
+ 1782791068556
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1-2025年/58-202503-新DEMO环境/批量指令.sh b/1-2025年/58-202503-新DEMO环境/批量指令.sh
index af5696c..0c98c44 100644
--- a/1-2025年/58-202503-新DEMO环境/批量指令.sh
+++ b/1-2025年/58-202503-新DEMO环境/批量指令.sh
@@ -28,5 +28,8 @@ ssh root@192.168.40.50 <<< 'scyd@lab1234'
192.168.40.58
192.168.40.64
+192.168.40.60
root
-SuperCyy@123
\ No newline at end of file
+SuperCyy@123
+
+boge14@Good
\ No newline at end of file
diff --git a/71-202601-XA监管平台/260629-证书升级/real-nginx-proxy.conf b/71-202601-XA监管平台/260629-证书升级/real-nginx-proxy.conf
new file mode 100644
index 0000000..b7584b6
--- /dev/null
+++ b/71-202601-XA监管平台/260629-证书升级/real-nginx-proxy.conf
@@ -0,0 +1,98 @@
+upstream proxy_server {
+ ip_hash;
+ server 10.22.57.8:30500;
+ server 10.22.57.5:30500;
+ server 10.22.57.6:30500;
+ server 10.22.57.7:30500;
+}
+
+server {
+ http2 on;
+ listen 443 ssl;
+ server_name airtraffic.xadcity.com;
+
+ ssl_session_timeout 1d;
+ ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
+ ssl_session_tickets off;
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
+ ssl_prefer_server_ciphers off;
+ ssl_certificate /etc/nginx/conf.d/ssl_key/x.xadcity.com.cert.pem;
+ ssl_certificate_key /etc/nginx/conf.d/ssl_key/x.xadcity.com.key.pem;
+
+ location / {
+ proxy_pass http://proxy_server;
+ client_max_body_size 5120m;
+ client_body_buffer_size 5120m;
+ client_body_timeout 6000s;
+ proxy_send_timeout 10000s;
+ proxy_read_timeout 10000s;
+ proxy_connect_timeout 600s;
+ proxy_max_temp_file_size 5120m;
+ proxy_request_buffering on;
+ proxy_buffering off;
+ proxy_buffer_size 4k;
+ proxy_buffers 4 12k;
+ proxy_set_header Host fake-domain.xa-dcity-uas-260116.io;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+
+ location /_AMapService/v4/map/styles {
+ set $args "$args&jscode=cf66cea95bdcdfcf8048456b36f357a1";
+ proxy_pass https://webapi.amap.com/v4/ap/styles;
+ }
+
+ location /_AMapService/ {
+ set $args "$args&jscode=cf66cea95bdcdfcf8048456b36f357a1";
+ proxy_pass https://restapi.amap.com/;
+ }
+
+ location /rtc/v1/ {
+ add_header Access-Control-Allow-Headers X-Requested-With;
+ add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
+ proxy_pass http://127.0.0.1:30985/rtc/v1/;
+ }
+
+ location ~ ^/\w*/actuator/ {
+ return 403;
+ }
+
+ location /live-play/flv/ {
+ proxy_pass http://192.168.10.1:7088/;
+ }
+ location /live-play/hls/ {
+ proxy_pass http://192.168.10.1:7088/zlm/hls/;
+ }
+ location /live-play/webrtc {
+ proxy_pass http://192.168.10.1:7088/index/api/webrtc;
+ }
+ location /live-play/whip {
+ proxy_pass http://192.168.10.1:7088/index/api/whip;
+ }
+ location /live-replay/hls-by-time-range/ {
+ proxy_pass http://192.168.10.1:8088/api/v2/storage/hls-by-time-range/;
+ }
+ location /live-replay/fetch_file/ {
+ proxy_pass http://192.168.10.1:8088/api/v2/storage/fetch_file/;
+ }
+ location /live-replay/hls-downloader {
+ proxy_pass http://192.168.10.1:7080/hls-downloader;
+ }
+
+ location /player {
+ proxy_pass http://144.7.88.50:8081/player;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "Upgrade";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ #WebSocket 超时设置
+ proxy_read_timeout 3600s;
+ proxy_send_timeout 3600s;
+ }
+}
\ No newline at end of file
diff --git a/85-20260617-江西环境整理/2-节点驱逐-完整解决方案.md b/85-20260617-江西环境整理/2-节点驱逐-完整解决方案.md
new file mode 100644
index 0000000..d34ccb2
--- /dev/null
+++ b/85-20260617-江西环境整理/2-节点驱逐-完整解决方案.md
@@ -0,0 +1,1034 @@
+# 节点驱逐 - 完整解决方案
+
+## 一、环境概览
+
+### 集群节点信息
+
+| 节点 IP | 角色 | 操作系统 | 状态 | 处理方式 |
+|---------------|-------------------------------|-----------------------------------------|--------|------------------------|
+| 10.20.1.130 | controlplane, etcd, worker | openEuler 20.03 (LTS-SP3) | 保留 | master 节点不动 |
+| 10.20.1.133 | worker | openEuler 20.03 (LTS-SP3) | 保留 | 中间件调度目标 |
+| 10.20.1.134 | worker | openEuler 20.03 (LTS-SP3) | 保留 | 中间件调度目标 (mysql) |
+| 10.20.1.141 | worker | BigCloud Enterprise Linux For Euler | 保留 | 中间件调度目标 |
+| **10.20.1.142** | worker | BigCloud Enterprise Linux For Euler | **清退** | 一个月后清退 |
+| **10.20.1.144** | worker | BigCloud Enterprise Linux For Euler | **清退** | 一个月后清退 |
+| **10.20.1.145** | worker | BigCloud Enterprise Linux For Euler | **清退** | 一个月后清退 |
+
+### 需求拆解
+
+1. **清退节点**:10.20.1.142、10.20.1.144、10.20.1.145(一个月后执行)
+2. **jxyd 命名空间 Deployment**:生命周期与被清退节点一致 → 即**直接删除**
+3. **jxyd 命名空间中间件**:需保留,迁移调度到 10.20.1.133、10.20.1.134、10.20.1.141
+4. **资源调整**:超标 Deployment 统一降配 + 同步调整 JVM 参数
+
+---
+
+## 二、操作流程总览
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Phase 1: 信息采集与备份(立即执行) │
+│ - 导出所有 deployment/statefulset 信息 │
+│ - 记录当前 Pod 分布 │
+│ - 备份所有 YAML │
+├─────────────────────────────────────────────────────────────────┤
+│ Phase 2: 资源调整(立即执行) │
+│ - 扫描 jxyd 业务 deployment 的资源配置(不扫描中间件) │
+│ - 将超标资源统一降配 │
+│ - 同步修改 CUST_JAVA_OPTS 环境变量 │
+├─────────────────────────────────────────────────────────────────┤
+│ Phase 3: 中间件及业务迁移(立即执行) │
+│ - 给保留节点及待清退节点打 label │
+│ - 调度中间件至保留节点,调度业务 Deployment 至待清退节点 │
+│ - 验证迁移及调度在对应节点上正常运行 │
+├─────────────────────────────────────────────────────────────────┤
+│ Phase 4: 节点清退(一个月后执行) │
+│ - 删除 jxyd 命名空间中的业务 deployment │
+│ - cordon + drain 节点 │
+│ - 从集群中移除节点 │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 三、Phase 1:信息采集与备份
+
+### 1.1 备份脚本 `phase1_backup.sh`
+
+```bash
+#!/bin/bash
+# ============================================================
+# Phase 1: 信息采集与备份
+# 在 master 节点 (10.20.1.130) 上执行
+# ============================================================
+
+BACKUP_DIR="/root/wdd/backup_260617/$(date +%Y%m%d_%H%M%S)"
+NAMESPACE="jxyd"
+mkdir -p "${BACKUP_DIR}"
+
+echo "=========================================="
+echo " Phase 1: 信息采集与备份"
+echo " 备份目录: ${BACKUP_DIR}"
+echo "=========================================="
+
+# 1. 导出节点信息
+echo "[1/6] 导出节点信息..."
+kubectl get nodes -o wide --show-labels > "${BACKUP_DIR}/nodes_info.txt"
+
+# 2. 导出 jxyd 命名空间所有资源
+echo "[2/6] 导出 jxyd 命名空间所有资源 YAML..."
+kubectl get all -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/all_resources.yaml"
+
+# 3. 单独导出每个 deployment
+echo "[3/6] 导出每个 Deployment YAML..."
+mkdir -p "${BACKUP_DIR}/deployments"
+for dep in $(kubectl get deployments -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}'); do
+ kubectl get deployment "${dep}" -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/deployments/${dep}.yaml"
+ echo " 已备份 deployment: ${dep}"
+done
+
+# 4. 单独导出每个 statefulset(中间件通常用 statefulset)
+echo "[4/6] 导出每个 StatefulSet YAML..."
+mkdir -p "${BACKUP_DIR}/statefulsets"
+for sts in $(kubectl get statefulsets -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do
+ kubectl get statefulset "${sts}" -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/statefulsets/${sts}.yaml"
+ echo " 已备份 statefulset: ${sts}"
+done
+
+# 5. 导出 ConfigMap 和 Secret
+echo "[5/6] 导出 ConfigMap 和 Secret..."
+mkdir -p "${BACKUP_DIR}/configmaps" "${BACKUP_DIR}/secrets"
+for cm in $(kubectl get configmaps -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}'); do
+ kubectl get configmap "${cm}" -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/configmaps/${cm}.yaml"
+done
+for sec in $(kubectl get secrets -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}'); do
+ kubectl get secret "${sec}" -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/secrets/${sec}.yaml"
+done
+
+# 6. 导出 Pod 分布信息(关键:记录哪些 Pod 在待清退节点上)
+echo "[6/6] 导出 Pod 分布信息..."
+echo "=== 所有 Pod 的节点分布 ===" > "${BACKUP_DIR}/pod_distribution.txt"
+kubectl get pods -n ${NAMESPACE} -o wide >> "${BACKUP_DIR}/pod_distribution.txt"
+
+echo ""
+echo "--- 待清退节点上的 Pod ---" >> "${BACKUP_DIR}/pod_distribution.txt"
+for NODE in 10.20.1.142 10.20.1.144 10.20.1.145; do
+ echo "" >> "${BACKUP_DIR}/pod_distribution.txt"
+ echo "=== 节点 ${NODE} 上的 Pod ===" >> "${BACKUP_DIR}/pod_distribution.txt"
+ kubectl get pods -n ${NAMESPACE} -o wide --field-selector spec.nodeName=${NODE} >> "${BACKUP_DIR}/pod_distribution.txt"
+done
+
+# 7. 导出 Service 和 Ingress
+mkdir -p "${BACKUP_DIR}/services" "${BACKUP_DIR}/ingresses"
+for svc in $(kubectl get svc -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}'); do
+ kubectl get svc "${svc}" -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/services/${svc}.yaml"
+done
+for ing in $(kubectl get ingress -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do
+ kubectl get ingress "${ing}" -n ${NAMESPACE} -o yaml > "${BACKUP_DIR}/ingresses/${ing}.yaml"
+done
+
+# 8. 生成资源使用汇总
+echo ""
+echo "=== 资源使用汇总 ===" | tee "${BACKUP_DIR}/resource_summary.txt"
+kubectl top nodes 2>/dev/null | tee -a "${BACKUP_DIR}/resource_summary.txt"
+echo "" | tee -a "${BACKUP_DIR}/resource_summary.txt"
+kubectl top pods -n ${NAMESPACE} 2>/dev/null | tee -a "${BACKUP_DIR}/resource_summary.txt"
+
+echo ""
+echo "=========================================="
+echo " 备份完成!目录: ${BACKUP_DIR}"
+echo "=========================================="
+```
+
+---
+
+## 四、Phase 2:资源检查与调整
+
+### 2.1 Python 脚本 `phase2_resource_adjust.py`
+
+此脚本会:
+- 扫描 jxyd 命名空间下所有 Deployment(中间件不存在资源限制,且只在 StatefulSet,因此阶段二不考虑中间件)
+- 检测哪些容器的资源超标(超过 limits cpu:2 / memory:2Gi)
+- 对超标的 Deployment 自动执行 `kubectl patch` 降配
+- 同步修改 `CUST_JAVA_OPTS` 环境变量
+
+```python
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Phase 2: 资源检查与调整
+功能:
+ 1. 扫描 jxyd 命名空间下所有 Deployment 的资源配置
+ 2. 识别超标资源(超过 limits cpu:2 / memory:2Gi)
+ 3. 自动 patch 降配 + 修改 CUST_JAVA_OPTS
+ 4. 生成变更报告
+
+使用方式:
+ python3 phase2_resource_adjust.py --dry-run # 仅预览,不执行变更
+ python3 phase2_resource_adjust.py --apply # 执行变更
+"""
+
+import json
+import subprocess
+import sys
+import re
+import argparse
+from datetime import datetime
+
+
+# ============================================================
+# 配置区
+# ============================================================
+NAMESPACE = "jxyd"
+
+# 目标资源规格
+TARGET_RESOURCES = {
+ "limits": {"cpu": "2", "memory": "2Gi"},
+ "requests": {"cpu": "1", "memory": "500Mi"}
+}
+
+# 目标 JVM 参数
+TARGET_JAVA_OPTS = "-Xms500m -Xmx2000m -Dlog4j2.formatMsgNoLookups=true"
+
+# ============================================================
+# 工具函数
+# ============================================================
+
+def run_kubectl(args, capture=True):
+ """执行 kubectl 命令"""
+ cmd = ["kubectl"] + args
+ result = subprocess.run(cmd, capture_output=capture, text=True)
+ if result.returncode != 0:
+ print(f" [错误] 命令失败: {' '.join(cmd)}")
+ print(f" stderr: {result.stderr}")
+ return None
+ return result.stdout
+
+
+def parse_memory(mem_str):
+ """将内存字符串转换为字节数"""
+ if not mem_str:
+ return 0
+ mem_str = str(mem_str)
+ units = {
+ 'Ki': 1024, 'Mi': 1024**2, 'Gi': 1024**3, 'Ti': 1024**4,
+ 'K': 1000, 'M': 1000**2, 'G': 1000**3, 'T': 1000**4,
+ 'k': 1000, 'm': 0.001 # millibytes (unusual but valid)
+ }
+ for suffix, multiplier in sorted(units.items(), key=lambda x: -len(x[0])):
+ if mem_str.endswith(suffix):
+ try:
+ return float(mem_str[:-len(suffix)]) * multiplier
+ except ValueError:
+ return 0
+ try:
+ return float(mem_str) # 纯数字,单位为字节
+ except ValueError:
+ return 0
+
+
+def parse_cpu(cpu_str):
+ """将 CPU 字符串转换为核心数(float)"""
+ if not cpu_str:
+ return 0.0
+ cpu_str = str(cpu_str)
+ if cpu_str.endswith('m'):
+ return float(cpu_str[:-1]) / 1000.0
+ return float(cpu_str)
+
+
+def is_resource_over_limit(resources):
+ """检查资源是否超标"""
+ limits = resources.get("limits", {})
+ cpu_limit = limits.get("cpu", "0")
+ mem_limit = limits.get("memory", "0")
+
+ cpu_over = parse_cpu(cpu_limit) > parse_cpu(TARGET_RESOURCES["limits"]["cpu"])
+ mem_over = parse_memory(mem_limit) > parse_memory(TARGET_RESOURCES["limits"]["memory"])
+
+ return cpu_over or mem_over
+
+
+# ============================================================
+# 主逻辑
+# ============================================================
+
+def get_deployments():
+ """获取所有 Deployment 信息"""
+ output = run_kubectl([
+ "get", "deployments", "-n", NAMESPACE,
+ "-o", "json"
+ ])
+ if not output:
+ return []
+ data = json.loads(output)
+ return data.get("items", [])
+
+
+def analyze_and_patch(workloads, kind, dry_run=True):
+ """分析并 patch 超标资源,若副本数为 0 则直接删除"""
+ report = {
+ "business": [],
+ "patched": [],
+ "skipped": [],
+ "errors": [],
+ "deleted": []
+ }
+
+ for wl in workloads:
+ name = wl["metadata"]["name"]
+ replicas = wl["spec"].get("replicas", 1)
+
+ if replicas == 0:
+ print(f"\n [删除] {kind}/{name} (副本数为 0)")
+ if dry_run:
+ print(" [DRY-RUN] 跳过删除")
+ report["skipped"].append({"name": name, "reason": "replicas=0"})
+ else:
+ result = run_kubectl([
+ "delete", kind.lower(), name,
+ "-n", NAMESPACE
+ ])
+ if result is not None:
+ print(" [成功] 已删除")
+ report["deleted"].append(name)
+ else:
+ print(" [失败] 删除失败")
+ report["errors"].append({"name": name, "reason": "delete failed"})
+ continue
+
+ spec = wl["spec"]["template"]["spec"]
+ containers = spec.get("containers", [])
+
+ category = "business"
+
+ for idx, container in enumerate(containers):
+ container_name = container.get("name", f"container-{idx}")
+ resources = container.get("resources", {})
+ limits = resources.get("limits", {})
+ requests = resources.get("requests", {})
+
+ # 提取当前 JAVA_OPTS
+ current_java_opts = ""
+ env_list = container.get("env", [])
+ for env in env_list:
+ if env.get("name") == "CUST_JAVA_OPTS":
+ current_java_opts = env.get("value", "")
+ break
+
+ item = {
+ "name": name,
+ "kind": kind,
+ "container": container_name,
+ "container_index": idx,
+ "category": category,
+ "current_limits_cpu": limits.get("cpu", "未设置"),
+ "current_limits_memory": limits.get("memory", "未设置"),
+ "current_requests_cpu": requests.get("cpu", "未设置"),
+ "current_requests_memory": requests.get("memory", "未设置"),
+ "current_java_opts": current_java_opts,
+ "over_limit": is_resource_over_limit(resources),
+ }
+
+ report[category].append(item)
+
+ # 只调整超标的
+ if item["over_limit"]:
+ print(f"\n [超标] {kind}/{name} (容器: {container_name})")
+ print(f" 当前: limits={{cpu:{limits.get('cpu', 'N/A')}, memory:{limits.get('memory', 'N/A')}}}")
+ print(f" 目标: limits={{cpu:{TARGET_RESOURCES['limits']['cpu']}, memory:{TARGET_RESOURCES['limits']['memory']}}}")
+
+ if dry_run:
+ print(f" [DRY-RUN] 跳过 patch")
+ report["skipped"].append(item)
+ else:
+ # 构造 patch JSON
+ patch = {
+ "spec": {
+ "template": {
+ "spec": {
+ "containers": []
+ }
+ }
+ }
+ }
+
+ # 构建容器 patch(需要按索引)
+ container_patch = {
+ "name": container_name,
+ "resources": TARGET_RESOURCES
+ }
+
+ # 同步修改 CUST_JAVA_OPTS(如果存在该 env)
+ if current_java_opts:
+ # 使用 kubectl set env 更简洁,这里用 strategic merge patch
+ new_env = []
+ for env in env_list:
+ if env.get("name") == "CUST_JAVA_OPTS":
+ new_env.append({
+ "name": "CUST_JAVA_OPTS",
+ "value": TARGET_JAVA_OPTS
+ })
+ else:
+ new_env.append(env)
+ container_patch["env"] = new_env
+
+ patch["spec"]["template"]["spec"]["containers"] = [container_patch]
+
+ patch_json = json.dumps(patch)
+
+ result = run_kubectl([
+ "patch", kind.lower(), name,
+ "-n", NAMESPACE,
+ "--type", "strategic",
+ "-p", patch_json
+ ])
+
+ if result is not None:
+ print(f" [成功] 已 patch {kind}/{name}")
+ report["patched"].append(item)
+ else:
+ print(f" [失败] patch {kind}/{name} 失败")
+ report["errors"].append(item)
+ else:
+ # 未超标但可能也需要同步 JAVA_OPTS
+ if current_java_opts and current_java_opts != TARGET_JAVA_OPTS:
+ print(f"\n [JAVA_OPTS 不一致] {kind}/{name} (容器: {container_name})")
+ print(f" 当前: {current_java_opts}")
+ print(f" 目标: {TARGET_JAVA_OPTS}")
+
+ if not dry_run:
+ result = run_kubectl([
+ "set", "env",
+ f"{kind.lower()}/{name}",
+ "-n", NAMESPACE,
+ f"CUST_JAVA_OPTS={TARGET_JAVA_OPTS}",
+ "-c", container_name
+ ])
+ if result is not None:
+ print(f" [成功] 已更新 JAVA_OPTS")
+ else:
+ print(f" [失败] 更新 JAVA_OPTS 失败")
+
+ return report
+
+
+def print_summary(report, kind):
+ """打印汇总报告"""
+ print(f"\n{'='*60}")
+ print(f" {kind} 资源扫描报告")
+ print(f"{'='*60}")
+
+ print(f"\n 🚀 业务 Deployment ({len(report['business'])} 个容器):")
+ for item in report["business"]:
+ over = "⚠️ 超标" if item["over_limit"] else "✅ 正常"
+ print(f" - {item['name']}/{item['container']}: "
+ f"limits(cpu={item['current_limits_cpu']}, mem={item['current_limits_memory']}) "
+ f"requests(cpu={item['current_requests_cpu']}, mem={item['current_requests_memory']}) "
+ f"{over}")
+
+ if report.get("deleted"):
+ print(f"\n 🗑️ 已删除 (副本数为0): {len(report['deleted'])} 个")
+ if report["patched"]:
+ print(f"\n 🔧 已 Patch: {len(report['patched'])} 个")
+ if report["skipped"]:
+ print(f" ⏭️ 跳过 (DRY-RUN): {len(report['skipped'])} 个")
+ if report["errors"]:
+ print(f" ❌ 失败: {len(report['errors'])} 个")
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Phase 2: jxyd 命名空间资源检查与调整")
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--dry-run", action="store_true", help="仅预览,不执行变更")
+ group.add_argument("--apply", action="store_true", help="执行变更")
+ args = parser.parse_args()
+
+ dry_run = args.dry_run
+
+ print(f"\n{'#'*60}")
+ print(f" Phase 2: 资源检查与调整")
+ print(f" 命名空间: {NAMESPACE}")
+ print(f" 模式: {'DRY-RUN(仅预览)' if dry_run else '⚡ APPLY(执行变更)'}")
+ print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
+ print(f"{'#'*60}")
+
+ if not dry_run:
+ confirm = input("\n ⚠️ 确认要执行资源调整吗?(yes/no): ")
+ if confirm.lower() != "yes":
+ print(" 已取消。")
+ return
+
+ # 扫描 Deployment (业务)
+ print(f"\n{'='*60}")
+ print(" 扫描 Deployment...")
+ print(f"{'='*60}")
+ deployments = get_deployments()
+ dep_report = analyze_and_patch(deployments, "Deployment", dry_run)
+ print_summary(dep_report, "Deployment")
+
+ # 生成变更日志
+ log_file = f"/root/wdd/backup_260617/phase2_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
+ log_data = {
+ "timestamp": datetime.now().isoformat(),
+ "mode": "dry-run" if dry_run else "apply",
+ "deployment_report": {
+ "business_count": len(dep_report["business"]),
+ "patched_count": len(dep_report["patched"]),
+ "error_count": len(dep_report["errors"]),
+ }
+ }
+
+ try:
+ with open(log_file, 'w') as f:
+ json.dump(log_data, f, indent=2, ensure_ascii=False)
+ print(f"\n 📄 报告已保存到: {log_file}")
+ except Exception as e:
+ print(f"\n [警告] 无法保存报告文件: {e}")
+
+ print(f"\n{'#'*60}")
+ print(f" Phase 2 完成!")
+ print(f"{'#'*60}")
+
+
+if __name__ == "__main__":
+ main()
+```
+
+---
+
+## 五、Phase 3:中间件迁移
+
+### 3.1 给保留节点打 Label
+
+```bash
+#!/bin/bash
+# ============================================================
+# Phase 3.1: 给保留节点打 Label
+# ============================================================
+
+echo "=== 给保留节点打 label: jxyd-middleware=true ==="
+
+# 保留节点
+kubectl label node 10.20.1.133 jxyd-middleware=true --overwrite
+kubectl label node 10.20.1.134 jxyd-middleware=true --overwrite
+kubectl label node 10.20.1.141 jxyd-middleware=true --overwrite
+
+echo ""
+echo ""
+echo "=== 给待清退节点打 label: jxyd-business=true ==="
+# 待清退节点(将业务部署到这些节点)
+kubectl label node 10.20.1.142 jxyd-business=true --overwrite
+kubectl label node 10.20.1.144 jxyd-business=true --overwrite
+kubectl label node 10.20.1.145 jxyd-business=true --overwrite
+
+echo ""
+echo "=== 验证 label ==="
+kubectl get nodes -l jxyd-middleware=true -o wide
+kubectl get nodes -l jxyd-business=true -o wide
+```
+
+### 3.2 调度与迁移脚本 `phase3_migrate_workloads.py`
+
+```python
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Phase 3: 工作负载迁移与调度
+功能:
+ 1. 识别 jxyd 命名空间下的中间件 StatefulSet 与 业务 Deployment
+ 2. 为中间件添加 nodeSelector,调度到保留节点(过滤包含 hostPath 的中间件)
+ 3. 为业务 Deployment 添加 nodeSelector,调度到待清退节点
+ 4. 验证调度和迁移结果
+
+使用方式:
+ python3 phase3_migrate_workloads.py --list # 列出迁移计划
+ python3 phase3_migrate_workloads.py --dry-run # 预览变更
+ python3 phase3_migrate_workloads.py --apply # 执行迁移
+ python3 phase3_migrate_workloads.py --verify # 验证迁移结果
+"""
+
+import json
+import subprocess
+import sys
+import argparse
+from datetime import datetime
+
+
+NAMESPACE = "jxyd"
+
+# 目标节点 label
+TARGET_NODE_SELECTOR = {"jxyd-middleware": "true"}
+BUSINESS_NODE_SELECTOR = {"jxyd-business": "true"}
+
+# 中间件关键词
+MIDDLEWARE_KEYWORDS = [
+ "mysql", "redis", "rabbitmq", "kafka", "zookeeper", "elasticsearch",
+ "nacos", "minio", "mongo", "postgres", "nginx",
+ "sentinel", "rocketmq", "emqx", "mqtt", "influxdb", "grafana",
+ "prometheus", "xxl-job", "seata"
+]
+
+# 待清退节点
+EVICT_NODES = ["10.20.1.142", "10.20.1.144", "10.20.1.145"]
+
+
+def run_kubectl(args):
+ cmd = ["kubectl"] + args
+ result = subprocess.run(cmd, capture_output=True, text=True)
+ if result.returncode != 0:
+ print(f" [错误] {' '.join(cmd)}: {result.stderr.strip()}")
+ return None
+ return result.stdout
+
+
+def is_middleware(name):
+ name_lower = name.lower()
+ return any(kw in name_lower for kw in MIDDLEWARE_KEYWORDS)
+
+
+def get_workloads(kind):
+ output = run_kubectl(["get", kind, "-n", NAMESPACE, "-o", "json"])
+ if not output:
+ return []
+ return json.loads(output).get("items", [])
+
+
+def list_workloads():
+ """列出工作负载迁移与调度计划"""
+ print(f"\n{'='*60}")
+ print(f" jxyd 命名空间 - 迁移与调度计划")
+ print(f"{'='*60}")
+
+ # StatefulSet (中间件)
+ sts_workloads = get_workloads("statefulsets")
+ mw_list = [w for w in sts_workloads if is_middleware(w["metadata"]["name"])]
+ print(f"\n 📦 中间件 StatefulSet (调度到保留节点,{len(mw_list)} 个):")
+ for w in mw_list:
+ name = w["metadata"]["name"]
+ replicas = w["spec"].get("replicas", 1)
+ node_selector = w["spec"]["template"]["spec"].get("nodeSelector", {})
+ volumes = w["spec"]["template"]["spec"].get("volumes", [])
+ has_host_path = any("hostPath" in v for v in volumes)
+ host_path_flag = "[含 hostPath,不可迁移]" if has_host_path else ""
+ print(f" ✅ {name} (replicas={replicas}, nodeSelector={node_selector}) {host_path_flag}")
+
+ # Deployment (业务)
+ dep_workloads = get_workloads("deployments")
+ print(f"\n 🚀 业务 Deployment (调度到待清退节点,{len(dep_workloads)} 个):")
+ for w in dep_workloads:
+ name = w["metadata"]["name"]
+ replicas = w["spec"].get("replicas", 1)
+ node_selector = w["spec"]["template"]["spec"].get("nodeSelector", {})
+ if replicas == 0:
+ print(f" - {name} (replicas=0, 忽略调度)")
+ else:
+ print(f" ✅ {name} (replicas={replicas}, nodeSelector={node_selector})")
+
+ print(f"\n 💡 提示: 若状态不准确可人工审核配置。")
+
+
+def migrate_workloads(dry_run=True):
+ """执行工作负载的迁移与调度"""
+ print(f"\n{'='*60}")
+ print(f" 工作负载调度 - {'DRY-RUN' if dry_run else 'APPLY'}")
+ print(f"{'='*60}")
+
+ patched = 0
+ errors = 0
+
+ def apply_patch(kind, w, target_ns, is_middleware_check=False):
+ nonlocal patched, errors
+ name = w["metadata"]["name"]
+ replicas = w["spec"].get("replicas", 1)
+ current_ns = w["spec"]["template"]["spec"].get("nodeSelector", {})
+
+ if replicas == 0:
+ return # 忽略副本数为 0 的
+
+ if is_middleware_check:
+ volumes = w["spec"]["template"]["spec"].get("volumes", [])
+ has_host_path = any("hostPath" in v for v in volumes)
+ if has_host_path:
+ print(f" [跳过] {kind}/{name} 包含 hostPath 挂载,不能进行迁移")
+ return
+
+ # 检查是否已包含所有的目标 selector
+ if all(current_ns.get(k) == v for k, v in target_ns.items()):
+ print(f" [跳过] {kind}/{name} 已满足 nodeSelector 目标")
+ return
+
+ print(f" [调度] {kind}/{name}")
+ print(f" 当前 nodeSelector: {current_ns}")
+ print(f" 目标 nodeSelector: {target_ns}")
+
+ if dry_run:
+ print(f" [DRY-RUN] 跳过")
+ return
+
+ merged_selector = {**current_ns, **target_ns}
+ patch = {"spec": {"template": {"spec": {"nodeSelector": merged_selector}}}}
+ result = run_kubectl([
+ "patch", kind, name, "-n", NAMESPACE, "--type", "strategic", "-p", json.dumps(patch)
+ ])
+
+ if result is not None:
+ print(f" [成功] 已 patch")
+ patched += 1
+ else:
+ print(f" [失败]")
+ errors += 1
+
+ # 处理中间件
+ sts_workloads = get_workloads("statefulsets")
+ for w in [w for w in sts_workloads if is_middleware(w["metadata"]["name"])]:
+ apply_patch("statefulset", w, TARGET_NODE_SELECTOR, True)
+
+ # 处理业务
+ dep_workloads = get_workloads("deployments")
+ for w in dep_workloads:
+ apply_patch("deployment", w, BUSINESS_NODE_SELECTOR, False)
+
+ print(f"\n 汇总: 已调度 {patched} 个, 失败 {errors} 个")
+
+
+def verify_migration():
+ """验证迁移及调度结果"""
+ print(f"\n{'='*60}")
+ print(f" 调度结果验证")
+ print(f"{'='*60}")
+
+ issues = []
+
+ def check_workloads(kind, workloads, target_label_key, target_nodes_list, is_middleware_check=False):
+ for w in workloads:
+ name = w["metadata"]["name"]
+ replicas = w["spec"].get("replicas", 1)
+ if replicas == 0:
+ continue
+
+ ns = w["spec"]["template"]["spec"].get("nodeSelector", {})
+ if is_middleware_check:
+ volumes = w["spec"]["template"]["spec"].get("volumes", [])
+ if any("hostPath" in v for v in volumes):
+ continue
+
+ if ns.get(target_label_key) != "true":
+ issues.append(f" ⚠️ {kind}/{name} 未设置 {target_label_key} nodeSelector")
+
+ pods_output = run_kubectl(["get", "pods", "-n", NAMESPACE, "-l", f"app={name}", "-o", "json"])
+ if not pods_output or json.loads(pods_output).get("items", []) == []:
+ pods_output = run_kubectl(["get", "pods", "-n", NAMESPACE, "-o", "json"])
+ if pods_output:
+ all_pods = [p for p in json.loads(pods_output).get("items", []) if p["metadata"]["name"].startswith(name)]
+ else:
+ all_pods = []
+ else:
+ all_pods = json.loads(pods_output).get("items", [])
+
+ for pod in all_pods:
+ pod_name = pod["metadata"]["name"]
+ node = pod["spec"].get("nodeName", "unknown")
+ phase = pod["status"].get("phase", "Unknown")
+
+ # Check target nodes
+ if target_nodes_list is not None and node not in target_nodes_list:
+ issues.append(f" ⚠️ Pod {pod_name} 调度异常,当前节点 {node},应在目标节点集合 {target_nodes_list} 中")
+ # Exclude nodes
+ elif target_nodes_list is None and node in EVICT_NODES:
+ issues.append(f" ⚠️ Pod {pod_name} 仍在待清退节点 {node} 上")
+ else:
+ print(f" ✅ {pod_name} -> 节点 {node} (状态: {phase})")
+
+ # 1. 中间件需在保留节点上(非待清退节点)
+ sts_workloads = get_workloads("statefulsets")
+ mw_list = [w for w in sts_workloads if is_middleware(w["metadata"]["name"])]
+ check_workloads("statefulset", mw_list, "jxyd-middleware", None, True)
+
+ # 2. 业务需在待清退节点上
+ dep_workloads = get_workloads("deployments")
+ check_workloads("deployment", dep_workloads, "jxyd-business", EVICT_NODES, False)
+
+ if issues:
+ print(f"\n ⚠️ 发现 {len(issues)} 个问题:")
+ for issue in issues:
+ print(issue)
+ else:
+ print(f"\n ✅ 所有的工作负载已成功调度到对应的目标节点!")
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Phase 3: 工作负载迁移与调度")
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--list", action="store_true", help="列出迁移计划")
+ group.add_argument("--dry-run", action="store_true", help="预览变更")
+ group.add_argument("--apply", action="store_true", help="执行迁移")
+ group.add_argument("--verify", action="store_true", help="验证迁移结果")
+ args = parser.parse_args()
+
+ if args.list:
+ list_workloads()
+ elif args.dry_run:
+ migrate_workloads(dry_run=True)
+ elif args.apply:
+ confirm = input(" ⚠️ 确认要执行迁移和调度吗?(yes/no): ")
+ if confirm.lower() == "yes":
+ migrate_workloads(dry_run=False)
+ else:
+ print(" 已取消。")
+ elif args.verify:
+ verify_migration()
+
+
+if __name__ == "__main__":
+ main()
+```
+
+---
+
+## 六、Phase 4:节点清退(一个月后执行)
+
+### 4.1 清退脚本 `phase4_evict_nodes.sh`
+
+```bash
+#!/bin/bash
+# ============================================================
+# Phase 4: 节点清退(一个月后执行)
+#
+# ⚠️ 此脚本将执行不可逆操作,请确认:
+# 1. Phase 2 资源调整已完成
+# 2. Phase 3 中间件迁移已完成并验证
+# 3. 已获得相关审批
+# ============================================================
+
+set -e
+
+NAMESPACE="jxyd"
+EVICT_NODES=("10.20.1.142" "10.20.1.144" "10.20.1.145")
+LOG_DIR="/root/wdd/backup_260617/phase4_$(date +%Y%m%d_%H%M%S)"
+mkdir -p "${LOG_DIR}"
+
+echo "=========================================="
+echo " Phase 4: 节点清退"
+echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
+echo " 日志: ${LOG_DIR}"
+echo "=========================================="
+
+# ============================================================
+# Step 1: 最终确认
+# ============================================================
+echo ""
+echo "⚠️ 即将执行以下操作:"
+echo " 1. 删除 ${NAMESPACE} 命名空间中的业务 Deployment"
+echo " 2. Cordon 节点: ${EVICT_NODES[*]}"
+echo " 3. Drain 节点: ${EVICT_NODES[*]}"
+echo " 4. 从集群中删除节点: ${EVICT_NODES[*]}"
+echo ""
+read -p "确认执行?(输入 YES 继续): " CONFIRM
+if [ "${CONFIRM}" != "YES" ]; then
+ echo "已取消。"
+ exit 0
+fi
+
+# ============================================================
+# Step 2: 最终备份
+# ============================================================
+echo ""
+echo "=== Step 2: 最终备份 ==="
+kubectl get all -n ${NAMESPACE} -o yaml > "${LOG_DIR}/final_backup_all.yaml"
+kubectl get pods -n ${NAMESPACE} -o wide > "${LOG_DIR}/final_pod_distribution.txt"
+echo " 备份完成"
+
+# ============================================================
+# Step 3: 删除 jxyd 业务 Deployment
+# (说明:中间件仅存在于 StatefulSet,因此所有的 Deployment 均为业务)
+# ============================================================
+echo ""
+echo "=== Step 3: 删除 jxyd 业务 Deployment ==="
+
+# 获取所有 deployment
+ALL_DEPS=$(kubectl get deployments -n ${NAMESPACE} -o jsonpath='{.items[*].metadata.name}')
+
+for dep in ${ALL_DEPS}; do
+ echo " [删除-业务] ${dep}"
+ kubectl delete deployment "${dep}" -n ${NAMESPACE} --grace-period=30 2>&1 | tee -a "${LOG_DIR}/delete_deployments.log"
+done
+
+# ============================================================
+# Step 4: Cordon 节点(标记不可调度)
+# ============================================================
+echo ""
+echo "=== Step 4: Cordon 节点 ==="
+for NODE in "${EVICT_NODES[@]}"; do
+ echo " Cordon ${NODE}..."
+ kubectl cordon "${NODE}" 2>&1 | tee -a "${LOG_DIR}/cordon.log"
+done
+
+echo ""
+kubectl get nodes -o wide | tee "${LOG_DIR}/nodes_after_cordon.txt"
+
+# ============================================================
+# Step 5: Drain 节点(驱逐所有 Pod)
+# ============================================================
+echo ""
+echo "=== Step 5: Drain 节点 ==="
+for NODE in "${EVICT_NODES[@]}"; do
+ echo " Drain ${NODE}..."
+ kubectl drain "${NODE}" \
+ --ignore-daemonsets \
+ --delete-emptydir-data \
+ --force \
+ --grace-period=60 \
+ --timeout=300s \
+ 2>&1 | tee -a "${LOG_DIR}/drain_${NODE}.log"
+
+ echo " 等待 30 秒让 Pod 完成迁移..."
+ sleep 30
+done
+
+# ============================================================
+# Step 6: 验证 Pod 已全部迁走
+# ============================================================
+echo ""
+echo "=== Step 6: 验证 Pod 迁移情况 ==="
+for NODE in "${EVICT_NODES[@]}"; do
+ REMAINING=$(kubectl get pods --all-namespaces --field-selector spec.nodeName=${NODE} --no-headers 2>/dev/null | grep -v "kube-system" | wc -l)
+ if [ "${REMAINING}" -gt 0 ]; then
+ echo " ⚠️ 节点 ${NODE} 上仍有 ${REMAINING} 个非系统 Pod:"
+ kubectl get pods --all-namespaces --field-selector spec.nodeName=${NODE} -o wide | grep -v "kube-system"
+ else
+ echo " ✅ 节点 ${NODE} 上的用户 Pod 已全部迁走"
+ fi
+done
+
+# ============================================================
+# Step 7: 从集群中删除节点
+# ============================================================
+echo ""
+echo "=== Step 7: 从集群中删除节点 ==="
+read -p "确认从集群中删除节点?(输入 YES 继续): " CONFIRM2
+if [ "${CONFIRM2}" == "YES" ]; then
+ for NODE in "${EVICT_NODES[@]}"; do
+ echo " 删除节点 ${NODE}..."
+ kubectl delete node "${NODE}" 2>&1 | tee -a "${LOG_DIR}/delete_nodes.log"
+ done
+else
+ echo " 跳过节点删除。可稍后手动执行: kubectl delete node "
+fi
+
+# ============================================================
+# Step 8: 最终验证
+# ============================================================
+echo ""
+echo "=== Step 8: 最终验证 ==="
+echo "--- 集群节点状态 ---"
+kubectl get nodes -o wide | tee "${LOG_DIR}/nodes_final.txt"
+echo ""
+echo "--- jxyd 命名空间 Pod 状态 ---"
+kubectl get pods -n ${NAMESPACE} -o wide | tee "${LOG_DIR}/pods_final.txt"
+
+echo ""
+echo "=========================================="
+echo " Phase 4 完成!"
+echo " 日志目录: ${LOG_DIR}"
+echo "=========================================="
+```
+
+---
+
+## 七、快速命令参考
+
+### 如果需要手动单独操作
+
+```bash
+# ============================================================
+# 常用单条命令(按需执行)
+# ============================================================
+
+# 1. 查看 jxyd 下所有 deployment 的资源配置
+kubectl get deploy -n jxyd -o custom-columns=\
+NAME:.metadata.name,\
+CPU_LIMIT:.spec.template.spec.containers[0].resources.limits.cpu,\
+MEM_LIMIT:.spec.template.spec.containers[0].resources.limits.memory,\
+CPU_REQ:.spec.template.spec.containers[0].resources.requests.cpu,\
+MEM_REQ:.spec.template.spec.containers[0].resources.requests.memory
+
+# 2. 查看某个 deployment 的完整资源配置
+kubectl get deploy -n jxyd -o jsonpath='{.spec.template.spec.containers[0].resources}' | python3 -m json.tool
+
+# 3. 手动 patch 单个 deployment 的资源
+kubectl patch deploy -n jxyd --type='strategic' -p '{
+ "spec": {
+ "template": {
+ "spec": {
+ "containers": [{
+ "name": "",
+ "resources": {
+ "limits": {"cpu": "2", "memory": "2Gi"},
+ "requests": {"cpu": "1", "memory": "500Mi"}
+ },
+ "env": [{
+ "name": "CUST_JAVA_OPTS",
+ "value": "-Xms500m -Xmx2000m -Dlog4j2.formatMsgNoLookups=true"
+ }]
+ }]
+ }
+ }
+ }
+}'
+
+# 4. 查看哪些 Pod 在待清退节点上
+for node in 10.20.1.142 10.20.1.144 10.20.1.145; do
+ echo "=== $node ==="
+ kubectl get pods -n jxyd --field-selector spec.nodeName=$node -o wide
+done
+
+# 5. 单独给 StatefulSet 设置 nodeSelector
+kubectl patch statefulset -n jxyd --type='strategic' -p '{"spec":{"template":{"spec":{"nodeSelector":{"jxyd-middleware":"true"}}}}}'
+
+# 5.1 单独给业务 Deployment 设置 nodeSelector
+kubectl patch deploy -n jxyd --type='strategic' -p '{"spec":{"template":{"spec":{"nodeSelector":{"jxyd-business":"true"}}}}}'
+
+# 6. 快速 cordon(标记不可调度)
+kubectl cordon 10.20.1.142
+kubectl cordon 10.20.1.144
+kubectl cordon 10.20.1.145
+
+# 7. 快速 drain(驱逐 + 清空)
+kubectl drain 10.20.1.142 --ignore-daemonsets --delete-emptydir-data --force --grace-period=60
+kubectl drain 10.20.1.144 --ignore-daemonsets --delete-emptydir-data --force --grace-period=60
+kubectl drain 10.20.1.145 --ignore-daemonsets --delete-emptydir-data --force --grace-period=60
+```
+
+---
+
+## 八、注意事项与风险提示
+
+### ⚠️ 关键风险点
+
+| 风险 | 说明 | 应对措施 |
+|------|------|----------|
+| 中间件识别遗漏 | 自动识别依赖关键词,可能漏判 | **先用 `--list` 模式人工审核** |
+| PVC/PV 数据丢失 | StatefulSet 的持久化数据可能绑定在特定节点的 local PV | 提前检查 PV 类型,如为 `local-storage` 需手动迁移数据 |
+| 资源降配影响服务 | CPU/内存缩减可能导致 OOM 或性能下降 | 降配后监控1-2天,关注 Pod 重启情况 |
+| Strategic Merge Patch 覆盖 env | patch containers 时注意 env 的合并策略 | 脚本已处理,但建议 patch 后验证 |
+| Drain 超时 | 存在强制终止 Pod 的风险 | 已设置 `grace-period=60` 和 `timeout=300s` |
+
+### ✅ 执行顺序清单
+
+```
+□ Phase 1: 执行备份脚本,确认备份完整
+□ Phase 2: 先 --dry-run 预览资源调整
+□ Phase 2: --apply 执行资源调整(仅针对 Deployment 业务)
+□ Phase 2: 监控1-2天确认服务稳定
+□ Phase 3: 给保留节点打 label
+□ Phase 3: 先 --list 查看迁移和调度计划(并确认 hostPath)
+□ Phase 3: 人工确认中间件识别结果,如有遗漏则修改 MIDDLEWARE_KEYWORDS
+□ Phase 3: --dry-run 预览迁移与调度
+□ Phase 3: --apply 执行迁移与调度
+□ Phase 3: --verify 验证迁移与调度结果
+□ Phase 3: 监控中间件服务是否正常
+□ ---- 等待一个月 ----
+□ Phase 4: 执行最终清退脚本
+□ Phase 4: 验证集群状态正常
+```
diff --git a/997-项目VPN虚拟机/3-ubuntu2204虚拟机模板.sh b/997-项目VPN虚拟机/3-ubuntu2204虚拟机模板.sh
index 4c4f1e0..c1fac85 100644
--- a/997-项目VPN虚拟机/3-ubuntu2204虚拟机模板.sh
+++ b/997-项目VPN虚拟机/3-ubuntu2204虚拟机模板.sh
@@ -32,6 +32,19 @@ virsh start ubuntu2204-vm
virsh destroy ubuntu2204-vm
+python3 vm_manager.py clone-linux \
+ --template ubuntu2204-vm\
+ --name-prefix RMDC-ubuntu2204 \
+ --ip 192.168.11.20 \
+ --prefix 24 \
+ --gateway 192.168.11.1 \
+ --dns 192.168.34.40,223.5.5.5 \
+ --vcpus 4 \
+ --memory 8192 \
+ --data-size 20000G \
+ --autostart
+
+
python3 vm_manager.py clone-linux \
--template ubuntu2204-vm\
--name-prefix ubuntu2204 \
diff --git a/998-常用脚本/f-nginx暴露/真实nginx-reverse-proxy.conf b/998-常用脚本/f-nginx暴露/真实nginx-reverse-proxy.conf
index 4ac635a..0d8ce3a 100644
--- a/998-常用脚本/f-nginx暴露/真实nginx-reverse-proxy.conf
+++ b/998-常用脚本/f-nginx暴露/真实nginx-reverse-proxy.conf
@@ -12,6 +12,8 @@ server {
listen 8088;
server_name localhost;
+ underscores_in_headers on;
+
location / {
proxy_pass http://proxy_server;
diff --git a/998-常用脚本/故障恢复脚本/删除状态不为Running的Pod.sh b/998-常用脚本/故障恢复脚本/删除状态不为Running的Pod.sh
index 38aeb2e..3fb9f0f 100644
--- a/998-常用脚本/故障恢复脚本/删除状态不为Running的Pod.sh
+++ b/998-常用脚本/故障恢复脚本/删除状态不为Running的Pod.sh
@@ -10,7 +10,7 @@ chmod +x /usr/local/bin/jq
export name_space_list=(bj-sh-uas-260511)
-export name_space_list=(kube-system kubernetes-dashboard ingress-nginx bj-sh-uas-260511)
+export name_space_list=(kube-system kubernetes-dashboard ingress-nginx xayd)
for name_space in ${name_space_list[*]};do
diff --git a/998-常用脚本/更新脚本/cmii-clean母脚本.sh b/998-常用脚本/更新脚本/cmii-clean母脚本.sh
new file mode 100644
index 0000000..cf9329c
--- /dev/null
+++ b/998-常用脚本/更新脚本/cmii-clean母脚本.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+set -euo pipefail
+
+TARGET_DIR="${1:-.}"
+RETENTION_DAYS="${2:-0}" # 0 表示不限时间,直接清理
+
+PATTERNS=(
+ "*.tar.gz" "*.tar.gz.*" "*.tgz"
+ "*.tar.bz2" "*.tbz2"
+ "*.tar.xz" "*.txz"
+ "*.zip" "*.7z" "*.rar"
+ "*.tar"
+ "wget-log*" "wget.log*" "*.wget.log"
+)
+
+cd "$TARGET_DIR" || { echo "无法进入目录: $TARGET_DIR"; exit 1; }
+LOG_FILE="./cleanup_$(date +%Y%m%d).log"
+log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
+
+log "开始清理目录: $(pwd) 保留天数: $RETENTION_DAYS"
+TOTAL_COUNT=0; TOTAL_SIZE=0
+
+for pattern in "${PATTERNS[@]}"; do
+ if [ "$RETENTION_DAYS" -gt 0 ]; then
+ mapfile -d '' files < <(find . -maxdepth 1 -type f -name "$pattern" -mtime +"$RETENTION_DAYS" -print0)
+ else
+ mapfile -d '' files < <(find . -maxdepth 1 -type f -name "$pattern" -print0)
+ fi
+ for f in "${files[@]}"; do
+ [ -e "$f" ] || continue
+ size=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f" 2>/dev/null || echo 0)
+ TOTAL_SIZE=$((TOTAL_SIZE + size)); TOTAL_COUNT=$((TOTAL_COUNT + 1))
+ log "删除: $f (${size} 字节)"
+ rm -f -- "$f"
+ done
+done
+
+HUMAN_SIZE=$(numfmt --to=iec --suffix=B "$TOTAL_SIZE" 2>/dev/null || echo "${TOTAL_SIZE}B")
+log "清理完成,共删除 ${TOTAL_COUNT} 个文件,释放 ${HUMAN_SIZE} 空间"
+find . -maxdepth 1 -type f -name "cleanup_*.log" -mtime +30 -delete
\ No newline at end of file