自己折腾 Zabbix 一年多,从裸机装到容器化部署,中间踩过的坑能写满一本流水账。这篇就聊聊我目前最常用的部署方式——用 Docker 搭 Zabbix,把整个架构、为什么这么选、具体怎么落地、常见问题怎么排查一次说清楚。
Docker 搭建 Zabbix 最直观的价值,是把原本要折腾半天的依赖环境、数据库初始化、定时任务配置全部打包成镜像,一条命令拉起一套监控系统。它适合三类人:一是测试环境想快速体验 Zabbix 的,二是生产环境想摆脱 OS 版本绑定、方便整体迁移的,三是被 CentOS 7.9 上那一堆 Python、OpenIPMI、libevent 依赖搞到崩溃的运维。
不过在动手之前,我先说点真话:Docker 化 Zabbix 不只是docker run zabbix-server那么简单,它牵扯到数据库持久化、网络模式、主动/被动监控链路、Web 与 Server 分离部署等一系列决策。
1. 整体架构与设计思路
1.1 为什么选择 Docker 而不是直接装 RPM 包
传统裸机安装 Zabbix 是一个典型的"环境地狱"场景。Zabbix Server 依赖 PHP、Apache/Nginx、MySQL/PostgreSQL、Net-SNMP、libcurl、OpenIPMI 等一堆底层库,5.0 和 6.0 对 PHP 版本要求还不一样,7.0 直接要求 PHP 8.0+。换一台最小化安装的 CentOS 7.9,光补齐这些依赖就够喝一壶的,更别提 PHP 版本还得从第三方源编译升级。
Docker 方案把这些问题全部封装进镜像。Zabbix 官方提供了完整的容器化矩阵:zabbix/zabbix-server-mysql、zabbix/zabbix-web-nginx-mysql、zabbix/zabbix-agent,数据库也有配套的初始化逻辑。官方镜像的构建脚本直接继承了发行版的编译参数,自带 OpenIPMI、SNMP 支持,省掉了我们手动编译扩展的麻烦。
生产环境用 Docker 还有一个隐性的好处——升级路径清晰。从 6.0 升 7.0 时,裸机方案要考虑 PHP 版本迁移、配置文件差异、前端资源兼容性,而容器方案只需要拉新镜像、迁移数据库,Server 和 Web 的同步版本由镜像 tag 保证,出问题的概率低得多。
1.2 Zabbix 核心组件拆解:Server、Web、Agent 各司其职
一个完整的 Zabbix 架构包含四类角色:
| 组件 | 职责 | 端口 | 容器化要点 |
|---|---|---|---|
| Zabbix Server | 数据采集调度、告警评估、触发器计算 | 10051(Trapper)、10050(Polling 时作为客户端) | 核心进程,需要连接数据库 |
| Zabbix Web | 前端界面,PHP-FPM + Nginx | 8080/80 | 与 Server 解耦,通过 PHP 连接 DB |
| Zabbix Agent | 部署在被监控主机上的采集器 | 10050(被动模式监听) | 可以用容器,但更推荐宿主机二进制方式 |
| 数据库 | MySQL / PostgreSQL | 3306 / 5432 | 强烈建议数据卷持久化 |
很多人把 Zabbix Server 和 Web 容器当成一个整体,实际上官方是拆开的。拆开的好处很明确:Web 层做反向代理时不用影响 Server 进程,API 压测时也不会占用采集进程的资源。热搜里有个词是"web与zabbix分离",指的就是把前端容器单独拎出来,甚至部署到另一台机器上。这在容器化环境里几乎零成本,只需要让 Server 和 Web 容器连同一个网络、访问同一个数据库即可。
1.3 版本选型:锁定 tag,别用 latest
Zabbix 镜像的 tag 格式通常是<version>-<database>-<os>,比如zabbix-server-mysql:7.0-centos-latest。我实际用下来,建议锁两个维度:
- 大版本锁死:比如 6.0 LTS 还是 7.0 LTS。Zabbix 官方对 LTS 版本提供更长的支持周期,生产环境别追新,6.0 和 7.0 的模板结构、API 路径有差异。
- 操作系统底包:官方镜像有
alpine、ubuntu、centos几种底包。Alpine 版本体积小(约 200MB),但遇到需要编译额外 Agent 插件的情况会比较难受;CentOS 底包兼容性最好,跟裸机环境行为最接近。
另外强调一点:不要在 compose 文件里写latest。Zabbix 的大版本升级通常伴随着数据库 Schema 变更,latest漂移会导致 Web 升级了、Server 没升级,前端报"数据库版本不兼容"。用固定 tag 才能真正享受镜像的可复现性。
2. 核心细节解析:网络、存储、时区与监控模式
2.1 Agent 主动与被动模式在容器环境下的差异
Zabbix Agent 的工作模式有两种。被动模式是 Server 主动连接 Agent 的 10050 端口去取数据,这种模式要求 Server 到 Agent 的网络可达,且在容器化部署时,Agent 容器需要把 10050 端口暴露出来。主动模式则是 Agent 直接连接 Server 的 10051 端口上报数据,Agent 需要配置ServerActive指向 Server 的地址。
我在容器环境里的经验是:被监控对象如果是宿主机自身(比如用容器监控物理机 CPU、内存、磁盘),强烈建议用 agent2 二进制部署在宿主机上,而不是再套一层 Agent 容器。原因有二:一是宿主机上的/proc、/sys文件系统挂载进容器会有隔离偏差,磁盘利用率、网络速率计算会不准确;二是被监控目标本身就是宿主机时,Agent 容器跟宿主机处于不同网络命名空间,Server 如果用被动模式反而多一层端口映射和 NAT 的故障点。
如果是监控容器集群本身的资源(比如监控 MySQL 容器实例),那 Agent 容器跟业务容器放同一个自定义 bridge 网络里,走 10051 主动上报更省事,不用刻意映射 10050 端口。
2.2 端口规划与网络模式选择
Zabbix 涉及三个关键端口:
10051/tcp:Zabbix Server 的 Trapper 端口,主动模式 Agent 上报数据的入口,必须对 Agent 可达。10050/tcp:Agent 监听端口,被动模式下等待 Server 来拉数据,仅在监听端口场景需要容器映射。8080/tcp:Web 界面端口,通常映射到宿主机某个端口对外提供访问。
Docker 网络模式我推荐创建自定义 bridge 网络(docker network create zabbix-net),并把所有 Zabbix 组件加到这个网络中。在这个网络里,容器之间通过服务名直接通信,比如 Server 连接数据库时用mysql这个主机名,Web 连接数据库时也直接用mysql主机名,这种内部 DNS 解析是 Docker 自带的,不用再去硬编码 IP。
如果非要用--network host模式,我提醒一句:host 模式在 Linux 下会让容器直接共享宿主机网络命名空间,Zabbix Server 的 10051 端口和被监控宿主机上其他服务的端口可能冲突,而且 Web 容器的 Nginx 配置里绑定的是镜像内的端口,host 模式下不容易灵活调整监听端口。除非你对网络很熟,否则别这么干。
2.3 数据持久化:数据库状态必须外置
Zabbix 的业务状态全在数据库里——主机列表、模板、触发器、历史数据、告警事件。容器销毁后如果数据库数据卷不保留,等于整个监控系统归零。我用 Docker 搭 Zabbix 时,第一件事就是规划数据卷。
MySQL 容器挂载本地目录时,建议直接在 compose 里指定volumes映射命名卷或本地路径。实际工作中我更推荐本地路径(比如/data/zabbix/mysql),因为备份和维护时直接用tar就能打包,不用绕一层docker run --volumes-from的复杂操作。Web 容器里也有一个隐蔽的持久化点:/usr/share/zabbix/modules和/etc/zabbix/web。前端如果改过字体、安装过模块,容器重启后会丢失,建议也挂载出来。
2.4 时区与中文乱码问题
这是大家最容易踩的坑。Zabbix Server 容器默认时区是 UTC,Web 界面用 UTC 会导致告警时间比北京慢 8 小时;前端界面显示中文时,如果容器里没有中文字体,图表上的中文文字全是方框乱码。
解决方案分两步:一是所有组件统一设置TZ=Asia/Shanghai环境变量,Zabbix Server、Web、Agent 容器都要设;二是给 Web 容器挂载中文字体。我一般从宿主机/usr/share/fonts/chinese/目录挂一个msyh.ttf进去,同时修改 Web 容器里的 PHP 配置date.timezone。具体操作在后面实操章节详细说。
3. 实操部署:从零开始用 Docker Compose 拉起 Zabbix
3.1 环境准备:Docker 安装与镜像加速
我的部署环境是一台 CentOS 7.9 服务器。CentOS 7.9 上安装 Docker 有点历史遗留问题需要处理:默认源里的docker是老版本,需要用官方源或阿里云源安装docker-ce。
# 卸载旧版本(如果有) yum remove -y docker docker-client docker-common docker-engine # 安装依赖 yum install -y yum-utils device-mapper-persistent-data lvm2 # 配置阿里云源 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 安装 Docker yum install -y docker-ce docker-ce-cli containerd.io # 启动 Docker systemctl start docker systemctl enable docker安装完成后立刻配镜像加速,不然拉 Zabbix 这种大型镜像会非常痛苦。修改/etc/docker/daemon.json:
{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://docker.mirrors.ustc.edu.cn" ] }重启 Docker 使配置生效:systemctl restart docker。这里实测下来,不同加速源在不同网络环境下的速度差异很大,建议多配几个,Docker 会按顺序尝试。
3.2 编写 docker-compose.yml:参数逐个解释
我用 Docker Compose 管理整套 Zabbix,Compose 文件是部署的核心。以下是我在 6.0 LTS 版本上验证过的完整配置:
version: '3.8' networks: zabbix-net: driver: bridge volumes: mysql-data: services: mysql: image: mysql:8.0.36 container_name: zabbix-mysql networks: - zabbix-net ports: - "33061:3306" environment: MYSQL_ROOT_PASSWORD: ZabbixRoot@123 MYSQL_DATABASE: zabbix MYSQL_USER: zabbix MYSQL_PASSWORD: ZabbixUser@123 TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_bin - --default-authentication-plugin=caching_sha2_password volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-pZabbixRoot@123"] interval: 10s timeout: 5s retries: 5 zabbix-server: image: zabbix/zabbix-server-mysql:6.0-centos-latest container_name: zabbix-server networks: - zabbix-net ports: - "10051:10051" environment: DB_SERVER_HOST: mysql DB_SERVER_PORT: 3306 MYSQL_DATABASE: zabbix MYSQL_USER: zabbix MYSQL_PASSWORD: ZabbixUser@123 TZ: Asia/Shanghai volumes: - ./zabbix-data/alertscripts:/usr/lib/zabbix/alertscripts - ./zabbix-data/externalscripts:/usr/lib/zabbix/externalscripts depends_on: mysql: condition: service_healthy zabbix-web: image: zabbix/zabbix-web-nginx-mysql:6.0-centos-latest container_name: zabbix-web networks: - zabbix-net ports: - "8080:8080" environment: DB_SERVER_HOST: mysql MYSQL_DATABASE: zabbix MYSQL_USER: zabbix MYSQL_PASSWORD: ZabbixUser@123 PHP_TZ: Asia/Shanghai TZ: Asia/Shanghai volumes: - ./zabbix-data/fonts:/usr/share/zabbix/assets/fonts:ro - ./zabbix-data/modules:/usr/share/zabbix/modules:ro depends_on: - zabbix-server这份配置里几个细节值得说明。
DB_SERVER_HOST: mysql用的是 compose 服务名,Docker 内嵌 DNS 会自动解析到 MySQL 容器的 IP。这里千万别写localhost,因为容器之间是隔离的,localhost指向容器自己。
MySQL 的加密插件参数--default-authentication-plugin=caching_sha2_password是兼容 Zabbix 6.0 的关键。Zabbix Server 是编译好的二进制,用的是 C 库连接 MySQL,对caching_sha2_password支持性有坑,老版本 Zabbix 必须用mysql_native_password。如果像我一样用 MySQL 8.0,并且 Zabbix 6.0 连接报认证失败,在 MySQL 命令行里执行:
ALTER USER 'zabbix'@'%' IDENTIFIED WITH mysql_native_password BY 'ZabbixUser@123';这是一个极其常见的坑,直接决定你健康检查那关能不能过。
PHP_TZ和TZ两个环境变量同时设,是为了保证 Web 前端的时间显示和 Server 端的告警评估时间一致。
3.3 启动与验证
在 compose 文件所在目录执行:
docker compose up -d首次启动会拉镜像,MySQL 初始化需要十几秒,Zabbix Server 启动后会自动执行数据库 Schema 升级(官方镜像内置了初始化 SQL 脚本)。等待一分钟后查看容器状态:
docker compose ps正常情况下三个容器都是running。观察日志,重点看 zabbix-server 容器是否报数据库连接错误:
docker logs zabbix-server --tail 50日志里出现Housekeeper process started和server #0 started表示 Zabbix Server 核心进程正常。浏览器访问http://服务器IP:8080,看到 Zabbix 登录页面就成功了。默认账号密码是Admin / zabbix。
3.4 Agent 客户端部署:监控第一台 Linux 主机
Agent 是整个监控链路上最容易被忽视、又最容易出问题的环节。这里我区分两种情况。
情况一:用容器跑 Agent 监控宿主机。
docker run -d \ --name zabbix-agent \ --network host \ --restart unless-stopped \ -e ZBX_HOSTNAME="CentOS-192.168.1.100" \ -e ZBX_SERVER_HOST="192.168.1.10" \ -e ZBX_ACTIVE_ALLOW="true" \ zabbix/zabbix-agent2:6.0-centos-latest注意这里用的--network host,目的是让 Agent 容器直接看到宿主机的物理网络接口,这样 Server 用被动模式连宿主机 IP 的 10050 端口就能取到数据。如果不用 host 模式,必须在启动时把 10050 映射到宿主机,而且 Agent 里看到的/proc是容器的,不是宿主机的,采集的 CPU 利用率数据会严重失真。
情况二:宿主机直接装二进制 Agent(生产环境我更推荐)。
RPM 包方式装 Agent 只需要两步:
rpm -Uvh https://repo.zabbix.com/zabbix/6.0/rhel/7/x86_64/zabbix-release-6.0-4.el7.noarch.rpm yum install -y zabbix-agent2然后修改/etc/zabbix/zabbix_agent2.conf三个参数:
Server=192.168.1.10 # Zabbix Server 的 IP,被动模式白名单 ServerActive=192.168.1.10 # Server 的 IP,主动模式上报地址 Hostname=CentOS-Node1 # 这个需要和前端添加主机时填的主机名完全一致启动 Agent:
systemctl start zabbix-agent2 systemctl enable zabbix-agent2在 Zabbix Web 界面添加主机时,主机的IP 地址填宿主机 IP,端口填 10050,模板选Linux by Zabbix agent active或Linux by Zabbix agent。我个人的习惯是选 active 模板,因为主动模式由 Agent 发起连接,不用 Server 额外开入站规则,防火墙好配很多。
4. 常见问题与排查技巧实录
4.1 镜像下载慢、超时怎么办
Docker 在国内拉官方镜像经常卡死,尤其是 Zabbix 这种多层镜像。除了前面说的配置镜像加速之外,还有一个暴力但有效的办法:换镜像源后重启 Docker,再重新拉取;如果某个源不行,切换到另一个源。实测下来 Daocloud 源速度不错,USTC 源偶尔会对部分镜像 404。
另外可以合并拉取,减少并发层数:
docker pull zabbix/zabbix-server-mysql:6.0-centos-latest如果网络实在太差,可以考虑在 Docker Hub 页面找人工下架时机的镜像 tag,或者从内网导出一份离线镜像包,这个在公司内网环境尤其管用。
4.2 Docker 服务启动失败:failed to start docker application container engine
systemctl start docker直接报这个错,常见原因有三个:daemon.json语法错误、iptables 相关组件缺失、内核 IP 转发关闭。
排查步骤:
# 查看详细日志 journalctl -u docker --no-pager | tail -50 # 手动启动 docker,看前台输出 dockerd --debug如果日志里提到iptables无法访问,多半是内核模块没加载:
modprobe iptable_nat modprobe iptable_filter echo 1 > /proc/sys/net/ipv4/ip_forward如果日志提到overlay2错误,检查内核版本是不是低于 4.0(CentOS 7 默认内核 3.10,部分老版本 Docker 支持不佳,升级内核或改用vfs存储驱动可解)。
4.3 Agent 显示"不可达":网络链路逐段排查
前端添加主机后,主机状态一直灰色,提示 "Zabbix agent is not available"。我总结了一个三段式排查法:
第一段,测试 Server 到 Agent 的 10050 端口连通性:
# 在 Zabbix Server 容器里执行 docker exec -it zabbix-server bash yum install -y nmap-ncat nc -vz 192.168.1.101 10050如果连接失败,检查 Agent 进程是否启动、防火墙是否放行 10050 端口。CentOS 7 上有个隐蔽坑:装了 firewalld 后即使systemctl stop firewalld,重启后又会自启动,建议systemctl disable firewalld或在防火墙规则里放行。
第二段,确认 Agent 配置里的Server参数是否包含 Zabbix Server 的 IP。Agent 的Server是白名单机制,如果填了 127.0.0.1,Server 即使网络通,Agent 也会直接忽略连接。
第三段,检查被动模式下 Server 用的主机名。前端主机配置里如果填了 DNS,而 Agent 的Hostname参数不匹配,也会出现频繁断连。我一般前端直接填 IP,不开自动 DNS 解析,少一层故障。
4.4 告警一直显示"问题"状态,如何手动消除
很多新手困惑:处理完故障后手动点击操作,告警还是"问题"状态。记住关键点:Zabbix 的告警是事件驱动的,不是手动消除的。告警恢复的前提是触发器条件从 true 变回 false,此时系统会自动生成一个"恢复"事件。你要做的不是手动改状态,而是修复指标本身。
如果指标恢复正常但告警依然存在,可能的原因有:
- 这个指标走的是主动模式,Agent 缓存了旧数据,还没上报新值。
- 触发器表达式用了
nodata()或last()这类函数,对时间窗口敏感,等待几个周期看效果。 - 触发器配置了多个条件且有
and关系,其中一个条件尚未恢复。
实操排查建议:打开监控 -> 最新数据,找到对应 item,查看最近 30 分钟的数据是否还在更新最新值。如果一直没新数据,问题就回到了 Agent 链路;如果有新数据且已经恢复正常值,一般下个评估周期(默认 30 秒)自动恢复。
4.5 Web 与 Server 分离部署、Postman 调试 API
"Web 与 Zabbix 分离"在容器架构下很好实现:你可以把 compose 文件拆成两部分,一部分在监控服务器上只跑 MySQL + Zabbix Server,另一部分在前面那台机器上跑 Web 容器,Web 容器的DB_SERVER_HOST指向 MySQL 的宿主机 IP,ZBX_SERVER_HOST指向 Zabbix Server 的宿主机 IP,前提是这两个 IP 在网络上互通。
分离部署后,调 API 是绕不开的话题。用 Postman 调试 Zabbix API 时,完整流程是:
POST http://192.168.1.10:8080/api_jsonrpc.php Content-Type: application/json-rpc { "jsonrpc": "2.0", "method": "user.login", "params": { "username": "Admin", "password": "zabbix" }, "id": 1 }拿到 token 后,再去调host.get、item.get、trigger.get。这里有个细节:Zabbix API 的用户名密码认证方式在 6.0 之后开始弱化,推荐用 API token 认证,方便点在 Web 界面 用户->API tokens 里直接生成,免去密码过期问题。调用时把auth字段换成 token 字符串即可。
4.6 中文乱码与字体问题
Web 界面选了中文语言后,图形界面的中文全部是方块,这是典型的字体缺失。Zabbix Web 容器用的默认字体是 DejaVu Sans,不支持中文渲染。
解决办法:
# 在宿主机准备字体 mkdir -p /data/zabbix/fonts # 从 Windows 或其它渠道拷贝一个中文字体,比如 msyh.ttf cp msyh.ttf /data/zabbix/fonts/ # 修改 Web 容器里的字体配置 docker exec -it zabbix-web bash cd /usr/share/zabbix/assets/fonts # 备份原字体,替换为新字体 mv graphfont.ttf graphfont.ttf.bak cp /data/zabbix/fonts/msyh.ttf /usr/share/zabbix/assets/fonts/graphfont.ttf注意:替换后要清一下浏览器缓存,不然看到的还是旧的字体文件。
4.7 Docker 权限问题:permission denied while trying to connect to the Docker daemon socket
新装 Docker 后,非 root 用户执行 docker 命令会报 permission denied。这个不是 Docker 本身问题,而是用户没有加入docker用户组:
sudo usermod -aG docker $USER newgrp docker然后重新登录 shell。再说一个安全提醒:把普通用户加进 docker 组,等于给了这个用户 root 权限(因为 docker 组用户可以通过挂载宿主机目录来提权)。生产服务器上如果只有专职运维用,问题不大;如果有多个协作者,建议用sudo来管理 docker 命令,而不是拉组权限。
5. 我的一些实操经验总结
最后分享几个自己踩过坑之后形成的习惯。
第一,所有 Zabbix 组件的时间必须一模一样。Server、Web、Agent、MySQL,四个容器一个TZ环境变量都不能漏。否则你会看到 Agent 采集的数据在时间轴上错位 8 小时,告警评估出的时间跟实际故障时间对不上,排查起来极其痛苦。
第二,数据库备份永远比配置备份重要。Zabbix 的前端、模板、脚本都可以重新配,唯独历史数据和监控拓扑在数据库里。我每周自动全量备份一次 MySQL 数据卷,另外用mysqldump做一次逻辑备份,防止数据卷文件系统损坏这种极端情况。具体命令:
docker exec zabbix-mysql mysqldump -uzabbix -pZabbixUser@123 zabbix | gzip > zabbix_$(date +%F).sql.gz第三,Web 容器里的 Nginx 默认只监听 8080,如果想用标准 80 端口,直接把宿主机端口映射改成"80:8080"即可,不用进容器改配置。
第四,安装 Zabbix 7.0 时,镜像 tag 里的 centos 底包会变成almalinux,这是上游发行版调整,不是 Zabbix 官方的问题,不用慌。
Docker 部署 Zabbix 的整体思路概括起来就一句话:把架构拆清楚、把持久化做好、把时间统一,剩下的交给镜像去处理。这套方案从 5.0 用到 6.0,再平滑升级到 7.0,一直是稳定可靠的。如果照着这篇部署过程中遇到其他问题,欢迎在评论里带上你的日志片段,我看到了会帮你拆一拆。