Archives de catégorie : LXC

LXC – virtualisation/conteneur

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
Sources :