Contain

von Simon Wilper am Mittwoch, 9. September 2020

Darum geht es

Sie kennen das: Sie setzen auf allen Maschinen eine auf musl basierte Linux-Distribution wie Alpine Linux ein. Jetzt kommt nun einer ihrer Kollegen an und möchte unbedingt sein Python-Programm, das tensorflow, keras und OpenCV benötigt.

Na, Gute Nacht, Marie.

Falls Sie jetzt auf die Idee kommen sollten alle Abhängigkeiten für musl zu bauen: Vergessen Sie es. Stattdessen: containers: Lightweight containers using Linux user namespaces -- Für alle, denen docker zu 2013 ist und systemd-nspawn zu viel systemd. Hier auch mal wieder Dank an Markus, der containers vorgeschlagen hat. containers macht also nichts anderes als den Inhalt eines Verzeichnisses als eigenständiges Betriebssystem zu behandeln, welches auch keinen Zugriff auf die Prozesse des Hostsystems hat.

Installation

Unter Archlinux ist containers im AUR zu finden, bei voidlinux scheint es eine im offiziellen Repository zu geben. Debian, Ubuntu und auch Alpine selber gehen leer aus, muss also selber gebaut werden.

Ist die Installation erfolgt, sollte der Befehl contain ausgeben:

[sxw@archeus] [~] > contain
Usage: contain [OPTIONS] DIR [CMD [ARG]...]
Options:
  -c        disable console emulation in the container
  -g MAP    set the container-to-host GID map
  -i CMD    run a helper child inside the new namespaces
  -n        share the host network unprivileged in the container
  -o CMD    run a helper child outside the new namespaces
  -u MAP    set the container-to-host UID map
GID and UID maps are specified as START:LOWER:COUNT[,START:LOWER:COUNT]...
[sxw@archeus] [~] >

Die Usage-Line sagt uns hier ganz klar, dass wir ein Verzeichnis mit dem System benötigen.

Anlegen eines Alpine Rootfs

Die Kollegen von Alpinelinux haben das Potential ihrer Distribution als prädestinierten Kandidaten für Container-System erkannt und bieten uns ein Mini Root Filesystem im Download-Bereich an. Dort laden wir das tar.gz für x86_64 in meinem Fall herunter und entpacken es.

Dafür habe ich mir mal ein Verzeichnis erstellt, nach Entpacken sollte das Ganze dann so aussehen:

[sxw@archeus] [~/my-container] > ls
bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[sxw@archeus] [~/my-container] >

Jetzt können wir den container starten indem wir eingeben:

[sxw@archeus] [~/my-container] > contain -n . /bin/sh

Wir sehen einen neuen root-Prompt und die Eingabe von ps ax zeigt lediglich unsere Prozesse, die nun in einem anderen Namespace laufen:

/ # ps ax
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    2 root      0:00 ps ax
/ #

Der aufmerksame Leser wird die Verwendung von -n bemerkt haben. Diese Option aktiviert lediglich die Netzwerkunterstützung und das System im Container sieht somit die gleichen Network Interfaces wie der Host.

Installation von Programmen

Damit auf einem Alpine-System glibc-Programm wie tensorflow laufen, muss zunächst die glibc installiert werden. Außerhalb des containers auf dem Host-System das apk herunterladen und ins root-Verzeichnis des containers abspeichern:

[sxw@archeus] [~/my-container/root] > wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.32-r0/glibc-2.32-r0.apk
[sxw@archeus] [~/my-container/root] > wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.32-r0/glibc-bin-2.32-r0.apk

und die URLs aus dem Repository auskommentieren. Die werden nicht benötigt:

[sxw@archeus] [~/my-container] > sed -ie 's/^/#/g' etc/apk/repositories

Wieder den Container aktivieren und das APK installieren:

[sxw@archeus] [~/my-container] > contain -n . /bin/sh
/ # cd root
~ # apk --allow-untrusted add glibc-2.32-r0.apk
~ # apk --allow-untrusted add glibc-bin-2.32-r0.apk

Und wir sollten sehen, dass der Linker installiert wurde:

~ # apk info -L glibc | grep ld
etc/ld.so.cache
lib/ld-linux-x86-64.so.2
lib64/ld-linux-x86-64.so.2
usr/glibc-compat/etc/ld.so.conf
usr/glibc-compat/etc/ld.so.cache
usr/glibc-compat/lib/ld-2.32.so
usr/glibc-compat/lib/ld-linux-x86-64.so.2
usr/glibc-compat/lib64/ld-linux-x86-64.so.2
~ #

Den Rest erledigen wir über Miniconda, einem Python-Installer, der auch jegliche Binaries, eigene Python-Installation fuer x86_64 mitbringt und ziemlich problemlos in meinen Tests funktioniert hat.

Damit Miniconda auch die Hostnamen auflösen kann sollten wir einen Nameserver in die /etc/resolv.conf des Containers eintragen:

~ # echo 'nameserver 141.1.1.1' > /etc/resolv.conf
~ # wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
Connecting to repo.anaconda.com (104.16.131.3:443)
saving to 'Miniconda3-latest-Linux-x86_64.sh'
Miniconda3-latest-Li   1% |                                |
...
'Miniconda3-latest-Linux-x86_64.sh' saved
~ # ls
Miniconda3-latest-Linux-x86_64.sh  glibc-2.32-r0.apk
~ # chmod 755 Miniconda3-latest-Linux-x86_64.sh 
~ # ./Miniconda3-latest-Linux-x86_64.sh 

Welcome to Miniconda3 py38_4.8.3

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>>

[... LICENSE ...]

Do you accept the license terms? [yes|no]
[no] >>> yes

Miniconda3 will now be installed into this location:
/miniconda3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

[/miniconda3] >>>

PREFIX=/miniconda3
Unpacking payload ...
...
installation finished.
Do you wish the installer to initialize Miniconda3
by running conda init? [yes|no]
[no] >>> yes

Nun sollten wir eine komplette Python-Umgebung in /miniconda3 bekommen haben, dessen Funktionalität wir durch Ausführen von /miniconda3/bin/python bestätigen können:

/ # /miniconda3/bin/python
Python 3.8.3 (default, May 19 2020, 18:47:26) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Jetzt nur noch die restlichen Abhängigkeiten installieren:

/ # /miniconda3/bin/conda install pandas
/ # /miniconda3/bin/conda install -c conda-forge opencv
/ # /miniconda3/bin/conda install scikit-image
/ # /miniconda3/bin/conda create -n tf tensorflow
/ # /miniconda3/bin/conda install -c conda-forge keras
/ # /miniconda3/bin/conda install -c conda-forge imutils
/ # /miniconda3/bin/conda install -c anaconda psycopg2
/ # /miniconda3/bin/conda install -c anaconda redis