LXC on Debian

19 minute read , Mar 08, 2016

Install packages:

root@debian01:~# aptitude install bridge-utils vlan debconf-utils

Setup the network bridge br0:

root@debian01:~# vi /etc/network/interfaces 
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto br0
iface br0 inet static
  bridge_ports eth0
  bridge_fd 0
  address 192.168.100.88 
  netmask 255.255.255.0
  gateway 192.168.100.1
  dns-nameservers 192.168.100.1

And restart networking:

root@debian01:~# service networking restart

Next we install LXC:

root@debian01:~# aptitude install lxc

and setup CGroups:

root@debian01:~# mkdir /cgroup
root@debian01:~# vi /etc/fstab
...
# LXC cgroup
cgroup        /cgroup        cgroup        defaults    0    0

NOTE: default is to mount under /sys/fs/cgroup

root@debian01:~# mount -a

Now run lxc-checkconfig utility to confirm if all requirements are satisfied:

root@debian01:~# lxc-checkconfig
Kernel config /proc/config.gz not found, looking in other places...
Found kernel config file /boot/config-3.2.0-4-amd64
--- Namespaces ---
Namespaces: enabled
Utsname namespace: enabled
Ipc namespace: enabled
Pid namespace: enabled
User namespace: enabled
Network namespace: enabled
Multiple /dev/pts instances: enabled

--- Control groups ---
Cgroup: enabled
Cgroup clone_children flag: enabled
Cgroup device: enabled
Cgroup sched: enabled
Cgroup cpu account: enabled
Cgroup memory controller: enabled
Cgroup cpuset: enabled

--- Misc ---
Veth pair device: enabled
Macvlan: enabled
Vlan: enabled
File capabilities: enabled

Note : Before booting a new kernel, you can check its configuration
usage : CONFIG=/path/to/config /usr/bin/lxc-checkconfig

If Cgroup memory controller is not enabled and we want to have memory management in the containers we need to recompile the kernel and enable this feature:

root@debian01:~# aptitude install build-essential kernel-package linux-source-3.2 libncurses5-dev libc6-dev zlib1g-dev
root@debian01:~# cd /usr/src
root@debian01:/usr/src# tar -xjvf linux-source-3.2.tar.bz2
root@debian01:/usr/src# ln -sf linux-source-3.2 linux
root@debian01:/usr/src# ls -l
root@debian01:/usr/src# cd linux
root@debian01:/usr/src/linux# make clean
root@debian01:/usr/src/linux# make mrproper
root@debian01:/usr/src/linux# cp /boot/config-$(uname -r) .config
root@debian01:/usr/src/linux# make menuconfig
  • Navigate to “Load Alternate Configuration File”.
  • Type in “.config” (without “) and hit enter.
  • Go to “General Setup —>” (enter)
  • Go to “Control Group support —>” (enter)
  • Go to “Resource Counters”, enable it (space)
  • A subentry arises, enable: “Memory Resource Controller for Control Groups”
  • Another subentry, enable: “Memory Resource Controller Swap” (this is optional and not “so” important.. if you are scared off by the experimental remark: dont activate it).
  • Now go to Exit (three or four times) and say “Yes” when it asks you whether to save the configuration.
root@debian01:/usr/src/linux# make-kpkg clean
exec make kpkg_version=12.036+nmu3 -f /usr/share/kernel-package/ruleset/minimal.mk clean 
====== making target minimal_clean [new prereqs: ]======
This is kernel package version 12.036+nmu3.
test ! -f .config || cp -pf .config config.precious
test ! -e stamp-building || rm -f stamp-building
test ! -f Makefile || \
            make    ARCH=x86_64 distclean
make[1]: Entering directory `/usr/src/linux-source-3.2'
  CLEAN   scripts/basic
  CLEAN   scripts/kconfig
  CLEAN   include/config include/generated
  CLEAN   .config .config.old
make[1]: Leaving directory `/usr/src/linux-source-3.2'
test ! -f config.precious || mv -f config.precious .config
rm -f modules/modversions.h modules/ksyms.ver scripts/cramfs/cramfsck scripts/cramfs/mkcramfs

