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.
266 lines
13 KiB
Markdown
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.
|