Zabbix: Part 2: Configuration and Usage

Published: Apr 18, 2023 by Isaac Johnson

We spoke of Zabbix last week showing setup and basic usage. Let’s continue that into a more full formed implementation.

Today we’ll cover installing it on an on-prem Kubernetes cluster with helm after checking its demands using Datadog. As this is a production version, we’ll setup the PostgreSQL (and show you how to work around the PostgreSQL version requirement). We’ll expose it for HTTPS ingress and LetsEncrypt. For outgoing mail, we’ll configure Sendgrid and lastly, we’ll setup Pagerduty integrations.

The parts that are not fully working, but we’ll touch on are Github Integration and Auto-discovery.

Impact tracking with Datadog

First, before I consider launching Zabbix into my primary cluster, let’s check Datadog to see how Zabbix is affecting my test cluster.

I can see from the namespace, all looks healthy

/content/images/2023/04/zabbix2-01.png

If I look at the cluster as a whole, I see only a spastic ingress controller having troubles (which in this disconnected cluster is not important)

/content/images/2023/04/zabbix2-02.png

If I look at the last couple days, monitoring initially spiked to 7 pods, but otherwise Zabbix has been hanging out with 4

/content/images/2023/04/zabbix2-03.png

If I head over to my Pods dashboard, I can see Zabbix is a bit up there on CPU, but not the highest. And on memory, we can see the Zabbix Postgres host and web pod in the middle

/content/images/2023/04/zabbix2-04.png

At this point, I would feel comfortable launching it into the main cluster

Helm install

As before, I’ll install with Helm

First, I’ll switch my kube context to the main cluster

$ kubectx mac77
Switched to context "mac77".
$ kubectl get nodes
NAME                          STATUS   ROLES                  AGE    VERSION
hp-hp-elitebook-850-g2        Ready    <none>                 261d   v1.23.9+k3s1
builder-hp-elitebook-850-g1   Ready    <none>                 261d   v1.23.9+k3s1
builder-hp-elitebook-850-g2   Ready    <none>                 253d   v1.23.9+k3s1
isaac-macbookair              Ready    control-plane,master   262d   v1.23.9+k3s1

To be safe, I’ll add the Helm Repo (if absent) and update

$ helm repo add zabbix-community https://zabbix-community.github.io/helm-zabbix
"zabbix-community" already exists with the same configuration, skipping
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "azure-samples" chart repository
...Successfully got an update from the "novum-rgi-helm" chart repository
...Successfully got an update from the "adwerx" chart repository
...Successfully got an update from the "zabbix-community" chart repository
...Successfully got an update from the "kubecost" chart repository
...Successfully got an update from the "jfelten" chart repository
...Successfully got an update from the "kuma" chart repository
...Successfully got an update from the "dapr" chart repository
...Successfully got an update from the "ngrok" chart repository
...Successfully got an update from the "open-telemetry" chart repository
...Successfully got an update from the "uptime-kuma" chart repository
...Successfully got an update from the "longhorn" chart repository
...Successfully got an update from the "castai-helm" chart repository
...Successfully got an update from the "rhcharts" chart repository
...Successfully got an update from the "lifen-charts" chart repository
...Successfully got an update from the "epsagon" chart repository
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "rook-release" chart repository
...Successfully got an update from the "freshbrewed" chart repository
...Successfully got an update from the "elastic" chart repository
...Successfully got an update from the "myharbor" chart repository
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "harbor" chart repository
...Successfully got an update from the "sonarqube" chart repository
...Successfully got an update from the "argo-cd" chart repository
...Successfully got an update from the "crossplane-stable" chart repository
...Successfully got an update from the "incubator" chart repository
...Successfully got an update from the "newrelic" chart repository
...Successfully got an update from the "gitlab" chart repository
...Successfully got an update from the "rancher-latest" chart repository
...Successfully got an update from the "bitnami" chart repository
...Successfully got an update from the "confluentinc" chart repository
...Successfully got an update from the "actions-runner-controller" chart repository
...Successfully got an update from the "sumologic" chart repository
Update Complete. ⎈Happy Helming!⎈

One thing I think I’ll do different is to create an externalized database for Zabbix.

I’ll need the ‘create.sql.gz’ that will set up the schema.

If we have installed Zabbix locally in linux(.e.g apt-get install zabbix-server-pgsql zabbix-frontend-php), we could find it in /usr/share/doc/zabbix-server-postgresql/

I had hope to pull from source but did not see where create_server.sql comes from. You can do a make dbschema on source to create it, however (see steps here).

Since I plan to launch the helm chart I just ran, I’ll just fetch from the last container.

I can see the create and timescale files in

zabbix@myzabbix-zabbix-server-5978d49678-znzfn:/usr/share/doc/zabbix-server-postgresql$ ls
create.sql.gz  timescaledb.sql

I’ll copy them locally

