Linux im chroot-Container
Teil II: Erweiterte Konfiguration
Inhalt
- Einleitung
- Netzwerkzugang zum chroot-Container
- Container-Verwaltung: cnt-utils
- Grenzen der chroot-Container
- Weiterführende Informationen
1. Einleitung
Im ersten Teil unseres Workshops zu chroot-Containern ging es primär um die Installation von Linux in eine chroot-Umgebung.
Der zweite Teil soll nun Wege aufzeigen, diese Container auch nutzen zu können.
Dazu brauchen wir zum einen Netzwerk und zum zweiten ein definiertes System zur Verwaltung der Container.
2. Netzwerkzugang zum chroot-Container
Haben wir einen oder mehrere Container eingerichtet wie im ersten Teil beschrieben, stellen wir zunächst einmal fest, dass das Netzwerk des Wirtssystems vollständig zur Verfügung steht. So steht beispielsweise der Internetzugang des Wirts zur Verfügung um auch das Containersystem zu aktualisieren oder um einfach nur durchs Web zu brausen. Soweit also alles gut?
Eine ssh-Verbindung zum Container etwa können wir nicht zustande bringen - wir landen immer wieder im Wirtssystem, ebenso verhält es sich mit allen anderen Diensten - eigene IP-Adressen für die Container müssten also her.
Eine weitere IP-Adresse auf eine vorhandene Netzwerkschnittstelle zu setzen, ist zunächst einmal kein Problem. So können wir im Container einfach eine neue IP-Adresse auf eth0 definieren:
chroot# /sbin/ifconfig eth0:0 192.168.1.10 netmask 255.255.255.0 up
Wohlgemerkt, den ifconfig-Befehl haben wir im Container, nicht auf dem Wirt abgesetzt!
Doch setzen wir vom Wirt (oder auch vom Container aus) ein
# ssh 192.168.1.10ab, landen wir doch wieder auf dem Wirtssystem. So einfach geht es anscheinend nicht. Das Problem ist der Kernel, den sich Wirt und alle Container nämlich teilen.
Dennoch: So einfach geht es eben doch!
2a. ssh für Container konfigurieren
Bleiben wir beim obigen Beispiel, hat unser Container die IP-Adresse 192.168.1.10. Damit wir uns via ssh zum Container verbinden können, lassen wir den ssh-Dämon auf dieser Adresse lauschen, dazu bearbeiten wir die entsprechende Sektion der Datei /etc/ssh/sshd_config wie folgt:
#Port 22
#Protocol 2,1
#AddressFamily any
ListenAddress 192.168.1.10#ListenAddress 127.0.0.1
#ListenAddress ::
Dies reicht aber noch nicht aus, da der ssh-Dämon unseres Wirtsystems auf allen möglichen IP-Adressen lauscht - somit auch auf der IP-Adresse, die wir dem Container zugeordnet haben. Also müssen wir auch auf dem Wirtssystem dafür sorgen, dass der ssh-Dämon nur auf den Adressen lauscht, die nicht zu einem Container gehören, meistens werden das die zum Hostnamen gehörende IP-Adresse und Localhost sein:
#Port 22
#Protocol 2,1
#AddressFamily any
ListenAddress 192.168.1.2 ListenAddress 127.0.0.1#ListenAddress ::
Der ssh-Dämon des Wirts muss jetzt durchgestartet werden, also etwa:
host# /etc/init.d/ssh° restart
Danach starten wir den ssh-Dämon im Container:
chroot# /etc/init.d/ssh start
Jetzt können wir den Container über Netzwerk erreichen, dies gelingt vom Wirt aus ebenso wie von jedem
anderen Rechner im Netzwerk.
2b. Apache im Container
Ähnlich wie der ssh-Dämon lässt sich auch der Apache-Listener auf eine bestimmte IP-Adresse begrenzen. In der Datei /etc/apache/httpd.conf sieht das dann so aus:
:
#
# Listen: Allows you to bind Apache to specific IP addresses and/or
# ports, instead of the default. See also the <VirtualHost>
# directive.
#
Listen 192.168.1.10:80
:
Unter Debian ist die Listener-Konfiguration ausgelagert in die Datei /etc/apache2/ports.conf. Sie enthält dann einen einzigen Eintrag, etwa:
Listen 192.168.1.10:80
Nun lauschen die verschiedenen Webserver nur auf der Adresse ihres eigenen Containers.
3. Container-Verwaltung: cnt-utils
Zur Verwaltung der Container brauchen wir ein Set von Konfigurations- und Verwaltungswerkzeugen. Dieses Set unterteilt sich in 3 Gruppen:
- eine zentrale Konfigurationsdatei (3a)
- Werkzeuge auf der Ebene des Wirts (3b, 3d, 3e)
- Anpassungen auf Seiten der Container (3b, 3c)
3a. Definition von Containern: cnt.conf
Um unsere Containerlandschaft etwas zu strukturieren, erzeugen wir zunächst einmal eine zentrale Konfigurationsdatei, in der wir Informationen über alle Container zusammenfassen. Die cnt-utils erwarten diese unter /usr/local/etc/cnt.conf. Sie können die Umgebungsvariable CNTCONF setzen, um auf eine andere Konfigurationsdatei zu verweisen, z.B.:
export CNTCONF=/etc/containers/cnt.conf
Die Datei cnt.conf besteht aus einem globalen Abschintt und einem Container-Abschnitt. So könnte eine solche Datei z.B. aussehen:
# I. ENVIRONMENT
CNTROOT=/var/lib/.cnt CNTSTART=/root/cstart CNTSTOP=/root/cstop# II. CONTAINER DEFINITIONS
# Container Slackware
CNTNAME[0]=slack CNTIF[0]=eth0:0 CNTIP[0]=192.168.1.8 CNTMASK[0]=255.255.255.0# Container Debian Etch
CNTNAME[1]=debian CNTIF[1]=eth0:1 CNTIP[1]=192.168.1.9 CNTMASK[1]=255.255.255.0# Container openSUSE
CNTNAME[2]=suse CNTIF[2]=eth0:2 CNTIP[2]=192.168.1.10 CNTMASK[2]=255.255.255.0# Container RedHat EL 4
CNTNAME[3]=redhat CNTIF[3]=eth0:3 CNTIP[3]=192.168.1.11 CNTMASK[3]=255.255.255.0
Im globalen Abschnitt wird definiert, wo sich die Container befinden (CNTROOT
) und welches
Programm beim Containerstart und -stopp chroot übergeben wird (CNTSTART
bzw.
CNTSTOP
). Aus CNTROOT
und dem Containernamen (z.B. CNTNAME[0]
für den ersten Container) wird das Verzeichnis
zusammengebaut, in das mit chroot gewechselt wird.
In den Abschnitten zu den jeweiligen Containern werden vier Containereigenschaften definiert. Alle werden durch die Nummer in eckigen Klammern dem jeweiligen Container eindeutig zugeordnet.
Zunächst der Containername CNTNAME
- dieser Name gibt das Verzeichnis unter
CNTROOT
an, in dem sich die Containerwurzel befindet. Alle Utilities aus cnt-utils
referenzieren den Container unter diesem Namen. Dieser Name ist nicht identisch mit dem Hostnamen des
Containers (s.u.).
CNTIF
definiert Netzwerkschnittstelle definiert, auf die
eine IP-Adresse monmtiert wird. Im Beispiel sind das IP-Aliase, doch könnte man auch eine eigene
Netzwerkkarte für einen Container reservieren. In jedem Fall dürfen die Container-IP-Adressen nicht schon
beim Systemstart des Wirts montiert werden, dafür is das Skript start_container zuständig.
IPNAME
definiert die IP-Adresse, die auf der Netzwerkschnittstelle definiert wird, und
CNTMASK
schließlich die zugehörige Netzmaske.
Wie bereits erwähnt, muss der Hostname¹ keineswegs dem Containernamen entsprechen (ist aber natürlich eine denkbare Möglichkeit der Organisation). Im Gegensatz zum Containernamen, der in der Datei cnt.conf konfiguriert wird, wird der Hostname¹ ganz normal über die Datei /etc/hosts oder DNS, NIS, ... gesteuert.
# Linux Container
192.168.1.10 slack.unixwerk.de slack# Slackware
192.168.1.11 debian.unixwerk.de debian# Debian Etch
192.168.1.12 suse.unixwerk.de suse# openSUSE
192.168.1.13 nahant.unixwerk.de nahant# Redhat EL 4
3b. Container starten und stoppen
Im vorigen Abschnitt haben wir nun vier Container definiert. Die besagten cnt-utils enthalten Skripts, mit denen die Container hoch- bzw. heruntergefahren können. Das Startskript lautet auf den Namen start_container
#!/bin/bash
MYNAME=$(basename $0) QUIET="--quiet" START_ALL="no"# Global container config file: # location can be overwritten by the CNTCONF environment variable # ===============================================================
CNTCONF=${CNTCONF:-/usr/local/etc/cnt.conf} [ -r "$CNTCONF" ] && . $CNTCONF CNTROOT=${CNTROOT:-/var/lib/.cnt} CNTSTART=${CNTSTART:-/root/cstart} CONTAINERS=$(sudo ls -d $CNTROOT/*/etc | sed -e "s%$CNTROOT/%%g" -e 's%/etc%%g' )# Find containers to start # ========================
if [ "$START_ALL" = "no" ] then cntuser=${2:-root} CLIST="$(echo $1 | sed -e 's/,/ /g')"# No container specified -> exit
[ -z "$CLIST" ] && exit 1# Get number of containers to start
NI=$(echo $CLIST | wc -w)# Assume quiet mode for multiple container startup
((NI>1)) && QUIET="--quiet" fi for container in $CLIST do if [ ! -x $CNTROOT/$container/$CNTSTART ] then printf "\n No such container -- $container\n\n" continue fi# Activate network interface # ==========================
if [ -r "$CNTCONF" ] then LFN=$(grep -w $container $CNTCONF | cut -d\[ -f2 | cut -d \] -f1) if [ -n "$LFN" ] then /sbin/ifconfig ${CNTIF[$LFN]} ${CNTIP[$LFN]} netmask ${CNTMASK[$LFN]} up fi fi# Bind devices from the host # ==========================
mount | grep ${CNTROOT}/${container}/proc > /dev/null 2>&1 [ "$?" = "0" ] || mount -t proc proc\($container\) ${CNTROOT}/${container}/proc grep -w sysfs ${CNTROOT}/${container}/etc/mtab > /dev/null 2>&1 if [ "$?" = "0" ] then mount | grep ${CNTROOT}/${container}/sys > /dev/null 2>&1 [ "$?" = "0" ] || mount -t sysfs sysfs\($container\) ${CNTROOT}/${container}/sys fi# Start the container # ===================
if [ "M$QUIET" = "M--quiet" ] then chroot ${CNTROOT}/${container} $CNTSTART $QUIET ${cntuser} else exec chroot ${CNTROOT}/${container} $CNTSTART $QUIET ${cntuser} fi done
Sie erkennen darin sicher vieles aus dem ersten Teil unseres Workshops wieder. Dies ist nur der Ausschnitt des funktionalen Teiles meines Skripts aus cnt-utils. Einen Container mit dem Namen debian starten Sie dann mit:
host# start_container debian
Der funktionale Ausschnitt des Skripts stop_container zum Herunterfahren eines Containers:
#!/bin/bash
MYNAME=$(basename $0)# Global container config file: # location can be overwritten by the CNTCONF environment variable # ===============================================================
CNTCONF=${CNTCONF:-/usr/local/etc/cnt.conf} [ -r "$CNTCONF" ] && . $CNTCONF CNTROOT=${CNTROOT:-/var/lib/.cnt} CNTSTOP=${CNTSTOP:-/root/cstop} CONTAINERS=$(sudo ls -d $CNTROOT/*/etc | sed -e "s%$CNTROOT/%%g" -e 's%/etc%%g' )# CNT_CHECK() # ===========
function cnt_check { echo $CONTAINERS | grep -w $container > /dev/null 2>&1 RC=$? [ "$RC" = "0" ] || echo "$MYNAME: No such container -- $container" return $RC }# CNT_STOP() # ==========
function cnt_stop { chroot $CNTROOT/$container/$CNTROOT } [ -z "$CLIST" ] && CLIST="$CONTAINERS" for container in $CLIST do cnt_check || continue echo "Stopping container $container..."# First run the local container stop script # =========================================
cnt_stop && echo Container $container successfully stopped || echo Shutting down container $container failed# Umount container mounts # =======================
TOUMOUNT=$(mount | grep -w $container | grep -v '^/dev/' | awk '{print $3}') for F in $TOUMOUNT do umount $F || mount -o remount,ro $F done# Deactivate network interface # ============================
if [ -r "$CNTCONF" ] then LFN=$(grep -w $container $CNTCONF | cut -d\[ -f2 | cut -d \] -f1) if [ -n "$LFN" ] then /sbin/ifconfig ${CNTIF[$LFN]} down fi fi done
Unser Debian-Container wird dann analog zum Starten heruntergefahren:
host# stop_container debian
start_container ruft im Container ein lokales Startskript, welches in cnt.conf definiert werden kann, normalerweise /root/cstart:
#!/bin/bash # # Container Startscript
STARTSHELL="yes" case "$1" in "-q"|"--quiet"|"quiet") STARTSHELL="no" shift ;; esac startuser=${1:-root}# mount /dev/pts
df | grep -w devpts > /dev/null 2>&1 if [ "$?" = "0" ]; then mount -n devpts else mount | grep -w devpts > /dev/null 2>&1 [ "$?" = "0" ] || mount devpts fi# mount /dev/shm
df | grep -w /dev/shm > /dev/null 2>&1 if [ "$?" = "0" ]; then mount -n /dev/shm else mount | grep -w /dev/shm > /dev/null 2>&1 [ "$?" = "0" ] || mount /dev/shm fi# starts services listed in /etc/ctab at container start
if [ -e /etc/ctab ] then TOSTART=$(grep -v '^#' /etc/ctab | egrep -n '(\:AUTO\:|\:START\:)' | cut -d: -f1) for st in $TOSTART do $(grep -v '^#' /etc/ctab | tail -n +$st | head -1 | cut -d: -f3) done fi# start shell
if [ "$STARTSHELL" = "yes" ] then exec su - $startuser fi
stop_container entsprechend /root/cstop:
#!/bin/bash # stops services listed in /etc/ctab at container shutdown
if [ -e /etc/ctab ] then TOSTOP=$(grep -v '^#' /etc/ctab | egrep -n '(\:AUTO\:|\:STOP\:)' | cut -d: -f1) for st in $TOSTOP do $(grep -v '^#' /etc/ctab | tail -n +$st | head -1 | cut -d: -f4) done fi# umounts all devices mounted at container startup
mount | grep -w devpts > /dev/null 2>&1 [ "$?" = "0" ] && umount devpts mount | grep -w /dev/shm > /dev/null 2>&1 [ "$?" = "0" ] && umount /dev/shm exit 0
3c. Die Container-Inittab: /etc/ctab
Bei genauer Analyse von cstart und cstop werden Sie schon bemerkt habe, dass eine Datei /etc/ctab ausgewertet wird. Da der gesamte init-Prozess im Container nicht ausgeführt wird, laufen zunächst auch keine Dienste. Der eine oder andere Dienst soll im Container aber laufen. Dies kann über die Datei /etc/ctab gesteuert werden. Die folgende ctab startet den ssh-Dämon und den Apache-Webserver:
chroot# cat /etc/ctab
#
# Container inittab
#
# aml, 2007-08-26
#
# Format is:
# <label>:<AUTO|START|STOP>:<start command>:<stop command>
SSH:AUTO:/etc/init.d/ssh start:/etc/init.d/ssh stop
HTTPD:AUTO:/etc/init.d/apache start:/etc/init.d/apache stop
Pro Service wird eine Zeile hinzugefügt, an erster Position steht ein frei wählbares Label, an zweiter
eines der Schlüsselwörter 'AUTO
', 'START
' oder 'STOP
'.
Steht in diesem Feld das Schlüsselwort 'START
' oder 'AUTO
' wird beim
Containerstart das Kommando aus dem dritten Feld ausgeführt.
Beim Herunterfahren wird das Kommando aus dem vierten Feld ausgeführt, sofern eines der Schlüsselwörter
'STOP
' oder 'AUTO
' an zweiter Position steht.
Dabei ist zu beachten: Bei der Verwendung der obigen Start- und Stoppskripts dürfen keine Dateiumleitungen
oder Shellkonstrukte verwendet werden. Solcherlei Dinge verpacken Sie in ein Shellskript, das dann an
der Stelle des Kommandos in der Datei /etc/ctab verankert wird.
3d. Container-Status: cstat
Die cnt-utils enthalten auch ein Skript namens cstat, das einen Überblick über die Stati unserer Container gibt:
#!/bin/bash
MYNAME=$(basename $0)# Global container config file: # location can be overwritten by the CNTCONF environment variable # ===============================================================
CNTCONF=${CNTCONF:-/usr/local/etc/cnt.conf} [ -r "$CNTCONF" ] && . $CNTCONF || exit CNTROOT=${CNTROOT:-/var/lib/.cnt} CONTAINERS=$(sudo ls -d $CNTROOT/*/etc | sed -e "s%$CNTROOT/%%g" -e 's%/etc%%g')# FINFO() # =======
function finfo { BR=$(hostname -d) [ -r $CNTROOT/$v/etc/vhostname ] && B1=$(cat $CNTROOT/$v/etc/vhostname) || { B1="NOT CONFIGURED" ; BR="" ; } mount | fgrep "proc($v)" >/dev/null 2>&1 [ "$?" = "0" ] && STATE="RUNNING" || STATE="DOWN" if [ "$STATE" = "DOWN" ] then if [ "$(grep -w $v $CNTCONF)" = "" ] then STATE="NOT_CFG" fi fi if [ -r $CNTROOT/$v/etc/slax-version ]; then LX_RELEASE="$(cat $CNTROOT/$v/etc/slax-version)" elif [ -r $CNTROOT/$v/etc/zenwalk-version ]; then LX_RELEASE="$(cat $CNTROOT/$v/etc/zenwalk-version)" elif [ -r $CNTROOT/$v/etc/slackware-version ]; then LX_RELEASE="$(cat $CNTROOT/$v/etc/slackware-version)" elif [ -r $CNTROOT/$v/etc/SuSE-release ]; then LX_RELEASE="$(head -1 $CNTROOT/$v/etc/SuSE-release)" elif [ -r $CNTROOT/$v/etc/fedora-release ]; then LX_RELEASE="$(cat $CNTROOT/$v/etc/fedora-release | sed -e s'/release //')" elif [ -r $CNTROOT/$v/etc/redhat-release ]; then LX_RELEASE="$(cat $CNTROOT/$v/etc/redhat-release | sed -e 's/Enterprise Linux //' -e s'/release //')" elif [ -r $CNTROOT/$v/etc/lsb-release ]; then LX_RELEASE="$(cat $CNTROOT/$v/etc/lsb-release | grep DISTRIB_DESCRIPTION | cut -d\" -f2)" elif [ -r $CNTROOT/$v/etc/debian_version ]; then LX_RELEASE="Debian $(cat $CNTROOT/$v/etc/debian_version)" else LX_RELEASE="$(cat $CNTROOT/$v/etc/*release $CNTROOT/$v/etc/*version | sort | tail -1)" fi printf " %-24s %-26s %-9s %s\n" "$CNTROOT/$v" "$B1.$BR" "$STATE" "$LX_RELEASE" }# MAIN() # ======
printf "\n %-24s %-26s %-9s %s" "Container" "Hostname" "State" "Description" printf "\n ------------------------------------------------------------------------------\n" for v in $CONTAINERS do finfo done | sed -e 's/ (.*)//' echo
Die Ausgabe könnte dann etwa so aussehen:
host$ cstat Container Hostname State Description ------------------------------------------------------------------------------ /var/lib/.cnt/centos centos.unixwerk.de DOWN CentOS 5 /var/lib/.cnt/debian debian.unixwerk.de RUNNING Debian 4.0r1 /var/lib/.cnt/fedora fedora.unixwerk.de DOWN Fedora Core 4 /var/lib/.cnt/rhel4 nahant.unixwerk.de DOWN Red Hat WS 4 /var/lib/.cnt/novell NOT CONFIGURED. NOT_CFG SuSE Linux 9.3 /var/lib/.cnt/slack NOT CONFIGURED. NOT_CFG Slackware 11.0.0 /var/lib/.cnt/slax slax.unixwerk.de DOWN SLAX 5.0.7 /var/lib/.cnt/suse suse.unixwerk.de DOWN openSUSE 10.2 /var/lib/.cnt/rhel3 taroon.unixwerk.de DOWN Red Hat ES 3 /var/lib/.cnt/ubuntu ubuntu.unixwerk.de DOWN Ubuntu 6.10 /var/lib/.cnt/zenwalk zenwalk.unixwerk.de RUNNING Zenwalk 2.2
Im Beispiel sehen wir neben den Stati 'DOWN
' und 'RUNNING
' auch den Status
'NOT_CFG
'. Diesen enthält ein Container, wenn ein Containerdateisystem zwar vorhanden ist,
aber der entsprechende Container nicht in der Datei cnt.conf auf dem Wirt eingetragen ist.
Den Hostnamen (ohne Domainanteil) des Containers liest cstat aus der Datei /etc/vhostname
des Containers. Ist diese Datei nicht vorhanden, erscheint unter cstat dort
'NOT CONFIGURED.
'.
Die Datei /etc/vhostname kann auch für den Shellprompt genutzt werden, damit Sie gleich sehen, dass Sie sich am Container angemeldet haben und nicht am Wirt. Tragen Sie folgendes in Ihre .bashrc ein:
chroot# vi .bashrc VHOSTNAME=$(cat /etc/vhostname) PS1=$VHOSTNAME':\w\$ '
3e. Datenaustausch zwischen Wirt und Containern
Ist ein Container erstmal konfiguriert und gestartet, können Sie Daten bequem über scp zwischen Container und Wirt, aber auch zwischen Containern untereinander austauschen.
In der Einrichtungsphase oder wenn ein Container einfach heruntergefahren ist, geht diese natürlich nicht. Die Verzeichnisse sind vom Wirt aus aber natürlich zugänglich und so können Sie z.B. die Host-Tabelle ihres Wirts unter Angabe des kompletten Pfadnamens auf Ihren Debian-Container kopieren:
host# cp /etc/hosts /var/lib/.cnt/debian/etc
Das geht natürlich auch in die andere Richtung, ist aber umständlich. Zwei kleine Skripts funktionieren ähnlich wie scp. Wollen wir eine Datei vom Wirt auf den Container kopieren nutzen wir cput:
Die obige Kopieraktion wird dann zu:#!/bin/bash
MYNAME=$(basename $0)# Global container config file: # location can be overwritten by the CNTCONF environment variable # ===============================================================
CNTCONF=${CNTCONF:-/usr/local/etc/cnt.conf} [ -r "$CNTCONF" ] && . $CNTCONF CNTROOT=${CNTROOT:-/var/lib/.cnt} CONTAINERS=$(sudo ls -d $CNTROOT/*/etc | sed -e "s%$CNTROOT/%%g" -e 's%/etc%%g' ) TARGET=$(echo $TARGET | sed -e "s§:~root/§:root/§") TARGET=$(echo $TARGET | sed -e "s§:~\([a-z][a-z0-9]*\)/§:home/\1/§") TARGET=$(echo $TARGET | sed -e "s§:~§:$HOME§") TARGET=$(echo $TARGET | sed -e "s§:~§:$HOME§") CNT=$(echo $TARGET | cut -d: -f1) DEST=$(echo $TARGET | sed -e "s§^$CNT:§§") sudo cp -p $FILES $CNTROOT/$CNT/$DEST
host# cput /etc/hosts debian:/etc
Wollen wir eine Datei vom Container auf den Wirt kopieren, tun wir dies mit cget:
#!/bin/bash
MYNAME=$(basename $0)# Global container config file: # location can be overwritten by the CNTCONF environment variable # ===============================================================
CNTCONF=${CNTCONF:-/usr/local/etc/cnt.conf} [ -r "$CNTCONF" ] && . $CNTCONF CNTROOT=${CNTROOT:-/var/lib/.cnt} CONTAINERS=$(sudo ls -d $CNTROOT/*/etc | sed -e "s%$CNTROOT/%%g" -e 's%/etc%%g' ) ORIGIN=$1 shift case $ORIGIN in *:*) true ;; *) exit 1 ;; esac ORIGIN=$(echo $ORIGIN | sed -e "s§:~root/§:root/§") ORIGIN=$(echo $ORIGIN | sed -e "s§:~\([a-z][a-z0-9]*\)/§:home/\1/§") ORIGIN=$(echo $ORIGIN | sed -e "s§:~§:$HOME§") ORIGIN=$(echo $ORIGIN | sed -e "s§:~§:$HOME§") CNT=$(echo $ORIGIN | cut -d: -f1) SRC=$(echo $ORIGIN | sed -e "s§^$CNT:§§") DEST="$1" sudo sh -c "cp $CNTROOT/$CNT/$SRC $DEST"
So können Sie zum Beispiel die /etc/ctab Ihres Containers auf den Wirt kopieren:
host# cget debian:/etc/ctab /tmp
Alles, was für den Datentransfer gilt, kann auch auf das Entern eines Contaners angewandt werden. Läuft der Container, ist ssh der beste Weg. Doch was, wenn der Container nicht läuft? chroot geht natürlich immer:
host# chroot /var/lib/.cnt/debian
Doch auch hier können wir ein kleines Skript erstellen, das uns dies erleichtert, nennen wir es cshell:
#!/bin/bash # Global container config file: # location can be overwritten by the CNTCONF environment variable # ===============================================================
CNTCONF=${CNTCONF:-/usr/local/etc/cnt.conf} [ -r "$CNTCONF" ] && . $CNTCONF CNTROOT=${CNTROOT:-/var/lib/.cnt} CONTAINERS=$(sudo ls -d $CNTROOT/*/etc | sed -e "s%$CNTROOT/%%g" -e 's%/etc%%g' )# Find container to start # =======================
cntuser=${2:-root} container=$1 [ -z "$container" ] && exit 1# Start a shell in the container # ==============================
exec chroot ${CNTROOT}/${container} su - ${cntuser}
Der Container lässt sich nun so erreichen:
host# cshell debian
Einige der hier vorgestellten Skripts nutzen sudo. Es kann sich also lohnen, sudo zu konfigurieren.
Möchten Sie nur als root arbeiten, sollten die sudo-Aufrufe nicht weiter stören, sudo muss aber installiert
sein
4. Grenzen der chroot-Container
Die komplette Konstruktion der Containerlandschaft beruht auf nichts weiter als dem chroot-Befehl. Alles drumherum sind nichts weiter als ein paar Shellskripts, die etwas Struktur in die Landschaft bringen. Es bestehen keinerlei Anforderungen an das Wirtssystem - wir arbeiten mit einem ganz normalen ungepatchten Kernel, wir benutzen keinerlei spezielle Systembibliotheken.
Dies hat zunächst große Vorteile:
- Das Wirtssystem kann ganz normal mit Distributionswerkzeugen aktuell gehalten werden
- Da ein einziger Kernel alle Prozesse sowohl des Wirts als auch aller Container kontrolliert, erhalten wir eine natürliche Ressourcenzuteilung an die Container, die sie benötigen.
- Es gibt keinerlei Overhead, der die Performance des Gesamtsystems nach unten zieht. Auf meinem Laptop mit einem ganz normalen AMD Sempron und und nicht einmal einem halben Gigabyte Hauptspeicher laufen zur Zeit nicht weniger als 11 Container.
Doch neben den großen Vorteilen gibt es auch Nachteile, die sich daraus ergeben, dass ein einziger Kernel alle 12 Linux-Installationen kontrolliert. Der größte Nachteil ist die gemeinsame Prozesstabelle, die bewirkt, dass jeder Container die Prozesse aller anderen Container und des Wirtssystems sieht². So würde ein killall sshd in einem der Container die ssh-Dämonen aller Container und unseres Wirtssystems gleich mit abschießen. Die /etc/ctab der Container trägt dem Rechnung - doch interaktiv gestartete Prozesse (etwa ein kdm) sind schwer wieder sauber zu beenden.
Um dies Problem zu lösen, muss ein Kernelpatch her. Ein ähnliches Containerkonzept mit einem solchen Kernelpatch gepaart verfolgt das Linux VServer-Projekt.
Die Container-Skripts als tar-Ball
Diesen Text verstehe ich als Anregung, sich mit den Möglichkeiten von chroot-Containern einmal näher zu beschäftigen. Vieles lässt sich sicherlich noch viel besser lösen. Für den Fall, dass Sie sich für das hier entwickelte Framework interessiern, habe ich die Skripts zusammen mit einigen Beispieldateien für verschiedene Linux-Distributionen als Tar-Ball zusammengestellt:
Bitte beachten Sie, dass dies lediglich ein Tar-Ball ist und kein Slackware-Paket! Sie können es zum Beispiel ins Verzeichnis /usr/local entpacken:
# tar xvjf cnt-utils-0.9.1.tar.bz2 -C /usr/local
Um die enthaltenen Manual-Pages zu lesen, müssen Sie möglicherweise die MANPATH-Variable setzen:
# export MANPATH=$MANPATH:/usr/local/share/man
5. Weiterführende Informationen
Teil I: Installation und erste Schritte