root@debian01:/usr/src/linux# make-kpkg --append-to-version "-cgroup-memcap" --revision 1 --us --uc --initrd kernel_image kernel_headers
root@debian01:/usr/src/linux# dpkg -i linux-image-3.2.32-cgroup-memcap_1_i386.deb
root@debian01:/usr/src/linux# mkinitramfs -o /boot/initrd.img-3.2.32-cgroup-memcap 3.2.32-cgroup-memcap
root@debian01:/usr/src/linux# update-grub2
root@debian01:/usr/src/linux# reboot

IMPORTANT: Debian Wheezy LXC template is broken and the above procedure will NOT work

Download the one from http://freedomboxblog.nl/wp-content/uploads/lxc-debian-wheezy.gz and unpack it under /usr/share/lxc/templates/ (lxc-debian-wheezy file). Then create the containers as follows:

$ sudo chmod +x /usr/share/lxc/templates/lxc-debian-wheezy
$ sudo lxc-create -n wheezy01 -t debian-wheezy
$ sudo lxc-create -n ubuntu01 -t ubuntu --fssize 8G --fstype xfs
$ sudo lxc-start -n wheezy01		# login root/root
$ sudo lxc-start -n ubuntu01		# login ubuntu/ubuntu

Configure the first container

root@debian01:~# vi /var/lib/lxc/vm0/config
...
## Limits
lxc.cgroup.cpu.shares                  = 512
lxc.cgroup.cpuset.cpus                 = 0
lxc.cgroup.memory.limit_in_bytes       = 256M
lxc.cgroup.memory.memsw.limit_in_bytes = 512M
...
# MY ADDED CONFIG
# networking
lxc.utsname = wheezy01 
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.veth.pair = vethvm1_0
lxc.network.hwaddr = 00:FF:12:34:56:78
lxc.network.ipv4 = 192.168.100.89/24

lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br1
lxc.network.name = eth1
lxc.network.veth.pair = vethvm1_1
lxc.network.hwaddr = 00:FF:FF:11:22:33
lxc.network.ipv4 = 10.10.1.10/24

root@debian01:~# lxc-start -n vm0 -d
root@debian01:~# lxc-info -n vm0
state:   RUNNING
pid:     28068

root@debian01:~# lxc-console -n vm0

To generate random MAC address that we can use in the above config:

$ printf 'DE:AD:BE:EF:%02X:%02X\n' $((RANDOM%256)) $((RANDOM%256))

Setup private network between containers

Static IP’s

root@debian01:~# aptitude install uml-utilities
root@debian01:~# tunctl -t tap0
root@debian01:~# brctl addbr br1
root@debian01:~# brctl addif br1 tap0
root@debian01:~# brctl show
bridge name	bridge id		STP enabled	interfaces
br0		8000.5254001d993e	no		eth0
br1		8000.528a275a4139	no		tap0

root@debian01:~# vi /etc/network/interfaces
...
# Containers external network bridge
auto br0
iface br0 inet static
  bridge_ports eth0
  bridge_fd 0
  address 192.168.100.88 
  netmask 255.255.255.0
  gateway 192.168.100.1
  dns-nameservers 192.168.100.1 

# Containers virtual private network bridge
auto br1
iface br1 inet static
  address 10.10.1.1
  netmask 255.255.255.0
  bridge_ports tap0
  bridge_fd 0
  pre-up /usr/sbin/tunctl -t tap0
  pre-up /sbin/ifconfig tap0 up
  post-down /sbin/ifconfig tap0 down

Another option for setting the tap interface is using the “vde2” driver. In that case the config would be:

$ sudo aptitude install vde2

Then setup in /etc/network/interfaces:

# Virtual interface
auto tap0
iface tap0 inet manual
  vde2-switch -t tap0

# Containers virtual private network bridge
auto br1
iface br1 inet static
  bridge-ports tap0
  bridge_fd 0
  address 10.10.1.1
  netmask 255.255.255.0

Restart networking:

root@debian01:~# service networking restart

and add a new interface in the container:

