openSUSE Leap als Container-Host

Datum: 23. August 2020

Ziel ist es, ein stabiles Host-System für Linux-Container auf Basis von openSUSE Leap einzurichten, das als Testumgebung für verschiedene Netzwerk- und Client/Server-Szenarien verwendet werden kann.

Linux-Distribution: openSUSE Leap 15.2

Linux-Bridge einrichten

Um transparentes Arbeiten zu ermöglichen, ist eine native Linux-Bridge (= Software-Switch) ratsam. Die Container befinden sich dadurch im selben phyischen Netzsegment wie der Host und nicht hinter NAT. Unter openSUSE ist solch eine Bridge schnell eingerichtet.

Als root direkt an der Maschine eingeloggt in der Konsole ‚yast‘ aufrufen:

  • „System“ / „Network Settings“ in die Netzwerkkonfiguration gehen

  • Im Menü „Overview:

    • Mit F3 ein neues Device hinzufügn

    • Mit Alt + B „Bridge“ auswählen, mitF10 übernehmen

    • Mit Alt + Y „Dynamic Address“ auswählen (oder gerne auch fest vergeben)

    • Mit Alt + R in „Bridged Deviges“ gehen

    • Mit Alt + I sowie danach Leertaste „eth0“ auswählen, F10 drücken

    • Mit F10 alles übernehmen, schlißlich mit F9 beenden

LXD installieren

Mit den folgenden Zeilen installieren wir „Lex Dee“, die von Ubuntu entwickelte Hypervisorlösung für LXC. Dabei wird dem Nutzer ‚tux‘ die sekundäre Gruppe ‚lxd‘ zugewiesen, damit die Arbeit später nicht als ‚root‘ durchgeführt werden muss:

zypper update
zypper in lxd lxd-bash-completion
usermod -aG lxd tux

systemctl start lxd
systemctl start lxcfs
systemctl enable lxd
systemctl enable lxcfs

Bei der nun folgenden Initialisierung alle Vorgaben einfach mit „ENTER“ übernehmen:

testhost:~ # lxd init
Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]:
Name of the storage backend to use (btrfs, dir, lvm) [default=btrfs]:
Would you like to create a new btrfs subvolume under /var/lib/lxd? (yes/no) [default=yes]:
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
Would you like LXD to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
testhost:~ #
testhost:~ # exit
tux@testhost:~> logout

Nach dem aus- und wieder einloggen von ‚tux‘ (falls mehrfach eingeloggt: überall ausloggen!) kann er sich die Liste der aktuell vorhandenen Container, Profile und Netzwerke ausgeben lassen:

tux@testhost:~> lxc list
To start your first instance, try: lxc launch ubuntu:18.04

+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+
tux@testhost:~>
tux@testhost:~> lxc profile list
+---------+---------+
|  NAME   | USED BY |
+---------+---------+
| default | 0       |
+---------+---------+
tux@testhost:~>
tux@testhost:~> lxc network list
+--------+----------+---------+---------------+---------------------------+-------------+---------+
|  NAME  |   TYPE   | MANAGED |     IPV4      |           IPV6            | DESCRIPTION | USED BY |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| br0    | bridge   | NO      |               |                           |             | 0       |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| eth0   | physical | NO      |               |                           |             | 0       |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
| lxdbr0 | bridge   | YES     | 10.31.99.1/24 | fd42:fc0c:b479:f27d::1/64 |             | 1       |
+--------+----------+---------+---------------+---------------------------+-------------+---------+
tux@testhost:~>

Dies ist ein guter Ausgangspunkt für einfache Szenarien mit Client-/Serverarchitekturen, für geroutete Netzwerke wie hier unter https://pemmann.de/cc/Doc/Testumgebung/Bridged/testnetz-mit-Netzwerkbruecke.html skizziert, benötigen wir aber mehr…

LXD-Networking für Testumgebung

Alle Kommandozeilen bitte als einfacher Nutzer ‚tux‘ ausführen. Aus Gründen der Übersichtlichkeit sind hier nur die Kommandos ohne ihren Output aufgelistet.

Profil für die Host-Brücke erstellen

Um die Konfigurationsarbeit zu erleichtern, bieten sich Profile an. Sie lassen sich einem Container zuweisen, der dann gleich mehrere Voreinstellungen erhält.

