Monitoring Setup

To monitor the Balena application in production, follow these steps to set up the Screenly/Balena-Prometheus-Exporter.

Docker Configuration

Firstly, a Docker container is required that runs the exporter. The following Dockerfile can be used for building the container:

FROM python:3.10-alpine

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .

USER nobody

CMD [ "python", "./main.py" ]

This Dockerfile sets up the Python environment, installs required dependencies, and specifies the command to run the exporter.

Building and Running the Docker Image

Create the Docker image and run it with the necessary environment variable configuration. Use the following commands to build and run the container:

$ docker build -t balena-exporter .
$ docker run -d \
--name balena-exporter \
-p 8000:8000 \
-e BALENA_TOKEN=your_balena_token_here \
balena-exporter

After running the container, you can use curl to confirm it’s operating correctly:

curl http://localhost:8000/metrics

Configuration Parameters

The exporter supports an optional environment variable CRAWL_INTERVAL, which defaults to 60 seconds. This variable can be set when running the Docker container:

$ docker run -d \
--name balena-exporter \
-p 8000:8000 \
-e BALENA_TOKEN=your_balena_token_here \
-e CRAWL_INTERVAL=30 \
balena-exporter

Data Collection and Metrics Exposure

The main.py file contains the core functionality for collecting metrics from the Balena API and exposing them in a Prometheus-compatible format.

Key Classes and Methods

  1. BalenaCollector Class: Handles the data collection from the Balena API.
class BalenaCollector(object):
    def __init__(self):
        pass

    def get_balena_fleets(self):
        ...
    
    def get_fleet_metrics(self, fleet_id):
        ...
    
    def collect(self):
        ...
  1. Data Collection

The collect method is responsible for gathering metrics from different fleets. It utilizes get_balena_fleets to retrieve all accessible fleets and get_fleet_metrics to fetch metrics for each fleet:

def collect(self):
    gauge = GaugeMetricFamily(
        "balena_devices_online", "Devices by status", labels=["fleet_name"]
    )

    for fleet_id in self.get_balena_fleets():
        fleet_name, device_online_count = self.get_fleet_metrics(str(fleet_id))
        gauge.add_metric([fleet_name], float(device_online_count))

    return [gauge]
  1. API Calls

The get_fleet_metrics method performs API calls to retrieve the count of online devices for each fleet.

def get_fleet_metrics(self, fleet_id):
    headers = {
        "Authorization": f"Bearer {BALENA_TOKEN}",
        "Content-Type": "application/json",
    }

    response = requests.get(
        f"https://api.balena-cloud.com/v6/application({fleet_id})?$expand=owns__device/$count($filter=is_online eq true)",
        headers=headers,
    )

    if not response.ok:
        print("Error: {}".format(response.text))
        sys.exit(1)

    device_online_count = response.json()["d"][0]["owns__device"]
    fleet_name = response.json()["d"][0]["app_name"]

    return fleet_name, device_online_count

Testing Metrics Collection

Unit tests ensure the functionality of the exporter. The tests/test_exporter.py file contains unit tests for verifying metrics collection:

def test_get_fleet_metrics(self):
    with mock.patch("main.requests.get") as mock_get:
        mock_get.return_value.ok = True
        mock_get.return_value.json.return_value = {"d": [{"owns__device": 3, "app_name": "test_fleet"}]}
        result = self.collector.get_fleet_metrics("fleet1")
        expected = ("test_fleet", 3)
        self.assertEqual(result, expected)

This test mocks the API response and checks if the metric collection is working as expected.

By following the outlined steps, the Balena application can be effectively monitored in a production environment, utilizing the capabilities of the Screenly/Balena-Prometheus-Exporter for real-time metrics collection and monitoring.

Sources:

  • Dockerfile
  • README.md
  • requirements.txt
  • main.py
  • tests/test_exporter.py