Links

Structuring Applications with Multiple Services

Best practices guide on Hhw to structure larger applications consisting of multiple services efficiently and optimize storage requirements
Some applications are composed of multiple micro-services, as is the case in our Photo Gallery example application. Docker promotes the idea of running each micro-service that comprises an application as a separate docker container. This is generally no issue for servers or even desktop computers, but special consideration is required on embedded systems such as Skynode where resources are limited.
Below are a few tips on how to efficiently structure applications consisting of multiple containers, which are described in more detail down below:
  1. 1.
    Package your services that are docker images into a single application. This ensures that the user only needs to handle a single .auterionos file.
  2. 2.
    Write a common base Dockerfile for a shared environment used by your services. This ensures that the resulting .auterionos file, as well as the storage space requirements after installation are as low as possible.

Packaging multiple micro-services into one application

While your app can be composed of multiple docker containers, we advise that they are all packaged into a single Application. The user should only need to handle a single .auterionos file when installing or updating your application. This brings a few benefits:
  • You can specify the launch order of the individual docker containers, in case some services rely on others
  • You can provide all your services at the exact version required for a specific release of the application
  • There is no risk of your application being installed incompletely
  • There is no risk of your application being updated only partially, resulting in a mismatch of the services inside

Makefile

Build and tag the required docker images / micro-services in the Makefile:
build-camera-control:
docker build --platform=arm64 ./src/camera-control -t camera-control:1.0.0
build-gallery-service:
docker build --platform=arm64 ./src/gallery-service -t gallery-service:1.0.0
In the Makefile's target rule for building your application, you need to export all services as a single image, for example:
build: build-camera-control build-gallery-service
docker save camera-control:1.0.0 gallery-service:1.0.0 | gzip > gallery.image

App.yml

Last but not least include all services the app.yml file:
version: '3.7'
services:
camera-control:
image: camera-control:#VERSION#
container_name: camera-control
gallery-service:
image: gallery-service:#VERSION#
container_name: gallery-service
Take a look at the Photo Gallery example application to see this in action.

Leveraging Docker layers to save resources

Docker builds its images using layers. Images that share the same base image can therefore reuse its layers, thus saving storage space. More details can be found in Docker's official documentation.
In the context of Skynode and AuterionOS, identifying, breaking out and reusing base images in your application brings the following advantages:
  • Your app uses less space on Skynode, because the base needs to be stored only once in Docker
  • Smaller download size of your app, which saves time and bandwidth for downloading files, and speeds up the installation process. This benefit is only achieved when all micro-services are packaged together.
The example below uses a custom base image. The same principle can be applied when most of your services inherit from an existing public image such as "arm64v8/python:buster" for example.

Example folder structure

Example folder structure using a base Dockerfile and services:
.
├── app.yml
├── Makefile
└── src
├── Dockerfile.base
├── service-1
│   ├── app.py
│   └── Dockerfile
└── service-2
├── app.py
└── Dockerfile

Dockerfiles

Create Dockerfile.base that defines a common environment for your micro-service's images:
Dockerfile.base
FROM arm64v8/ubuntu:focal
ARG DEBIAN_FRONTEND=noninteractive
# Install common dependencies for the micro-services
RUN apt-get update \
...
Create one Dockerfile for each of your services and reuse the base image:
src/service-[1,2]/Dockerfile
FROM my-arm64-base
ARG DEBIAN_FRONTEND=noninteractive
# Install dependencies specific to this service
RUN apt-get update \
...

Makefile

In the Makefile, build the base image as well as your services:
build-base:
docker build --platform=arm64 ./src -f src/Dockerfile.base -t my-arm64-base
build-service-1:
docker build --platform=arm64 ./src/service-1 -t service-1:1.0.0
build-service-2:
docker build --platform=arm64 ./src/service-2 -t service-2:1.0.0
Combine the use of base images with the method for packaging multiple micro-services into one application to achieve the most compact .auterionos files.