Das neu zu erzeugende Profil „extbridge“ wird aus dem bei lxd init erstellten Standardprofil „default“ generiert (Zeile 1), wobei das ursprüngliche Device „eth0“ entfernt (Zeile 2) und mit „br0“ als übergeordnetes Device wieder hinzugefügt wird (Zeile 3):

lxc profile copy default extbridge
lxc profile device remove extbridge eth0
lxc profile device add extbridge eth0 nic nictype=bridged parent=br0

Profil für eine interne Gast-Brücke erstellen

Mit dem Initialisieren von LXD ist automatisch eine erste Bridge „lxdbr0“ erstellt und dem Profil „default“ zugeordnet worden. Sie ist für Situationen gedacht, in denen ein Container direkt mit dem Host und via NAT mit dem Internet kommunizieren soll. Für unsere Testumgebung ist dies aber nicht ganz ausreichend.

Wir erzeugen deshalb eine weitere LXD-Bridge „lxdbr1“ (Zeile 1), die wie oben vom Standardprofil Gebrauch gemacht (Zeile 2), wobei das ursprüngliche Device „eth0“ entfernt (Zeile 3) und mit „lxdbr1“ als übergeordnetes Device wieder hinzugefügt wird (Zeile 4):

lxc network create lxdbr1 ipv4.nat=false ipv6.nat=false ipv4.address=none ipv6.dhcp=false dns.mode=none
lxc profile copy default intbridge
lxc profile device remove intbridge eth0
lxc profile device add intbridge eth0 nic nictype=bridged parent=lxdbr1

Container „alp-router“ mit 2 NICs erstellen (eth0 via lxdbr0, eth1 via lxdbr1)

Der erste Container soll ein Alpine Linux namens „alp-router“ werden, er bekommt beim Erstellen das Profil „extbridge“ zugewiesen (Zeile 1) und danach eine zweite NIC spendiert, die als übergeordnetes Device die interne Bridge „lxdbr1“ verwendet (Zeile 2):

lxc launch -p extbridge images:alpine/3.9 alp-router
lxc config device add alp-router eth1 nic nictype=bridged parent=lxdbr1 name=eth1

Container „alp-server“ mit einer NIC erstellen (eth0 via lxdbr1)

Der zweite Container soll ebenfalls ein Alpine Linux werden, „alp-server“ heißen und beim Erstellen das Profil „intbridge“ zugewiesen bekommen:

lxc launch -p intbridge images:alpine/3.9 alp-server

Zur Kontrolle

Neben lxc list und lxc network list ist auch lxc profile list interessant, weil anhand der Spalte „USED BY“ gut zu erkennen ist, dass die beiden neuen Profile nun von jeweils einer Instanz benutzt werden:

tux@vhost ~$ lxc profile list
+-----------+---------+
|   NAME    | USED BY |
+-----------+---------+
| default   | 0       |
+-----------+---------+
| extbridge | 1       |
+-----------+---------+
| intbridge | 1       |
+-----------+---------+
tux@vhost ~$

Aufschlussreicher sind dann die Details, hier speziell für den Router, wobei wir zuerst das Profil „extbridge“ betrachten sollten:

tux@vhost ~$ lxc profile show extbridge
config: {}
description: ""
devices:
eth0:
    nictype: bridged
    parent: br0
    type: nic
root:
    path: /
    pool: default
    type: disk
name: extbridge
used_by:
- /1.0/instances/alp-router
tux@vhost ~$

Und nun betrachten wir unseren „alp-router“ genauer, der mit eth0 via extbridge an der nativen Linux-Brücke br0 hängt und zum zweiten mit eth1 direkt mit der internen Brücke lxdbr1 verbunden ist:

tux@vhost ~$ lxc config show alp-router | grep -A2 profile
profiles:
- extbridge
stateful: false
tux@vhost ~$
tux@vhost ~$ lxc config show alp-router | grep -A5 devices
devices:
eth1:
    name: eth1
    nictype: bridged
    parent: lxdbr1
    type: nic
tux@vhost ~$

Manuelle IP-Konfiguration der Container, erste Tests

Warnung

Wie dort beschrieben, müssen wir noch den Promiscous-Modus in VirtualBox bezüglich „Adapter 1“ aktivieren, damit die von den Containern stammenden Datenpakete die Netzwerkbrücke passieren können.

