Настройка LXC в ручном режиме

N.B.

Информация актуальна для lxc и штатного ПО, поставляемого в slackware-15.0.

N.B. 2

Для некоторых настроек придётся модифицировать штатные стартовые скрипты из /etc/rc.d.

Исходим из предпосылок:

Cеть с контейнерами 10.200.0.0/24
GW, через который ходят конты во вне 10.200.0.1
DNS resolver 10.200.0.1
Интерфейс, в который "воткнуты" сетевушки контейнеров lxcbr0

Подготовка среды

Настройка elogind для нормального запуска контейнеров с systemd

Надо пропатчить /etc/rc.d/rc.elogind вот таким образом:

--- rc.elogind.orig 2022-08-20 15:26:00.000000000 +0300
+++ rc.elogind  2022-11-04 14:00:00.000000000 +0300
@@ -21,11 +21,14 @@
   if [ -x /lib64/elogind/elogind ]; then
     if [ ! -d /run/user ]; then
       mkdir -p /run/user
+      chmod 1777 /run/user
     fi
     if [ ! -d /run/systemd ]; then
-      mkdir -p /run/elogind /sys/fs/cgroup/elogind
-      ( cd /run; rm -rf systemd; ln -sf elogind systemd; )
-      ( cd /sys/fs/cgroup; rm -rf systemd; ln -sf elogind systemd; )
+       mkdir -p /run/systemd /sys/fs/cgroup/systemd
+       mount -t cgroup -o rw,nosuid,nodev,noexec,none,name=systemd none /sys/fs/cgroup/systemd
+       ( cd /run; rm -rf elogind; ln -sf systemd elogind; )
+       #( cd /sys/fs/cgroup; rm -rf elogind; ln -sf systemd elogind; )
+       ( cd /sys/fs/cgroup; mkdir -p elogind; mount -t cgroup -o rw,nosuid,nodev,noexec,none,name=elogind none /sys/fs/cgroup/elogind)
     fi
     if pgrep -l -F /run/elogind.pid 2>/dev/null | grep -q elogind; then
       echo "Elogind is already running"

Иначе контейнеры с systemd не смогут стартовать, будут жаловаться на чтьо-то странное, а на самом деле им только-то нужен /sys/fs/cgroup/systemd смонтированный правильным образом.

Настройка сетевого моста для lxc

Чтобы нам собрать lxc-контейнеры в местную локальную сеть, нам нужен сетевой мост. В его качестве будет выступать классический linux-овый bridge.

Тут начинается кастом в /etc/rc.d.

Для создания сетевого моста, нам надо подгрузить пару модулей в ядро. Модуль dummy для "прогрева" моста (чтобы не было накладок с arp-ом). И, собственно, модуль bridge.

Для этого нам надо дописать в /etfc/rc.d/rc.modules.local вот такие строки:

if [ -x /etc/rc.d/rc.netdevice ]; then
        /etc/rc.d/rc.netdevice start
fi

А уже в файле /etc/rc.d/rc.netdevice мы будем настраивать наши виртуальные сетевые адаптеры:

#!/bin/sh

PATH='/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin'

case "$1" in
    "start")
        # setup device dummy0
        modprobe dummy
        ifconfig dummy0 up

        # setup bridge
        modprobe bridge
        brctl addbr lxcbr0
        brctl addif lxcbr0 dummy0
        brctl stp lxcbr0 off
        brctl setfd lxcbr0 0
        ifconfig lxcbr0 up
        ethtool -K lxcbr0 tx off >/dev/null 2&>1

        ;;
    "stop")
        ifconfig lxcbr0 down
        brctl delif lxcbr0 dummy0
        brctl delbr lxcbr0
        ifconfig docker0 down
        brctl delbr docker0
        ifconfig docker1 down
        brctl delbr docker1
        modprobe -r bridge

        ifconfig dummy0 down
        modprobe -r dummy
        ;;
    *)
        echo "Usage: $0 start|stop"
        ;;
esac

Остаётся самая малость - нам надо настроить ip адрес нашего gw, через который во вне будут ходить наши контейнеры.

Это делается в файле /etc/rc.d/rc.inet1.conf, просто настраиваем ещё один интерфейс (например, второй по счёту):

# IPv4 config options for eth1:
IFNAME[1]="lxcbr0"
IPADDRS[1]="10.200.0.1/24"
USE_DHCP[1]=""
# IPv6 config options for eth1:
IP6ADDRS[1]=""
USE_SLAAC[1]=""
USE_DHCP6[1]=""
# Generic options for eth1:
DHCP_HOSTNAME[1]=""

Включение ip_forward

Здесь всё просто, для работы интернетов и вообще внешних (по отношению к стенду lxc) сетей, нужно настроить форвардинг пакетов между сетевыми интерфейсами.

chmod +x /etc/rc.d/rc.ip_forward

Настройка NAT

Чтобы уметь ходить за пределы localhost-а lxc-машинками, от имени ip-адреса этого localhost-а надо научиться в NAT.

Для этого надо создать файрволл - создаём файлик /etc/rc.d/rc.firewall и делаем на него