# MY ADDED CONFIG
## Network
lxc.utsname = wheezy01
lxc.network.type = veth
lxc.network.veth.pair = vethvm1_0
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.hwaddr = 00:FF:12:34:56:78
lxc.network.ipv4 = 192.168.100.89/24

lxc.network.type = veth
lxc.network.veth.pair = vethvm1_1
lxc.network.flags = up
lxc.network.link = br1
lxc.network.name = eth1
lxc.network.hwaddr = 00:FF:FF:11:22:33
lxc.network.ipv4 = 10.10.1.10/24

Then in the containers we have to set the default gateway to get Internet access:

root@wheezy01:~# ip route add default via 192.168.100.1 dev eth0
root@wheezy02:~# ip route add default via 192.168.100.1 dev eth0

in case the default route is missing.

DHCP

In case we want to have a DHCP service on the private network instead of static IP’s, we can use dnsmasq as DHCP server and DNS forwarder.

First we install dnsmasq and stop the service and disable its autostart:

root@debian01:~# aptitude install dnsmasq
root@debian01:~# vi /etc/default/dnsmasq
..
START=no

root@debian01:~# service dnsmasq stop

then we create the user the service will run under and the pid file directory:

root@debian01:~# useradd -c "LXC dnsmasq" -s /bin/false -d /var/lib/lxc -m lxc-dnsmasq
root@debian01:~# mkdir /var/run/lxc

and start the particular dnsmasq service:

root@debian01:~# dnsmasq -u lxc-dnsmasq --strict-order --bind-interfaces --pid-file=/var/run/lxc/dnsmasq.pid --conf-file= --listen-address 10.10.1.1 --dhcp-range 10.10.1.2,10.10.1.49 --dhcp-lease-max=253 --dhcp-no-override --except-interface=lo --interface=br1

in this way we can have many dnsmasq processes listening on different ports.

Now we can remove the lxc.network.ipv4 = 10.10.1.10/24 from the containers config, example for wheezy02, and set its eth1 in its /etc/network/interfaces for DHCP:

auto eth1
iface eth1 inet dhcp

After restarting the container we can see in the host syslog:

Jan 31 00:38:50 debian01 dnsmasq-dhcp[17625]: DHCPREQUEST(br1) 10.10.1.6 00:ff:ff:11:22:44 
Jan 31 00:38:50 debian01 dnsmasq-dhcp[17625]: DHCPACK(br1) 10.10.1.6 00:ff:ff:11:22:44 wheezy02

the wheezy02 container asking for IP and getting 10.10.1.6 in response which can be confirmed on the container it self:

root@wheezy02:~# ifconfig eth1
eth1      Link encap:Ethernet  HWaddr 00:ff:ff:11:22:44
          inet addr:10.10.1.6  Bcast:10.10.1.255  Mask:255.255.255.0
          inet6 addr: fe80::2ff:ffff:fe11:2244/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:19 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:1564 (1.5 KiB)  TX bytes:1244 (1.2 KiB)

One unwanted effect of having eth0 and eth1 in the container to DHCP is that both interfaces will set default gateway thus braking the Internet access. To avoid that we do:

# aptitude install ifmetric

and set /etc/network/interfaces as:

auto eth0
iface eth0 inet dhcp
  metric 0

auto eth1
iface eth1 inet dhcp
  metric 100
  #post-up /sbin/route del default gw 10.10.1.1

and then even though two default gateways are going to get set-up on boot time:

root@lxc02:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.3.1        0.0.0.0         UG    0      0        0 eth0
0.0.0.0         10.10.10.1      0.0.0.0         UG    100    0        0 eth1
10.0.3.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
10.10.10.0      0.0.0.0         255.255.255.0   U     0      0        0 eth1

the traffic will always have priority routing through eth0.

NOTE: Uncomment the post-up line for eth1 in case routing is still broken or DNS resolution is too slow

In case routing for the eth1 still fails to get created we set:

auto eth0
iface eth0 inet dhcp

auto eth1
iface eth1 inet dhcp
  metric 1
  post-up /sbin/route del default gw 10.10.10.1 || true
  post-up /sbin/route add -net 10.10.10.0/24 dev eth1 || true
  post-down /sbin/route del -net 10.10.10.0/24 dev eth1 || true

