目录

安装 Zabbix Agent

对于 CentOS 7

sudo rpm -Uvh https://repo.zabbix.com/zabbix/6.4/rhel/7/x86_64/zabbix-release-6.4-1.el7.noarch.rpm

对于 CentOS 8

sudo rpm -Uvh https://repo.zabbix.com/zabbix/6.4/rhel/8/x86_64/zabbix-release-6.4-1.el8.noarch.rpm

对于 CentOS 9

sudo rpm -Uvh https://repo.zabbix.com/zabbix/6.4/rhel/9/x86_64/zabbix-release-6.4-1.el9.noarch.rpm

更新缓存

sudo yum clean all

安装

sudo yum install zabbix-agent -y

配置 Zabbix Agent

  1. 编辑配置文件

    sudo vi /etc/zabbix/zabbix_agentd.conf
  2. 修改以下关键参数

    Server=<Zabbix_Server_IP>
    ServerActive=<Zabbix_Server_IP>
    Hostname=<Client_Hostname>
  • Server:填写 Zabbix Server 的 IP 地址。指定允许连接到代理的 Zabbix 服务器或代理端 IP 地址。它是一个安全控制参数,确保只有列出的 IP 地址可以与代理通信
  • ServerActive:填写 Zabbix Server 的 IP 地址。指定 Zabbix 代理将主动连接到的 Zabbix 服务器的 IP 地址或主机名,用于主动检查模式。代理会向列出的服务器发送监控数据。
  • Hostname:填写此客户端在 Zabbix Server 上的唯一名称(应与 Zabbix Server 上配置的主机名一致)。

配置多个主动模式

Server=172.19.20.107,172.19.20.106   #多个IP地址用逗号(,)分割
ServerActive=172.19.20.147:10051    #添加多个的主动模式
ServerActive=172.19.20.146:10051

其它配置参数

如要要使用system.run,需要添加支持的key,否则会报错:Unsupported item key.

AllowKey=system.run[*]

自定义UserParameter

UserParameter=custom.basic,/root/zabbix-agent/zabbix_basic.sh
UserParameter=custom.disk_free,df -mP | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted usedd"} {print $1,$3,$4,$5,$6,$3}'

3. 配置防火墙

sudo firewall-cmd --permanent --add-port=10050/tcp
sudo firewall-cmd --permanent --add-port=10051/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

启动 Zabbix Agent

  1. 设置开机自启动

    sudo systemctl enable zabbix-agent
  2. 启动 Zabbix Agent

    sudo systemctl start zabbix-agent
  3. 检查状态

    sudo systemctl status zabbix-agent

验证 Zabbix Agent

  1. 确保 Zabbix Agent 正常运行

    sudo systemctl status zabbix-agent
  2. 测试 Zabbix Agent 与 Zabbix Server 通信

    zabbix_get -s <Client_IP> -k agent.ping
  • 返回 1 表示正常通信。

查看Zabbix Agent日志

tail -f /var/log/zabbix/zabbix_agentd.log

我在 WSL 启动 zabbix agent 报错:

sudo systemctl start zabbix-agent Failed to get D-Bus connection: Operation not permitted

可以直接启动 zabbix_agentd:

sudo /usr/sbin/zabbix_agentd -c /etc/zabbix/zabbix_agentd.conf

ps aux | grep zabbix_agentd

SpringBoot自定义Server和Zabbix-agent通信

比较简单,生产环境未测试,仅供开发测试。

ZabbixSocketServerRunner.java

package com.xxx.xxx.runner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class ZabbixSocketServerRunner implements CommandLineRunner {

    @Autowired
    private ZabbixSocketServer zabbixSocketServer;

    @Override
    public void run(String... args) throws Exception {
        System.out.println("✅ Zabbix Server Socket are ready.");
        zabbixSocketServer.startServer(10051);
    }
}

ZabbixSocketServer.java

package com.xxx.xxx.runner;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

@Service
@Slf4j
class ZabbixSocketServer {

