zabbixserver/README.md
hygienic-books 7664d0f3f6 fix(zabbixserver): Zabbix server now works without override
Upstream github.com/zabbix/zabbix-docker/issues/1643 is now fixed.
This previously prevented us from using both HashiCorp Vault and the
default docker-entrypoint.sh file that came with the the
zabbix/zabbix-server-pgsql:alpine-7.2-latest image.

Upstreams commit hash 435e92f made it so that when Vault params are
present both the Zabbix server config params DBUser and DBPassword
are unset.
2025-03-14 09:54:37 +01:00

266 lines
13 KiB
Markdown

# Zabbix Docker Compose files
Docker Compose files to spin up an instance of Zabbix.
# How to run
Add a `COMPOSE_ENV_FILE` and save its location as a shell variable along with the location where this repo lives, here for example `/opt/containers/zabbixserver` plus all other variables. At [env/fqdn_context.env.example](env/fqdn_context.env.example) you'll find an example environment file.
When everything's ready start Zabbix with Docker Compose, otherwise head down to [Initial setup](#initial-setup) first.
## Environment
Make sure that Zabbix' upstream repo at [github.com/zabbix/zabbix-docker](https://github.com/zabbix/zabbix-docker) is checked out locally. We're going with example dir `/opt/git/github.com/zabbix/zabbix-docker/branches/latest`. We're also assuming that **_this_** repo exists at `/opt/containers/zabbixserver`.
```
export UPSTREAM_REPO_DIR='/opt/git/github.com/zabbix/zabbix-docker/branches/latest'
export UPSTREAM_COMPOSE_FILE="${UPSTREAM_REPO_DIR%/}"'/docker-compose_v3_alpine_pgsql_latest.yaml'
export UPSTREAM_ENV_FILE="${UPSTREAM_REPO_DIR%/}"'/.env'
export COMPOSE_CTX='ux_vilnius'
export COMPOSE_PROJECT_NAME='zabbixserver-'"${COMPOSE_CTX}"
export COMPOSE_ENV_FILE=<add accordingly>
export COMPOSE_OVERRIDE='/opt/containers/zabbixserver/compose.override.yaml'
```
In Zabbix' Git repo check out latest tag for whatever version you want to use, we're going with the latest `7.2.*` version.
```
git -C "${UPSTREAM_REPO_DIR}" reset --hard origin/trunk
git -C "${UPSTREAM_REPO_DIR}" checkout trunk
git -C "${UPSTREAM_REPO_DIR}" pull
git -C "${UPSTREAM_REPO_DIR}" checkout "$(git --no-pager -C "${UPSTREAM_REPO_DIR}" tag -l --sort -version:refname | grep -Fi -- '7.2.' | head -n 1)"
```
## Context
On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:
```
docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'
```
## Pull
Pull images from Docker Hub verbatim.
```
docker compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV_FILE}" pull
```
## Copy to target
Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local `docker save` and a remote `docker load`. Consider the helper mini-project [quico.space/Quico/copy-docker](https://quico.space/Quico/copy-docker) where [copy-docker.sh](https://quico.space/Quico/copy-docker/src/branch/main/copy-docker.sh) allows the following workflow.
```
images="$(docker compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV_FILE}" config | grep -Pi -- 'image:' | awk '{print $2}' | sort | uniq)"
while IFS= read -u 10 -r image; do
copy-docker "${image}" fully.qualified.domain.name
done 10<<<"${images}"
```
This will for example copy over:
```
REPOSITORY TAG
postgres 16-alpine
zabbix/zabbix-web-nginx-pgsql alpine-7.2-latest
zabbix/zabbix-server-pgsql alpine-7.2-latest
busybox latest
```
## Start
```
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT_NAME}" --file "${UPSTREAM_COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${UPSTREAM_ENV_FILE}" --env-file "${COMPOSE_ENV_FILE}" up --detach
```
## Clean up
```
docker --context 'fully.qualified.domain.name' system prune -af
docker system prune -af
```
# Initial setup
We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.
## Datasets
Create ZFS datasets and set permissions as needed.
* Parent dateset
```
export "$(grep -Pi -- '^CONTEXT=' "${COMPOSE_ENV_FILE}")"
zfs create -o canmount=off zpool/data/opt
zfs create -o mountpoint=/opt/docker-data zpool/data/opt/docker-data
```
* Container-specific datasets
```
zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/postgres/config'
zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/postgres/data'
zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixserver/config'
zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixserver/data'
zfs create -p 'zpool/data/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixwebnginx/config'
```
* Change ownership
```
chown -R 70:70 '/opt/docker-data/zabbixserver-'"${CONTEXT}"'/postgres/'*
chown -R 101:101 '/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixwebnginx/config/'*
```
The PostgreSQL container will run its processes as user ID 70, the Zabbix web frontend container will be using user ID 101.
## Additional files
Per [Datasets](#datasets) your Docker files will live at `'/opt/docker-data/zabbixserver-'"${CONTEXT}"`. Over in [build-context](build-context) you'll find a subdirectory `docker-data` that has an example file and directory structure that explains the layout you'll want to create at `'/opt/docker-data/zabbixserver-'"${CONTEXT}"`. Match the `postgres` to your `postgres` dir, the `zabbixserver` dir to your `zabbixserver` dir and lastly the `zabbixwebnginx` dir to yours.
```
docker-data/
├── postgres
│ ├── cert
│ │ ├── .ZBX_DB_CA_FILE
│ │ ├── .ZBX_DB_CERT_FILE
│ │ └── .ZBX_DB_KEY_FILE
│ └── docker-entrypoint-initdb.d
│ └── init-user-db.sh
├── zabbixserver
│ ├── config
│ │ └── cert
│ │ ├── .ZBX_SERVER_CA_FILE
│ │ ├── .ZBX_SERVER_CERT_FILE
│ │ └── .ZBX_SERVER_KEY_FILE
│ └── data
│ ├── usr
│ │ └── lib
│ │ └── zabbix
│ │ ├── alertscripts
│ │ └── externalscripts
│ └── var
│ └── lib
│ └── zabbix
│ ├── dbscripts
│ ├── enc
│ ├── export
│ ├── mibs
│ ├── modules
│ ├── snmptraps
│ ├── ssh_keys
│ └── ssl
│ ├── certs
│ ├── keys
│ └── ssl_ca
└── zabbixwebnginx
└── config
├── cert
│ ├── dhparam.pem
│ ├── ssl.crt
│ └── ssl.key
└── modules
```
### postgres (PostgreSQL)
In `postgres/cert` place SSL certificate files that Postgres should serve to TLS-capable database clients for encrypted database connections such as for a domain `db.zabbix.example.com`. `.ZBX_DB_CA_FILE` is a certificate authority (CA) certificate, `.ZBX_DB_CERT_FILE` is a "full chain" certificate as in your domain's certificate followed by any intermediate certs concatenated one after the other. Lastly `.ZBX_DB_KEY_FILE` is your cert's unencrypted key file.
In `postgres/config/docker-entrypoint-initdb.d/init-user-db.sh` you'll find an example script file that - when your Postgres database is uninitialized - will create a second Postgres account in your database. Check out the example environment variables file [env/fqdn_context.env.example](env/fqdn_context.env.example) and specifically `ZBX_DB_USERNAME_PW` and `ZBX_DB_USERNAME_RO` to define a password and a username.
Zabbix' PostgreSQL instance by default doesn't expose a TCP port outside of its container. This setup, however, assumes that you have for example a Grafana instance or a similar entity that wants to directly connect to Postgres. Dedicated read-only database credentials come in handy in that situation.
### zabbixserver (main Zabbix server daemon)
In `zabbixserver/config/cert` place your SSL cert files. These are what the Zabbix server process serves to clients that connect to it such as `server.zabbix.example.com`. As with [PostgreSQL](#postgres-postgresql) you'll need a CA cert, a domain cert and a key file; file names are `.ZBX_SERVER_CA_FILE`, `.ZBX_SERVER_CERT_FILE` and `.ZBX_SERVER_KEY_FILE`.
There's also `zabbixserver/data` with what looks like a daunting amount of subdirectories. In our example they are all empty and they all belong to bind mounts that are configured with `create_host_path: true`.
```
- type: bind
source: /opt/docker-data/zabbixserver-${CONTEXT}/zabbixserver/data/usr/lib/zabbix/alertscripts
target: /usr/lib/zabbix/alertscripts
read_only: true
bind:
--> create_host_path: true
```
If you don't want to mount any files into your Zabbix instance you can leave `zabbixserver/data` alone and Docker will create the necessary subdirs on your Docker host on container start.
If you do want all subdirs feel free to go like this:
```
cd '/opt/docker-data/zabbixserver-'"${CONTEXT}"'/zabbixserver/data'
mkdir -p {'./usr/lib/zabbix/'{'alert','external'}'scripts','./var/lib/zabbix/'{'dbscripts','enc','export','mibs','modules','snmptraps','ssh_keys','ssl/'{'certs','keys','ssl_ca'}}}
```
This will create the entire directory tree underneath `zabbixserver/data`:
```
data/
├── usr
│ └── lib
│ └── zabbix
│ ├── alertscripts
│ └── externalscripts
└── var
└── lib
└── zabbix
├── dbscripts
├── enc
├── export
├── mibs
├── modules
├── snmptraps
├── ssh_keys
└── ssl
├── certs
├── keys
└── ssl_ca
```
### zabbixwebnginx (Nginx web server)
First things first, directory `zabbixwebnginx/config/modules` is empty and due to `create_host_path: true` will be created anyway if you don't create it yourself so no worries there. In `zabbixwebnginx/config/cert` - as the name suggests - you'll place frontend SSL cert files. That's the domain certificate you want to get served when visiting Zabbix frontend with a web browser. In line with our earlier examples this might be a cert for example for `zabbix.example.com`.
Note that the file names here look relatively normal as opposed to `.ZBX_SERVER_CERT_FILE` and `.ZBX_DB_CERT_FILE` from before. We will be bind-mounting the entire `cert` directory like so:
```
- type: bind
source: /opt/docker-data/zabbixserver-${CONTEXT}/zabbixwebnginx/config/cert
target: /etc/ssl/nginx
read_only: true
bind:
create_host_path: true
```
The `cert` dir ends up getting bind-mounted into `/etc/ssl/nginx` inside the container. Since Zabbix uses a standard Nginx setup we stick to the Nginx way of calling a default cert and key file. Store your full certificate chain as `ssl.crt` and the corresponding unencrypted key as `ssl.key`. Make sure to also save a `dhparam.pem` parameters file. You can get one such file the quick and dirty way for example from Mozilla at [https://ssl-config.mozilla.org/ffdhe2048.txt](https://ssl-config.mozilla.org/ffdhe2048.txt) - just save it as `dhparam.pem` if you're so inclined. You can alternatively render a file yourself. Assuming the `parallel` binary exists on your machine you can follow [unix.stackexchange.com/a/749156](https://unix.stackexchange.com/a/749156) like so:
```
seq 10000 | parallel -N0 --halt now,success=1 openssl dhparam -out dhparam.pem 4096
```
This starts as many parallel `openssl dhparam` processes as you have CPU cores (assuming you have at most 10,000 cores). Processes essentially race each other which typically lowers waiting time for a finished parameters file by an order of magnitude since you only need one random process to finish. On a moderately modern desktop CPU with four cores this will take about 30 seconds.
When done head back up to [How to run](#how-to-run).
# Development
## Conventional commits
This project uses [Conventional Commits](https://www.conventionalcommits.org/) for its commit messages.
### Commit types
Commit _types_ besides `fix` and `feat` are:
- `refactor`: Keeping functionality while streamlining or otherwise improving function flow
- `docs`: Documentation for project or components
### Commit scopes
The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
- `zabbixserver`: A change to how the `zabbixserver` service component works
- `build`: Build-related changes such as `Dockerfile` fixes and features.
- `mount`: Volume or bind mount-related changes.
- `net`: Networking, IP addressing, routing changes
- `meta`: Affects the project's repo layout, file names etc.