and reboot.

At the end, to auto-start and auto-stop dnsmasq service on the host every time the bridge br1 gets brought up/down we do:

# Containers virtual private network bridge
auto br1
iface br1 inet static
  address 10.10.1.1
  netmask 255.255.255.0
  bridge_ports tap0
  bridge_stp off
  bridge_maxwait 0
  bridge_fd 0
  pre-up /usr/sbin/tunctl -t tap0
  pre-up /sbin/ifconfig tap0 up
  post-down /sbin/ifconfig tap0 down
  post-down /bin/kill -KILL `/bin/cat /var/run/lxc/dnsmasq-br1.pid`
  post-up /usr/sbin/dnsmasq -u lxc-dnsmasq \
          --strict-order --bind-interfaces \
          --pid-file=/var/run/lxc/dnsmasq-br1.pid \
          --conf-file= \
          --listen-address 10.10.1.1 \
          --dhcp-range 10.10.1.2,10.10.1.49 \
          --dhcp-lease-max=253 --dhcp-no-override \
          --except-interface=lo --interface=br1

NOTE: To assign an IP to a linux bridge it has to have at least one interface associated hence the need of the tap0 in the bridge.

Then we disable dnsmasq from autostart on the host:

root@igor-laptop:~# update-rc.d dnsmasq disable

Add support for loop device control in the containers

In the containers config:

root@debian01:~# vi /var/lib/lxc/wheezy[01]/config
...
lxc.cgroup.devices.allow = c 10:236 rwm
lxc.cgroup.devices.allow = c 10:237 rwm
lxc.cgroup.devices.allow = b 7:* rwm

Then create the loop-control node for the containers:

root@debian01:~# mknod -m 0600 /var/lib/lxc/wheezy02/rootfs/dev/loop-control c 10 137
root@debian01:~# mknod -m 0600 /var/lib/lxc/wheezy01/rootfs/dev/loop-control c 10 137
root@debian01:~# chmod +t /var/lib/lxc/wheezy01/rootfs/dev/loop-control 
root@debian01:~# chmod +t /var/lib/lxc/wheezy02/rootfs/dev/loop-control

This is needed so when LVM2 gets installed it creates the necessary /dev/mapper/control device.

Setting up the second container

root@debian01:~# cd /var/lib/lxc/wheezy01/
root@debian01:/var/lib/lxc/wheezy01# ls -l
total 12
-rw-r--r--  1 root root 1262 Jan 29 00:45 config
-rw-r--r--  1 root root  996 Jan 29 00:41 config.bkp
drwxr-xr-x 22 root root 4096 Jan 29 00:35 rootfs
 
root@debian01:/var/lib/lxc/wheezy01# tar -czf ../template.tar.gz *
root@debian01:/var/lib/lxc/wheezy01# cd ../
root@debian01:/var/lib/lxc# ls -l
total 241760
-rw-r--r-- 1 root root 247557259 Jan 29 01:09 template.tar.gz
drwxr-xr-x 3 root root      4096 Jan 29 00:45 wheezy01

root@debian01:/var/lib/lxc# mkdir wheezy02
root@debian01:/var/lib/lxc# cd wheezy02/
root@debian01:/var/lib/lxc/wheezy02# tar -xzf ../template.tar.gz 
root@debian01:/var/lib/lxc/wheezy02# ls -l
total 12
-rw-r--r--  1 root root 1262 Jan 29 00:45 config
-rw-r--r--  1 root root  996 Jan 29 00:41 config.bkp
drwxr-xr-x 22 root root 4096 Jan 29 00:35 rootfs

and change the name, IP’s, MAC’s etc in the config file:

root@debian01:/var/lib/lxc/wheezy02# vi config

# networking
lxc.utsname = wheezy02
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.name = eth0
lxc.network.veth.pair = vethvm2_0
lxc.network.hwaddr = 00:FF:12:34:56:79
lxc.network.ipv4 = 192.168.100.90/24

lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br1
lxc.network.name = eth1
lxc.network.veth.pair = vethvm2_1
lxc.network.hwaddr = 00:FF:FF:11:22:44
lxc.network.ipv4 = 10.10.1.11/2

Check the container list:

root@debian01:/var/lib/lxc/wheezy02# lxc-list
RUNNING

FROZEN

STOPPED
  wheezy01
  wheezy02

Now we can start both containers as:

root@debian01:/var/lib/lxc/wheezy02# lxc-start -n wheezy01 -d
root@debian01:/var/lib/lxc/wheezy02# lxc-start -n wheezy02 -d
root@debian01:/var/lib/lxc/wheezy02# lxc-list
RUNNING
  wheezy01
  wheezy02

FROZEN

STOPPED

root@debian01:/var/lib/lxc/wheezy02#
root@debian01:/var/lib/lxc/wheezy02# lxc-console -n wheezy02

We can also use cloning to setup the second container:

# lxc-freeze -n wheezy01
# lxc-clone -o trusty01 -n trusty02
Created container trusty02 as copy of trusty01
# lxc-unfreeze -n wheezy01

NOTE: Actually the source container has to be stopped first for this to work

# lxc-stop -n trusty01
# lxc-clone -o trusty01 -n trusty02
Created container trusty02 as copy of trusty01
# lxc-start -n trusty01

Then edit the trusty02 container config file and replace the name, IP’s and MAC’s of the interfaces. Use:

$ printf '00:16:3e:%02x:%02x:%02x\n' $((RANDOM%256)) $((RANDOM%256))

for example to generate random MAC addresses and then start the second container:

# lxc-start -n trusty02 -d
# lxc-ls --fancy
NAME      STATE    IPV4                          IPV6  AUTOSTART
----------------------------------------------------------------
trusty01  RUNNING  192.168.16.11, 10.10.1.17     -     NO
trusty02  RUNNING  192.168.16.12, 10.10.1.15  	 -     NO
wheezy01  STOPPED  -                             -     NO

Then attach to the container and edit the /etc/network/interfaces to reflect what we have set in the config file. Then stop and start the container again.

Autostart

It is often the case that we’ll want the containers to autostart after a reboot, particularly if they are hosting services. By default, containers will not be started after a reboot, even if they were running prior to the shutdown.

To make a container autostart, we simply need to symlink its config file into the /etc/lxc/auto directory:

$ sudo ln -s /var/lib/lxc/test-container/config /etc/lxc/auto/test-container.conf

Container storage

LXC gives us awesome flexibility for storage. The container is a standard folder on our host, and can be moved easily across any Linux host. This is a new level of mobility for our server. Our server is now portable and can be simply zipped or rsynced anywhere, and be back up in seconds. Tasks like cloning, snapshotting and backups become fast and simple.

The container file system is in the default LXC container folder usually /var/lib/lxc. The individual container file systems are in the ‘containername’ folder rootfs directory.

To access host data from inside a container we can simply bind-mount a folder or drive in the host in the container’s fstab file located in the container’s directory.

/var/www var/www none bind,create=dir

This will mount the /var/www folder from the host in the container at /var/www. We can mount the same location in multiple containers to share data. We can also mount a shared folder in a separate container so it acts as a sort of shared storage outside our container. For instance:

/var/lib/lxc/mysqlvol/rootfs/var/lib/mysql var/lib/mysql

will mount mysqlvol’s /var/lib/mysql folder in mysql container so we can separate the application or container and it’s data. This storage container can also be shared across containers if required.

LXC passthrough devices

LXC can pass through devices from the host to the container. This is like using VT/d of Intel processors in virtualized systems to pass though hardware devices to the VM, but LXC does not need VTd

By default LXC prevents any such access using the devices cgroup as a filtering mechanism. The default LXC config allows certain devices to pass through. This is per container and is set in the container config file.

We can edit the individual container configuration to allow the additional devices and then restart the container. For one-off things, there’s a very convenient tool called ‘lxc-device’. With it, we can simply run:

$ sudo lxc-device add -n p1 /dev/ttyUSB0 /dev/ttyS0