$ kubectl cp myzabbix-zabbix-server-5978d49678-znzfn:/usr/share/doc/zabbix-server-postgresql/create.sql.gz /tmp/create.sql.gz -n monitoring
Defaulted container "zabbix-server" out of: zabbix-server, zabbix-agent
tar: Removing leading `/' from member names
$ kubectl cp myzabbix-zabbix-server-5978d49678-znzfn:/usr/share/doc/zabbix-server-postgresql/timescaledb.sql /tmp/timescaledb.sql -n monitoring
Defaulted container "zabbix-server" out of: zabbix-server, zabbix-agent

$ ls -l /tmp/create.sql.gz
-rw-r--r-- 1 builder builder 3769355 Apr 14 06:38 /tmp/create.sql.gz
$ ls -l /tmp/timescaledb.sql
-rw-r--r-- 1 builder builder 3437 Apr 14 06:38 /tmp/timescaledb.sql

I’ll pop over to my PostgreSQL host to check the databases that exist

isaac@isaac-MacBookAir:~$ sudo su - postgres
[sudo] password for isaac:
postgres@isaac-MacBookAir:~$ psql
psql (12.14 (Ubuntu 12.14-0ubuntu0.20.04.1))
Type "help" for help.

postgres=# \l
                                    List of databases
     Name      |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
---------------+----------+----------+-------------+-------------+-----------------------
 notary_server | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | harbor=CTc/postgres  +
               |          |          |             |             | instana=c/postgres
 notary_signer | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | harbor=CTc/postgres  +
               |          |          |             |             | instana=c/postgres
 postgres      | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | instana=CTc/postgres
 registry      | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | harbor=CTc/postgres  +
               |          |          |             |             | instana=c/postgres
 template0     | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | instana=c/postgres
 template1     | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | instana=c/postgres
(6 rows)
postgres=# \q

I’ll create a zabbix user first

isaac@isaac-MacBookAir:~$ sudo -u postgres createuser --pwprompt zabbix
[sudo] password for isaac:
Enter password for new role:
Enter it again:

Next, I’ll create a new Database on the cluster and grant ownership to zabbix

isaac@isaac-MacBookAir:~$ sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix

Back on my main Linux, I’ll just copy those files over (had I done all this on the postgres box, we wouldn’t need to copy)

$ sftp isaac@192.168.1.78
isaac@192.168.1.78's password:
Connected to 192.168.1.78.
sftp> cd /tmp
sftp> put /tmp/create.sql.gz
Uploading /tmp/create.sql.gz to /tmp/create.sql.gz
/tmp/create.sql.gz                                                                                                                                    100% 3681KB  12.1MB/s   00:00
sftp> put /tmp/timescaledb.sql
Uploading /tmp/timescaledb.sql to /tmp/timescaledb.sql
/tmp/timescaledb.sql                                                                                                                                  100% 3437     1.7MB/s   00:00
sftp> quit

Now I can load the PSQL DB. This step takes some time

$ zcat /tmp/create.sql.gz |  psql -U zabbix -d zabbix -h localhost
... snip ...
INSERT 0 1
INSERT 0 1
COMMIT
isaac@isaac-MacBookAir:~$

I’ll now login to verify the tables were created

$ PGPASSWORD=asdfasdfasdf; psql -U zabbix -d zabbix -h localhost
psql (12.14 (Ubuntu 12.14-0ubuntu0.20.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

zabbix=> /l
                                    List of databases
     Name      |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
---------------+----------+----------+-------------+-------------+-----------------------
 notary_server | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | harbor=CTc/postgres  +
               |          |          |             |             | instana=c/postgres
 notary_signer | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | harbor=CTc/postgres  +
               |          |          |             |             | instana=c/postgres
 postgres      | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | instana=CTc/postgres
 registry      | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =Tc/postgres         +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | harbor=CTc/postgres  +
               |          |          |             |             | instana=c/postgres
 template0     | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | instana=c/postgres
 template1     | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
               |          |          |             |             | postgres=CTc/postgres+
               |          |          |             |             | instana=c/postgres
 zabbix        | zabbix   | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
(7 rows)

zabbix=> \c zabbix
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
You are now connected to database "zabbix" as user "zabbix".
zabbix=> \x
Expanded display is on.
zabbix=> \pset pager off
Pager usage is off.
zabbix=> \dt;
List of relations
-[ RECORD 1 ]----------------------
Schema | public
Name   | acknowledges
Type   | table
Owner  | zabbix
-[ RECORD 2 ]----------------------
Schema | public
Name   | actions
Type   | table
Owner  | zabbix
-[ RECORD 3 ]----------------------
Schema | public
Name   | alerts
Type   | table
Owner  | zabbix
-[ RECORD 4 ]----------------------
Schema | public
Name   | auditlog
Type   | table
Owner  | zabbix
-[ RECORD 5 ]----------------------
Schema | public
Name   | autoreg_host
Type   | table
Owner  | zabbix
-[ RECORD 6 ]----------------------
Schema | public
Name   | conditions
Type   | table
Owner  | zabbix
-[ RECORD 7 ]----------------------
Schema | public
Name   | config
Type   | table
Owner  | zabbix
-[ RECORD 8 ]----------------------
Schema | public
Name   | config_autoreg_tls
Type   | table
Owner  | zabbix
-[ RECORD 9 ]----------------------
Schema | public
Name   | corr_condition
Type   | table
Owner  | zabbix
-[ RECORD 10 ]---------------------
Schema | public
Name   | corr_condition_group
Type   | table
Owner  | zabbix
-[ RECORD 11 ]---------------------
Schema | public
Name   | corr_condition_tag
Type   | table
Owner  | zabbix
-[ RECORD 12 ]---------------------
Schema | public
Name   | corr_condition_tagpair
Type   | table
Owner  | zabbix
-[ RECORD 13 ]---------------------
Schema | public
Name   | corr_condition_tagvalue
Type   | table
Owner  | zabbix
-[ RECORD 14 ]---------------------
Schema | public
Name   | corr_operation
Type   | table
Owner  | zabbix
-[ RECORD 15 ]---------------------
Schema | public
Name   | correlation
Type   | table
Owner  | zabbix
-[ RECORD 16 ]---------------------
Schema | public
Name   | dashboard
Type   | table
Owner  | zabbix
-[ RECORD 17 ]---------------------
Schema | public
Name   | dashboard_page
Type   | table
Owner  | zabbix
-[ RECORD 18 ]---------------------
Schema | public
Name   | dashboard_user
Type   | table
Owner  | zabbix
-[ RECORD 19 ]---------------------
Schema | public
Name   | dashboard_usrgrp
Type   | table
Owner  | zabbix
-[ RECORD 20 ]---------------------
Schema | public
Name   | dbversion
Type   | table
Owner  | zabbix
-[ RECORD 21 ]---------------------
Schema | public
Name   | dchecks
Type   | table
Owner  | zabbix

... snip ...

-[ RECORD 171 ]--------------------
Schema | public
Name   | valuemap_mapping
Type   | table
Owner  | zabbix
-[ RECORD 172 ]--------------------
Schema | public
Name   | widget
Type   | table
Owner  | zabbix
-[ RECORD 173 ]--------------------
Schema | public
Name   | widget_field
Type   | table
Owner  | zabbix
zabbix=> \q

I tried to add timescale, but perhaps it’s not important. (One could install the extension)

isaac@isaac-MacBookAir:~$ cat /tmp/timescaledb.sql |  psql -U zabbix -d zabbix -h localhost
NOTICE:  PostgreSQL version 12.14 (Ubuntu 12.14-0ubuntu0.20.04.1) is valid
ERROR:  TimescaleDB extension is not installed
CONTEXT:  PL/pgSQL function inline_code_block line 42 at RAISE

we can now pass the database settings to our Helm command:

--set "postgressql.enabled=false" --set "db_access.db_server_host=192.168.1.78" --set "db_access.postgres_password=asdfasdfsadf" --set "db_access.postgres_user=zabbix"

If we use just ‘zabbix’ for the user and password;

$ helm upgrade --install myzabbix zabbix-community/zabbix --create-namespace -n monitoring --set "postgressql.enabled=false" --set "db_access.db_server_host=192.168.1.78"
Release "myzabbix" has been upgraded. Happy Helming!
NAME: myzabbix
LAST DEPLOYED: Fri Apr 14 07:03:52 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=myzabbix-zabbix-web" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace monitoring $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8888 to use your application"
  kubectl --namespace monitoring port-forward $POD_NAME 8888:$CONTAINER_PORT

Note: one can also go back to using the embedded postgres container

$ helm upgrade --install myzabbix zabbix-community/zabbix --create-namespace -n monitoring --set "postgressql.enabled=true" --set "db_access.db_server_host=zabbix-postgresql"
Release "myzabbix" has been upgraded. Happy Helming!
NAME: myzabbix
LAST DEPLOYED: Fri Apr 14 07:08:53 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 3
TEST SUITE: None
NOTES:
Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=myzabbix-zabbix-web" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace monitoring $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8888 to use your application"
  kubectl --namespace monitoring port-forward $POD_NAME 8888:$CONTAINER_PORT

Installing:

$ helm upgrade --install myzabbix zabbix-community/zabbix --create-namespace -n monitoring --set postgressql.enabled=false --set db_access.db_server_host=192.168.1.78
Release "myzabbix" does not exist. Installing it now.
NAME: myzabbix
LAST DEPLOYED: Fri Apr 14 07:11:58 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=myzabbix-zabbix-web" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace monitoring $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8888 to use your application"
  kubectl --namespace monitoring port-forward $POD_NAME 8888:$CONTAINER_PORT

When it didn’t install, I realized I had a CloudCustodian policy kicking in on required lables! Caught by my own protections!

$ kubectl get rs -n monitoring
NAME                                    DESIRED   CURRENT   READY   AGE
myzabbix-zabbix-webservice-7d96f65f66   1         0         0       51s
myzabbix-zabbix-server-5978d49678       1         0         0       51s
myzabbix-zabbix-web-7fd6d4db87          1         0         0       51s

$ kubectl describe rs myzabbix-zabbix-webservice-7d96f65f66 -n monitoring | tail -n5
  ReplicaFailure   True    FailedCreate
Events:
  Type     Reason        Age                  From                   Message
  ----     ------        ----                 ----                   -------
  Warning  FailedCreate  28s (x15 over 110s)  replicaset-controller  Error creating: admission webhook "admission.cloudcustodian.io" denied the request: Failed admission due to policies:[{"name": "missing-recommended-labels", "description": "Kubernetes recommmended the following labels from its docs:\napp.kubernetes.io/name\napp.kubernetes.io/instance\napp.kubernetes.io/version\napp.kubernetes.io/component\napp.kubernetes.io/part-of\napp.kubernetes.io/managed-by\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/\n"}]

This cluster really didn’t need cloudcustodian limiting pods by lable. I’ll remove it for now

$ helm list -n c7n-system
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
c7n-kube        c7n-system      1               2023-04-06 16:41:34.593818836 -0500 CDT deployed        c7n-kube-0.1.2  0.1.2

And i can see it unblock Zabbix

$ kubectl get pods -n monitoring
NAME                                          READY   STATUS              RESTARTS   AGE
myzabbix-postgresql-0                         1/1     Running             0          4m9s
myzabbix-zabbix-web-7fd6d4db87-mtxjc          0/1     ContainerCreating   0          3s
myzabbix-zabbix-webservice-7d96f65f66-99vl7   0/1     ContainerCreating   0          3s
myzabbix-zabbix-server-5978d49678-z9hl2       0/2     ContainerCreating   0          3s

I’m slightly confused as to why Postgres was added

$ helm list -n monitoring
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
myzabbix        monitoring      1               2023-04-14 07:11:58.443324795 -0500 CDT deployed        zabbix-3.4.4    6.0.13
$ helm get values myzabbix -n monitoring
USER-SUPPLIED VALUES:
db_access:
  db_server_host: 192.168.1.78
postgressql:
  enabled: false

I immediately saw my fatfinger typo when I did a helm get values --all

... snip ...
postgresql:
  containerAnnotations: {}
  enabled: true
  extraContainers: []
  extraEnv: []
  extraInitContainers: []
  extraPodSpecs: {}
  extraRuntimeParameters:
    max_connections: 50
  extraVolumeMounts: []
  extraVolumes: []
  image:
    pullPolicy: IfNotPresent
    pullSecrets: []
    repository: postgres
    tag: 14
  persistence:
    enabled: false
    existing_claim_name: false
    storage_size: 5Gi
  service:
    annotations: {}
    clusterIP: null
    port: 5432
    type: ClusterIP
  statefulSetAnnotations: {}
postgressql:
  enabled: false

postgresql != postgressql….

I corrected the double “s” and upgraded

$ helm upgrade --install myzabbix zabbix-community/zabbix --create-namespace -n monitoring --set postgresql.enabled=false --set db_access.db_server_host=192.168.1.78
Release "myzabbix" has been upgraded. Happy Helming!
NAME: myzabbix
LAST DEPLOYED: Fri Apr 14 07:22:01 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=myzabbix-zabbix-web" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace monitoring $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8888 to use your application"
  kubectl --namespace monitoring port-forward $POD_NAME 8888:$CONTAINER_PORT

That bounced the server and the Postgres container went away

$ kubectl get pods -n monitoring
NAME                                          READY   STATUS        RESTARTS   AGE
myzabbix-zabbix-webservice-7d96f65f66-99vl7   1/1     Running       0          5m12s
myzabbix-zabbix-web-7fd6d4db87-mtxjc          1/1     Running       0          5m12s
myzabbix-zabbix-web-5d55dfb47b-lk9nz          0/1     Running       0          38s
myzabbix-zabbix-server-5d54b57c97-dwnkx       2/2     Running       0          38s
myzabbix-zabbix-server-5978d49678-z9hl2       2/2     Terminating   0          5m12s

However, I suspect it is not picking up the right vars

$ kubectl logs myzabbix-zabbix-server-5d54b57c97-dwnkx -n monitoring
Defaulted container "zabbix-server" out of: zabbix-server, zabbix-agent
** Preparing Zabbix server
** Preparing database
** Using POSTGRES_USER variable from ENV
** Using POSTGRES_PASSWORD variable from ENV
********************
* DB_SERVER_HOST: myzabbix-postgresql
* DB_SERVER_PORT: 5432
* DB_SERVER_DBNAME: zabbix
* DB_SERVER_SCHEMA: public
********************
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...
**** PostgreSQL server is not available. Waiting 5 seconds...

Indeed, the secret was not updated

$ kubectl get secret zabbixdb-pguser-zabbix -n monitoring -o yaml
apiVersion: v1
data:
  dbname: emFiYml4
  host: bXl6YWJiaXgtcG9zdGdyZXNxbA==
  password: OEhiak1pZ1U0enBJaEZTdg==
  port: NTQzMg==
  user: emFiYml4
kind: Secret
... snip ...

$ echo bXl6YWJiaXgtcG9zdGdyZXNxbA== | base64 --decode && echo
myzabbix-postgresql
$ echo  OEhiak1pZ1U0enBJaEZTdg== | base64 --decode && echo
8HbjMigU4zpIhFSv
$ echo NTQzMg== | base64 --decode && echo
5432
$ echo emFiYml4 | base64 --decode && echo
zabbix

I’ll correct manually.. i just need to base64 the secrets i used. e.g.

$ echo 192.168.1.78 | tr -d '\n' | base64
MTkyLjE2OC4xLjc4

I’ll apply and rotate the server pod

$ kubectl apply -f ./zabbix.db.settings.yaml
Warning: resource secrets/zabbixdb-pguser-zabbix is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
secret/zabbixdb-pguser-zabbix configured

$ kubectl delete pod myzabbix-zabbix-server-5d54b57c97-dwnkx -n monitoring
pod "myzabbix-zabbix-server-5d54b57c97-dwnkx" deleted

This crapped out because evidently Postresql 12 is not supported:

$ kubectl logs myzabbix-zabbix-server-5d54b57c97-hxwsg -n monitoring
Defaulted container "zabbix-server" out of: zabbix-server, zabbix-agent
** Preparing Zabbix server
** Preparing database
** Using POSTGRES_USER variable from ENV
** Using POSTGRES_PASSWORD variable from ENV
********************
* DB_SERVER_HOST: 192.168.1.78
* DB_SERVER_PORT: 5432
* DB_SERVER_DBNAME: zabbix
* DB_SERVER_SCHEMA: public
********************
** Table 'zabbix.dbversion' already exists.
** Preparing Zabbix server configuration file
** Updating '/etc/zabbix/zabbix_server.conf' parameter "ListenIP": ''...removed
... snip ...kubectl get
** Updating '/etc/zabbix/zabbix_server.conf' parameter "User": 'zabbix'...added
Starting Zabbix Server. Zabbix 6.0.13 (revision fdfa8ce).
Press Ctrl+C to exit.

     7:20230414:123102.951 Starting Zabbix Server. Zabbix 6.0.13 (revision fdfa8ce).
     7:20230414:123102.951 ****** Enabled features ******
     7:20230414:123102.951 SNMP monitoring:           YES
     7:20230414:123102.951 IPMI monitoring:           YES
     7:20230414:123102.951 Web monitoring:            YES
     7:20230414:123102.951 VMware monitoring:         YES
     7:20230414:123102.951 SMTP authentication:       YES
     7:20230414:123102.951 ODBC:                      YES
     7:20230414:123102.951 SSH support:               YES
     7:20230414:123102.951 IPv6 support:              YES
     7:20230414:123102.951 TLS support:               YES
     7:20230414:123102.951 ******************************
     7:20230414:123102.951 using configuration file: /etc/zabbix/zabbix_server.conf
     7:20230414:123103.018
     7:20230414:123103.018 Unable to start Zabbix server due to unsupported PostgreSQL database version (12.14).
     7:20230414:123103.018 Must be at least (13.0).
     7:20230414:123103.018 Use of supported database version is highly recommended.
     7:20230414:123103.018 Override by setting AllowUnsupportedDBVersions=1 in Zabbix server configuration file at your own risk.
     7:20230414:123103.018

I have one more try, one can change the variable in the conf to be “AllowUnsupportedDbVersions=1”, however, one can pass it in as an env var as well. Since our Config isn’t exposed as a CM, we’ll use the env var.

$ helm upgrade --install myzabbix zabbix-community/zabbix --create-namespace -n monitoring --set postgresql.enabled=false --set db_access.db_server_host=192.168.1.78 --set zabbixserver.extraEnv[0].name=ZBX_ALLOWUNSUPPORTEDDBVERSIONS,zabbixserver.extraEnv[0].value=1
Release "myzabbix" has been upgraded. Happy Helming!
NAME: myzabbix
LAST DEPLOYED: Fri Apr 14 07:42:10 2023
NAMESPACE: monitoring
STATUS: deployed
REVISION: 3
TEST SUITE: None
NOTES:
Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=myzabbix-zabbix-web" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace monitoring $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8888 to use your application"
  kubectl --namespace monitoring port-forward $POD_NAME 8888:$CONTAINER_PORT

This seemed to work

$ kubectl logs myzabbix-zabbix-server-ff444849c-2zp2x -n monitoring
Defaulted container "zabbix-server" out of: zabbix-server, zabbix-agent
** Preparing Zabbix server
** Preparing database
** Using POSTGRES_USER variable from ENV
** Using POSTGRES_PASSWORD variable from ENV
********************
* DB_SERVER_HOST: 192.168.1.78
* DB_SERVER_PORT: 5432
* DB_SERVER_DBNAME: zabbix
* DB_SERVER_SCHEMA: public
********************
** Database 'zabbix' already exists. Please be careful with database owner!
** Table 'zabbix.dbversion' already exists.
** Preparing Zabbix server configuration file
** Updating '/etc/zabbix/zabbix_server.conf' parameter "ListenIP": ''...removed
** Updating '/etc/zabbix/zabbix_server.conf' parameter "ListenPort": ''...removed
** Updating '/etc/zabbix/zabbix_server.conf' parameter "ListenBacklog": ''...removed
** Updating '/etc/zabbix/zabbix_server.conf' parameter "SourceIP": ''...removed
** Updating '/etc/zabbix/zabbix_server.conf' parameter "LogType": 'console'...added
... snip ...
** Updating '/etc/zabbix/zabbix_server.conf' parameter "User": 'zabbix'...added
Starting Zabbix Server. Zabbix 6.0.13 (revision fdfa8ce).
Press Ctrl+C to exit.

     7:20230414:124218.123 Starting Zabbix Server. Zabbix 6.0.13 (revision fdfa8ce).
     7:20230414:124218.123 ****** Enabled features ******
     7:20230414:124218.123 SNMP monitoring:           YES
     7:20230414:124218.123 IPMI monitoring:           YES
     7:20230414:124218.123 Web monitoring:            YES
     7:20230414:124218.123 VMware monitoring:         YES
     7:20230414:124218.123 SMTP authentication:       YES
     7:20230414:124218.123 ODBC:                      YES
     7:20230414:124218.123 SSH support:               YES
     7:20230414:124218.123 IPv6 support:              YES
     7:20230414:124218.123 TLS support:               YES
     7:20230414:124218.123 ******************************
     7:20230414:124218.123 using configuration file: /etc/zabbix/zabbix_server.conf
     7:20230414:124218.182
     7:20230414:124218.182 Warning! Unsupported PostgreSQL database version (12.14).
     7:20230414:124218.182 Should be at least (13.0).
     7:20230414:124218.182 Use of supported database version is highly recommended.
     7:20230414:124218.182
     7:20230414:124218.250 current database version (mandatory/optional): 06000000/06000018
     7:20230414:124218.251 required mandatory version: 06000000
   202:20230414:124218.361 starting HA manager
   202:20230414:124218.403 HA manager started in active mode
     7:20230414:124218.404 server #0 started [main process]

Expose

Now I’ll add an A record so we can expose it

$ cat ./r53-zabbix.json
{
    "Comment": "CREATE zabbix fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "zabbix.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "73.242.50.46"
            }
          ]
        }
      }
    ]
  }

and apply it

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-zabbix.json
{
    "ChangeInfo": {
        "Id": "/change/C04745933CKMXVOAAWFIK",
        "Status": "PENDING",
        "SubmittedAt": "2023-04-14T12:47:35.116Z",
        "Comment": "CREATE zabbix fb.s A record "
    }
}

Then I just need to add an ingress file

$ cat ingress.zabbix.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "600"
    nginx.org/proxy-read-timeout: "600"
  labels:
    app.kubernetes.io/name: zabbix
  name: zabbix
  namespace: monitoring
spec:
  defaultBackend:
    service:
      name: myzabbix-zabbix-web
      port:
        name: http
  rules:
  - host: zabbix.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: myzabbix-zabbix-web
            port:
              name: http
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - zabbix.freshbrewed.science
    secretName: zabbix-tls
$ kubectl apply -f ./ingress.zabbix.yaml -n monitoring
ingress.networking.k8s.io/zabbix created

$ kubectl get ingress -n monitoring
NAME     CLASS    HOSTS                        ADDRESS                                                PORTS     AGE
zabbix   <none>   zabbix.freshbrewed.science   192.168.1.215,192.168.1.36,192.168.1.57,192.168.1.78   80, 443   42ss

I got some errors until I fixed the port to 80 instead of http. I really cannot explain why my Nginx seemed unhappy with http when it means the same

$ cat ingress.zabbix.yaml.3
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    field.cattle.io/publicEndpoints: '[{"addresses":["192.168.1.215","192.168.1.36","192.168.1.57","192.168.1.78"],"port":443,"protocol":"HTTPS","serviceName":"monitoring:myzabbix-zabbix-web","ingressName":"monitoring:zabbix","hostname":"zabbix.freshbrewed.science","path":"/","allNodes":false}]'
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{"cert-manager.io/cluster-issuer":"letsencrypt-prod","field.cattle.io/publicEndpoints":"[{\"addresses\":[\"192.168.1.215\",\"192.168.1.36\",\"192.168.1.57\",\"192.168.1.78\"],\"port\":443,\"protocol\":\"HTTPS\",\"serviceName\":\"monitoring:myzabbix-zabbix-web\",\"ingressName\":\"monitoring:zabbix\",\"hostname\":\"zabbix.freshbrewed.science\",\"path\":\"/\",\"allNodes\":false}]","ingress.kubernetes.io/proxy-body-size":"0","ingress.kubernetes.io/ssl-redirect":"true","kubernetes.io/ingress.class":"nginx","nginx.ingress.kubernetes.io/proxy-body-size":"0","nginx.ingress.kubernetes.io/proxy-read-timeout":"600","nginx.ingress.kubernetes.io/proxy-send-timeout":"600","nginx.ingress.kubernetes.io/ssl-redirect":"true","nginx.org/client-max-body-size":"0","nginx.org/proxy-connect-timeout":"600","nginx.org/proxy-read-timeout":"600"},"creationTimestamp":"2023-04-14T12:50:28Z","generation":1,"labels":{"app.kubernetes.io/name":"zabbix"},"name":"zabbix","namespace":"monitoring","resourceVersion":"116075932","uid":"02117543-6aa1-4752-9e96-3bb895f60544"},"spec":{"defaultBackend":{"service":{"name":"myzabbix-zabbix-web","port":{"number":8080}}},"rules":[{"host":"zabbix.freshbrewed.science","http":{"paths":[{"backend":{"service":{"name":"myzabbix-zabbix-web","port":{"number":8080}}},"path":"/","pathType":"ImplementationSpecific"}]}}],"tls":[{"hosts":["zabbix.freshbrewed.science"],"secretName":"zabbix-tls"}]},"status":{"loadBalancer":{"ingress":[{"ip":"192.168.1.215"},{"ip":"192.168.1.36"},{"ip":"192.168.1.57"},{"ip":"192.168.1.78"}]}}}
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "600"
    nginx.org/proxy-read-timeout: "600"
  creationTimestamp: "2023-04-14T12:50:28Z"
  generation: 2
  labels:
    app.kubernetes.io/name: zabbix
  name: zabbix
  namespace: monitoring
  resourceVersion: "116079466"
  uid: 02117543-6aa1-4752-9e96-3bb895f60544
spec:
  rules:
  - host: zabbix.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: myzabbix-zabbix-web
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - zabbix.freshbrewed.science
    secretName: zabbix-tls
status:
  loadBalancer:
    ingress:
    - ip: 192.168.1.215
    - ip: 192.168.1.36
    - ip: 192.168.1.57
    - ip: 192.168.1.78

$ kubectl apply -f ingress.zabbix.yaml.3 -n monitoring
ingress.networking.k8s.io/zabbix configured

/content/images/2023/04/zabbix2-05.png

The first thing to do, now that we exposed our endpoint, is to change the password of the admin user

/content/images/2023/04/zabbix2-06.png

And we can save and see it updated

/content/images/2023/04/zabbix2-07.png

The default timezone is Riga, Latvia. So I’ll change that next

/content/images/2023/04/zabbix2-08.png

Under Administration, General, Other I’ll give our Frontend URL and set Host Inventory mode to Automatic

/content/images/2023/04/zabbix2-09.png

In maps, I want to make my “Local network” a favourite

/content/images/2023/04/zabbix2-09.png

which will now give us a link to the local network map

/content/images/2023/04/zabbix2-10.png

Sendgrid

We need to go to “Administration/Media Types” to find “Email” and “Email (HTML)”

/content/images/2023/04/zabbix2-11.png

In SendGrid, we can go to the SMTP Relay settings

/content/images/2023/04/zabbix2-12.png

Now let’s test

/content/images/2023/04/zabbix2-14.png

I see it was a success in Zabbix

/content/images/2023/04/zabbix2-13.png

I see the verification in Sendgrid

/content/images/2023/04/zabbix2-15.png

And I can see the email in Gmail

/content/images/2023/04/zabbix2-16.png

Hosts

Adding a NAS

/content/images/2023/04/zabbix2-17.png

I added a IPMI user as MD5, but no login details

/content/images/2023/04/zabbix2-19.png

Then I could show ICMP details now

/content/images/2023/04/zabbix2-20.png

which worked

/content/images/2023/04/zabbix2-18.png

Adding a Linux VM Host

Just as before, we can add a host

/content/images/2023/04/zabbix2-22.png

Then “Detect Operating System”

/content/images/2023/04/zabbix2-21.png

One thing, we will need to remove the monitoring by the test Zabbix instance as it’s unneccessary to track in both

/content/images/2023/04/zabbix2-23.png

So we delete from the ‘test’ instance:

/content/images/2023/04/zabbix2-24.png

Discovery

I checked Discovery under Configuration and ensured it was enabled.

/content/images/2023/04/zabbix2-25.png

But then it Dawned on my that my network was 192.168.1.0/24 not 192.168.0.0/24 which is why nothing had been picked up.

I then changed my rules accordingly and saved

/content/images/2023/04/zabbix2-26.png

I should add that later I decided limiting to only hosts with the Zabbix agent wouldn’t suffice

/content/images/2023/04/zabbix2-67.png

So I changed it to ICMP ping

/content/images/2023/04/zabbix2-68.png

And verified it was enabled

/content/images/2023/04/zabbix2-69.png

Discovery

For Linux hosts, we will want Auto Discovery enabled as well.

Go to Configuration/Actions/Discovery actions. Here we see the “Auto discovery. Linux servers” is disabled by default

/content/images/2023/04/zabbix2-64.png

I’ll enable it

/content/images/2023/04/zabbix2-65.png

I can also verify it’s actions

/content/images/2023/04/zabbix2-66.png

The mistake I initially made is that why I added afew Operations, I missed the most important one.

I came back and saw the Discovered Hosts

/content/images/2023/04/zabbix2-75.png

But none were actually added as “Hosts” in my dashboard.

Going to Actions/Discovery Actions, I immediately saw the mistake in “Operations”.

/content/images/2023/04/zabbix2-76.png

I opened the “Auto discovery. Linux servers.” rule, and went to Operations, there I added the (doh!) “Add Host” action

/content/images/2023/04/zabbix2-77.png

I saved the Discovery Action, noting the added Operation

/content/images/2023/04/zabbix2-78.png

I then set it to a 5m schedule to get it to kick off sooner

/content/images/2023/04/zabbix2-79.png

Guest Access

I checked my Audit log and was surprised to see some one or system had been hammering my login as guest endlessly since I exposed Zabbix externally

/content/images/2023/04/zabbix2-27.png

Guest, by default, is disabled

/content/images/2023/04/zabbix2-28.png

What I decided would be best is to create a new ‘Invited Guest’ account (with a password).

/content/images/2023/04/zabbix2-30.png

This user can view things, but no administration.

/content/images/2023/04/zabbix2-29.png

I then just deleted the default ‘guest’ as that will only cause issues if unblocked…

/content/images/2023/04/zabbix2-31.png

Interestingly, one cannot remove guest - I did not expect that…

/content/images/2023/04/zabbix2-32.png

Notifications

If I wish to get, say, email Notifications, then I need to add a “Media Type” on my Admin user

/content/images/2023/04/zabbix2-33.png

I love how we have a “When active” cron style field

/content/images/2023/04/zabbix2-34.png

I cannot recall another tool (outside Pagerduty, perhaps) that let’s us only notify people by hour ranges. It does seem obvious, but in many cases, people don’t really want to get woken up due to a High CPU system if they aren’t on-call or working at those hours.

One really clever thing is we could setup a very nice Pagerduty off-hours alerting system on severe issues.

Pagerduty by email

I can go to Pagerduty Services and select a service or create a new one

/content/images/2023/04/zabbix2-35.png

I think for this I’ll create a new service called “ZabbixHighPriorityIssues”

/content/images/2023/04/zabbix2-36.png

I’ll leave the default Policy

/content/images/2023/04/zabbix2-37.png

Then I’ll create a Zabbix and Email integration

/content/images/2023/04/zabbix2-38.png

I’ll first add by email which I can copy from the “Integration Email” field

/content/images/2023/04/zabbix2-39.png

It will look something like zabbixhighpriorityissues-email.asdfasdfasdfasfd@princessking.pagerduty.com

When I add it, I’ll make sure to set it to High and Disaster only

/content/images/2023/04/zabbix2-40.png

Once saved, I can now see I have proper email type of escalation through to PagerDuty if the severity is high enough

/content/images/2023/04/zabbix2-41.png

To test this, I’ll set the severity to all, then go to Scheduled reports

In “scheduled reports” I’ll create a report

/content/images/2023/04/zabbix2-50.png

Add the Pagerduty User and a report to send

/content/images/2023/04/zabbix2-51.png

Then we can click “Test”. I can see it sent to gmail and Pagerduty

/content/images/2023/04/zabbix2-52.png

I can see the server report in GMail

/content/images/2023/04/zabbix2-53.png

And Pagerduty

/content/images/2023/04/zabbix2-54.png

As well as my Phone

/content/images/2023/04/tempFileForShare_20230415-082303.jpg

I’ll resolve the issue in PD so it isn’t in alert

/content/images/2023/04/zabbix2-55.png

If you had done as I and set the Pagerduty email in Admin to all types, make sure to go change that back so regular reports do not trigger Pagerduty

Actually, in testing this, it seems it sent them ANYWAYS

/content/images/2023/04/zabbix2-56.png

In our next section, we will create a Pagerduty user. I’ll move the PD email to that user and off of admin just to ensure I don’t pages on scheduled reports:

Pagerduty by Integration

Before we add the Zabbix PD integration, we need to add a “ZABBIX.URL” macro.

Go to Administration/General/Macros

/content/images/2023/04/zabbix2-42.png

and I’ll add {$ZABBIX.URL} to the value of my Front end URL. That could be HTTPS or something like http://192.168.1.123:8888 depending on your setup.

/content/images/2023/04/zabbix2-43.png

Next, we go to Administration/Media Types and click on PagerDuty. If you don’t have it, you can get the file here to import.

/content/images/2023/04/zabbix2-44.png

Next, I’ll copy the “Integration Key” into the “Token” variable

/content/images/2023/04/zabbix2-45.png

While I could add PD to my one Admin user, I’ll create a new “Pagerduty User” just for PD alerts.

/content/images/2023/04/zabbix2-46.png

I’ll add to “Guests” (so it can view things) and to “No Access to the Frontend” to ensure it is just for API calls

/content/images/2023/04/zabbix2-47.png

In Media Types, we’ll add Pagerduty. The “Send to” is not used, but is required to be set to something.

/content/images/2023/04/zabbix2-48.png

Lastly, I’ll set the role to “User” so it can view things

/content/images/2023/04/zabbix2-49.png

As I mentioned in the prior section, I’ll move the Pagerduty email to this user as well (for reports)

/content/images/2023/04/zabbix2-57.png

Github

In the “Github” Media Type, we need to set a Github Token (Usually start with GHP_)

You can create a PAT in Settings, Developer Settings for your Github user

/content/images/2023/04/zabbix2-58.png

Here we can create a new Classic token

/content/images/2023/04/zabbix2-59.png

I gave it a lot of read and write perms, but not delete

/content/images/2023/04/zabbix2-60.png

When saved, you’ll see the token displayed once.

In Zabbix, Administration/Media Types/Github we can set the Token there

/content/images/2023/04/zabbix2-61.png

I’ll then create a Github User in the Users area

/content/images/2023/04/zabbix2-62.png

The “Send to” is our Github Repo where Issues will be created

/content/images/2023/04/zabbix2-63.png

Defaults

One thing I had to keep doing in my rules was to set the time. If you want to stop messing with that, go to Administration/General and change the default time zone from Riga, Latvia

/content/images/2023/04/zabbix2-70.png

I also wanted to tweak the Dashboard.

I made sure at least one host had a Lat/Long

/content/images/2023/04/zabbix2-71.png

Then on the main dashboard, I clicked Edit and “+ Add” to add a widget. There I used type “Geomap”

/content/images/2023/04/zabbix2-72.png

I’ll also add a Map for the Local Network

/content/images/2023/04/zabbix2-73.png

When saved, I now have a Dashboard with hosts and maps

/content/images/2023/04/zabbix2-74.png

Alternatively, you can just change the Timezone for your user and leave the system default alone (I did this as I found changing the System Timezone away from Riga then broke auto-discovery)

/content/images/2023/04/zabbix2-80.png

API

The REST API is fairly easy with which to engage.

We can check our version without auth

$ curl --request POST --url 'https://zabbix.freshbrewed.science/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --data '{"jsonrpc":"2.0","method":"apiinfo.version","params":{},"id":1}' && echo
{"jsonrpc":"2.0","result":"6.0.13","id":1}

Or if you are port-forwarding to the local instance

$ curl --request POST --url 'http://localhost:8888/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --data '{"jsonrpc":"2.0","method":"apiinfo.version","params":{},"id":1}' && echo                                                                                                                                                                      {"jsonrpc":"2.0","result":"6.0.13","id":1}

If we want to do anything interesting, we’ll need an API token.

We can get to that under User Settings/API Tokens, then clicking “Create API Token”

/content/images/2023/04/zabbix2-81.png

Since this is a test instance, I’ll not use Token Expiration and give it a name, then click “Add”

/content/images/2023/04/zabbix2-82.png

I now have an API Token we’ll use in a moment (b79783bb317a0055fa6c5f747fbda37c4e16b9d1f4b20534bd17e98aea7c7b27)

/content/images/2023/04/zabbix2-83.png

We can now use that API token for the “auth” block in our payload

$ curl --request POST --url 'http://localhost:8888/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --data '{ "jsonrpc": "2.0", "method": "host.get", "params": { "output": [     "hostid", "host" ], "selectInterfaces": [ "interfaceid", "ip" ] }, "auth": "b79783bb317a0055fa6c5f747fbda37c4e16b9d1f4b20534bd17e98aea7c7b27",  "id": 2 }' && echo
{"jsonrpc":"2.0","result":[{"hostid":"10084","host":"Zabbix server","interfaces":[{"interfaceid":"1","ip":"127.0.0.1"}]},{"hostid":"10559","host":"sassynassy","interfaces":[{"interfaceid":"15","ip":"192.168.1.129"}]},{"hostid":"10560","host":"vote-front-azure-vote-1678278477-67bb8bb55c-k9cxj","interfaces":[{"interfaceid":"16","ip":"10.42.2.13"}]}],"id":2}

In future versions, this will move to Bearer token as we see in the API Documentation

However, in all my testing, at least for the version I’m running (6.0.13), it does not seem to work

$ curl --request POST --url 'http://localhost:8888/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --header 'Authorization: Bearer b79783bb317a0055fa6c5f747fbda37c4e16b9d1f4b20534bd17e98aea7c7b27' --data '{ "jsonrpc": "2.0", "method": "host.get", "params": { "output": [     "hostid", "host" ], "selectInterfaces": [ "interfaceid", "ip" ] }, "id": 2 }' && echo
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params.","data":"Not authorized."},"id":2}

After googling a lot to see if I’m missing some part of the flow, ChatGPT confirmed the way I was try to use it:

/content/images/2023/04/zabbix2-84.png

import requests

# Set the API endpoint URL
url = "http://your_zabbix_server/api_jsonrpc.php"

# Set the API request headers
headers = {
    "Content-Type": "application/json-rpc",
    "Authorization": "Bearer {token_value}"
}

# Set the API request data
data = {
    "jsonrpc": "2.0",
    "method": "host.get",
    "params": {},
    "id": 1
}

# Send the API request and get the response
response = requests.post(url, headers=headers, json=data)

# Print the API response
print(response.json())

Using API

We can get a list of hosts, for instance:

$ cat data.json
{
    "jsonrpc": "2.0",
    "method": "hostgroup.get",
    "params": {
        "output": "extend",
        "filter": {
            "name": [
                "Zabbix servers",
                "Linux servers"
            ]
        }
    },
    "auth": "b79783bb317a0055fa6c5f747fbda37c4e16b9d1f4b20534bd17e98aea7c7b27",
    "id": 1
}

And see the results

$ curl --request POST --url 'http://localhost:8888/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --data @data.json | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   569    0   252  100   317   1737   2186 --:--:-- --:--:-- --:--:--  3924
{
  "jsonrpc": "2.0",
  "result": [
    {
      "groupid": "2",
      "name": "Linux servers",
      "internal": "0",
      "flags": "0",
      "uuid": "dc579cd7a1a34222933f24f52a68bcd8"
    },
    {
      "groupid": "4",
      "name": "Zabbix servers",
      "internal": "0",
      "flags": "0",
      "uuid": "6f6799aa69e844b4b3918f779f2abf08"
    }
  ],
  "id": 1
}

I can see some real discovered hosts using my production endpoint

$ cat data2.json
{
    "jsonrpc": "2.0",
    "method": "dhost.get",
    "params": {
        "output": "extend",
        "selectDServices": "extend",
        "druleids": "2"
    },
    "auth": "asdfasdfasdfasdfasdfasdfasdfasdfasdfsadfasdfasdfb",
    "id": 1
}

and Using

builder@DESKTOP-QADGF36:/mnt/c/Users/isaac/Downloads/zabbix-master@89a9e3ebe91$ curl --request POST --url 'https://zabbix.freshbrewed.science/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --data @data2.json | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4476    0  4230  100   246  16145    938 --:--:-- --:--:-- --:--:-- 17019
{
  "jsonrpc": "2.0",
  "result": [
    {
      "dhostid": "97",
      "druleid": "2",
      "status": "0",
      "lastup": "1681733022",
      "lastdown": "0",
      "dservices": [
        {
          "dserviceid": "97",
          "dhostid": "97",
          "value": "",
          "port": "22",
          "status": "0",
          "lastup": "1681733022",
          "lastdown": "0",
          "dcheckid": "8",
          "ip": "192.168.1.11",
          "dns": "retropie"
        }
      ]
    },
    {
      "dhostid": "98",
      "druleid": "2",
      "status": "0",
      "lastup": "1681733023",
      "lastdown": "0",
      "dservices": [
        {
          "dserviceid": "98",
          "dhostid": "98",
          "value": "",
          "port": "22",
          "status": "0",
          "lastup": "1681733023",
          "lastdown": "0",
          "dcheckid": "8",
          "ip": "192.168.1.12",
          "dns": ""
        }
      ]
    },
    
    ... snip ...
  ],
  "id": 1
}

We can feed this to jq to get just IPs or just DNS names

$ curl --request POST --url 'https://zabbix.freshbrewed.science/api_jsonrpc.php' --header 'Content-Type: application/json-rpc' --data @data2.json | jq '.result[] | .dservices[] | .dns'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4476    0  4230  100   246  24736   1438 --:--:-- --:--:-- --:--:-- 26175
"retropie"
""
"builder-HP-EliteBook-745-G5"
"builder-hp-elitebook-850-g1"
"hp-hp-elitebook-850-g2"
""
"isaac-macbookair"
""
"anna-MacBookAir"
"raspberrypi"
"SassyNassy"
"builder-MacBookPro2"
""
"isaac-MacBookPro"
"builder-hp-elitebook-850-g2"
""

My next steps will likely be to write a bridge that will take the Discovered hosts and add them to my hosts (using host.create). This is because I cannot seem to get the already discovered hosts to re-trigger discovery rules, thus those that were found before I added “create host” to my discovery trigger are rather hung in Limbo.

Changing, Disabling, and Deleting Tokens

If one comprimises an API token (say they paste it in a blog), one can rotate it via the API menu:

/content/images/2023/04/zabbix2-85.png

One can also delete or disable via the API tokens menu as well

/content/images/2023/04/zabbix2-86.png

Summary

Today we setup Zabbix in Kubernetes running in an on-prem K3s cluster. We exposed it to outside traffic using an NGinx ingress controller and LetsEncrypt certs via cert-manager. We then secured it and set the basic settings (like on-prem CIDRs and Timezones). We explored adding hosts, auto-discovery, Sendgrid setup and Pagerduty/Github integrations. We wrapped by touching on using the REST API backend.

There is more, much much more, to do with Zabbix. I have more ideas like getting the Github Issue integration working (or using JIRA), setting up more monitors (like web and synthetic tests) and configuring more usable alerts.

I think Zabbix is a pretty slick, albeit old, tool for monitoring things. One really gets a lot for free out of the box, provided one wants to invest the time in configuring. I think that is were the Zabbix paid-implementation add-ons make sense. Arguably it was the model that Sun tried to pursue with Java - make the money on certifications and professional services.

There is a subtle challenge, however, in a system where the orchestrator calls out - it means to monitor ‘things’ our Zabbix server fundamentally needs some form of network access to the ‘thing’ to which it wants to monitor. If we think about all other systems out there, the destinations, in general, have monitors that call back out to the orchestrator. That means that APM suites like Datadog, New Relic, Dynatrace, Mezmo, Sumo Logic, etc all have local agents that send collected data up to the collectors. Here we have a system that scans and asks an agent for data.

In a way, that isn’t bad - it puts the burden on the main Zabbix instance, not the collector. But then there is a time shift on data and (possibly) the delay in adding host. I plan to explore that in a future article (could we not use the REST API to ‘self-register’ newly created hosts? seems a job for a provisioner).

Zabbix APM Pagerduty

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes