浏览代码

docs: 自监控-only 变更说明 + 启动自动化 bootstrap 辅助容器

clm0081 4 天之前
父节点
当前提交
d44a25fb88
共有 3 个文件被更改,包括 202 次插入 和 0 次删除
  1. 40 0
      README.md
  2. 37 0
      bootstrap/docker-compose.bootstrap.yaml
  3. 125 0
      bootstrap/zbx-bootstrap.py

+ 40 - 0
README.md

@@ -12,6 +12,46 @@
 ## 文件
 - `docker-compose.yaml` —— 生产用编排文件(当前 7.4.16)
 - `compose-audit.md` —— 原 compose 的问题审计与缓存容量重估说明
+- `bootstrap/zbx-bootstrap.py` —— 「自监控-only」清理脚本(启动后摘除自身 agent 模板)
+- `bootstrap/docker-compose.bootstrap.yaml` —— 可选叠加编排:随 `up` 自动运行上述脚本
+
+## 自监控-only:服务器自身主机不挂 agent(2026-11 变更)
+
+Zabbix 服务端首次启动会自动创建名为 `Zabbix server` 的自身监控主机,默认挂
+`Linux by Zabbix agent` + `Zabbix server health`。由于本机**不安装 zabbix-agent**,
+agent 那批项永远空采,还会周期性尝试连本机 10050 而产生 "agent 不可用" 类无效告警/报错。
+
+已做的调整(手工,一次):
+1. 解除自身主机上的 `Linux by Zabbix agent` 模板(连带其监控项/触发器),仅保留
+   `Zabbix server health` —— 即“自己监控自己、不连 agent”,不再报错。
+2. 关闭了遗留的 "Linux: Zabbix agent is not available" 触发器。
+3. 首页 `Global view` 中原引用 agent 项(Available memory / CPU utilization)的 svggraph
+   已改指向自身内部项 `Configuration cache, % used` / `History write cache, % used`
+   (重命名为「Cache usage」),避免图报 "item not found"。
+
+> 本机仍保留 11 个与未启用进程(IPMI/Java/Report/VMware/Connector 等)相关的内部项,
+> 但已**禁用**,不会产生红点/报错。
+
+## 启动即自动化(可选):bootstrap 辅助容器
+
+想让 `docker compose up` 时**自动**完成上面第 1 步(摘除自身 agent 模板),用附带的
+叠加编排即可,无需改 Zabbix 源码:
+
+```bash
+# 启用(每次 up 自动跑一次;幂等)
+docker compose -f docker-compose.yaml -f bootstrap/docker-compose.bootstrap.yaml up -d
+
+# 手动单独重跑一次
+docker compose -f docker-compose.yaml -f bootstrap/docker-compose.bootstrap.yaml run --rm zbx-bootstrap
+```
+
+- 原理:一次性容器 `python:3.12-slim` 运行 `bootstrap/zbx-bootstrap.py`,轮询等 API 就绪,
+  再用 API 把「服务器自身主机」(带 `Zabbix server health` 模板的那台)上的
+  `Linux by Zabbix agent` 模板摘除;已干净则跳过。
+- 认证:默认 `Admin/zabbix` 登录;生产建议在 `.env` 设置 `ZBX_API_TOKEN`(API Token),
+  脚本会优先使用它。
+- 局限:`python:3.12-slim` 镜像首次拉取需外网;该步只负责“摘除自身 agent 模板”,
+  首页那张 CPU/内存图的重指向属展示层,可在 UI 手动改(见上)。
 
 ## 部署位置
 - 服务器:`10.160.10.213`

+ 37 - 0
bootstrap/docker-compose.bootstrap.yaml