Which will add (mknod) /dev/ttyS0 in the container with the same type/major/minor as /dev/ttyUSB0 and then add the matching cgroup entry allowing access from the container.

The same tool also allows moving network devices from the host to within the container.

Adding the containers to libvirt

# vi add-lxc-container.xml

<domain type="lxc">
<name>NAME</name>
<memory>332768</memory>
<os>
<type>exe</type>
<init>/sbin/init</init>
</os>
<vcpu>1</vcpu>2:8.3.13-2
<clock offset="utc"/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/lib/libvirt/libvirt_lxc</emulator>
<filesystem type="mount">
<source dir="/var/lib/lxc/NAME/rootfs"/>
<target dir="/"/>
</filesystem>
<interface type="network">
<source network="default"/>
</interface>
<console type="pty"/>
</devices>
</domain>

# sed 's/NAME/wheezy01/g' add-lxc-container.xml > wheezy01.xml
# sed 's/NAME/wheezy02/g' add-lxc-container.xml > wheezy02.xml

# virsh -c lxc:// define wheezy01.xml
# virsh -c lxc:// start wheezy01
# virsh -c lxc:// console wheezy01

# virsh -c lxc:// define wheezy02.xml
# virsh -c lxc:// start wheezy02
# virsh -c lxc:// console wheezy02

Press Ctrl+] to exit the containers console.

We can also go to virt-manager GUI and add new Connection as type LXC to interact with these containers as regular VM’s.

Create Ubuntu-14.04 (trusty) container

For Ubuntu-12.04 we need to install from backports:

root@igor-laptop:~# aptitude install -t precise-backports lxc

It has testing version of LXC 1.0.0~alpha1-0ubuntu14~ubuntu12.04.1 which supports setting default gateway in the container config so we don’t need to do that manually:

lxc.network.ipv4.gateway = 192.168.16.1

However, the /usr/share/lxc/templates/lxc-ubuntu-cloud template that comes with Wheezy looks for ubuntu-cloudimg-query which is nowhere to be found. Workaround is to comment out every line mentioning ubuntu-cloudimg-query and create the container like this:

root@debian01:~# lxc-create -t ubuntu-cloud -n trusty01 -- -r trusty -T http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-root.tar.gz

On Ubuntu:

root@igor-laptop:~# lxc-create -t ubuntu -n trusty01 -- -d ubuntu -r trusty -a amd64

OR if we want to limit disk space we can put the container into LVM we create for it, for example:

$ sudo lxc-create -t ubuntu -B lvm -n trusty01 --vgname=VG --lvname=LVNAME --fssize 8G --fstype xfs

root@igor-laptop:~# cat /var/lib/lxc/trusty01/config 
lxc.tty = 4
lxc.pts = 1024
lxc.rootfs = /var/lib/lxc/trusty01/rootfs
lxc.mount  = /var/lib/lxc/trusty01/fstab
lxc.arch = amd64
lxc.cap.drop = sys_module mac_admin
lxc.pivotdir = lxc_putold

# uncomment the next line to run the container unconfined:
#lxc.aa_profile = unconfined

lxc.cgroup.devices.deny = a
# Allow any mknod (but not using the node)
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
# /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
#fuse
lxc.cgroup.devices.allow = c 10:229 rwm
#tun
lxc.cgroup.devices.allow = c 10:200 rwm
#full
lxc.cgroup.devices.allow = c 1:7 rwm
#hpet
lxc.cgroup.devices.allow = c 10:228 rwm
#kvm
lxc.cgroup.devices.allow = c 10:232 rwm
# loop devices
lxc.cgroup.devices.allow = c 10:236 rwm
lxc.cgroup.devices.allow = c 10:237 rwm
lxc.cgroup.devices.allow = b 7:* rwm
# DRBD devices
lxc.cgroup.devices.allow = b 147:* rwm

# mounts point
lxc.mount.entry=proc /var/lib/lxc/trusty01/rootfs/proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry=devpts /var/lib/lxc/trusty01/rootfs/dev/pts devpts defaults 0 0
lxc.mount.entry=sysfs /var/lib/lxc/trusty01/rootfs/sys sysfs defaults  0 0

