Was ist LXC?

LXC ist eine Benutzeroberfläche für die Linux-Kernel-Containment-Funktionen. Mit einer leistungsstarken API und einfachen Tools können Linux-Benutzer System- oder Anwendungscontainer problemlos erstellen und verwalten werden.

Funktionen

LXC verwendet Kernelfunktionen, um Prozesse zu enthalten:

  • Kernel-Namespaces (mount, pid, uts, ipc, network und user)
  • Apparmor- und SELinux-Profile
  • Seccomp-Richtlinien
  • Chroots
  • Kernel-Fähigkeiten
  • Gruppen

LXC-Container werden oft als etwas in der Mitte zwischen einer chroot- und einer vollwertigen virtuellen Maschine betrachtet. Das Ziel von LXC ist es, eine Umgebung zu schaffen, die einer Standard-Linux-Installation so nahe wie möglich kommt, ohne dass ein separater Kernel erforderlich ist.

Komponenten

LXC besteht aus einigen separaten Komponenten:

  • Die liblxc-Bibliothek
  • Mehrere Sprachbindungen für die API:
    • python3
    • lua
    • Go
    • Ruby
    • und weitere.....
  • Eine Reihe von Standardwerkzeugen zur Steuerung der Container
  • Vorlagen für die Container

Linux-Container bestehen aus drei Teilen:

  • KomponentenEine Konfigurationsdatei mit Informationen über die verwendeten Ressourcen
  • Eine Datei im fstab-Format, welche die Einhängepunkte für den Container enthält. Diese Informationen kann man alternativ auch direkt in der Konfigurationsdatei angeben.
  • Das Root-Dateisystem des Containers.

Soll nur eine einzelne Anwendung in einem Container ausgeführt werden, spricht man von einem Anwendungscontainer. Bei Anwendungscontainern sollte man sich genau überlegen, welche Ressourcen man isolieren will. Man kann zum Beispiel den Rechnernamen (Hostname) ändern und das Netzwerk isolieren. Um Konflikte zwischen Dateien zu vermeiden, kann man Teile des Dateisystem neu einhängen, oder man erstellt ein eigenes Root-Dateisystem und kann Teile des Haupt-Dateisystems über Bind-Mounts gemeinsam nutzen. Wenn keine Konfiguration angegeben wird, werden nur Prozessnummern, System V IPC und Einhängepunkte virtualisiert und isoliert.

Wird ein vollständiges System in einem Container eingerichtet, spricht man von einem Systemcontainer. Die Konfiguration eines Systemcontainers ist einfacher, da man sich keine Gedanken über die isolierten Ressourcen machen muss, weil alles isoliert werden muss.

Folgende Einstellungen sind in der Konfigurationsdatei möglich:

lxc.utsname Hostname für den Container
lxc.tty Anzahl der ttys innerhalb des Containers
lxc.pts Pseudo ttys für den Container anlegen.
lxc.mount Kann eine fstab-Datei übergeben werden.
lxc.mount.entry Einhängepunk in Format einer fstab-Zeile.
lxc.rootfs Root-Dateisystem des Containers
lxc.network.type Netzwerkvirtualisierung, folgende Werte sind möglich: empty, veth, vlan, macvlan und phys
lxc.network.flags Aktiviert das Netzwerkinterface: up
lxc.network.link Das vom Container verwendete Netzwerkinterface.
lxc.aa_profile

Container können mit AppArmor abgesichert werden, AppArmor hat ein Standardprofil. Es kann jedoch auch ein eigenes Profile angegeben werden oder mit "unconfined" komplett abgeschaltet werden.

 

Container Konfiguration

Es gibt zwei Möglichkeiten Container zu Konfigurieren.

  1. Konfigurations-Template (Einträge werden in CONTAINERNAME/config übernommen)
  2. Schreibt jede Konfig für einen Container /var/lib/lxc/CONTAINERNAME/config

Es macht jedoch mehr Sinn ein Template zu schreiben und nur ergänzende Einstellungen am Container durchzuführen. Debian erstellt solch ein Template mit nur einem Eintrag, damit lassen sich zwar Container aufbauen und starten, aber ohne Netzwerk, was wenig Sinn macht. Dieses Template ist unter /etc/lxc/default.conf

lxc.network.type = empty

Mit den oben beschriebene Werte kann das Template Konfiguriert werden.

Beispiel Konfiguration:

  1. lxc.network.type = veth
  2. lxc.network.link = lxcbr0
  3. lxc.network.falgs = up
  4. lxc.network.hwaddr = 00:16:3e:xx:xx:xx
  5. lxc.start.auto = 1
  6. lxc.group = onboot

