Experiments with Docker

abstract: My experiments with Docker helped me to understand Docker technology a bit. I try to understand the principles and to see where it can be used -- and where not.

Experiments with Docker

Understanding Docker

The idea of Docker is simple: run on top of the Linux OS installed, but use only the (Linux) kernel, which can be installed on any hardware and is already present. Put the changes in the files on top of the existing filesystem using a layered, union filesystem1 on top of the native (Linux) filesystem.

A Dockerfile is essentially a text file with the instruction on how to install the application, using as a start point a (simplified) Linux distribution. The result is a downloadable Docker image, which can be expanded to a container, which is ready to run and only dependent on the kernel of the host. It is thus the same whenever and wherever installed -- in principle (for the limitations see later).

docker build produces an image or a container. A docker container is the image populated and ready to run. The process of building the container is the same as when installing the required packages in a distribution (e.g. using apt-get install).

The docker container is isolated from the underlying OS and has its own namespaces for, among other things 2

The command docker run establishes the mappings from the host to the objects in the container. It produces a volume which can execute.

First Experiment: hello world

Installing Docker on my main computer was easy (following) and the test with running hello-world successful. For a raspi4 use the curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh

Now I was searching for a productive example: something I have to install often, but most real world applications appeared to complicated:

- syncthing
- mpd (a music daemon) on an old Raspberry

The processing power of the (old 512MB) Raspberry is o.k. for running mpd, but development is painfully slow. I will use it with buildx which allows preparing a container to run on another hardware (e.g. develop on a amd64 PC and run on a armv6l Raspi).

syncthing

I tried to reconstruct a syncthing client on an odroid xu4. Successful, till I wanted to use the already existing data on a hard disk (more later).

mpd

Raspberry (the first model with 512MB memory) can be used as mpd server to have music playing controlled by a smartphone. Installing mpd is difficult and time consuming, with complex dependencies and issues with file protection, thus I felt it would be a good realistic project. Unfortunately, I proofed to be too complex for my understanding of Docker and the old Raspi too limited.

Install

I installed Raspbian (a Debian derived distro for all Raspberry versions), which takes a moment for apt-get update && apt-get upgrade. I did even a rpi-upgrade which was probably not a good idea, because stable firmwares are installed with apt-get upgrade; I have not kernel version 5.4.51.

The first raspi (of which I have a few laying around) use the armv6l instruction set (shown with uname -a) and I found docker images for debian and alpine. Editing in vscode and then scp the files to build on the raspi worked smooth, but slow.

uname -a
Linux pi107 5.4.51+ #1333 Mon Aug 10 16:38:02 BST 2020 armv6l GNU/Linux

I gave up after discovering too many issues with the installation of mpd and alsa; installing without docker was difficult enough!

using buildx

I started anew and got mpd quickly working for my main machine. This was intended as a test to make sure I have a working example. I worked except for pulseaudio (which refuses connection); this would not be a problem with raspi (using alsa).

Now trying buildx, which should allow me to build the image on the fast machine and then move it to the slow raspi.

Follow the instructions

It needs enabling experimental with a file ~/.docker/config.json with

cat config.json 
{
      "experimental" : "enabled"
}

and install QEMU (with apt-get install qemu-user-static)

issue with installing qemu and enabling flag F

to check for flag F cat /proc/sys/fs/binfmt_misc/qemu-aarch64

to fix (on stretch) after each reboot docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

to check if qemu is properly installed docker run --rm arm64v8/alpine ls

Then followed the instructions and I was able to produce images for 3 architectures with

docker buildx build -t andrewufrank/buildx-test:latest \
      --platform linux/amd64,linux/arm64,linux/ppc64le --push .

I would have been more interested in building for the raspi:

docker buildx build -t andrewufrank/buildx-test:latest --platform linux/armel,linux/arm64 --push .

Test the image:

docker buildx imagetools inspect andrewufrank/buildx-test:latest

Manifests: 
Name:      docker.io/andrewufrank/buildx-test:latest@sha256:863e046300866c9312f8366cc39bc66f4f6adbff505a61e04a84bd3587d1cffb
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform:  linux/arm/v6
            
Name:      docker.io/andrewufrank/buildx-test:latest@sha256:f751450a427aac6a5a079236e1873be312ed5f506fa50a2ab3536a994fbe217c
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform:  linux/arm64

Running on the raspi with

docker run --rm andrewufrank/buildx-test:latest 

