Mithilfe von LXC lagern Sie mit wenigen Handgriffen unsichere Applikationen oder Testsysteme einfach in einen virtuellen Container aus.
Der Markt für Software zum Virtualisieren wächst seit Jahren stetig. Namen wie VMware, Virtualbox, Xen, Qemu oder KVM sind jedem ernsthaften Anwender geläufig. Mit den Linux Containers (LXC, [1]) gesellt sich der Gattung ein noch recht junger Vertreter hinzu, der einige Annehmlichkeiten bietet.
Das System gleicht dabei in etwa den sogenannten Jails unter BSD oder den Containern unter Solaris. Im laufenden System, dem Host, richten Sie damit für den Gast einen abgeschotteten und gesicherten Bereich ein, innerhalb dessen Sie einzelne Dienste oder ganze Systeme virtualisieren. Im LXC-Jargon heißt diese Umgebung wie bei Solaris Container.
Im Vergleich zu vielen anderen Lösungen arbeitet LXC dabei direkt auf der Ebene des Betriebssystems. Während VMware oder Virtualbox einen vollständigen PC samt BIOS und Hardware emulieren und sich daher ideal zum Installieren von anderen Betriebssystemen eignen, nutzt LXC die bereits vorhandene Hardware und den bestehenden Kernel, um entsprechende Container bereitzustellen.
Dieses Verfahren spart durch das Weglassen zahlreicher Abstraktionsebenen viele Ressourcen. Dafür ermöglicht es aber nur ein “Linux im Linux”, denn es stellt keine virtuelle Maschine bereit, sondern lediglich eine virtuelle Umgebung. Möchten Sie beispielsweise einen Windows-Gast innerhalb eines Linux-Hosts virtualisieren, müssen Sie auf andere Lösungen ausweichen [2].
LXC eignet sich sowohl für Umgebungen, die aus Sicherheitsgründen getrennte Instanzen mit eigenen Ressourcen erfordern, als auch für Bereiche, in denen es eine gewisse Flexibilität zu gewährleisten gilt. Bei Bedarf ziehen Sie einen Container mit wenigen Handgriffen auf einen anderen Host um. Der Ansatz eignet sich unter anderem für den Einsatz bei Providern, die kostengünstig virtuelle Server bereitstellen wollen, sowie für Software-Entwickler, die in definierten Umgebungen arbeiten müssen.
LXC bringt aber auch zwei Nachteile mit sich: Zum einen steigt durch das Virtualisieren auf Betriebssystemebene das Risiko, dass virtuelle Hosts bei Sicherheitslücken im Kernel oder bei schlechter Konfiguration einen Zugang zum eigentlichen Host erhalten. Zum anderen fallen das erste Einrichten und die Konfiguration bisweilen noch recht schwer.
Das Testsystem
Als Testsystem für diesen Artikel nutzten wir Ubuntu 11.10 “Oneiric Ocelot” in der 64-Bit-Variante für Server. Als Hardware diente ein Root-Server eines deutschen Hosters, ausgestattet mit Intel-Xeon-Prozessor.
Vorarbeiten
Einer der großen Vorteile von LXC liegt darin, dass neuere Kernel-Versionen (in der Regel ab 2.6.31) es direkt integrieren – ein aufwändiges Übersetzen von Modulen oder gar Patchen des Systems entfällt. Die Inbetriebnahme erfordert lediglich drei Schritte: Das Einrichten der Programme zum Verwalten (also der Tools, mit denen Sie die Container erstellen und bearbeiten), das Einrichten eines solchen Containers, und zu guter Letzt das Konfigurieren der Netzwerkanbindung.
Vor der eigentlichen Installation von LXC stellen Sie zunächst sicher, dass Sie das Host-System ausreichend abgesichert haben. Soll die virtuelle Maschine später online gehen, empfiehlt es sich, eine zusätzliche IPv4-Adresse bereit zu halten. Diese schalten Sie zwar nicht auf den Host selbst auf, haben sie aber idealerweise beim Einrichten der virtuellen Umgebung bereits zur Hand.
Sind diese Voraussetzungen erfüllt, installieren Sie die eigentlichen Pakete über die Kommandzeile mittels sudo apt-get install lxc cgroup-lite debootstrap libcap2-bin libvirt-bin bridge-utils. Anschließend zeigt der Befehl lxc-checkconfig, ob das System alle Anforderungen erfüllt.
Schutzverpackung
LXC kennt zwei Betriebsmodi: Das Virtualisieren eines einzelnen Programms wie etwa einer Bash-Shell oder aber die Installation eines kompletten Basissystems in einem Container. Ersteres eignet sich unter anderem dazu, um einzelne Dienste und Programme in einer gesicherten Umgebung (“Sandbox”) zu starten. Letzteres erweist sich insbesondere dann als ideal, wenn Sie verschiedene virtuelle Server planen.
Der Start eines einzelnen Programms innerhalb eines Containers zeigt sehr schön das Prinzip hinter LXC. Rufen Sie am Host-System den Befehl w auf, erhalten Sie – gesetzt den Fall, dass Sie keinen speziell präparierten Sicherheits-Kernel einsetzen – eine Übersicht aller angemeldeten Nutzer (Listing 1, oben). Zudem fördert ein beherztes ps auxw alle Prozesse zutage, die insgesamt auf dem System aktiv sind (Listing 1, unten).
Listing 1
$ w 13:37:48 up 2 days, 1:36, 2 users, load average: 0,02, 0,03, 0,05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT floeff pts/0 188-1-2-3-dynip. 13:31 23.00s 0.29s 0.29s -bash mmuster pts/16 188-4-5-6-dynip. 13:37 0.00s 0.27s 0.00s w $ ps auxw USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND proftpd 561 0.0 0.0 98312 2128 ? SNs Feb19 0:00 proftpd: (accepting connections) root 810 0.0 0.0 47272 9560 ? Ss Feb18 0:06 /usr/sbin/munin-node daemon 1572 0.0 0.0 16776 380 ? Ss Feb18 0:00 atd polw 1713 0.0 0.0 55104 12848 ? Ss Feb18 0:00 policyd-weight (master) postfw 1718 0.0 0.0 62436 15756 ? Ss Feb18 0:03 /usr/sbin/postfwd clamav 2005 0.0 0.8 195516 137080 ? Ssl Feb18 0:11 /usr/sbin/clamd ...
Nun geht es an das Einrichten einer virtuellen Instanz. Starten Sie nun (dazu benötigen Sie administrative Rechte) unter Zuhilfenahme des Befehls
# lxc-execute -n RestrictedShell /bin/bash
einen Container namens RestrictedShell, der nichts anderes macht, als /bin/bash aufzurufen. Sie befinden sich anschließend direkt auf der Kommandozeile dieser LXC-Instanz.
Der Container (der Gast) erbt vom System (dem Host) nahezu die gesamte Umgebung. Dazu zählen unter anderem das Dateisystem samt Benutzern, Gruppen und Quotas, der Kernel samt zugehöriger Module sowie der Zugriff auf die Hardware. Dennoch bleibt der Container in weiten Teilen vom übrigen System isoliert, wie ein Blick auf die vollkommene leere Übersicht beweist, die der Befehl w zutage fördert (Listing 2, oben). Ein ps auxw zeigt ebenfalls nicht gerade viel (Listing 2, unten).
Listing 2
# w 14:24:47 up 2 days, 2:23, 1 user, load average: 0,03, 0,11, 0,08 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT # ps auxw USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 10496 632 pts/0 S 14:16 0:00 /usr/lib/lxc/lxc-init -- /bin/bash root 2 0.0 0.0 30372 5552 pts/0 S 14:16 0:00 /bin/bash root 70 0.0 0.0 22060 1244 pts/0 R+ 14:25 0:00 ps auxw
Noch deutlicher erscheint das Ganze im Vergleich der Ausgabe von Htop: Der Container (Abbildung 1) sieht die Prozesse des Hosts (Abbildung 2) nicht. An dieser Stelle beenden Sie das Experiment durch die Eingabe von exit, um wieder zur Kommandozeile des physikalischen Systems zu gelangen.
Aller Anfang ist schwer
Als wesentlich vielseitiger und zudem sicherer erweist sich allerdings die Installation eines kompletten Basissystems im Container. Das grundlegende Prinzip gleicht dabei jenem des vorherigen Versuchs: Einen Teil des Systems teilen sich Container und Host, der Rest bleibt isoliert in der virtuellen Umgebung. Dazu erzeugen Sie einen Container, der ein komplett eigenes Dateisystem erhält, und somit ein eigenes Basissystem samt Benutzern und eigener IP-Adresse.
Als Dreh- und Angelpunkt dient dabei das Tool lxc-create (siehe Tabelle “LXC-Befehle”). Es greift auf sogenannte Vorlagen zurück, welche die Installation eines frischen Systems beschreiben. Auf unserem Testsystem hält das Verzeichnis /usr/lib/lxc/templates unter anderem Vorlagen für Debian, Fedora, OpenSuse und Ubuntu als virtualisierte Umgebung bereit, wobei im Test Letzteres zum Einsatz kam.
LXC-Befehle
| Befehl | Funktion |
|---|---|
lxc-execute |
startet eine einzelne Applikation in der Sandbox |
lxc-start |
startet einen kompletten Container |
lxc-stop |
erzwingt das Beenden des Containers (nicht empfohlen) |
lxc-destroy |
löscht einen Container samt aller enthaltenen Dateien |
lxc-netstat |
führt den Befehl netstat im Container aus |
lxc-ps |
listet Prozesse eines Containers auf |
lxc-monitor |
überwacht den Status eines Containers |
lxc-ls |
listet alle verfügbaren Container auf |
lxc-info |
zeigt den Status eines Containers an |
lxc-freeze |
hält einen Container an |
lxc-unfreeze |
setzt einen Container fort |
Zum Erstellen eines Containers für den Host vm1.domain.local legen Sie im Verzeichnis /etc/lxc eine Konfigurationsdatei gleichen Namens an und füllen diese mit dem Inhalt aus Listing 3. Damit weisen Sie der Maschine eine IPv4-Adresse mit entsprechender Netzmaske zu (Zeile 2), aktivieren diese und koppeln sie an das Netzwerkinterface eth0 (Zeile 3). Die Netzmaske hängt von der lokalen Konfiguration ab – im Test war /27 Voraussetzung beim Provider. Für Verbindungen zur Außenwelt nutzt der Container eine sogenannte virtuelle MAC-Adresse (Zeile 4), ebenfalls eine Vorgabe des Providers.
Listing 3
lxc.network.type=macvlan lxc.network.ipv4=1.2.3.4/27 lxc.network.link=eth0 lxc.network.hwaddr=00:11:22:33:44:55 lxc.network.flags=up
Mit dieser Minimalkonfiguration erstellen Sie den Container unter Zuhilfenahme des schon bekannten Tools. Der Befehl
# lxc-create -t ubuntu -n vm1.domain.local -f /etc/lxc/vm1.domain.local.conf
installiert das Basissystem, das der Host unter /var/lib/lxc/vm1.domain.local/rootfs abbildet. Um den Container im Test tatsächlich ans Netz zu bekommen, brauchte es allerdings noch etwas Handarbeit.
Zum einen galt es, die nötigen Netzwerkrouten zu setzen. In vielen anderen Netzwerken entfällt diese Aufgabe. Übertragen Sie die Inhalte aus Listing 4 in die Datei /etc/network/interfaces im Container, die entsprechend als /var/lib/lxc/vm1.domain.local/rootfs/etc/network/interfaces am Host vorliegt.
Listing 4
auto lo iface lo inet loopback auto eth0 iface eth0 inet manual up route add default gw 1.2.3.1 up route add -net 1.2.3.0 netmask 255.255.255.224 gw 1.2.3.1 eth0
Ganz gleich, ob beim Hoster oder im Heimnetzwerk – der Container benötigt zum Auflösen der Namen noch Zugriff auf einen DNS-Server, den Sie einfach in der Datei /etc/resolvconf/resolv.conf.d/head im Container eintragen. Das entspricht der Datei /var/lib/lxc/vm1.domain.local/rootfs/etc/resolvconf/resolv.conf.d/head am Host. Der Eintrag nameserver 8.8.8.8 bindet beispielsweise den Google-Nameserver ein.
Der erste Start
Nach diesen Konfigurationsarbeiten steht der Container für erste Experimente bereit. Sie starten dazu die virtuelle Maschine im Vordergrund über den Befehl
# lxc-start -n vm1.domain.local
Die Konsole zeigt also das Login des Containers an. Melden Sie sich dort als Benutzer root mit gleichlautendem Passwort an, und ändern Sie letzteres sodann mittels passwd.
Als nächstes stellen Sie sicher, dass der Zugriff auf das Netzwerk funktioniert: Ein Ping auf eine externe Seite wie www.linux-user.de und der Versuch, sich von außen per SSH auf die neue Maschine einzuloggen, sorgen für Klarheit.
TIPP
Im geschilderten Setup greift die Firewall des Host-Systems in der Regel nicht – der Container ist also völlig ungeschützt. Schnelle Abhilfe schafft beispielsweise die Installation von UFW und das Sperren nicht benötigter Ports im Gast.
Derzeit läuft die virtuelle Maschine noch im Vordergrund. Funktioniert alles wie gewünscht, beenden Sie den Container mittels halt. Achten Sie darauf, den Befehl im Terminal des Containers einzugeben. Dann starten Sie ihn erneut, diesmal aber mittels:
# lxc-start -d -n vm1.domain.local
Das zusätzliche -d sorgt dafür, dass LXC nun im Hintergrund läuft, im sogenannten Detached- oder Headless-Modus. Alles weitere richten Sie jetzt per SSH ein.
Spitze des Eisbergs
Der Gast steht jetzt zum Einsatz bereit. Es bestünde nun die Möglichkeit, ihn nach entsprechendem Anpassen der Konfigurationsdatei beliebig oft zu klonen. Verfügen Sie über drei weitere IP-Adressen, dann kopieren Sie dazu einfach die Konfiguration des Containers in /etc/lxc, passen jeweils die Angaben zum Netzwerk an, und erzeugen die gewünschte Anzahl an Containern mit lxc-create (siehe Kasten “Das doppelte Lottchen”).
Das doppelte Lottchen
Nach Anlegen des Containers befindet sich dessen Konfiguration unterhalb von /var/lib/lxc. Um nachträglich Parameter anzupassen, müssen Sie diese direkt dort ändern – die anfangs angelegte Datei in /etc/lxc dient nur als Vorlage zum Erstellen des Gasts.
Auf dem Testsystem galt es allerdings im Container noch einige weitere Anpassungen vorzunehmen – insbesondere die Datei /etc/hosts war unvollständig. Das Muster in Listing 5 kann als Beispiel für eigene Ergänzungen dienen. Zudem fehlten in unserem Template die Verzeichnisse /run/lock und /run/shm, was unter Umständen zu Fehlermeldungen führt (und vermutlich auf einen Bug zurückzuführen ist).
Listing 5
127.0.0.1 localhost 1.2.3.4 vm1.domain.local vm1 ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts
Über die Konfiguration des Containers regeln Sie bei Bedarf noch wesentlich mehr, als dieses kleine Beispiel zu zeigen vermag: So definieren Sie eigene Mountpoints und Root-Dateisysteme, bilden falls nötig komplexe Netzwerkkonfigurationen ab und weisen über die Control Groups (Cgroups) des Kernels bestimmte Berechtigungen und Ressourcen zu.
Damit sollten Sie sich spätestens dann beschäftigen, wenn Sie virtuelle Maschinen für Dritte bereitstellen. In der Ubuntu-Standardkonfiguration darf beispielsweise jede virtuelle Maschine die Systemzeit des Hosts ändern und kann dadurch für ein kleines Chaos sorgen. Abhilfe schafft in diesem Fall übrigens der Parameter lxc.cap.drop=sys_time in der Konfiguration. Ausführlichere Informationen zu allen Konfigurationsmöglichkeiten erhalten Sie mittels man lxc.conf. Weitere Informationen liefern die Dokumentation unter /usr/share/doc/lxc und das Ubuntuusers.de-Wiki [3].
Funktionieren alle Container zu Ihrer Zufriedenheit, weisen Sie LXC noch an, sie bei jedem Reboot des Hosts automatisch zu starten. Dazu setzen Sie in /etc/default/lxc den Eintrag RUN auf yes. Für jeden Gast, den Sie beim Systemstart laden möchten, legen Sie anschließend einen symbolischen Link der Konfiguration in /etc/lxc/auto an (Listing 6).
Listing 6
# ln -s /var/lib/lxc/vm1.domain.local/config /etc/lxc/auto/vm1.domain.local.conf
Fazit
Die noch recht junge Virtualisierungslösung LXC überzeugt vor allem durch ein Ressourcen sparendes Verhalten und die direkte Integration in den Kernel. Die Konfiguration fällt zwar etwas umständlich aus, eignet sich aber prinzipbedingt ideal zum Einbinden in eigene Skripte, um etwa auf Knopfdruck neue Container zu erzeugen. Falls Sie es nicht scheuen, etwas Zeit zu investieren und auf grafische Oberflächen verzichten können, dann sollten Sie unbedingt einen Blick auf LXC wagen.
Infos
[1] LXC: http://lxc.sourceforge.net
[2] Desktop-Virtualisierung: LU 04/2012, Schwerpunkt, S. 20 bis 47, https://www.linux-community.de/Internal/Artikel/Print-Artikel/LinuxUser/2012/04
[3] Ubuntuusers.de-Wiki: http://wiki.ubuntuusers.de/LXC