Erklärung:

Die Zeile lxc.network.type = veth ein virtuelles Ethernet-Pair wird erstellt, wobei eine Seite dem Container zugewiesen wird und die andere Seite an einer Bridge gebunden ist. Über lxc.network.link = lxcbr0 wird LXC angewiesen die veth Interface an die Brücke lxcbr0 anzubinden und das Netzwerk wird mit lxc.network.flags = up gestartet. Die MAC-Adressen für Virtualisierungen sollten aus dem Bereich 00:16:3e stammen, was über die Zeile lxc.network.hwaddr = 00:16:3e:xx:xx:xx realisiert wird. LXC ergänzt die MAC-Adresse Automatisch. Die MAC-Adresse Identifiziert ein Netzwerkgerät, warum die MAC-Adresse aus dem Bereich 00:16:3e sein soll habe ich bisher noch keine weiteren Informationen im Netz gefunden, viele Links zu diesem Thema existieren leider nicht mehr. Ich gehe davon aus das im Code von LXC dieser MAC-Bereich besser verarbeitet wird.

LXC-Konfiguration

Sucht man in den Hilfeseiten (Manpages) danach wie LXC Konfiguriert wird, wird man nur schwer fündig. Deswegen habe ich mir den Bootprozess von LXC einmal näher angesehen.

  1. [Unit]
  2. Description=LXC network bridge setup
  3. After=network-online.target
  4. Before=lxc.service
  5.  
  6. [Service]
  7. Type=oneshot
  8. RemainAfterExit=yes
  9. ExecStart=/usr/lib/x86_64-linux-gnu/lxc/lxc-net start
  10. ExecStop=/usr/lib/x86_64-linux-gnu/lxc/lxc-net stop
  11.  
  12. [Install]
  13. WantedBy=multi-user.target

Jetzt ist klar, LXC wird von "/usr/lib/x86_64-linux-gnu/lxc/lxc-net" gestartet. Schauen wir uns das noch etwas genauer an.

file /usr/lib/x86_64-linux-gnu/lxc/lxc-net
/usr/lib/x86_64-linux-gnu/lxc/lxc-net: POSIX shell script, ASCII text executable

Super, es handelt sich also um ein Bash-Script. Schaut man sich dieses an, verraten einem die ersten 24 Zeilen einiges.

  1. #!/bin/sh -
  2.  
  3. distrosysconfdir="/etc/default"
  4. varrun="/run/lxc"
  5. varlib="/var/lib"
  6.  
  7. # These can be overridden in /etc/default/lxc
  8. #    or in /etc/default/lxc-net
  9.  
  10. USE_LXC_BRIDGE="true"
  11. LXC_BRIDGE="lxcbr0"
  12. LXC_BRIDGE_MAC="00:16:3e:00:00:00"
  13. LXC_ADDR="10.0.3.1"
  14. LXC_NETMASK="255.255.255.0"
  15. LXC_NETWORK="10.0.3.0/24"
  16. LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"
  17. LXC_DHCP_MAX="253"
  18. LXC_DHCP_CONFILE=""
  19. LXC_DOMAIN=""
  20.  
  21. LXC_IPV6_ADDR=""
  22. LXC_IPV6_MASK=""
  23. LXC_IPV6_NETWORK=""
  24. LXC_IPV6_NAT="false"

In Zeile 7 und 8 steht der Hinweis, dass die Default Werte für lxc in "/etc/default/lxc-net" Konfiguriert werden können, diese wird von "/etc/default/lxc" eingelesen und verarbeitet. Glücklicherweise haben die Entwickler aussagekräftige Variablennamen verwendet, so das die Zeilen 10 bis 24 relativ einfach zu verstehen sind.

Nehmen wir einfach mal an, du hast nichts gegen das 10er Netz und du möchtest lediglich die Standard Bridge verwenden und deine LXC-Container mit einer Statischen IP-Adresse betreiben (was zu empfehlen ist) und ein Domain-Name (diese hat nichts mit der Domain des Servers zu tun, sondern ist lediglich nur für interne zwecke gedacht) betreiben, dann könnte deine /etc/default/lxc-net wie folgt aussehen.

USE_LXC_BRIDGE="true"
LXC_DHCP_CONFILE="/etc/lxc/dhcp.conf"
LXC_DOMAIN="knasan.local"

In der Datei /etc/lxc/dhcp.conf werden die Container für Dnsmasq eingetragen, somit laufen die LXC mit einer statischen IP-Adresse obwohl der Container lediglich auf DHCP gestellt bleibt. In dieser Datei müssen die Einträge wie folgt eingetragen werden.