gives the expected result running on armv6l. The correct image is selected automatically out of the multi-arch build!

Install

to install on raspi

example

install on debian

buildx with mpd (for test)

Is it possible to build the Dockerfile I used to build on london - changed for output with alsa?

The build takes minutes initially, but redoing it is fast if only changes in the later parts of the Dockerfile. The download and installation on the raspi is slow, on other machines reasonably fast.

The immediate goal is achieved but the resulting Docker image does not run (some issues with using the port 6600).

Next

What is a good project?

The CalDAV server for contacts and calendar running Raicale disappeared a while ago. It could be an example to use docker? There is a docker image. The dockerfile seems straight-forward.

Run with (minimal) interactive. Then open Localhost:5232

docker run -i --name radicale \
    -p 5232:5232 \
    tomsquest/docker-radicale

Run for production (plug all security holes) docker run -d --name radicale \ -p 127.0.0.1:5232:5232 \ --read-only \ --init \
--security-opt="no-new-privileges:true" \ --cap-drop ALL \ --cap-add CHOWN \ --cap-add SETUID \ --cap-add SETGID \ --cap-add KILL \ --pids-limit 50 \ --memory 256M \ --health-cmd="curl --fail http://localhost:5232 || exit 1" \ --health-interval=30s \ --health-retries=3 \ -v ~/radicale/data:/data \ -v ~/radicale/config:/config:ro \ tomsquest/docker-radicale

Needs now to understand data storage on Docker.

config radicale

manual

Volumes in Docker

I follow the tutorial

Mit docker run -it -v /home/frank/Workspace8/dockerWork/mpdMusic:/music debian /bin/bash

wird ein directory eingebunden (alle Pfade absolut!).

Mit touch /music/emptyFile kann im laufenden Container ein File erzeugt werden, der auch von ausserhalb des containers sichtbar ist.

Mit diesem Trick kann z.B. ein config file während der Entwicklung eingebunden werden und von ausserhalb veraendert werden so dass er be.

Um ein export Directory in ein volume einzubinden genuegt:

docker volume create --name nfsMusicVolume --driver local \
    --opt type=nfs \
    --opt o=addr=10.0.0.14,rw,noatime,rsize=8192,wsize=8192,tcp,timeo=14 \
    --opt device=:/home/frank/Music

was dann mit

docker run -it -v nfsMusicVolume:/MusicDir \
    --user 1010:1010 debian /bin/bash

verwendet wird. Beachte, dass bereits die richtigen uid und gid gesetzt sind. Allgemein ginge wohl auch --user $(id -u):$(id -g). Schwieriger wird es in composer (mit env vorher setzten; siehe)

other helpful

Compose

Das Beispiel mit einer Phyton App und einer Redis Datenbank scheint mir ein guter start.

Braucht Instalation mit For alpine, the following dependency packages are needed: py-pip, python-dev, libffi-dev, openssl-dev, gcc, libc-dev, and make.

sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Das Beispiel funktioniert nach Anleitung sofort; es braucht eine simple App und darum einen Dockerfile. Der Rest ist ein compose yaml file, ein file mit requirements. Build und run ist impliziert mit docker-compose up.

Sicherheit etc.

Registry fuer Images und Container

see

Sicherheit gegen Angriffe

Die Docker Container sind gegeneinander abgeschottet (siehe)

Update packages

Der Zustand eines Containers ist die Version des Programmes, das bei der Erstellung eingebunden wurde. Wenn diese aktualisiert wird (was normalerweise mit apt-get upgrade erledigt wird) muss beim Docker das Image neu gebaut werden und dann ein neuer Container gestartet werden.