    @Async
    public void startServer(int port) throws Exception {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            log.info("✅ Listening on 0.0.0.0:{}", port);

            while (true) {
                Socket clientSocket = serverSocket.accept();
                log.info("\uD83D\uDD17 Connection from {}", clientSocket.getInetAddress());
                new Thread(() -> handleClient(clientSocket)).start();
            }
        }
    }

    private void handleClient(Socket clientSocket) {
        try (InputStream input = clientSocket.getInputStream(); OutputStream output = clientSocket.getOutputStream()) {
            byte[] header = new byte[13];
            int bytesRead = input.read(header);

            if (bytesRead < 13 || !new String(header, 0, 4).equals("ZBXD")) {
                log.error("无效的header");
                return;
            }

            // 提取协议版本
            byte protocolVersion = header[4];
            if (protocolVersion != 1) {
                log.error("不支持的协议版本: {}", protocolVersion);
                return;
            }

            // 提取数据长度(在 Zabbix 协议中始终为 little-endian)
            long dataLength = 0;
            for (int i = 0; i < 8; i++) {
                dataLength |= ((long) (header[5 + i] & 0xFF)) << (i * 8);
            }

            if (dataLength > 10 * 1024 * 1024) {
                log.error("数据长度过大,限制10MB");
                return;
            }

            byte[] dataBytes = new byte[(int) dataLength];
            int totalRead = 0;
            while (totalRead < dataLength) {
                int read = input.read(dataBytes, totalRead, (int) (dataLength - totalRead));
                if (read == -1) break;
                totalRead += read;
            }

            String receivedJson = new String(dataBytes, StandardCharsets.UTF_8);
            log.info("接收到的JSON:\n{}", receivedJson);

            String responseJson = generateResponse(receivedJson);
            log.info("生成的返回JSON:\n{}", responseJson);

            byte[] responsePayload = responseJson.getBytes(StandardCharsets.UTF_8);

            // 创建header - 'ZBXD\1' 后跟 little-endian 格式的 payload 长度
            byte[] responseHeader = new byte[13];
            responseHeader[0] = 'Z';
            responseHeader[1] = 'B';
            responseHeader[2] = 'X';
            responseHeader[3] = 'D';
            responseHeader[4] = 1;  // Protocol version

            // 以 little-endian 格式写入长度
            long responseLength = responsePayload.length;
            for (int i = 0; i < 8; i++) {
                responseHeader[5 + i] = (byte) ((responseLength >> (i * 8)) & 0xFF);
            }

            output.write(responseHeader);
            output.write(responsePayload);
            output.flush();

        } catch (Exception e) {
            log.info("处理 TCP 客户端时出错: {}", e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                log.info("关闭client socket出错: {}", e.getMessage());
            }
        }
    }

    private String generateResponse(String receivedJson) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode rootNode = mapper.readTree(receivedJson);

            if (rootNode.has("request")) {
                String request = rootNode.get("request").asText();

                if ("active checks".equals(request)) {
                    // 从请求中获取主机
                    String host = rootNode.has("host") ? rootNode.get("host").asText() : "unknown";
                    log.info("active checks的主机: {}", host);

                    // 日志
                    ObjectNode dataItem = mapper.createObjectNode();
                    dataItem.put("itemid", 12345);
                    dataItem.put("name", "log item");
                    dataItem.put("type", "ZABBIX_ACTIVE"); // 监控项类型,ZABBIX_ACTIVE Zabbix主动模式
                    dataItem.put("key", "log[/var/log/zabbix/zabbix_agentd.log,error]");
                    dataItem.put("delay", 120);
                    dataItem.put("lastlogsize", 0); // 上次已读取的日志文件字节偏移量,下次从这里继续读取,返回的数据中会有{"request":"agent data","session":"979b49792d714c5cfcc49831d05802fd","data":[{"host":"Zabbix server","key":"log[/var/log/zabbix/zabbix_agentd.log,error]","value":"test8 error","lastlogsize":1210802,"id":16134,"clock":1747817135,"ns":113673130}],"clock":1747817135,"ns":113941398}
                    dataItem.put("value_type", "LOG");
                    dataItem.put("mtime", 0); // 日志文件的修改时间(时间戳)
                    dataItem.put("logtimefmt", "pppppp:yyyyMMdd:hhmmss");

                    // 执行命令结果,脚本用双引号包裹,脚本内容中的双引号要\转义
                    ObjectNode systemRun = mapper.createObjectNode();
                    systemRun.put("itemid", 12346);
                    systemRun.put("name", "system run item");
                    systemRun.put("type", "ZABBIX_ACTIVE");
                    systemRun.put("key", "system.run[\"fre -h | awk 'BEGIN{print \\\"total used free\\\"} NR==3{print $2,$3,$4}'\"]");
                    systemRun.put("lastlogsize", 0);
                    systemRun.put("mtime", 0);
                    systemRun.put("delay", 120);

                    ObjectNode systemRun2 = mapper.createObjectNode();
                    systemRun2.put("itemid", 12347);
                    systemRun2.put("name", "system run item2");
                    systemRun2.put("type", "ZABBIX_ACTIVE");
                    systemRun2.put("key", "system.run[\"ps -aux | sort -k3nr | awk 'BEGIN{ print \\\"id pid cpu_usage mem_usage command\\\" } {print NR,$2, $3, $4, $11}'| head -n 11\"]");
                    systemRun2.put("lastlogsize", 0);
                    systemRun2.put("mtime", 0);
                    systemRun2.put("delay", 120);

                    ObjectNode systemRun3 = mapper.createObjectNode();
                    systemRun3.put("itemid", 12348);
                    systemRun3.put("name", "system run item3");
                    systemRun3.put("type", "ZABBIX_ACTIVE");
                    systemRun3.put("key", "system.run[\"LANG=C lscpu | awk -F: '$1==\\\"Model name\\\" {print $2}';awk '/processor/{core++} END{print core}' /proc/cpuinfo;uptime | sed 's/,/ /g' | awk '{for(i=NF-2;i<=NF;i++)print $i }' | xargs;vmstat 1 1 | awk 'NR==3{print $11}';vmstat 1 1 | awk 'NR==3{print $12}';vmstat 1 2 | awk 'NR==4{print $15}'\"]");
                    systemRun3.put("lastlogsize", 0);
                    systemRun3.put("mtime", 0);
                    systemRun3.put("delay", 120);

                    ObjectNode custom = mapper.createObjectNode();
                    custom.put("itemid", 12349);
                    custom.put("name", "custom.disk_free");
                    custom.put("type", "ZABBIX_ACTIVE");
                    custom.put("key", "custom.disk_free");
                    custom.put("lastlogsize", 0);
                    custom.put("mtime", 0);
                    custom.put("delay", 120);

                    ArrayNode dataArray = mapper.createArrayNode();
                    dataArray.add(dataItem);
                    dataArray.add(systemRun);
                    dataArray.add(custom);
                    dataArray.add(systemRun2);
                    dataArray.add(systemRun3);

                    ObjectNode response = mapper.createObjectNode();
                    response.put("response", "success");
                    response.set("data", dataArray);

                    return mapper.writeValueAsString(response);

                } else if ("agent data".equals(request)) {
                    // 处理agent data请求
                    if (rootNode.has("data") && rootNode.get("data").isArray()) {
                        JsonNode dataArray = rootNode.get("data");
                        log.info("收到的 agent data 数量: {}", dataArray.size());

                        for (JsonNode item : dataArray) {
                            if (item.has("key") && item.has("value")) {
                                log.info("返回结果: \n{} → \n{}", item.get("key").asText(), item.get("value").asText());
                            }
                        }
                    }

                    // agent data成功响应
                    ObjectNode response = mapper.createObjectNode();
                    response.put("response", "success");
                    return mapper.writeValueAsString(response);
                }
            }

            // 无法识别请求类型时的默认响应
            ObjectNode response = mapper.createObjectNode();
            response.put("response", "failed");
            response.put("info", "Unsupported request type: " + receivedJson);
            return mapper.writeValueAsString(response);

        } catch (Exception e) {
            log.info("生成响应时出错: {}", e.getMessage());
            e.printStackTrace();

            try {
                ObjectNode response = new ObjectMapper().createObjectNode();
                response.put("response", "failed");
                response.put("info", "Internal error: " + e.getMessage());
                return new ObjectMapper().writeValueAsString(response);
            } catch (Exception ex) {
                return "{\"response\":\"failed\",\"info\":\"Internal error\"}";
            }
        }
    }
}

zabbix_agentd.conf 配置修改

Server=172.19.20.107
ServerActive=172.19.20.147:10051

AllowKey=system.run[*]

UserParameter=custom.disk_free,df -mP | tail -n +2 | awk 'BEGIN{ print "filesystem used available usage mounted usedd"} {print $1,$3,$4,$5,$6,$3}'

参考文档

Header: https://www.zabbix.com/documentation/current/zh/manual/appendix/protocols/header_datalen

主被动Agent: https://www.zabbix.com/documentation/current/zh/manual/appendix/items/activepassive

Agent支持内容:https://www.zabbix.com/documentation/7.2/zh/manual/config/items/itemtypes/zabbix_agent#log