dhcp-host=containername,IP-Adresse

Hier ein Beispiel:

  1. dhcp-host=c1,10.0.3.10
  2. dhcp-host=c2,10.0.3.20
  3. dhcp-host=c3,10.0.3.30
  4. dhcp-host=c4,10.0.3.40
  5. dhcp-host=c5,10.0.3.50

Nachdem der Dienst lxc-net neugestartet wurde, können die Container eingerichtet werden und erhalten sofort die passende IP-Adresse.

Container erstellen

Ein Container wird mit dem Kommando "lxc-create" erstellt. Die wichtigsten Parameter dieses Kommandos:

-n Name des Containers
-t  Template, unter /usr/share/lxc/templates gibt es einige
-f Alternative Konfigurationsdatei

Wenn du ein Debian Container mit dem Namen c1 erstellen möchtest, musst du einfach nur

lxc-create -n c1 -t debian

ausführen. Nach einer Kurzen Zeit findest du im Ordner /var/lib/lxc den Container c1.

 

Anwendungscontainer Container starten und beenden

Eine Anwendung in einem Container startet man mit:

lxc-execute -n CONTAINERNAME Befehl

 

Systemcontainer starten

Einen Systemcontainer startet man mit:

lxc-start -n CONTAINERNAME -d

Man erhält eine Konsole, auf der man sich anmelden kann. Diese Konsole hat eine feste Größe, unabhängig von der tatsächlichen Größe des Terminals und ist nur bedingt geeignet. Die mit lxc-console geöffneten Konsolen haben diese Einschränkung nicht.

lxc-console -n CONTAINERNAME

 

Login in Container

Im Container selbst kann man sich einen SSH-Damon installieren, was sehr praktikabel ist. Für schnelle Aktinen, Konfiguration oder falls ssh nicht funktioniert kann man auch "lxc-attach" verwenden. Jedoch ist in dieser Umgebung nicht alles möglich, da nicht alle Pseudo-Treiber für die Konsole geladen werden. Aber die meisten Befehle lassen sich daran ausführen (wenn auch nur bedingt).

lxc-attach -n CONTAINERNAME

 

Weitere Tools:

Es gibt einige tools für lxc. Schau dir einfach mal die Manpage von lxc an, dort wirst du fündig. Ganz Interessant finde ich lxc-top um zu sehen wie sich deine Container so auf dem Server machen und wie viel Performance diese verschlingen. Hier mal eine Liste von Nützlichen lxc-tools.

lxc-attach Aufführen von Programme innerhalb eines Containers. Gibt man nichts an, wird die Bash gestartet.
lxc-checkconfig Prüft den aktuellen Kernel auf lxc Support.
lxc-console Startet eine Konsole für den angegebenen Container
lxc-copy Kopiert eine vorhandenen Container
lxc-create Erstellt einen Container
lxc-destroy Löscht einen vorhandenen Container
lxc-execute Führt eine Application aus (Anwendungscontainer)
lxc-freeze Alle Prozesse des Containers einfrieren
lxc-info Informationen über einen Container abfragen
lxc-ls Liste der auf dem System vorhandenen Container
lxc-start Startet einen Container
lxc-stop Schaltet einen Container ab
lxc-snapshot Erstellt ein Snapshot für einen existierenden Container

 

Kernel-Namespaces

Linux-Prozesse bilden eine einzige Hierarchie, bei der alle Prozesse verwurzelt sind init. Normalerweise können privilegierte Prozesse in diesem Baum andere Prozesse verfolgen oder beenden. Mit dem Linux-Namespace können wir viele Hierarchien von Prozessen mit ihren eigenen „Unterstrukturen“ haben, sodass Prozesse in einem Teilbaum nicht auf Prozesse in einem anderen Baum zugreifen können.

Ein Namespace Umschließt eine globale Ressource so, dass Prozesse in diesem Namespace über eine eigene isolierte Instanz dieser Ressource verfügen. Nehmen wir als Beispiel den PID-Namespace. Ohne Namensraum gehen alle Prozesse hierarchisch von PID 1 (init) ab. Wenn wir einen PID-Namespace erstellen und darin einen Prozess ausführen, wird dieser erste Prozess zur PID 1 in diesem Namespace. In diesem Fall haben wir eine globale Systemressource (Prozess-IDs) verpackt. Der Prozess, der Namespace erstellt, bleibt zwar im übergeordneten Namespace, macht jedoch das untergeordnete Element zum Stamm der neuen Prozessstruktur.

