GästebuchIhr Eintrag in unser Gästebuch KontaktNehmen Sie Kontakt mit den Autoren auf ArchivAlle Unixwerk- Artikel seit 2003
10. November 2009

Linux im chroot-Container

Teil II: Erweiterte Konfiguration

Inhalt

  1. Einleitung
  2. Netzwerkzugang zum chroot-Container
    1. ssh für Container konfigurieren
    2. Apache im Container
  3. Container-Verwaltung: cnt-utils
    1. Definition von Containern: cnt.conf
    2. Container starten und stoppen
    3. Die Container-Inittab: /etc/ctab
    4. Container-Status: cstat
    5. Datenaustausch zwischen Wirt und Containern
  4. Grenzen der chroot-Container
  5. 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.10
ab, 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
°Das Beispiel gilt etwa für Debian, andere Beispiele sind /etc/init.d/sshd restart oder /etc/rc.d/rc.sshd 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:

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
¹Genaugenommen ist der Name der IP-Adresse des Containers nicht der Hostname, wie ihn der Befehl hostname zurückliefern würde. Dieser liefert nämlich immer den Hostnamen des Wirts zurück. Eine Konsequenz daraus, dass sich Wirt und Container einen Kernel teilen.

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:

#!/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
Die obige Kopieraktion wird dann zu:
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:

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.

²Dies gilt für unser hier entwickeltes Containerkonzept, in dem wir das /proc-Verzeichnis in die Container importieren. Für einzelne unsichere Dienste in einer chroot-Umgebung, wie etwa tftp, ist diese nicht nötig - der Container hat dann einfach keinen Zugriff auf die Prozesstabelle.

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:

zucnt-utils-0.9.1.tar.bz2

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

zu Teil I: Installation und erste Schritte