# limits
lxc.cgroup.cpu.shares = 1024
lxc.cgroup.cpuset.cpus = 0
lxc.cgroup.memory.limit_in_bytes = 512M
lxc.cgroup.memory.memsw.limit_in_bytes = 2G

# autostart
#lxc.start.auto = 1
#lxc.start.delay = 5
#lxc.start.order = 0

# networking
lxc.utsname = trusty01 

lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr0 
lxc.network.name = eth0
lxc.network.veth.pair = vethlxc1_0
lxc.network.hwaddr = 00:16:3e:38:e1:31 
lxc.network.ipv4 = 192.168.16.11/24

lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr1 
lxc.network.name = eth1
lxc.network.veth.pair = vethlxc1_1
lxc.network.hwaddr = 00:16:3e:38:a7:75
#lxc.network.ipv4 = 10.10.1.11/24

NOTE: To be able to use the Memory cgroups limit we need to enable this in the kernel on startup by adding:

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

to /etc/default/grub file OR add the options if the line already exists.

root@igor-laptop:~# cat /etc/network/interfaces
...
#
# LXC
#
# Containers public network bridge
auto lxcbr0
iface lxcbr0 inet static
  bridge_ports none 
  bridge_fd 0
  bridge_stp off
  address 192.168.16.1 
  netmask 255.255.255.0

# Containers private network bridge
auto lxcbr1
iface lxcbr1 inet static
  address 10.10.1.1
  netmask 255.255.255.0
  bridge_ports lxctap0 
  bridge_fd 0
  pre-up /usr/sbin/tunctl -t lxctap0
  pre-up /sbin/ifconfig lxctap0 up
  post-down /sbin/ifconfig lxctap0 down
  post-up dnsmasq -u lxc-dnsmasq --strict-order --bind-interfaces \
  --pid-file=/var/run/lxc/lxcbr1.pid --conf-file= \
  --except-interface lo --listen-address 10.10.1.1 \
  --dhcp-range 10.10.1.10,10.10.1.20 \
  --dhcp-leasefile=/var/run/lxc/lxcbr1.leases \
  --dhcp-lease-max=11 --dhcp-no-override

Upon startup if we see the following error:

root@igor-laptop:~# lxc-start -n trusty01 -l debug
lxc-start: Device or resource busy - failed to mount 'proc' on '/usr/lib/lxc/root//proc'
lxc-start: failed to setup the mount entries for 'trusty01'
lxc-start: failed to setup the container
lxc-start: invalid sequence number 1. expected 2
lxc-start: failed to spawn 'trusty01'
lxc-start: Device or resource busy - failed to remove cgroup '/sys/fs/cgroup//lxc/trusty01'

and the trusty01 cgroup is left behind, we can manually remove it like this:

root@igor-laptop:~# find /sys/fs/cgroup/lxc/trusty01/ -type d | tac | xargs rmdir

Finally we need to apply the following firewall rules on the host to filter traffic and protect from mixing with other virtual networks on the host:

root@igor-laptop:~# iptables -A INPUT -i lxcbr0 -p udp -m udp --dport 53 -j ACCEPT
root@igor-laptop:~# iptables -A INPUT -i lxcbr0 -p tcp -m tcp --dport 53 -j ACCEPT
root@igor-laptop:~# iptables -A INPUT -i lxcbr0 -p udp -m udp --dport 67 -j ACCEPT
root@igor-laptop:~# iptables -A INPUT -i lxcbr0 -p tcp -m tcp --dport 67 -j ACCEPT
root@igor-laptop:~# iptables -A INPUT -i lxcbr1 -p udp -m udp --dport 67 -j ACCEPT
root@igor-laptop:~# iptables -A INPUT -i lxcbr1 -p tcp -m tcp --dport 67 -j ACCEPT
root@igor-laptop:~# iptables -A FORWARD -d 192.168.16.0/24 -i eth0 -o lxcbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT
root@igor-laptop:~# iptables -A FORWARD -s 192.168.16.0/24 -i lxcbr0 -o eth0 -j ACCEPT
root@igor-laptop:~# iptables -A FORWARD -i lxcbr0 -o lxcbr0 -j ACCEPT
root@igor-laptop:~# iptables -A FORWARD -o lxcbr0 -j REJECT --reject-with icmp-port-unreachable
root@igor-laptop:~# iptables -A FORWARD -i lxcbr0 -j REJECT --reject-with icmp-port-unreachable
# next one not needed
root@igor-laptop:~# iptables -t nat -A POSTROUTING -o eth0 -s 192.168.16.0/24 ! -d 192.168.16.0/24 -j MASQUERADE

