Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Getting started with multicontainer on balena

This example will get you up and running quickly with a multicontainer setup on balena. The application creates a plot of your device's CPU load average and memory usage at the device's public URL, which is piped over using websockets. The system is composed of a simple static site server, a websocket server, and a proxy. These 3 components are defined in the [docker-compose.yml](docker-compose.yml) as services and are only given as much privilege as is needed.
This example will get you up and running quickly with a multicontainer setup on balena. The application creates a simple chart of your device's CPU usage at the device's public URL (and also shows the 1-minute load average). The system is composed of a Python data API, a Streamlit frontend, and a proxy. These 3 components are defined in the [docker-compose.yml](docker-compose.yml) as services and are only given as much privilege as is needed.

Because `depends_on` is not supported by balena, startup ordering is handled with shared `flock` lock files on a named volume. Each container has a small bash startup script that waits for required dependency locks before starting.

To get this project up and running, you'll need to [sign up](https://dashboard.balena-cloud.com/signup) for a balena account, create a microservices or starter application, and provision a device (device specific instructions can be found in our [getting started guide](https://balena.io/docs/getting-started).

Expand All @@ -10,12 +12,8 @@ Once you are set up, clone this repo locally:
```
$ git clone git@github.com:balena-io-projects/multicontainer-getting-started.git
```
Copy the command in the upper-right corner of your application dashboard to add your remote repository:
```
$ git remote add balena username@git.balena-cloud.com:username/myapp.git
```
Finally, push the code to the newly added remote:
From the project directory, push the code to your balena application with the balena CLI:
```
$ git push balena master
$ balena push <org-name>/<app-name>
```
It should take a few minutes for your project to build. While you wait, you can enable device URLs, so you can see the server outside of our local network. This option can be found in the *Actions* drop down in your device dashboard.
16 changes: 6 additions & 10 deletions data/Dockerfile.template
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
FROM balenalib/%%BALENA_ARCH%%-debian-node:14-bullseye-run
FROM python:3.11-alpine

# Defines our working directory in container
WORKDIR /usr/src/app

# Copies the package.json first for better cache on later pushes
COPY package.json package.json
RUN apk add --no-cache bash util-linux

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

# This install npm dependencies on the resin.io build server,
# making sure to clean up the artifacts it creates in order to reduce the image size.
RUN JOBS=MAX npm install --production
# This will copy all files in our root to the working directory in the container
COPY . ./

# server.js will run when container starts up on the device
CMD ["npm", "start"]
CMD ["bash", "./start.sh"]
58 changes: 58 additions & 0 deletions data/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import time
from flask import Flask, jsonify

app = Flask(__name__)


def _read_proc_cpu_totals():
with open("/proc/stat", "r", encoding="utf-8") as proc_stat:
cpu_line = proc_stat.readline().strip()

parts = cpu_line.split()
counters = [int(value) for value in parts[1:]]

idle = counters[3]
iowait = counters[4] if len(counters) > 4 else 0
idle_all = idle + iowait
total = sum(counters)
return total, idle_all


def _cpu_percent_once(interval):
previous_total, previous_idle = _read_proc_cpu_totals()
time.sleep(interval)
current_total, current_idle = _read_proc_cpu_totals()

total_delta = current_total - previous_total
idle_delta = current_idle - previous_idle

if total_delta <= 0:
return 0.0

busy_delta = total_delta - idle_delta
return (busy_delta / total_delta) * 100.0


def _cpu_percent(interval=0.5, samples=5):
if samples <= 0:
return 0.0

sample_interval = interval / samples
sample_values = [_cpu_percent_once(sample_interval) for _ in range(samples)]
return sum(sample_values) / len(sample_values)


def _load_1m():
with open("/proc/loadavg", "r", encoding="utf-8") as proc_loadavg:
return float(proc_loadavg.readline().split()[0])


@app.get("/cpu")
def cpu_usage():
usage_percent = _cpu_percent(interval=0.5, samples=5)
one_minute_load = _load_1m()
return jsonify({"cpu_percent": usage_percent, "load_1m": one_minute_load})


if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
57 changes: 0 additions & 57 deletions data/index.js

This file was deleted.

Loading