Linux-контейнеры дома: зачем и как
Рассуждения
При упоминании словосочетания «контейнерная виртуализация», многим на ум сразу же приходят Virtuozzo и OpenVZ, а также Docker. Ассоциируется же это все, в первую очередь, с хостингом, VPS и другими подобными вещами.
Дома, на личных компьютерах многие используют виртуальные машины: в основном, пожалуй, Virtualbox. Как правило, для того, чтобы работая под Linux, иметь под рукой Windows или наоборот. Однако, при наличии множества родственных Linux-операционок, я стал замечать, что использование виртуальных машин — это, мягко говоря, нерационально.
В первую очередь, очень быстро расходуется дисковое пространство. Каждой виртуальной машине нужно место, даже если несколько из них отличаются только конфигами. Особенно критично это на невеликих размеров SSD лаптопа. В принципе, Virtualbox умеет работать с raw-девайсами и, теоретически, машинам можно назначать rw LVM-снапшот, но тут опять же встают вопросы с изменением размера файловой системы в дальнейшем, автоматизацией клонирования, переноса, удаления и тому подобное.
Во вторую — это больший расход оперативной памяти. В третью — не самые удобные инструменты взаимодействия…
Потому, возникла идея опробовать в домашних условиях контейнерную виртуализацию. OpenVZ отмел сразу, по причине необходимости возиться с кастомным ядром. Выбор же пал на LXC, поставляющийся в репозитарии стабильного Debian'a.
Зачем это нужно:
— Для сборки ПО при нежелании захламлять разномастными *-dev пакетами основную рабочую систему.
— Потребность в другом дистрибутиве для запуска каких-либо специфических программ и, опять же, сборки.
— Изоляция потенциально небезопасного софта, вроде того же скайпа совершающего разные непонятные действия в домашнем каталоге пользователя и всяких сомнительных веб-технологий: уязвимость во флеше, в java, в обработчике pdf — это только то, что плавает на поверхности.
— Анонимность. Эдак можно банально остаться залогиненым в своей любимой социалочке, забыть подчистить куки или оказаться незнакомым с очередной новой веб-технологией вроде этой webrtc. Можно, конечно, держать несколько профилей браузера, но от перечисленных выше дыр и технологий это не защитит.
Итак, рассмотрим плюсы и минусы LXC:
+ Работает на ванильном ядре
+ Простота проброса устройств и каталогов хоста, так как работает это все через cgroups
+ Очень нетребовательно к ресурсам, в отличии от виртуальных машин типа Virtualbox или qemu
— Контейнеры будут работать на том же ядре, что и хост, хотя — это скорей особенность контейнерной виртуализации в целом.
— Некоторая недоделанность идущих в комплекте утилит.
Развертывание и настройка контейнера
В первую очередь, ставим пакет lxc и все необходимые утилиты:
sudo apt-get install lxc bridge-utils
Смотрим доступные группы томов LVM:
$sudo vgs
VG #PV #LV #SN Attr VSize VFree
nethack-vg 1 6 0 wz--n- 119,00g 7,36g
sudo lxc-create -t debian -B lvm --vgname nethack-vg --fssize 2G -n deb_test
Указываем использовать LVM в качестве системы хранения, Volume Group ( в моем случае — nethack-vg) и размер 2 гигабайта, иначе по умолчанию будет создан одногиговый том. Хотя, если вдруг стало тесновато, можно будет сделать lvresize.
Смотрим:
$sudo lvs
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
deb_test nethack-vg -wi-ao---- 2,00g
home nethack-vg -wi-ao---- 93,09g
root nethack-vg -wi-ao---- 8,38g
tmp nethack-vg -wi-ao---- 380,00m
var nethack-vg -wi-ao---- 2,79g
vm nethack-vg -wi-ao---- 5,00g
Видим, что у нас появился том deb_test.
Типовой конфиг, созданный скриптом:
# Template used to create this container: /usr/share/lxc/templates/lxc-debian
# Parameters passed to the template:
# For additional config options, please look at lxc.container.conf(5)
lxc.network.type = empty
lxc.rootfs = /dev/nethack-vg/deb_test
# Common configuration
lxc.include = /usr/share/lxc/config/debian.common.conf
# Container specific configuration
lxc.mount = /var/lib/lxc/deb_test/fstab
lxc.utsname = deb_test
lxc.arch = amd64
lxc.autodev = 1
lxc.kmsg = 0
Стартуем:
sudo lxc-start -n deb_test
Залогинимся с указанным паролем. Для запуска в headless-режиме используется ключ -d, а рутовую консоль можно получить с помощью команды
sudo lxc-attach -n deb_test
Пока у нас ни сети, ни нужных для работы программ. Для этого на хосте поднимаем мост, задаем IP, заворачиваем трафик из подсети виртуалки, при отключении интерфейса разрушаем мост.
На хосте прописываем в /etc/network/interfaces
auto lo br0
iface br0 inet static
address 172.20.0.1
netmask 255.255.255.0
pre-up /sbin/brctl addbr br0
post-up /sbin/brctl setfd br0 0
post-up iptables -t nat -A POSTROUTING -s 172.20.0.0/24 -j MASQUERADE
post-up echo 1 > /proc/sys/net/ipv4/ip_forward
pre-down /sbin/brctl delbr br0
В конфиг контейнера дописываем:
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.hwaddr = 00:01:02:03:04:05
Чтобы сразу получить рабочую сеть и возможность установки пакетов apt'ом, допишем
lxc.network.ipv4 = 172.20.0.3
lxc.network.ipv4.gateway = 172.20.0.1
И выполним
echo "nameserver 192.168.18.1">/etc/resolv.conf
Понятно, что 192.168.18.1 — IP моего DNS.
Установим нужные пакеты:
#apt-get install vim openvpn zsh iftop
Дальше либо на хосте, либо на другой рабочей виртуалке можно получить список установленных пакетов и установить их все в нашем новом контейнере:
scp user@172.20.0.2:/etc/apt/sources.list /etc/apt/
scp -r user@172.20.0.2:/etc/apt/sources.list.d /etc/apt/
apt-get update
ssh user@172.20.0.2 'dpkg --get-selections|grep -v deinstall'|dpkg --set-selections
apt-get dselect-upgrade
Теперь можно по-человечески настроить сетевой интерфейс в контейнере, использовав любимый текстовый редактор:
/etc/network/interfaces:
auto lo eth0
iface lo inet loopback
iface eth0 inet static
address 172.20.0.3
netmask 255.255.255.0
gateway 172.20.0.1
dns-nameservers 192.168.18.1
Впрочем, это можно было сделать с хост-системы, например, смонтировав логический том. Способов много.
В принципе, в качестве DNS можно указать любой публичный, если не опасаетесь за свою приватность. Например, гугловские 8.8.8.8 и 8.8.4.4.
По доступу к устройствам хост-системы, я придерживаюсь политики «все, что не разрешено, запрещено». Добавим для этого следующую строчку в конфиг:
lxc.cgroup.devices.deny = a
Удаляем
lxc.include = /usr/share/lxc/config/debian.common.conf
Попробуем подключиться через OpenVPN. Сразу же получаем ошибку:
Thu Oct 15 16:39:33 2015 ERROR: Cannot open TUN/TAP dev /dev/net/tun: No such file or directory (errno=2)
Thu Oct 15 16:39:33 2015 Exiting due to fatal error
Система пишет, что интерфейсы TUN/TAP недоступны по причине их отсутствия. Очевидно, что нужно разрешить гостевой системе использовать устройства хоста. Открываем конфигурационный файл контейнера, /var/lib/lxc/deb_test/config и добавляем туда строчку:
lxc.cgroup.devices.allow = c 10:200 rwm
В контейнере выполняем:
root@deb_test:/# mkdir /dev/net; mknod /dev/net/tun c 10 200
root@deb_test:/# echo 'mkdir /dev/net; mknod /dev/net/tun c 10 200; exit 0'>/etc/rc.local
Обратим внимание на 10:200 — это идентификатор типа устройств. Если выполним на хосте:
$ls -l /dev/net/tun
crw-rw-rw- 1 root root 10, 200 окт 13 10:30 /dev/net/tun
То увидим идентификаторы 10, 200. По ним и будем ориентироваться, разрешая доступ к устройства, например камере — video0.
lxc.cgroup.devices.allow = c 81:* rwm
Точно также добавляем остальные нужные устройства:
# /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 rm
#usb passthrough
lxc.cgroup.devices.allow = c 189:* rwm
#video
lxc.cgroup.devices.allow = c 81:* rwm
#sound
lxc.cgroup.devices.allow = c 116:* rwm
lxc.cgroup.devices.allow = c 14:* rwm
Для функционирования иксов и возможности их проброса через ssh, нужно добавить точку монтирования:
lxc.mount.entry = /tmp/.X11-unix/X0 tmp/.X11-unix/X0 none bind,optional,create=file
По аналогии можно примонтировать и другие, нужные каталоги и файлы:
lxc.mount.entry = /home/user/.vim home/user/.vim none bind,optional,create=dir 0 0
lxc.mount.entry = /home/user/.vimrc home/user/.vimrc none bind,optional,create=file 0 0
Для воспроизведения звука, можно разрешить доступ к звуковому устройству, если карта многопоточная (с однопоточной при использовании dmix возникают проблемы с блокировкой):
lxc.cgroup.devices.allow = c 116:* rwm
lxc.cgroup.devices.allow = c 14:* rwm
lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir 0 0
А можно настроить pulseaudio на воспроизведение звука по сети, как это описано здесь. Кратко:
Отредактировать на хосте /etc/pulse/default.pa, дописав туда:
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;172.20.0.3 auth-anonymous=1
В итоге, у нас получается вот такой конифиг:
lxc.network.type = empty
lxc.rootfs = /dev/nethack-vg/deb_test
lxc.mount = /var/lib/lxc/deb_test/fstab
lxc.utsname = deb_test
lxc.arch = amd64
lxc.autodev = 1
lxc.kmsg = 0
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = br0
lxc.network.hwaddr = 00:01:02:03:04:05
lxc.network.ipv4 = 172.20.0.3
lxc.network.ipv4.gateway = 172.20.0.1
#deny acces for all devices
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 rm
#sound
lxc.cgroup.devices.allow = c 116:* rwm
lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir 0 0
#tun/tap adapters
lxc.cgroup.devices.allow = c 10:200 rwm
#video0
lxc.cgroup.devices.allow = c 81:* rwm
lxc.mount.entry = /dev/video0 dev/video0 none bind,optional,create=file
lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir
lxc.mount.entry = /tmp/.X11-unix/X0 tmp/.X11-unix/X0 none bind,optional,create=file
Контейнер готов к использованию.
Использование
Доустановим, например, i2p с Tor'ом, если не сделали этого ранее, и сходу настроим privoxy:
wget -q https://geti2p.net/_static/i2p-debian-repo.key.asc -O- | sudo apt-key add -
echo "deb http://deb.i2p2.no/ jessie main" >/etc/apt/sources.list.d/i2p.list
echo "deb-src http://deb.i2p2.no/ jessie main" >>/etc/apt/sources.list.d/i2p.list
apt-get update
apt-get install privoxy i2p tor
user-manual /usr/share/doc/privoxy/user-manual
confdir /etc/privoxy
logdir /var/log/privoxy
actionsfile user.action # User customizations
filterfile default.filter
filterfile user.filter # User customizations
logfile logfile
listen-address localhost:8118
toggle 1
enable-remote-toggle 1
enable-remote-http-toggle 1
enable-edit-actions 1
enforce-blocks 0
buffer-limit 4096
enable-proxy-authentication-forwarding 0
forwarded-connect-retries 0
accept-intercepted-requests 0
allow-cgi-request-crunching 0
split-large-forms 0
keep-alive-timeout 5
tolerate-pipelining 1
socket-timeout 300
forward .i2p localhost:4444
forward-socks5 .onion localhost:9050 .
Запускать графические приложения вроде браузера удобнее всего через ssh:
ssh -Y 172.20.0.2 "PULSE_SERVER=172.20.0.1 http_proxy=127.0.0.1:8118 chromium"
Так же, разумеется, LXC предоставляет средства для клонирования контейнеров и снятия снапшотов.
Так, например, склонировать контейнер, файловая система которого будет являться LVM-снапшотом можно командой:
sudo lxc-clone -s -H -o deb_test -L 200M --new deb_test2
Будет создан контейнер deb_test2 с файловой системой, размещенной на LVM-снапшоте размером 200MB (под хранение diff'ов). Это будет точная копия deb_test, над которой можно провести пару экспериментов и, например, безболезненно удалить.
А вот lxc-snapshot с LVM в качестве хранилища, на версии lxc-1.0.6 почему-то не работает:
->sudo lxc-snapshot -n deb_test
lxc_container: deb_test's backing store cannot be backed up.
lxc_container: Your container must use another backing store type.
lxc_container: Error creating a snapshot
Проблема описывается и обсуждается здесь. Потому, снимки придется делать по старинке:
sudo lvcreate -L100M -s -n deb_test_before_rm_rf -p r /dev/nethack-vg/deb_test
В данном случае, создали read-only снапшот с именем deb_test_before_rm_rf размером 100MB. Что с ним делать дальше? Например, его можно сдампить посредством dd, перенести на другую машину вместе с конфигами контейнера, создать там том нужного размера и пролить тем же dd (cp, cat, итд) — эдакая «живая миграция».
Как писалось выше, областей применения контейнерам можно найти массу. Но главной, при домашнем использовании, на мой взгляд, является изоляция приложений.
На этом пока все.