We are often asked which monitoring solution we recommend for PostgreSQL, by people attending our PostgreSQL workshops. We usually answer: What do you have in place already? If a solution already is in place, the most obvious way is trying to integrate into the existing solution. If nothing is there already, you have plenty of choices. You can find some of them on this blog, but the list is quite old. Usually, PostgreSQL is not the only component to monitor, so a proper solution needs to come with agents/integrations/extensions, whatever you call it, for all the components which are critical for your environment. This makes the decision quite hard, as you have plenty of requirements and choices. That’s why I thought, that starting a little blog series about open source monitoring solutions that might give the right hints for choosing a solutions that fits for you, is good idea.

One of the well established, more than 20 years old, solution you might want to look at, is Zabbix. Zabbix, like most of the tools, comes with a server part and an agent that will be be deployed on the targets. You can download it pre-packaged for most of the well known Linux distributions from here. Another option is to install Zabbix from source code, which can be downloaded from here. Zabbix uses a database to store it’s data, and of course I’ll use PostgreSQL for that. Other options are MySQL, Oracle and TimescaleDB.

For PostgreSQL there is not much to do. Initialize a new cluster:

postgres@patronipgbackrest:/home/postgres/ [pg141] mkdir -p /u02/pgdata/14/zabbix
postgres@patronipgbackrest:/home/postgres/ [pg141] initdb /u02/pgdata/14/zabbix/

Create a systemd service definition so the instance will start automatically when the node comes up:

postgres@patronipgbackrest:/home/postgres/ [pg141] cat /etc/systemd/system/postgres-zabbix.service 
[Unit]
Description=PostgreSQL for Zabbix
After=tuned.service

[Service]
User=postgres
Type=notify
ExecStart=/u01/app/postgres/product/14/db_1/bin/postgres -D /u02/pgdata/14/zabbix/
ExecReload=/u01/app/postgres/product/14/db_1/bin/pg_ctl -D /u02/pgdata/14/zabbix/ reload
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=0

[Install]
WantedBy=multi-user.target

That’s it, once the service is started this is the status you should see:

postgres@patronipgbackrest:/etc/systemd/system/ [pg141] systemctl status postgres-zabbix.service
● postgres-zabbix.service - PostgreSQL for Zabbix
     Loaded: loaded (/etc/systemd/system/postgres-zabbix.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-11-28 14:57:48 CET; 4s ago
   Main PID: 1311 (postgres)
      Tasks: 9 (limit: 1133)
     Memory: 19.0M
        CPU: 80ms
     CGroup: /system.slice/postgres-zabbix.service
             ├─1311 /u01/app/postgres/product/14/db_1/bin/postgres -D /u02/pgdata/14/zabbix/
             ├─1312 postgres: zabbix: logger
             ├─1314 postgres: zabbix: checkpointer
             ├─1315 postgres: zabbix: background writer
             ├─1316 postgres: zabbix: walwriter
             ├─1317 postgres: zabbix: autovacuum launcher
             ├─1318 postgres: zabbix: archiver
             ├─1319 postgres: zabbix: stats collector
             └─1320 postgres: zabbix: logical replication launcher

As Zabbix should use it’s own database and user, create both:

postgres@patronipgbackrest:/home/postgres/ [pg141] psql -c "create user zabbix with login password 'zabbix'"
CREATE ROLE
postgres@patronipgbackrest:/home/postgres/ [pg141] psql -c "create database zabbix with owner=zabbix"
CREATE DATABASE
postgres@patronipgbackrest:/home/postgres/ [pg141] psql -l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 zabbix    | zabbix   | UTF8     | en_US.UTF-8 | en_US.UTF-8 | 
(4 rows)

Nothing more to do for the PostgreSQL, except loading the Zabbix schema later on.

Usually Zabbix gets installed with it’s one operating system user and group. For simplicity, I’ll do everything as the “postgres” user. Before we can install Zabbix from source code, we need to get it and then prepare the PostgreSQL database:

postgres@patronipgbackrest:/home/postgres/ [pg141] wget https://cdn.zabbix.com/zabbix/sources/stable/5.4/zabbix-5.4.7.tar.gz
postgres@patronipgbackrest:/home/postgres/ [pg141] tar axf zabbix-5.4.7.tar.gz 
postgres@patronipgbackrest:/home/postgres/ [pg141] cd zabbix-5.4.7/
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] ls database/postgresql/
data.sql  double.sql  images.sql  Makefile.am  Makefile.in  schema.sql  timescaledb.sql
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] psql -f database/postgresql/schema.sql -U zabbix zabbix
CREATE TABLE
CREATE INDEX
CREATE TABLE
...
ALTER TABLE
ALTER TABLE
ALTER TABLE
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] psql -f database/postgresql/images.sql -U zabbix zabbix
INSERT 0 1
INSERT 0 1
INSERT 0 1
...
INSERT 0 1
INSERT 0 1
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] psql -f database/postgresql/data.sql -U zabbix zabbix
START TRANSACTION
INSERT 0 1
INSERT 0 1
INSERT 0 1
...
INSERT 0 1
INSERT 0 1
INSERT 0 1
COMMIT

