Docker Deployment

Step 1: Create Docker Image

The Docker image for the Balena Prometheus Exporter is built from a Python base image. The Dockerfile is structured as follows:

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" ]

Step 2: Build the Docker Image

To build the Docker image, run the following command in the terminal:

$ docker build -t balena-exporter .

Step 3: Run the Docker Container

When running the container, you need to provide the BALENA_TOKEN environment variable. This token is used to authenticate access to the Balena API.

$ docker run -d \
--name balena-exporter \
-p 8000:8000 \
-e BALENA_TOKEN=YOUR_BRELA_TOKEN_HERE \
balena-exporter

Step 4: Optional Configuration

You can also set the optional CRAWL_INTERVAL environment variable to adjust how frequently the exporter pulls metrics. The default is set to 60 seconds:

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

Step 5: Verification

After the container is running, verify that the exporter is functioning correctly by sending a request to the exposed port:

$ curl http://localhost:8000

You should see the Prometheus formatted metrics.

Code Insights

Main Application Logic

The main logic of the exporter is encapsulated in main.py. The functionality begins by checking for the BALENA_TOKEN:

def main():
    if not BALENA_TOKEN:
        print("Please set the BALENA_TOKEN environment variable")
        sys.exit(1)

    start_http_server(8000)
    REGISTRY.register(BalenaCollector())
    while True:
        time.sleep(int(CRAWL_INTERVAL))

main()

Collecting Metrics

The BalenaCollector class handles the process of fetching metrics from the Balena API. For example, the method to fetch fleet metrics is implemented as:

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 the Exporter

Unit tests for the exporter can be structured using unittest. Below is a simple test to check the fleet collection:

def test_get_balena_fleets(self):
    with mock.patch("main.requests.get") as mock_get:
        mock_get.return_value.ok = True
        mock_get.return_value.json.return_value = {"d": [{"id": "fleet1"}, {"id": "fleet2"}]}
        result = list(self.collector.get_balena_fleets())
        expected = ["fleet1", "fleet2"]
        self.assertEqual(result, expected)

Final Notes

Ensure to replace YOUR_BRELA_TOKEN_HERE with the actual token for production deployment. It is crucial to monitor the deployed service to ensure reliability and performance. Consider using a dedicated monitoring stack for proactive alerting and insights.