Grafana – James Batchelor https://james-batchelor.com Useful I.T & VoIP Ramblings Mon, 29 Dec 2025 18:07:40 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.5 https://james-batchelor.com/wp-content/uploads/2025/05/cropped-cropped-logo-jb-202505-32x32.png Grafana – James Batchelor https://james-batchelor.com 32 32 Grafana Static Displays https://james-batchelor.com/index.php/2025/12/29/grafana-static-displays/ Mon, 29 Dec 2025 18:07:40 +0000 https://james-batchelor.com/?p=1080 Continue reading "Grafana Static Displays"]]> In the last post I migrated a Grafana database to a new server and took the opportunity to upgrade the Grafana version in the process.

This created a new issue, since Grafana’s supported browser list moves with the times, my two “NOC” screens consisting each of a Raspberry Pi 3A running Raspbian Buster and Hyperpixel 4.0 screens no longer worked. Instead, the outdated Chromium browsers now displayed the “If you’re seeing this Grafana has failed to load its application files” page when visiting the Grafana web interface.

Bringing the OS on the Pi’s up to date is the logical resolution, however, there are a couple of issues with this:

  • One of the Hyperpixel 4.0 screens is an early revision model, meaning the official and legacy drivers do not work.
  • Even with working screen drivers, compatible browsers will not load on the 512MB RAM available to the Pi 3A.

Not wanting to obsolete a pair of Pi 3A’s with a now unique form factor, instead a workaround was found to continue displaying Grafana dashboards on this older hardware. The solution even gave a slight benefit over the original URL…

To achieve this, Grafana has released grafana-image-renderer, essentially a binary with a self-contained and headless Chromium instance that will load a dashboard and capture an image of the output.

Until recently, this was available as a Grafana plugin, however this plugin has since been depreciated in favour of it being a standalone application or docker container. A lot of online documentation still refers to it in its plugin form and so is now outdated.

The current iteration of grafana-image-renderer recommends using it within a docker container due to the potential resource usage it can generate. However, for this guide, only the standalone binary will be used as its only generating two images, and I find resource usage is at a minimum.

Installation

Firstly, grab the binary and store it in a easy but sensible location:

cd /opt
wget https://github.com/grafana/grafana-image-renderer/releases/download/v5.0.6/grafana-image-renderer-linux-amd64

Then make the file executable:

chmod +x grafana-image-renderer-linux-amd64

To have it run as a service, create a service file for it:

nano /etc/systemd/system/grafana-image-renderer.service

And populate the file with the following:

[Unit]
Description=Grafana Image Renderer service
After=network.target

[Service]
ExecStart=/opt/grafana-image-renderer-linux-amd64 server --
WorkingDirectory=/opt/
Restart=on-failure
User=root
Environment=GRAFANA_RENDERER_PLUGIN_STARTUP_TIMEOUT=30s 
[Install]
WantedBy=multi-user.target

Reload the service daemon, start the new service, and check it is running:

systemctl daemon-reload
systemctl start grafana-image-renderer.service
systemctl status grafana-image-renderer.service

You can check If the service is running by visiting the web page of the server on port 8081:

Authentication

A default Grafana server install requires login before being able to see any dashboards, this extends to the image renderer. As we can’t login to the web interface interactively within the service, a service account needs to be created.

In the main Grafana web interface, login and navigate to Home > Administration > Users and access > Service accounts, then click “Add service account”.

Give it a display name and set the role to viewer:

Now in the newly created service account, click “Add service token”, then on the popup dialog, click “Generate Token”.

A token with a “glsa_” prefix is displayed, make a copy of this as it will not be available to view after closing this dialog box:

This will be used as authentication when generating our dashboard images…

Generating Images

With the renderer installed, and authentication available, images can now be generated.

Images are generated by requesting a URL from the service with the dashboard within the variables, the syntax is:

http://{grafana-image-renderer}:8081/render?encoding=png&url=http://{grafana-dashboard}?{options}

Where:
Grafana-image-renderer – The IP of the machine the renderer is running on.
Grafana-dashboard – The URL of the dashboard you’d like to capture.
Options – Options to manipulate the output/how the dashboard is displayed.

