zbx-bootstrap.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Zabbix 「自监控-only」bootstrap 脚本(幂等,可重复执行)
  5. 背景
  6. ----
  7. Zabbix 服务端首次启动会自动创建名为 "Zabbix server" 的自身监控主机,并默认挂上
  8. "Linux by Zabbix agent" + "Zabbix server health" 两个模板。
  9. 但本机并不安装 zabbix-agent,于是:
  10. - 那批 agent 监控项永远采集不到数据(空采);
  11. - 还会周期性尝试连接本机 10050(agent 端口)而产生 "agent 不可用" 之类的无效告警/报错。
  12. 本脚本在每次 `docker compose up` 后运行一次,把「服务器自身主机」上的
  13. "Linux by Zabbix agent" 模板摘除(连同其监控项/触发器),只保留
  14. "Zabbix server health" 自监控模板 —— 即“自己监控自己、不连 agent”。
  15. 幂等:每次先查询再决定是否操作;主机已干净则跳过。
  16. 认证:优先使用环境变量 ZBX_API_TOKEN(推荐,API Token);否则用
  17. ZBX_API_USER / ZBX_API_PASSWORD 登录获取会话令牌。
  18. """
  19. import os
  20. import sys
  21. import time
  22. import json
  23. import urllib.request
  24. import urllib.error
  25. URL = os.environ.get("ZBX_API_URL", "http://zabbix-web-nginx-mysql:8080/api_jsonrpc.php")
  26. USER = os.environ.get("ZBX_API_USER", "Admin")
  27. PWD = os.environ.get("ZBX_API_PASSWORD", "zabbix")
  28. TOKEN = os.environ.get("ZBX_API_TOKEN", "").strip()
  29. HEALTH = os.environ.get("ZBX_HEALTH_TEMPLATE", "Zabbix server health")
  30. AGENT = os.environ.get("ZBX_AGENT_TEMPLATE", "Linux by Zabbix agent")
  31. WAIT = int(os.environ.get("ZBX_WAIT_SECONDS", "300"))
  32. def call(method, params, token=None):
  33. payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
  34. headers = {"Content-Type": "application/json-rpc"}
  35. if token:
  36. headers["Authorization"] = "Bearer " + token
  37. req = urllib.request.Request(URL, data=json.dumps(payload).encode(), headers=headers)
  38. with urllib.request.urlopen(req, timeout=30) as resp:
  39. j = json.loads(resp.read().decode())
  40. if "error" in j:
  41. raise RuntimeError("%s -> %s" % (method, j["error"]))
  42. return j["result"]
  43. def wait_api():
  44. deadline = time.time() + WAIT
  45. while time.time() < deadline:
  46. try:
  47. call("apiinfo.version", {})
  48. print("[bootstrap] API is up")
  49. return
  50. except Exception as e: # noqa: BLE001
  51. print("[bootstrap] waiting for API ... (%s)" % e)
  52. time.sleep(5)
  53. print("[bootstrap] ERROR: API not reachable within %ss" % WAIT)
  54. sys.exit(1)
  55. def main():
  56. wait_api()
  57. if TOKEN:
  58. token = TOKEN
  59. print("[bootstrap] using provided API token")
  60. else:
  61. token = call("user.login", {"username": USER, "password": PWD})
  62. print("[bootstrap] logged in as %s" % USER)
  63. tpls = call(
  64. "template.get",
  65. {"output": ["templateid", "host", "name"], "filter": {"host": [AGENT, HEALTH]}},
  66. token,
  67. )
  68. ids = {t["host"]: t["templateid"] for t in tpls}
  69. if AGENT not in ids:
  70. print("[bootstrap] agent template %r not found -> nothing to do" % AGENT)
  71. return
  72. if HEALTH not in ids:
  73. print("[bootstrap] health template %r not found -> cannot identify server host, skip" % HEALTH)
  74. return
  75. agent_id = ids[AGENT]
  76. health_id = ids[HEALTH]
  77. hosts = call(
  78. "host.get",
  79. {
  80. "output": ["hostid", "host"],
  81. "templateids": [health_id],
  82. "selectParentTemplates": ["templateid", "host"],
  83. },
  84. token,
  85. )
  86. changed = 0
  87. for h in hosts:
  88. tids = [p["templateid"] for p in h.get("parentTemplates", [])]
  89. if agent_id in tids:
  90. keep = [{"templateid": x} for x in tids if x != agent_id]
  91. call(
  92. "host.update",
  93. {
  94. "hostid": h["hostid"],
  95. "templates": keep,
  96. "templates_clear": [{"templateid": agent_id}],
  97. },
  98. token,
  99. )
  100. print("[bootstrap] removed %r from host %s (%s)" % (AGENT, h["host"], h["hostid"]))
  101. changed += 1
  102. else:
  103. print("[bootstrap] host %s (%s) already clean" % (h["host"], h["hostid"]))
  104. print("[bootstrap] done, changed %d host(s)" % changed)
  105. if __name__ == "__main__":
  106. main()