Next, configure (this is a Debian 11 system, package names might be different in your case):

postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] sudo apt install libsnmp-dev libopenipmi-dev libevent-dev libcurl4-openssl-dev libpcre3-dev
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] ./configure --prefix=/u01/app/postgres/product/zabbix_5.4.7 --enable-server --enable-agent --with-postgresql --enable-ipv6 --with-net-snmp --with-libcurl --with-libxml2 --with-openipmi
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
...
config.status: creating src/zabbix_java/Makefile
config.status: creating man/Makefile
config.status: creating include/config.h
config.status: include/config.h is unchanged
config.status: executing depfiles commands


Configuration:

  Detected OS:           linux-gnu
  Install path:          /u01/app/postgres/product/zabbix_5.4.7
  Compilation arch:      linux

  Compiler:              cc
  Compiler flags:         -g -O2  

  Library-specific flags:
    database:                -I/u01/app/postgres/product/14/db_0/include 
    libXML2:               -I/usr/include/libxml2
    Net-SNMP:               -I/usr/local/include -I/usr/lib/x86_64-linux-gnu/perl/5.32/CORE -I. -I/usr/include
    OpenIPMI:              -I/usr/include

  Enable server:         yes
  Server details:
    With database:         PostgreSQL
    WEB Monitoring:        cURL
      SSL certificates:      /u01/app/postgres/product/zabbix_5.4.7/share/zabbix/ssl/certs
      SSL keys:              /u01/app/postgres/product/zabbix_5.4.7/share/zabbix/ssl/keys
    SNMP:                  yes
    IPMI:                  yes
    SSH:                   no
    TLS:                   no
    ODBC:                  no
    Linker flags:           -L/usr/lib/x86_64-linux-gnu    -L/u01/app/postgres/product/14/db_0/lib     -L/usr/lib       -rdynamic     
    Libraries:                -lpq    -lnetsnmp   -lOpenIPMI -lOpenIPMIposix -lz -lpthread -levent    -lcurl -lm -ldl  -lresolv -lxml2   -lpcre 
    Configuration file:    /u01/app/postgres/product/zabbix_5.4.7/etc/zabbix_server.conf
    External scripts:      /u01/app/postgres/product/zabbix_5.4.7/share/zabbix/externalscripts
    Alert scripts:         /u01/app/postgres/product/zabbix_5.4.7/share/zabbix/alertscripts
    Modules:               /u01/app/postgres/product/zabbix_5.4.7/lib/modules

  Enable proxy:          no

  Enable agent:          yes
  Agent details:
    TLS:                   no
    Modbus:                no
    Linker flags:                -rdynamic     
    Libraries:              -lz -lpthread    -lcurl -lm -ldl  -lresolv -lxml2   -lpcre 
    Configuration file:    /u01/app/postgres/product/zabbix_5.4.7/etc/zabbix_agentd.conf
    Modules:               /u01/app/postgres/product/zabbix_5.4.7/lib/modules

  Enable agent 2:        no

  Enable web service:    no

  Enable Java gateway:   no

  LDAP support:          no
  IPv6 support:          yes

***********************************************************
*            Now run 'make install'                       *
*                                                         *
*            Thank you for using Zabbix!                  *
*                                  *
***********************************************************

All fine, compile and install:

postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] make install
Making install in src
make[1]: Entering directory '/home/postgres/zabbix-5.4.7/src'
Making install in libs
make[2]: Entering directory '/home/postgres/zabbix-5.4.7/src/libs'
Making install in zbxcrypto
make[3]: Entering directory '/home/postgres/zabbix-5.4.7/src/libs/zbxcrypto'
...
make[2]: Entering directory '/home/postgres/zabbix-5.4.7'
make[2]: Nothing to be done for 'install-exec-am'.
make[2]: Nothing to be done for 'install-data-am'.
make[2]: Leaving directory '/home/postgres/zabbix-5.4.7'
make[1]: Leaving directory '/home/postgres/zabbix-5.4.7'
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] 

Nothing really complicated and easy to prepare. Before we can startup the Zabbix server there needs to be a minimal configuration for it:

postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ [pg141] cd
postgres@patronipgbackrest:/home/postgres/ [pg141] vi /u01/app/postgres/product/zabbix_5.4.7/etc/zabbix_server.conf
postgres@patronipgbackrest:/home/postgres/ [pg141] egrep -v "^$|^#"  /u01/app/postgres/product/zabbix_5.4.7/etc/zabbix_server.conf
SourceIP=192.168.100.173
LogFile=/u01/app/postgres/local/dmk/log/zabbix_server.log
LogFileSize=10
PidFile=/u01/app/postgres/local/dmk/tmp/zabbix_server.pid
SocketDir=/u01/app/postgres/local/dmk/tmp/
DBHost=localhost
DBName=zabbix
DBSchema=public
DBUser=zabbix
DBPassword=zabbix
Timeout=4
LogSlowQueries=3000
StatsAllowedIP=StatsAllowedIP=192.168.100.0/24

If the configuration is fine, the Zabbix server should start up without any issues:

postgres@patronipgbackrest:/home/postgres/ [pg141] /u01/app/postgres/product/zabbix_5.4.7/sbin/zabbix_server 
postgres@patronipgbackrest:/home/postgres/ [pg141] ps -ef | grep zabbix
...
postgres   41541    1311  0 15:27 ?        00:00:00 postgres: zabbix: zabbix zabbix [local] idle
postgres   41606       1  0 15:32 ?        00:00:00 /u01/app/postgres/product/zabbix_5.4.7/sbin/zabbix_server
postgres   41615   41606  0 15:32 ?        00:00:00 /u01/app/postgres/product/zabbix_5.4.7/sbin/zabbix_server: configuration syncer [synced configuration in 0.051028 sec, idle 60 sec]
postgres   41616    1311  0 15:32 ?        00:00:00 postgres: zabbix: zabbix zabbix ::1(36310) idle
postgres   41618   41606  0 15:32 ?        00:00:00 /u01/app/postgres/product/zabbix_5.4.7/sbin/zabbix_server: alert manager #1 [sent 0, failed 0 alerts, idle 5.104997 sec during 5.105235 sec]
...

Starting the Zabbix server after the PostgreSQL instance automatically with systemd is just a matter of this ( we usually do “current” links so we can easily switch between versions ):

postgres@patronipgbackrest:/home/postgres/ [pg141] ln -s /u01/app/postgres/product/zabbix_5.4.7/ /u01/app/postgres/product/zabbix-current
postgres@patronipgbackrest:/home/postgres/ [zabbix] cat /etc/systemd/system/zabbix.service
[Unit]
Description=Zabbix server service
After=network.target,postgres-zabbix.service

[Service]
User=postgres
Type=notify
ExecStart=/u01/app/postgres/product/zabbix-current/sbin/zabbix_server -f
Restart=always
RestartSec=10s
LimitNOFILE=40000

[Install]
WantedBy=multi-user.target

postgres@patronipgbackrest:/home/postgres/ [zabbix] sudo systemctl daemon-reload
postgres@patronipgbackrest:/home/postgres/ [zabbix] sudo systemctl enable zabbix.service
postgres@patronipgbackrest:/home/postgres/ [zabbix] sudo reboot

Done, for the Zabbix server. What you usually want to have in addition, is a graphical user interface and Zabbix comes web application written in PHP. The easiest way for me was to install the Apache webserver with PHP support:

