Un petit article pour décrire le fonctionnement de LXC (LinuX Containers) et son utilisation sur une Debian Wheezy.
J’avais commencé ce blog en parlant de Linux Vserver, un système de « virtualisations au niveau du système d’exploitation ».
Ça m’a bien rendu service, mais vserver n’est plus intégré à la Debian Wheezy, il a été remplacé par LXC, hors il fallait bien que je fasse la mise à jour de mes Squeeze.
Je vais faire court car il existe de nombreuses docs sur LXC.
On commence par mettre à jour la Debian (sauf exceptions, il vaut mieux utiliser aptitude que apt-get) :
aptitude update && aptitude full-upgrade
On installe le paquet LXC :
aptitude install lxc
Je rajoute aussi le paquet bridge-utils, mais il n’est pas obligatoire.
Dans /etc/fstab, on ajoute un point de montage pour gérer les ressources :
cgroup /sys/fs/cgroup cgroup defaults 0 0
Attention, il y a un bug dans le script de création des premières Wheezy (corrigé en 7.4 de mémoire).
Le plus simple est de créer un nouveau fichier de modèle, je me suis librement inspiré de celui proposé ici.
Voici mon template /usr/share/lxc/templates/lxc-debian-wheezy :
#!/bin/bash #Debian Wheezy LXC template configure_debian() { rootfs=$1 hostname=$2 # squeeze only has /dev/tty and /dev/tty0 by default, # therefore creating missing device nodes for tty1-4. for tty in $(seq 1 4); do if [ ! -e $rootfs/dev/tty$tty ]; then mknod $rootfs/dev/tty$tty c 4 $tty fi done # configure the inittab cat <<EOF > $rootfs/etc/inittab id:3:initdefault: si::sysinit:/etc/init.d/rcS l0:0:wait:/etc/init.d/rc 0 l1:1:wait:/etc/init.d/rc 1 l2:2:wait:/etc/init.d/rc 2 l3:3:wait:/etc/init.d/rc 3 l4:4:wait:/etc/init.d/rc 4 l5:5:wait:/etc/init.d/rc 5 l6:6:wait:/etc/init.d/rc 6 # Normally not reached, but fallthrough in case of emergency. z6:6:respawn:/sbin/sulogin 1:2345:respawn:/sbin/getty 38400 console c1:12345:respawn:/sbin/getty 38400 tty1 linux c2:12345:respawn:/sbin/getty 38400 tty2 linux c3:12345:respawn:/sbin/getty 38400 tty3 linux c4:12345:respawn:/sbin/getty 38400 tty4 linux EOF # disable selinux in debian mkdir -p $rootfs/selinux echo 0 > $rootfs/selinux/enforce # configure the network cat <<EOF > $rootfs/etc/network/interfaces auto lo iface lo inet loopback EOF # set the hostname cat <<EOF > $rootfs/etc/hostname $hostname EOF # reconfigure timezone echo "Europe/Paris" > $rootfs/etc/timezone chroot $rootfs dpkg-reconfigure -f noninteractive tzdata # reconfigure language sed -i "s/^# en_US.UTF-8/en_US.UTF-8/" $rootfs/etc/locale.gen chroot $rootfs locale-gen # remove pointless services in a container chroot $rootfs /usr/sbin/update-rc.d -f umountfs remove chroot $rootfs /usr/sbin/update-rc.d -f hwclock.sh remove chroot $rootfs /usr/sbin/update-rc.d -f hwclockfirst.sh remove echo "root:root" | chroot $rootfs chpasswd echo "Root password is 'root', please change !" return 0 } download_debian() { packages= ifupdown, locales, libui-dialog-perl, dialog, netbase, net-tools, iproute, vim, bash-completion, locales, iputils-ping, aptitude cache=$1 arch=$2 # check the mini debian was not already downloaded mkdir -p "$cache/partial-$arch" if [ $? -ne 0 ]; then echo "Failed to create '$cache/partial-$arch' directory" return 1 fi # download a mini debian into a cache echo "Downloading debian minimal ..." debootstrap --verbose --variant=minbase --arch=$arch --include $packages wheezy $cache/partial-$arch http://ftp.debian.org/debian if [ $? -ne 0 ]; then echo "Failed to download the rootfs, aborting." return 1 fi mv "$1/partial-$arch" "$1/rootfs-$arch" echo "Download complete." return 0 } copy_debian() { cache=$1 arch=$2 rootfs=$3 # make a local copy of the minidebian echo -n "Copying rootfs to $rootfs..." cp -a $cache/rootfs-$arch $rootfs || return 1 return 0 } install_debian() { cache="/var/cache/lxc/debian-wheezy" rootfs=$1 mkdir -p /var/lock/subsys/ ( flock -n -x 200 if [ $? -ne 0 ]; then echo "Cache repository is busy." return 1 fi arch=$(arch) if [ "$arch" == "x86_64" ]; then arch=amd64 fi if [ "$arch" == "i686" ]; then arch=i386 fi if [ "$arch" == "armv5tel" ]; then arch=armel fi echo "Checking cache download in $cache/rootfs-$arch ... " if [ ! -e "$cache/rootfs-$arch" ]; then download_debian $cache $arch if [ $? -ne 0 ]; then echo "Failed to download 'debian base'" return 1 fi fi copy_debian $cache $arch $rootfs if [ $? -ne 0 ]; then echo "Failed to copy rootfs" return 1 fi return 0 ) 200>/var/lock/subsys/lxc return $? } copy_configuration() { path=$1 rootfs=$2 name=$3 cat <<EOF >> $path/config lxc.tty = 4 lxc.pts = 1024 lxc.rootfs = $rootfs lxc.cgroup.devices.deny = a # /dev/null and zero lxc.cgroup.devices.allow = c 1:3 rwm lxc.cgroup.devices.allow = c 1:5 rwm # consoles lxc.cgroup.devices.allow = c 5:1 rwm lxc.cgroup.devices.allow = c 5:0 rwm lxc.cgroup.devices.allow = c 4:0 rwm lxc.cgroup.devices.allow = c 4:1 rwm # /dev/{,u}random lxc.cgroup.devices.allow = c 1:9 rwm lxc.cgroup.devices.allow = c 1:8 rwm lxc.cgroup.devices.allow = c 136:* rwm lxc.cgroup.devices.allow = c 5:2 rwm # rtc lxc.cgroup.devices.allow = c 254:0 rwm # mounts point lxc.mount.entry=proc $rootfs/proc proc nodev,noexec,nosuid 0 0 lxc.mount.entry=devpts $rootfs/dev/pts devpts defaults 0 0 lxc.mount.entry=sysfs $rootfs/sys sysfs defaults 0 0 # networking lxc.utsname = $name lxc.network.type = veth lxc.network.flags = up lxc.network.link = br0 lxc.network.ipv4 = 10.1.1.2/24 lxc.network.ipv4.gateway = 10.1.1.1 lxc.network.hwaddr = 00:1E:$(hex):$(hex):$(hex):$(hex) EOF if [ $? -ne 0 ]; then echo "Failed to add configuration" return 1 fi return 0 } # nice trick from: http://mindref.blogspot.com/2011/01/debian-lxc-create.html hex() { echo "`tr -dc A-F0-9 < /dev/urandom | head -c 2 | xargs`" } clean() { cache="/var/cache/lxc/debian-wheezy" if [ ! -e $cache ]; then exit 0 fi # lock, so we won't purge while someone is creating a repository ( flock -n -x 200 if [ $? != 0 ]; then echo "Cache repository is busy." exit 1 fi echo -n "Purging the download cache..." rm --preserve-root --one-file-system -rf $cache && echo "Done." || exit 1 exit 0 ) 200>/var/lock/subsys/lxc } usage() { cat <<EOF $1 -h|--help -p|--path=<path> --clean EOF return 0 } options=$(getopt -o hp:n:c -l help,path:,name:,clean -- "$@") if [ $? -ne 0 ]; then usage $(basename $0) exit 1 fi eval set -- "$options" while true do case "$1" in -h|--help) usage $0 && exit 0;; -p|--path) path=$2; shift 2;; -n|--name) name=$2; shift 2;; -c|--clean) clean=$2; shift 2;; --) shift 1; break ;; *) break ;; esac done if [ ! -z "$clean" -a -z "$path" ]; then clean || exit 1 exit 0 fi type debootstrap if [ $? -ne 0 ]; then echo "'debootstrap' command is missing" exit 1 fi if [ -z "$path" ]; then echo "'path' parameter is required" exit 1 fi if [ "$(id -u)" != "0" ]; then echo "This script should be run as 'root'" exit 1 fi rootfs=$path/rootfs install_debian $rootfs if [ $? -ne 0 ]; then echo "failed to install debian" exit 1 fi configure_debian $rootfs $name if [ $? -ne 0 ]; then echo "failed to configure debian for a container" exit 1 fi copy_configuration $path $rootfs $name if [ $? -ne 0 ]; then echo "failed write configuration file" exit 1 fi if [ ! -z $clean ]; then clean || exit 1 exit 0 fi
Attention à la ligne 65, il faut bien évidemment changer le mot de passe, soit dans le template lui-même, soit dans le conteneur une fois déployé.
Pour le reste, libre à vous d’adapter le script, en particulier les lignes 74 à 85 (paquets installés par défaut) et les lignes 179 à 212 qui contiennent le paramétrage du conteneur, en particulier la configuration réseau :
lxc.network.type = veth #type réseau pour le conteneur (empty/veth/vlan/macvlan/phys) lxc.network.flags = up #la carte monte au démarrage lxc.network.link = br0 #elle est attachée à l'interface br0 de l'hôte lxc.network.ipv4 = 10.1.1.2/24 #son IP/masque lxc.network.ipv4.gateway = 10.1.1.1 #le routeur par défaut lxc.network.hwaddr = 00:1E:$(hex):$(hex):$(hex):$(hex) #une adresse physique aléatoire
Le paquet LXC vient avec toute une panoplie d’outils de gestion des conteneurs, entre autre :
- lxc-create – déployer un conteneur
- lxc-ls – lister les conteneurs déployés
- lxc-list – lister le status des conteneurs
- lxc-start – démarrer un conteneur
- lxc-stop – arrêter un conteneur
- lxc-console – « entrer » dans un conteneur
- lxc-destroy – supprimer un conteneur
- Par exemple pour créer un conteneur :
lxc-create -n mon.premier.conteneur -t debian-wheezy
- On peut modifier sa configuration en éditant le fichier :
/var/lib/lxc/mon.premier.conteneur/config
- On démarre le conteneur avec :
lxc-start -n mon.premier.conteneur
- On se connecte à la console avec :
lxc-console mon.premier.conteneur
Pour quitter la console il faut faire CTRL+a, q
- On arrête le conteneur avec :
lxc-stop -n mon.premier.conteneur
- On le supprime avec :
lxc-destroy -n mon.premier.conteneur
- Pour qu’un conteneur démarre avec l’hôte, il suffit de créer un lien vers son fichier « config » dans /etc/lxc/auto/ :
ln -s /var/lib/lxc/mon.premier.conteneur/config /etc/lxc/auto/mon.premier.conteneur
Quelques recommandations pour terminer :
- l’utilisation de LVM est fortement recommandée, ne serait ce que pour faire des sauvegardes à chaud;
- il vaut mieux laisser les conteneurs dans l’emplacement par défaut (/var/lib/lxc),
- donc il est recommandé de faire un point de montage sur ce dossier