Diskussion im Netz noch nicht abgeschlossen...: [https://www.laub-home.de/wiki/Watchtower_-_Docker_Container_automatisch_aktualisieren]

Radicale mit meinen Daten

Named Volume

Das bringe ich nicht fertig; mein mount

hilft vielleicht

NGINX

tutorial

volumes and ngingx

start

compose mit roycec

Ein Vorschlag fuer radicale und nginx in [einem compose] (https://github.com/roycec/docker-radicale/blob/master/docker-compose.yaml):

Versuch genau aus dem Buechlein vorzugehen. Hilft das Fehler zu vermeiden? Nein, denn:

Der Dockerfile von roycec baut auf apline:latest auf, was inzwischen nicht mehr gleich ist und darum funktioniert es jetzt nicht mehr. Das zeigt ein grundsaetzliches Problem von Docker: images sind stabil und immer gleich aber Dockerfiles nicht. Ein build kann nur funktionieren auf dem gleichen Image aufgebaut wird - und dann sind die neusten (Sicherheits-)Updates nicht enthalten. Oder es wird mit latest das beste Image verlangt, dann kann der build misgluecken.

Am besten ist es wohl den Radicale server zu benutzen, den ich bereits gebaut habe und der vielleicht funktioniert. Dann den Code von roycec zu benutzen, um compose zu nutzen oder noch besser den reverse proxy srever. der die verbindung zwischen den services und dem proxy-server automatisiert.

Nginx Proxy server

Ich will den reverse proxy srever benutzen, mit einer dokerization von seitgeist. Additionale information using Nginx and radicale from 2015

Anleitung zum build des images

Build mit docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro -t frontproxy jwilder/nginx-proxy das image ist: 5e2a47e8afa6264e12ae1f82cd0b502c8edf847b698c67c7ae58588d592ec481

zuerst hatte ich eine fehlermeldung, dass port 80 bereist alloziert ist. docker ps zeigt welches image noch aktiv ist und gestoppt werden muss.

anlage des containers frontproxy

Das Directory, in dem der proxy server operiert (d.h. nginx). Das docker-image wird gestartet ohne ports:

docker run -d -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy

dann cd /home/myuser/frontproxy docker-compose up -d

Dabei werden die ports 80 und 443 reserviert.

scheint zu funktionieren, zumindest auf 443 gibt es eine fehlermeldung (kein cert)

Certificate

Erweiterung des comppose des frontproxy:

version: '2'

services:
frontproxy:
    restart: always
    image: jwilder/nginx-proxy
    labels:
    - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
    ports:
    - "80:80"
    - "443:443"
    volumes:
    - "/var/run/docker.sock:/tmp/docker.sock:ro"
    - "certs-volume:/etc/nginx/certs:ro"
    - "/etc/nginx/vhost.d"
    - "/usr/share/nginx/html"
nginx-letsencrypt-companion:
    restart: always
    image: jrcs/letsencrypt-nginx-proxy-companion
    volumes:
    - "certs-volume:/etc/nginx/certs"
    - "/var/run/docker.sock:/var/run/docker.sock:ro"
    volumes_from:
    - "frontproxy"
volumes:
certs-volume:

dann docker-compose down docker-compose up

weil keine services eingebaut, ein default certificate produziert. https://localhost:443 gibt eine antwort.

caldav mit radicale

Der Dockerfile mit docker build gebaut und die ID 4a0e8b143d3c in den compose file eingebaut:

wie waere ein build in den compose einzubauen? wo werden nun die mappgins fuer die daten eingegeben?

build docker image : sudo docker build -t radicale:2.1.11 .

Failed - used an old version of alpine with python 2 (or at least < 3.6>) - issues with pip missing.

checkout brave browser

web addresses und IP

wie bauen?

swarm

Ein versuch

Befehl: docker swarm init gibt:

docker swarm join --token SWMTKN-1-1133sefvg0mwg954xvwer2g1l8mn2xnd4szdhsn2udxr4uxx4z-4jd1b6c3n7as1p9cx5op8dtiu 10.0.0.2:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

Beispiel

Eroeffnen einer registry: docker service create --name registry --publish published=5000,target=5000 registry:2

Check mit docker service ls

Kopiert alle files, dann docker-compose up. docker-compose ps gitb Name Command State Ports
---------------------------------------------------------------------swarmexperimentredi docker- Up 6379/tcp
s
1 entrypoint.sh redis
...
swarmexperimentweb python app.py Up 0.0.0.0:8000->8000/ 1 tcp

Stop mit docker-compose down --volumes

Wie stoppen?

Der swarm laeuft und laesst sich nicht stoppen. Was tun?

Ich moechte gerne eine clean slate um nicht staendig altes herumzuschleppen: systemctl stop docker cd /var/lib/ rm -R docker systemctl start docker

und auch ein neues directory fuer works - vielleicht auch ein neuer expriment file?


  1. see OverlayFS in wiki↩︎

  2. The full list

    • PID namespace for process isolation.
    • NET namespace for managing network interfaces.
    • IPC namespace for managing access to IPC resources.
    • MNT namespace for managing filesystem mount points.
    • UTS namespace for isolating kernel and version identifiers.

    More detail↩︎

Produced with SGG on with master5.dtpl.