However, the authentication header is needed to successfully access the dashboard. For this reason its easier to use a curl command to set the headers and grab the image

For example, if the renderer is running on the same machine as the main Grafana, a dashboard can be generated by executing:

curl -H "X-Auth-Token: {glsa_token}" "http://localhost:8081/render?encoding=png&url=http://localhost:3000/d/{id}/{dashboad}?kiosk&width=800&height=600" -o image.png

The options used here is kiosk; to remove the menus from the output, and height and width to match the intended displays.

Automation:

One minute updates are fine for this use case, so the easiest way automate this is via cron. Using crontab -e, add the following line:

/usr/bin/curl -H "X-Auth-Token: {glsa_token}" "http://localhost:8081/render?encoding=png&url=http://localhost:3000/d/{id}/{dashboard}?kiosk&width=800&height=600" -o /var/www/html/dash1.png

This line generates the dashboard every minute and stores it on the default web server directory, allowing it to be viewed on the network via an installed web server.

But this will only create a static image, to allow the image to update on a display, a simple webpage can be created and stored on the webserver for easier access and reference.

Create a new webpage:

nano /var/www/html/dashboard.html

And paste the following:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Grafana Dashboard</title>
  <style>
    body {
      margin: 0;
      background-color: black;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
    }
    img {
      max-width: 100%;
      max-height: 100%;
      object-fit: contain;
    }
  </style>
</head>
<body>
  <img id="liveImage" src="dash1.png" alt="Live Image">
  <script>
    const img = document.getElementById("liveImage");
    setInterval(() => {
      const timestamp = new Date().getTime(); // avoid browser cache
      img.src = "dash1.png?t=" + timestamp;
    }, 60000); // 60 seconds
  </script>
</body>
</html>

When visited, this webpage will update the same image (dash1.png) every 60 seconds to ensure the browser updates with the latest available image instead of caching.

Displays

Viewing this on the display is as simple as visiting the above webpage. As the html is served from the Grafana server and where the auto refresh is taken care of, as long as the display is able to load the page, all is taken care of at the Grafana server.

Summary

Since all that is required from a display is being able to load and render a static image, this allows the oldest and slowest machines to be able to display a Grafana dashboard, negating the need to keep the displays up to date with Grafana’s evolving browser support.

I wish I learnt of this sooner, as it gives more standardised control of the layout of a dashboard. The HyperPixel displays are quite high resolution for their size, and loading a dashboard natively often lead to frustrations with the layout of panels not matching that displyed on a laptop/desktop screen. Presumably due to scaling. Using the image render allows you to test and fix the layout before deploying to a screen, and with a higher panel density too.

Another benefit is that most processing is taken away from the Pi 3A’s, as it is now only responsible for displaying a simple webpage with a single image, rather than loading the full Grafana UI. The Pis have been running on this new method for over a month without any lockups, which was previously seen on a weekly basis.

]]>
Migrate Zabbix/Grafana to new server https://james-batchelor.com/index.php/2025/11/09/migrate-zabbix-grafana-to-new-server/ Sun, 09 Nov 2025 15:38:07 +0000 https://james-batchelor.com/?p=1067 Continue reading "Migrate Zabbix/Grafana to new server"]]> I’m in the process of migrating hypervisors and the time has come to move my Zabbix instance that monitors my network, and Grafana that I use for dashboard displays.

Instead of a backup and restore of the VM, it seems the right time to migrate Zabbix and Grafana from an aging RHEL 8 instance to a new VM running a fresh copy of Debian 13. At the same time upgrading the applications to their latest versions…

Zabbix

New Server: Preparation

Begin by installing a LAMP stack on the new server:

apt install mariadb-server apache2 php php-mysql php-bcmath php-mbstring php-xml php-ldap php-json php-gd php-zip curl gnupg lsb-release

Then setup the basic configuration of MariaDB:

mysql -u root

ALTER USER 'root'@'localhost' IDENTIFIED BY 'password';
DELETE FROM mysql.user WHERE User='';
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;

To give it somewhere to migrate to, install a fresh, blank copy of Zabbix on the new server. The latest version can be used and is available on the Zabbix website, as the application will detect and auto upgrade your exisiting data (So long as a direct upgrade path between versions is supported).