Container mit mehr Rechten ausstatten

Containervirtualisierung bietet naturgemäß keine vollständige Abgrenzung der einzelnen Prozessumgebungen (gemeinsam genutzer Host-Kernel). Daher müssen die einzelnen Instanzen vom Host abgeschottet werden. Für reine Anwendungsvirtualisierung stellt das kein Problem dar - allerdings für komplexere Aufgabenstellungen, die tief ins Betriebssystem hineinreichen.

Voll privilegieren

Da die LXD-Container per Default nichtprivilegiert sind, d.h. mit eingeschränkten User-Prozessen laufen, gelingt z.B. samba-tool domain provision oder mount 10.2.2.22:/srv/nfs /mnt nicht („Operation not permitted“). Abhilfe bringt folgendes:

lxc config set alp-server security.privileged true
lxc config show alp-server | grep privi
lxc restart alp-server

AppArmor-Ausnahmen

Soll ein Container außerdem als NFS-Server auftreten, behindert AppArmor das Starten von ‚nfsd‘, weil dieser Daemon als ‚nfs-kernel-server‘ im System Space auf dem Host selber ausgeführt werden muss! Was übrigens für ‚mountd‘ nicht gilt; er kann innerhalb des Containers laufen.

Einen ähnlichen Fall haben wir bei OpenVPN, wenn das automatische Starten via ‚systemctl‘ erwünscht ist (Distribution: Debian 10; ‚openvpn –config …‘ stellt dagegen kein Problem dar).

Da temporäre Stoppen der Sicherheitseinrichtung ist mittels aa-teardown leicht möglich, das Starten der NFS-Daemons ist damit kein Problem mehr. Die Reaktivierung von AppArmor gelingt dann am besten mit reboot, alternativ kann systemctl restart apparmor oder auch apparmor_parser -r /etc/apparmor.d/* 2>/dev/null verwendet werden, wobei dann noch die verschiedenen Daemons und Container neu gestartet werden müssen, um die entsprechenden Profile und Prozesse in den enforce-Modus zu bringen. Dauerhaft kann AppArmor deaktiviert werden, indem in der Datei /etc/default/grub die Default-Cmdline auf diesen Wert gesetzt wird: GRUB_CMDLINE_LINUX_DEFAULT="apparmor=0". Danach nicht vergessen, update-grub auszuführen.

Besser als das generelle „Teardown“ ist allerdings, das Sicherheitsystem aktiviert zu lassen und dem betreffenden Container alles zu erlauben. Im folgenden Beispiel gibt es zwei Container namens ‚deb1‘ und ‚deb2‘, von denen der zweite volle Rechte erhält:

my@bash $ lxc config set deb2 raw.lxc 'lxc.apparmor.profile=unconfined'
my@bash $
my@bash $ lxc config show deb2 | grep app
  raw.lxc: lxc.apparmor.profile=unconfined
my@bash $
my@bash $ lxc restart deb2

Mit aa-status | grep -i deb können wir dann als root kontrollieren, ob das tatsächlich greift, bezüglich ‚deb1‘ sollten mehrere Zeilen zum Vorschein kommen, für ‚deb2‘ dagegen nur eine einzige.

Weitere Probleme

Eine weitere Einschränkung erlebt man im Zusammenhang mit einer iptables-Firewall, die in einem sogar voll privilegierten Container läuft: das Loggen von blockierten Paketen mit einer Zeile wie

iptables -A INPUT -m limit --limit 5/m --limit-burst 7 -j LOG --log-level info --log-prefix="iptables: "

zeigt keinerlei Wirkung!

LÖSUNG: Laut forum.proxmox.com kann als Log-Target ‚NFLOG‘ anstelle von ‚LOG‘ verwendet werden. Siehe dazu auch hier.

Neue Linux-Container

Es lassen sich problemlos weitere Containerabbilder aus dem Netz laden und an den Start bringen. Einen Gesamtüberblick kann man sich mittels

lxc image list images: | less

verschaffen. Zur Erinnerung: mit einem Slash lässt sich bei less eine Suche durchführen. So findet sich z.B. auch Debian Buster, was sich dann so in die Umgebung einbinden lässt:

lxc launch -p extbridge images:debian/buster deb10srv