chmod +x /etc/rc.d/rc.firewall

в сам файл записываем:

#!/bin/bash

if [[ "$1" == "stop" ]]; then
    iptables-restore < /etc/default/iptables-empty
elif [[ "$1" == "start" ]]; then
    iptables-restore < /etc/default/iptables
else
    echo "Usage: $0 start|stop"
fi

и нам потребуется создать пару файлов с пустым файрволлом и с настройками для lxc.

Пустой файрволл /etc/default/iptables-empty:

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT

*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT

файл с настройками файрволла для lxc:

# Generated by iptables-save v1.8.7 on Mon Aug 15 23:07:58 2022
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lxcbr0 -j ACCEPT
-A OUTPUT -o lxcbr0 -j ACCEPT
-A FORWARD -i lxcbr0 -j ACCEPT
-A FORWARD -o lxcbr0 -j ACCEPT
COMMIT

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [8:416]
:POSTROUTING ACCEPT [8:416]
-A POSTROUTING -s 10.200.0.0/24 -j MASQUERADE
COMMIT
# Completed on Mon Aug 15 23:07:58 2022

Настройка dhcpd

Для автоматической конфигурации сетевых настроек lxc-контейнеров, нам нужен сервис dhcpd. Он будет раздавать виртуалкам заданные адреса, маршрут по-умолчанию, список dns-серверов и hostname-ы. А также добавлять/удалять/обновлять записи в dns-е о хостах, получивших адреса.

Конфиг сервиса /etc/dhcpd.conf:

default-lease-time 600;
max-lease-time 7200;
authoritative;
ignore unknown-clients;
log-facility local7;

# DDNS statements
ddns-updates           on;
ddns-update-style      interim;
allow                  client-updates;
update-static-leases   on;
update-conflict-detection false;

subnet 10.200.0.0 netmask 255.255.255.0 {
    ignore unknown-clients;
    range 10.200.0.200 10.200.0.250;
    option domain-name-servers 10.200.0.1;
    option domain-name "lxc";
    option routers 10.200.0.1;
    option broadcast-address 10.200.0.255;
    default-lease-time 600;
    max-lease-time 7200;
}

# Ip addresses for known hosts
host mygrav      { hardware ethernet 00:00:00:00:00:03; fixed-address 10.200.0.3;   }

# DDNS, for named/bind zones autoupdate
zone lxc. {
    primary 127.0.0.1;
}

zone 0.200.10.in-addr.arpa. {
    primary 127.0.0.1;
}

СтОит заметить, что по-умолчанию сервис dhcpd в Slackware не запускается автоматически. Совсем, никак. Это, конечно, большой промах.

Его можно исправить, добавив сервису стартовый скрипт и прописав его запуск в /etc/rc.d/rc.local:

if [ -x /etc/rc.d/rc.dhcpd ]; then
        /etc/rc.d/rc.dhcpd start
fi

И сам стартовый скрипт:

!/bin/sh
# /etc/rc.d/rc.dhcpd - start/stop the dhcpd daemon

# To change the default options, edit /etc/default/dhcpd.
if [ -r /etc/default/dhcpd ]; then
    . /etc/default/dhcpd
fi

start_dhcpd() {
    if ! /usr/bin/pgrep --ns $$ --euid root -f "^/usr/sbin/dhcpd" 1> /dev/null 2> /dev/null ; then
        echo "Starting dhcpd:  /usr/sbin/dhcpd $DHCPD_OPTS"

        /usr/sbin/dhcpd -t

        if [ $? -gt 0 ]; then
            echo "Config /etc/dhcpd.conf is incorrect. Refuse to start dhcpd."
            exit 1
        fi

        /usr/sbin/dhcpd $DHCPD_OPTS
    fi
}

stop_dhcpd() {
    echo "Stopping dhcpd."
    /usr/bin/pkill --ns $$ --euid root -f "^/usr/sbin/dhcpd" 2> /dev/null
}

restart_dhcpd() {
    /usr/sbin/dhcpd -t

    if [ $? -gt 0 ]; then
        echo "Config /etc/dhcpd.conf is incorrect. Refuse to restart dhcpd."
        exit 1
    fi

    stop_dhcpd
    sleep 1
    start_dhcpd
}

case "$1" in
'start')
    start_dhcpd
    ;;
'stop')
    stop_dhcpd
    ;;
'restart')
    restart_dhcpd
    ;;
*)
    echo "usage $0 start|stop|restart"
esac

Настройки для него /etc/default/dhcpd:

DHCPD_OPTS="-4 -q lxcbr0"

Настройка логгирования сообщений от dhcpd в отдельный файл - это домашнее задание :)

Настройка Bind/Named

Сразу стОит отметить, что я использую named в том числе как локальный ресолвер (то есть для резолва не только зоны lxc, но и всего интернета). То есть в нём прописаны форвардеры и зоны.

Итак, для работы named нам надо настроить его общий конфиг и зоны, которые будет апдэйтить dhcpd.

Основной файл конфига у нас находится в /etc/named.conf:

options {
    directory "/var/named";
    forwarders {
        1.1.1.1;
        1.0.0.1;
        8.8.8.8;
        8.8.4.4;
        208.67.222.222;
        208.67.220.220;
        9.9.9.9;
        149.112.112.112;
        209.244.0.3;
        209.244.0.4;
        74.82.42.42;
    };
    allow-query { any; };
    allow-recursion { any; };
    allow-transfer { any; };
    allow-query-cache { any; };

    dnssec-validation yes;
    dnssec-enable yes;
};

// 
// a caching only nameserver config
// 
zone "." IN {
    type hint;
    file "caching-example/named.root";
};

zone "localhost" IN {
    type master;
    file "caching-example/localhost.zone";
    allow-update { none; };
};

zone "0.0.127.in-addr.arpa" IN {
    type master;
    file "caching-example/named.local";
    allow-update { none; };
};

zone "lxc" {
    type master;
    allow-update { any; }; # ip of dhcp server
    file "lxc.zone";
    notify          yes;
};

// reverse zone
zone "0.200.10.in-addr.arpa" {
    type master;
    allow-update { any; }; # ip of dhcp server
    file "0.200.10.in-addr.arpa.zone";
    notify          yes;
};

Настройки запуска named-а /etc/default/named:

# User to run named as:
NAMED_USER=named

# Group to use for chowning named related files and directories.
# By default, named will also run as the primary group of $NAMED_USER,
# which will usually be the same as what's listed below, but not
# necessarily if something other than the default of "named" is used.
NAMED_GROUP=named

# Options to run named with. At least -u $NAMED_USER is required, but
# additional options may be added if needed.
NAMED_OPTIONS="-u $NAMED_USER -4"

также надо убедиться, что /var/named действительно принадлежит пользователю named:

chown -R named:named /var/named

Reboot и проверка, что всё нужное запустилось

На этом моменте нужно удостовериться, что в каталоге /var/lib/lxc ничего нету (если есть, то надо это дело "подвинуть" в сторонку).

После ребута должны появиться процессы dhcpd, named, а также инрфейсы dummy0 и lxcbr0, кроме того, должны прмениться правила iptables.

Создание тестовой машинки

Нам понадобится утилита lxc-create, с её помощью надо бутстрапнуть любой доступный дистрибутив. И для примера назвать контейнер mygrav.

Однако, торопиться запускать контейнер не стОит. Нам нужно подправить его конфиг. Приведём его к такому виду:

# Template used to create this container: /usr/share/lxc/templates/lxc-slackware
# Parameters passed to the template:
# For additional config options, please look at lxc.conf(5)
lxc.arch       = amd64
lxc.uts.name   = mygrav
lxc.start.auto = 0

lxc.rootfs.path = dir:/var/lib/lxc/mygrav/rootfs

lxc.mount.entry = lxcpts dev/pts devpts defaults,newinstance 0 0
lxc.mount.entry = none   proc    proc   defaults             0 0
lxc.mount.entry = none   sys     sysfs  defaults             0 0
lxc.mount.entry = none   run     tmpfs  defaults,mode=0755   0 0
lxc.mount.entry = none   run     tmpfs  defaults,mode=0755   0 0
lxc.mount.entry = none   dev/shm tmpfs  nodev,nosuid,noexec,mode=1777,create=dir 0 0
lxc.mount.entry = none   tmp     tmpfs  defaults,mode=1777   0 0

lxc.net.0.type      = veth
lxc.net.0.veth.pair = mygrav
lxc.net.0.flags     = up
lxc.net.0.link      = lxcbr0
lxc.net.0.name      = eth0
lxc.net.0.hwaddr    = 00:00:00:00:00:03

#lxc.net.0.ipv4.address = 10.200.0.3/24
#lxc.net.0.ipv4.gateway = 10.200.0.1
#lxc.net.0.ipv6.address = fd00::1:3
#lxc.net.0.ipv6.gateway = fd00::1:0

lxc.tty.max = 1
lxc.pts.max = 1024

lxc.autodev = 1

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

# we don't trust even the root user in the container, better safe than sorry.
# comment out only if you know what you're doing.
#lxc.cap.drop = sys_module mknod mac_override mac_admin sys_time setfcap setpcap
#lxc.cap.drop = sys_module mknod mac_override mac_admin sys_time

# you can try also this alternative to the line above, whatever suits you better.
#lxc.cap.drop=sys_admin

#lxc.hook.autodev=/var/lib/lxc/mygrav/autodev

Тут для контейнеров с systemd надо убедиться, что все записи с lxc.cap.drop закоментированы, так как systemd для запуска нужны некоторые Linux Capabilities.

Запуск контейнера

Для пробного запуска контейнера, нам понадобится 2 консоли.

В первой консоле от рута делаем:

lxc-start -F -n mygrav

и смотрим, как происходит инициализация контейнера.

Во второй консоле можно попробоватьи прыгнуть внутрь контейнера, как только процесс его запуска дойёт до финальной точки.

lxc-attach -n mygrav

Если визуально с контейнером всё хорошо, надо проверить, что унего настроена сеть по dhcp и прописался resolver.

Next Post