As an example with version 7.4, first add the Zabbix repositories:

wget https://repo.zabbix.com/zabbix/7.4/release/debian/pool/main/z/zabbix-release/zabbix-release_latest_7.4+debian13_all.deb
dpkg -i zabbix-release_latest_7.4+debian13_all.deb
apt update

And install Zabbix:

apt install zabbix-server-mysql zabbix-frontend-php zabbix-apache-conf zabbix-sql-scripts zabbix-agent2

If following the install intructions from the Zabbix website, stop before starting the service. Instead, we’ll first pull and import the database from the old server…

Old Server: Export database

On the old/source server, temporarily stop Zabbix server:

systemctl stop zabbix-server

Now dump the database to a file. As this old server only ran Zabbix, we’ll dump the whole MySQL instance to preserve users and settings which will make migration easier.

If yours is more complex, dump just the Zabbix database and recreate the users manually on the new server.

Export the whole MySQL instance:

mysqldump -u root -p --all-databases > mysql.sql

When it completes, restart Zabbix on the old server, just incase you’d need another go at it later.

systemctl start zabbix-server

New Server: Import

Back on the new server, pull the exported database locally:

scp root@{old-server-ip}:/root/mysql.sql .

Also grab the configuration files of the old server, it’ll come in handy later:

scp -r root@{old-server-ip}:/etc/zabbix/* .

Import the copied database:

mysql -u root -p < mysql.sql

If importing an “–all-databases” export, the newly added users need to be commited before they can be used.

Note that after executing the below, the root credentials will be overwritten by those of the old server:

mysql -u root -p
FLUSH PRIVILEGES;

Next is the configuration file. I would recommend comparing the old and new /etc/zabbix/zabbix_server.conf files, and updating the new .conf to match the old rather than just copying the file.

nano /etc/zabbix/zabbix_server.conf

Now, its time to start Zabbix:

systemctl enable --now zabbix-server

Checking for any errors can be made by monitoring the log file:

tail -n 100 /var/log/zabbix/zabbix_server.log

Soon after starting the service you’ll notice a database upgrade completing in the file, this is Zabbix automatically upgrading your old data to the newly installed version, neat.

Visit the Zabbix web interface on the new server:

http://{new-server-ip}/zabbix/

You’ll be greeted with the setup wizard, follow the steps on screen to complete.

Don’t worry, Zabbix is running and logging now data to your existing Zabbix hosts, this wizard simply creates the /etc/zabbix/web/zabbix.conf.php file.

Once the wizard is completed, you’ll return to a familiar yet updated interface via the new server.

Zabbix Proxy

If the migration involves a major version change and you’re using proxies, the dashboard will quickly flag a problem with the proxy version being incompatible.

On the proxy, you’ll need to update the application by setting the associated version in the repositories and installing the updated version.

Using a Rocky 8 install as an example, add the updated repo:

rpm -Uvh https://repo.zabbix.com/zabbix/7.4/release/rocky/8/noarch/zabbix-release-latest-7.4.el8.noarch.rpm
dnf clean all

Then install the updated version:

Install
dnf install zabbix-proxy-mysql

Restart the service, and it will be up to date:

systemctl restart zabbix-proxy

Grafana

Migrating and updating Grafana is much the same process as Zabbix, if a little easier.

Install Grafana, on Debian 13 it is available from the main repository:

apt install grafana

Pull the required files direct from the old server to the new:

scp root@{old-server-ip}:/etc/grafana/grafana.ini .
scp root@{old-server-ip}:/var/lib/grafana/grafana.db .

Overwrite the installed files with the ones pulled from the old server, and set the correct permissions:

cp grafana.ini /etc/grafana/grafana.ini
chown root:grafana /etc/grafana/grafana.ini
cp grafana.db /var/lib/grafana/grafana.db
chown grafana:grafana /var/lib/grafana/grafana.db

Before starting, install any plugins used on the old server, for example:

grafana-cli plugins install alexanderzobnin-zabbix-app
grafana-cli plugins install grafana-clock-panel

Now Grafana can be started:

systemctl enable --now grafana-server

And visit the web interface:

http://{new-server-ip}:3000/

Where you can pick up at where you left on the old server.

]]>