postgres@patronipgbackrest:/home/postgres/ [pg141] sudo apt install apache libapache2-mod-php
postgres@patronipgbackrest:/home/postgres/ [pg141] systemctl status apache2.service
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-11-28 15:52:54 CET; 34s ago
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 8527 (apache2)
      Tasks: 6 (limit: 1133)
     Memory: 12.2M
        CPU: 83ms
     CGroup: /system.slice/apache2.service
             ├─8527 /usr/sbin/apache2 -k start
             ├─8530 /usr/sbin/apache2 -k start
             ├─8531 /usr/sbin/apache2 -k start
             ├─8532 /usr/sbin/apache2 -k start
             ├─8533 /usr/sbin/apache2 -k start
             └─8534 /usr/sbin/apache2 -k start
postgres@patronipgbackrest:/home/postgres/ [pg141] cd zabbix-5.4.7/ui/
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ui/ [pg141] sudo mkdir /var/www/html/zabbix/
postgres@patronipgbackrest:/home/postgres/zabbix-5.4.7/ui/ [pg141] sudo cp -a . /var/www/html/zabbix/

From now on you should be able to access the Zabbix console ( http://192.168.100.173/zabbix, in my case ):

Going to the next screen will trigger a prerequisite check and some stuff fails for me:

The whole list of checks in text format is this:

                                                Curent value |  Required    Result
-----------------------------------------------------------------------------------
PHP version                                     7.4.25          7.2.0       OK
PHP option              "memory_limit"          128M            128M        OK
PHP option              "post_max_size"         8M              16M         Fail
PHP option              "upload_max_filesize"   2M              2M          OK
PHP option              "max_execution_time"    30              300         Fail
PHP option              "max_input_time"        60              300         Fail
PHP databases support    off                                                Fail
PHP bcmath               off                                                Fail
PHP mbstring             off                                                Fail
PHP sockets	             on                                                 OK
PHP gd	                 unknown                                 2.0        Fail
PHP gd PNG support       off                                                Fail
PHP gd JPEG support      off                                                Fail
PHP gd GIF support       off                                                Warning
PHP gd FreeType support	 off                                                Fail
PHP libxml               2.9.10                                 2.6.15      OK
PHP xmlwriter            off                                                Fail
PHP xmlreader            off                                                Fail
PHP LDAP	             off                                                Warning
PHP OpenSSL              on                                                 OK
PHP ctype                on                                                 OK
PHP session	             on                                                 OK
PHP option               "session.auto_start" off off	                    OK
PHP gettext              on                                                 OK
PHP option               "arg_separator.output"                             OK

To fix the failed checks, I had to tweak the PHP configuration and to install additional packages:

postgres@patronipgbackrest:/home/postgres/ [pg141] sudo vi /etc/php/7.4/apache2/php.ini
postgres@patronipgbackrest:/home/postgres/ [pg141] egrep "max_input_time|max_execution_time|post_max_size" /etc/php/7.4/apache2/php.ini
; max_input_time
max_execution_time = 300
max_input_time = 300
post_max_size = 16M
postgres@patronipgbackrest:/home/postgres/ [pg141] sudo apt install php7.4-ldap php7.4-mbstring php7.4-pgsql php7.4-bcmath php7.4-gd php7.4-xml
postgres@patronipgbackrest:/home/postgres/ [pg141] sudo chown -R www-data:www-data /var/www/html/zabbix

The rest of the configuration is self explaining:





One last point to fix for the server part: As you can see in the last screenshot, the server node itself is reported as down. This is not a surprise as we did not configure the agent yet, but this is as well very simple:

postgres@patronipgbackrest:/home/postgres/ [pg141] egrep -v "^$|^#" /u01/app/postgres/product/zabbix-current/etc/zabbix_agentd.conf
PidFile=/u01/app/postgres/local/dmk/tmp/zabbix_agentd.pid
LogFile=/u01/app/postgres/local/dmk/log/zabbix_agentd.log
SourceIP=127.0.0.1
Server=192.168.100.173
ServerActive=127.0.0.1
Hostname=Zabbix server
postgres@patronipgbackrest:/home/postgres/ [pg141] cat /etc/systemd/system/zabbix-agent.service 
[Unit]
Description=dbi services Zabbix agent service
After=network.target,postgres-zabbix.service

[Service]
User=postgres
Type=notify
ExecStart=/u01/app/postgres/product/zabbix-current/sbin/zabbix_agentd -f
Restart=always
RestartSec=10s
LimitNOFILE=40000

[Install]
WantedBy=multi-user.target

After the agent is started up, all is green in the console:

In the next we’ll install and configure the agent on three Patroni nodes and configure Zabbix to monitor that.