Now start the container and add the lxcbr0 as default gateway:

root@igor-laptop:~# lxc-start -n trusty01 -d

root@igor-laptop:~# lxc-ls --fancy
NAME      STATE    IPV4           IPV6  AUTOSTART
-------------------------------------------------
trusty01  RUNNING  192.168.16.11  -     NO
wheezy01  STOPPED  -              -     NO

root@igor-laptop:~# lxc-attach -n trusty01

root@trusty01:~# route add default gw 192.168.16.1 dev eth0
 
root@trusty01:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.16.1    0.0.0.0         UG    0      0        0 eth0
192.168.16.0    0.0.0.0         255.255.255.0   U     0      0        0 eth0

root@trusty01:~# ifconfig
eth0      Link encap:Ethernet  HWaddr 00:16:3e:38:e1:31
          inet6 addr: fe80::216:3eff:fe38:e131/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:3431 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2017 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:5104305 (5.1 MB)  TX bytes:152183 (152.1 KB)

eth1      Link encap:Ethernet  HWaddr 00:16:3e:38:a7:75
          inet addr:10.10.1.17  Bcast:10.10.1.255  Mask:255.255.255.0
          inet6 addr: fe80::216:3eff:fe38:a775/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:36 errors:0 dropped:0 overruns:0 frame:0
          TX packets:32 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:7965 (7.9 KB)  TX bytes:2900 (2.9 KB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:1 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:112 (112.0 B)  TX bytes:112 (112.0 B)

From now on we can do everything we do with any other server install, run apt-get etc.

Connecting LXC on multiple hosts

GRE tunnel

A GRE tunnel is a simple point to point IP tunnel connecting 2 public IPs and networks. But it’s not encrypted. For private networks across the public Internet, encryption is often standard but if your traffic does not have high security requirements a GRE tunnel is a simple just works solution.

Connecting 2 LXC hosts with a GRE tunnel will enable your LXC containers to access each other. LXC containers will be using their default NAT bridge lxcbr0.

We are going to change the subnet provided by lxcbr0 on one side which is normally 10.0.3.0/24 to 10.0.4.0/24 so there is no clash of IPs on either side and we can route both ways.

So here is the network map. We are going to call our GRE tunnel ‘neta’ on Host A and ‘netb’ on Host B (You can call this anything you want)

Host A has public IP 1.2.3.4 Host B has public IP 2.3.4.5 Containers in Host A are on subnet 10.0.4.0/24 via default lxcbr0 nat bridge Containers in Host B are on subnet 10.0.3.0/24 via default lxcbr0 nat bridge

To change the subnet edit the /etc/init.d/lxc-net script. Change the subnet entries from 10.0.3.0/24 to 10.0.4.0/24.

Create the GRE tunnel on Host A and B:

sudo ip tunnel add neta mode gre remote 2.3.4.5 local 1.2.3.4 ttl 255

sudo ip tunnel add netb mode gre remote 1.2.3.4 local 2.3.4.5 ttl 255

On Host A:

sudo ip link set neta up
sudo ip addr add 10.0.4.254 neta
sudo ip route add 10.0.3.0/24 dev neta

On Host B:

sudo ip link set netb up
sudo ip addr add 10.0.3.254 netb
sudo ip route add 10.0.4.0/24 dev netb

The IP address we added to the tunnel on each side 10.0.3/4.254 is being used as the gateway to reach either side. This is a random link IP, you can use anything 10.0.0.1/2 for instance. To remove the tunnel run:

sudo ip link set netb down
sudo ip tunnel del netb

Leave a Comment