Dies bedeutet jedoch nur, dass die Prozesse im neuen Namespace den übergeordneten Prozess nicht sehen können, der übergeordnete Prozess-Namespace jedoch den untergeordneten Namespace. Und die Prozesse innerhalb des neuen Namensraums verfügen jetzt über zwei PIDs: eine für den neuen und eine für den globalen Namensraum.

Der Linux-Kernel verfolgt jetzt die PIDs von Prozessen mithilfe der upid-Struktur anstelle eines einzelnen pid-Werts. Die upid-Struktur gibt Auskunft über die PID und die Namespaces, in denen die PID gültig ist.

Namespace Typen

  • cgroup: Dies isoliert das Cgroup-Stammverzeichnis ( CLONE_NEWCGROUP ).
  • IPC: Isoliert System V IPC und POSIX-Nachrichtenwarteschlangen ( CLONE_NEWIPC )
  • Netzwerk: isoliert Netzwerkgeräte, Ports usw. ( CLONE_NEWNET )
  • Mount: isoliert Mountpunkte ( CLONE_NEWNS )
  • PID: isolierte Prozess-IDs ( CLONE_NEWPID )
  • Benutzer: isoliert Benutzer- und Gruppen-IDs ( CLONE_NEWUSER )
  • UTS: isoliert den Hostnamen und den NIS-Domänennamen ( CLONE_NEWUTS )

Als Teil der Namensraumverwaltung bietet Linux folgende APIs:

  • clone() - plain old clone () erstellt einen neuen Prozess. Wenn wir ein oder mehrere CLONE_NEW -Flags an clone () übergeben, werden für jedes
     Flagneue Namespaces erstellt, und der untergeordnete Prozess wird zu einem Mitglied dieser Namespaces.
  • setns() - Ermöglicht einem Prozess, einem vorhandenen Namespace beizutreten. Der Namensraum wird durch eine Dateideskriptorreferenz auf eine der proc/[pid]/ns Dateien angegeben.
  • unshare() - verschiebt den Aufrufenden Prozess in einen neuen Namespace, der mit den Argumenten CLONE_NEW erstellt wurde. Es können mehrere solcher Flags angegeben werden.

Hinweis: Für den PID-Namespace MUSS clone() aufgerufen werden, da dies nur während der Erstellung eines neuen Prozesses erstellt werden kann. Der durch clone() erzeugte Prozess hat die PID 1. Für den PID-Namespace ist unshare() nicht nützlich.

Netzwerk-Namensraum

Nehmen wir an, wir haben einen neuen PID-Namespace. Der in diesem neuen Namensraum sitzende Prozess wartet auf Port 80 auf eingehende Anforderungen. Dies bedeutet, dass alle anderen Prozesse im gesamten System daran gehindert werden. Dies ist keine sehr hilfreiche Isolation. Hier kommen Netzwerk-Namespaces.

Netzwerk-Namespaces helfen internen Prozessen, unterschiedliche Netzwerkschnittstellen einschließlich der lo (localhost) Schnittstellen zu sehen! Aber das ist nur die halbe Wahrheit. Wenn wir neue Netzwerk-Namespaces haben, müssen wir virtuelle Netzwerkschnittstellen einrichten, die viele Namespaces zusammen mit einem Routing-Prozess umfassen, der im globalen Namespace ausgeführt wird, um den Datenverkehr zu verarbeiten und an den korrekten Namespace weiterzuleiten.

Wie bereits erwähnt, isolieren andere Namespaces eine bestimmte globale Ressource und beschränken den Zugriff des inneren Prozesses auf die eigene Sandbox.

 

Noch keine Kommentare

Kommentar schreiben

Umschließende Sterne heben ein Wort hervor (*wort*), per _wort_ kann ein Wort unterstrichen werden.
Standard-Text Smilies wie :-) und ;-) werden zu Bildern konvertiert.
Sie können [geshi lang=LANG][/lang] Tags verwenden um Quellcode abhängig von der gewählten Programmiersprache einzubinden
Die angegebene E-Mail-Adresse wird nicht dargestellt, sondern nur für eventuelle Benachrichtigungen verwendet.

Um maschinelle und automatische Übertragung von Spamkommentaren zu verhindern, bitte die Zeichenfolge im dargestellten Bild in der Eingabemaske eintragen. Nur wenn die Zeichenfolge richtig eingegeben wurde, kann der Kommentar angenommen werden. Bitte beachten Sie, dass Ihr Browser Cookies unterstützen muss, um dieses Verfahren anzuwenden.
CAPTCHA