@@ -0,0 +1,37 @@
+# =============================================================================
+# 可选叠加文件:docker compose 启动时自动执行「Zabbix 自监控-only」清理
+#
+# 作用:等 API 就绪后,把「Zabbix server」自身主机上的 "Linux by Zabbix agent"
+#       模板摘除(只保留 "Zabbix server health" 自监控),避免本机没装 agent 时
+#       对自身 10050 的无效连接与告警。幂等,重复 up 安全。
+#
+# 用法:
+#   docker compose -f docker-compose.yaml -f bootstrap/docker-compose.bootstrap.yaml up -d
+# 手动重跑一次:
+#   docker compose -f docker-compose.yaml -f bootstrap/docker-compose.bootstrap.yaml run --rm zbx-bootstrap
+#
+# 认证:默认用 Admin/zabbix 登录;生产建议在 .env 里设置 ZBX_API_TOKEN(API Token)。
+# =============================================================================
+services:
+  zbx-bootstrap:
+    image: python:3.12-slim
+    container_name: zbx-bootstrap
+    restart: "no"
+    depends_on:
+      zabbix-server:
+        condition: service_started
+      zabbix-web-nginx-mysql:
+        condition: service_started
+    environment:
+      ZBX_API_URL: ${ZBX_API_URL:-http://zabbix-web-nginx-mysql:8080/api_jsonrpc.php}
+      ZBX_API_USER: ${ZBX_API_USER:-Admin}
+      ZBX_API_PASSWORD: ${ZBX_API_PASSWORD:-zabbix}
+      ZBX_API_TOKEN: ${ZBX_API_TOKEN:-}
+      ZBX_HEALTH_TEMPLATE: ${ZBX_HEALTH_TEMPLATE:-Zabbix server health}
+      ZBX_AGENT_TEMPLATE: ${ZBX_AGENT_TEMPLATE:-Linux by Zabbix agent}
+      ZBX_WAIT_SECONDS: ${ZBX_WAIT_SECONDS:-300}
+    volumes:
+      - ./bootstrap/zbx-bootstrap.py:/app/zbx-bootstrap.py:ro
+    command: ["python3", "/app/zbx-bootstrap.py"]
+    networks:
+      - zabbix

+ 125 - 0
bootstrap/zbx-bootstrap.py

@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Zabbix 「自监控-only」bootstrap 脚本(幂等,可重复执行)
+
+背景
+----
+Zabbix 服务端首次启动会自动创建名为 "Zabbix server" 的自身监控主机,并默认挂上
+"Linux by Zabbix agent" + "Zabbix server health" 两个模板。
+但本机并不安装 zabbix-agent,于是:
+  - 那批 agent 监控项永远采集不到数据(空采);
+  - 还会周期性尝试连接本机 10050(agent 端口)而产生 "agent 不可用" 之类的无效告警/报错。
+
+本脚本在每次 `docker compose up` 后运行一次,把「服务器自身主机」上的
+"Linux by Zabbix agent" 模板摘除(连同其监控项/触发器),只保留
+"Zabbix server health" 自监控模板 —— 即“自己监控自己、不连 agent”。
+
+幂等:每次先查询再决定是否操作;主机已干净则跳过。
+
+认证:优先使用环境变量 ZBX_API_TOKEN(推荐,API Token);否则用
+ZBX_API_USER / ZBX_API_PASSWORD 登录获取会话令牌。
+"""
+import os
+import sys
+import time
+import json
+import urllib.request
+import urllib.error
+
+URL = os.environ.get("ZBX_API_URL", "http://zabbix-web-nginx-mysql:8080/api_jsonrpc.php")
+USER = os.environ.get("ZBX_API_USER", "Admin")
+PWD = os.environ.get("ZBX_API_PASSWORD", "zabbix")
+TOKEN = os.environ.get("ZBX_API_TOKEN", "").strip()
+HEALTH = os.environ.get("ZBX_HEALTH_TEMPLATE", "Zabbix server health")
+AGENT = os.environ.get("ZBX_AGENT_TEMPLATE", "Linux by Zabbix agent")
+WAIT = int(os.environ.get("ZBX_WAIT_SECONDS", "300"))
+
+
+def call(method, params, token=None):
+    payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
+    headers = {"Content-Type": "application/json-rpc"}
+    if token:
+        headers["Authorization"] = "Bearer " + token
+    req = urllib.request.Request(URL, data=json.dumps(payload).encode(), headers=headers)
+    with urllib.request.urlopen(req, timeout=30) as resp:
+        j = json.loads(resp.read().decode())
+    if "error" in j:
+        raise RuntimeError("%s -> %s" % (method, j["error"]))
+    return j["result"]
+
+
+def wait_api():
+    deadline = time.time() + WAIT
+    while time.time() < deadline:
+        try:
+            call("apiinfo.version", {})
+            print("[bootstrap] API is up")
+            return
+        except Exception as e:  # noqa: BLE001
+            print("[bootstrap] waiting for API ... (%s)" % e)
+            time.sleep(5)
+    print("[bootstrap] ERROR: API not reachable within %ss" % WAIT)
+    sys.exit(1)
+
+
+def main():
+    wait_api()
+
+    if TOKEN:
+        token = TOKEN
+        print("[bootstrap] using provided API token")
+    else:
+        token = call("user.login", {"username": USER, "password": PWD})
+        print("[bootstrap] logged in as %s" % USER)
+
+    tpls = call(
+        "template.get",
+        {"output": ["templateid", "host", "name"], "filter": {"host": [AGENT, HEALTH]}},
+        token,
+    )
+    ids = {t["host"]: t["templateid"] for t in tpls}
+    if AGENT not in ids:
+        print("[bootstrap] agent template %r not found -> nothing to do" % AGENT)
+        return
+    if HEALTH not in ids:
+        print("[bootstrap] health template %r not found -> cannot identify server host, skip" % HEALTH)
+        return
+
+    agent_id = ids[AGENT]
+    health_id = ids[HEALTH]
+
+    hosts = call(
+        "host.get",
+        {
+            "output": ["hostid", "host"],
+            "templateids": [health_id],
+            "selectParentTemplates": ["templateid", "host"],
+        },
+        token,
+    )
+
+    changed = 0
+    for h in hosts:
+        tids = [p["templateid"] for p in h.get("parentTemplates", [])]
+        if agent_id in tids:
+            keep = [{"templateid": x} for x in tids if x != agent_id]
+            call(
+                "host.update",
+                {
+                    "hostid": h["hostid"],
+                    "templates": keep,
+                    "templates_clear": [{"templateid": agent_id}],
+                },
+                token,
+            )
+            print("[bootstrap] removed %r from host %s (%s)" % (AGENT, h["host"], h["hostid"]))
+            changed += 1
+        else:
+            print("[bootstrap] host %s (%s) already clean" % (h["host"], h["hostid"]))
+
+    print("[bootstrap] done, changed %d host(s)" % changed)
+
+
+if __name__ == "__main__":
+    main()