Apps: "LXD v.3.0.3", "389 Directory Server v.1.3.7".
Задача: запустить приложение в изолированной среде LXC-контейнера, с управляющей обёрткой LXD, на примере установки, минимальной предварительной настройки и проверки функционирования LDAP-сервиса в реализации "389 Directory Server".
Последовательность дальнейших действий такова:
1. Подготовка системного окружения и установка сопутствующего ПО;
2. Преднастройка компонентов LXD;
3. Создание LXD-контейнера желаемого типа;
4. Установка и запуск LDAP-сервиса внутри LXD-контейнера;
5. Проверка функциональности LDAP-сервиса и создание своего DIT;
6. Клонирование LXD-контейнера.
2. Преднастройка компонентов LXD;
3. Создание LXD-контейнера желаемого типа;
4. Установка и запуск LDAP-сервиса внутри LXD-контейнера;
5. Проверка функциональности LDAP-сервиса и создание своего DIT;
6. Клонирование LXD-контейнера.
Установка компонентов LXC/LXD.
LXD - не новая версия LXC и не обёртка поверх утилит такового - это отдельная подсистема, взаимодействующая с модулем ядра LXC-контейнеризации посредством библиотеки "liblxc". В частности, это заметно потому, что у LXD свой набор unix-сокетов и виртуальных сетевых интерфейсов. Контейнеры тоже по умолчанию хранятся в специфичной для LXD директории "/var/lib/lxd". Я воспринимаю LXD как альтернативу утилитам LXC с расширением функционала (в частности с добавлением кластеризации и возможностей управления по сети) - отчасти избыточного, в случае решения простых задач.
Прежде всего необходимо установить пакет приложений надстройки LXD, созданной для управления подсистемой контейнеризации (функционал таковой уже в ядре с "Linux 2.6.29" и отдельно его устанавливать не требуется) и создания сетевых интерфейсов для пропуска трафика к контейнерам:
# apt install lxd bridge-utils
По умолчанию сразу после развёртывания из дистрибутива подсистемы LXD не вполне настроены и потребуется запуск скрипта-конфигуратора, который в интерактивном режиме задаст ряд достаточно очевидных вопросов:
# lxd init
В процессе инициирования поинтересуются, желаем ли мы использовать технологии кластеризации (не сейчас), создать хранилище для контейнеров (при первом запуске очевидно в этом есть необходимость), задать тип хранилища (в производственных схемах это LVM или ZFS, но для тестового стенда вполне подойдёт директория в имеющейся файловой системе), подключиться к серверу автоматизированного конфигурирования MAAS (не сейчас), как-то особо настроить точку включения в сеть (оставляем "мост" по умолчанию), задать виртуальные локальные подсети для контейнеров (IPv4 оставляем по умолчанию, а IPv6 отключаем), обеспечить возможность управления LXD по сети (не сейчас) и позволить автоматическое обновление уже сохранённых в хранилище шаблонов (не стоит, на тестовом стенде лучше знать, что исходная конфигурация неизменна).
В отличии от "тёплого и лампового" LXC, новомодная система управления LXD ориентирована на запуск в среде кластера, с динамической репликацией конфигурации связанных по сети узлов, которую теперь удобнее хранить в формате "SQLite-DB" (в сборке для "Linux Ubuntu" это файл "/var/lib/lxd/database/local.db").
Технически sqlite-файл легко просматривается любым подходящим инструментом, но, разумеется, у LXD имеется полный набор утилит управления, в частности и для демонстрации параметров текущей конфигурации, отображаемой в YAML-формате:
# lxc profile list
# lxc profile show default
# lxc profile show default
config: {}
description: Default LXD profile
devices:
eth0:
name: eth0
nictype: bridged
parent: lxdbr0
type: nic
....
name: default
used_by: []
description: Default LXD profile
devices:
eth0:
name: eth0
nictype: bridged
parent: lxdbr0
type: nic
....
name: default
used_by: []
По состоянию на Декабрь 2019 года в "Linux Ubuntu" предлагается стабильная версия "LXD 3.0.3".
Преднастройка сетевой подсистемы.
Параметры сетевой подсистемы LXD мы задали (согласившись на значения "по умолчанию" в примере) в процессе конфигурирования посредством "lxd init" после установки компонентов из дистрибутивных пакетов.
По умолчанию на этапе запуска несущей операционной системы службой "lxd" создаётся виртуальный "мостовой" интерфейс "lxdbr0", который обслуживает изолированную виртуальную локальную сеть LXD-контейнеров, для каждого из которых в свою очередь создаются виртуальные интерфейсы "veth*"; при этом виртуальные интерфейсы контейнеров прозрачно обмениваются пакетами данных между собой и несущей машиной через "мостовой" интерфейс "lxdbr0" на уровне L2, а за пределы несущей операционной системы трафик выпускается через подсистему трансляции адресов "iptables" (NAT) на уровне L3 - эта способ виртуализации сети в LXD называется "bridged" (кроме него ещё поддерживаются "physical", "macvlan", "ipvlan", "p2p" и "sriov").
Вышеприведённым набором команд "lxc profile" выясняем, какой сетевой профиль задействован, и просматриваем параметры его конфигурации (в примере "по умолчанию"):
# lxc network show lxdbr0
config:
ipv4.address: 10.96.70.1/24
ipv4.nat: "true"
ipv6.address: none
....
ipv4.address: 10.96.70.1/24
ipv4.nat: "true"
ipv6.address: none
....
Упражняясь заменим выделенный под виртуальную сеть контейнеров диапазон IP-адресов другим:
# lxc network list
# lxc network set lxdbr0 ipv4.address 10.0.4.1/24
# lxc network set lxdbr0 ipv4.address 10.0.4.1/24
Среди компонентов сетевой подсистемы LXD имеется DHCP-сервер, по умолчанию запускаемый в рамках обслуживания диапазона IP-адресов виртуальной сети контейнеров, выдавая IP-адреса по очереди, также сопоставляя их для встроенного DNS-сервера с именами контейнеров.
Создание LXD-контейнера желаемого типа.
Как обычно, при желании можно создать LXD-контейнер самостоятельно, но проще и быстрее воспользоваться набором готовых шаблонов, воссоздающих минимально достаточное файловое окружение большинства распространённых Linux-систем, размещённых на web-ресурсе "https://images.linuxcontainers.org".
Перечень доступных источников шаблонов контейнеров (далее мы будем использовать "images" и "local"):
# lxc remote list
Выбрав из списка доступных в вышеуказанном каталоге загружаем требуемый шаблон, на основе которого в иерархии локальной файловой системы (по умолчанию "/var/lib/lxd") формируется корневая файловая LXD-контейнера:
# lxc init images:ubuntu/18.04/amd64 ubuntu-ldap1
В результате в директории "/var/lib/lxd/containers" будет создана структура с корневой файловой системой заказанного контейнера "ubuntu-ldap1" и файлом конфигурации LXD-контейнера "metadata.yaml":
# tree -L 2 /var/lib/lxd/containers/ubuntu-ldap1
/var/lib/lxd/containers/ubuntu-ldap1
├── metadata.yaml
├── rootfs
│ ├── bin
│ ├── boot
....
│ └── var
└── templates
├── hostname.tpl
└── hosts.tpl
├── metadata.yaml
├── rootfs
│ ├── bin
│ ├── boot
....
│ └── var
└── templates
├── hostname.tpl
└── hosts.tpl
Загруженный шаблон сохраняется в директории "/var/lib/lxd/images" для повторного использования. В директории "/var/cache/lxd" также сохраняется файл сопоставлений символических имён шаблонов с их уникальными UID, которыми оперирует LXD.
Для запроса сведений о сохранённых в локальной системе шаблонов контейнеров имеется набор команд:
# lxc image list
Запуск и преднастройка LXD-контейнера.
Запускаем свежесозданный контейнер:
# lxc start ubuntu-ldap1
Проверяем его статус встроенными средствами LXD:
# lxc list
....
+--------------+---------+-------------------+−−−−
| ubuntu-ldap1 | RUNNING | 10.0.4.11 (eth0) | ...
....
+--------------+---------+-------------------+−−−−
| ubuntu-ldap1 | RUNNING | 10.0.4.11 (eth0) | ...
....
Обладая именем контейнера, запрашиваем сведения о его текущем состоянии и отчасти параметры конфигурации:
# lxc info ubuntu-ldap1
Name: ubuntu-ldap1
....
Status: Running
....
Ips:
eth0: inet 10.0.4.11 veth*
....
Resources:
....
Memory usage:
Memory (current): 31.56MB
Memory (peak): 33.23MB
....
....
Status: Running
....
Ips:
eth0: inet 10.0.4.11 veth*
....
Resources:
....
Memory usage:
Memory (current): 31.56MB
Memory (peak): 33.23MB
....
Можно запросить сведения только лишь конфигурации контейнера, без детальной информации о состоянии:
# lxc config show ubuntu-ldap1
Если контейнер не стартует или что-то ещё на этапе запуска пошло не так, то включаем детализированное журналирование и пробуем снова:
# lxc start ubuntu-ldap1 --debug
Прежде всего после первого запуска нового контейнера можно подключиться к нему средствами LXD (без запроса логина и пароля, исполняя команды сразу в контексте локального суперпользователя) и задать пароль какому-то из имеющихся пользователей или создать нового (в примере заодно дав ему возможность переключаться в роль суперпользователя, введя в группу "sudo"):
# lxc exec ubuntu-ldap1 /bin/bash
root@ubuntu-ldap1:/# useradd -m user
root@ubuntu-ldap1:/# password user
root@ubuntu-ldap1:/# usermod --append --groups sudo user
root@ubuntu-ldap1:/# useradd -m user
root@ubuntu-ldap1:/# password user
root@ubuntu-ldap1:/# usermod --append --groups sudo user
После наладки возможности входа в контейнерезированную систему её собственными средствами контроля пользователей можно будет воспользоваться утилитой подключения к TTY-сеансам LXD-контейнера:
# lxc console ubuntu-ldap1
Для того, чтобы в контейнер можно было входить путём подключения с удалённых сетевых узлов по протоколу SSH, внутри такового потребуется установить OpenSSH-сервер:
root@ubuntu-ldap1:/# apt-get update && apt-get install openssh-server
Обращаю внимание на то, что использование "SSH" для подключения к локально эксплуатируемому LXD-контейнеру возможно излишне. Если запускаемый сервис не нужен за пределами несущей системы виртуализации, то проще и дешевле, с точки зрения расхода ресурсов, воспользоваться для входа внутрь контейнера утилитой "lxd console". Даже отдельного пользователя для входа в контейнер создавать иногда излишне - достаточно исполнения в контексте суперпользователя утилитой "lxc exec".
После этого подключаемся к SSH-серверу в контейнере, используя для этого IP-адрес, полученный из вывода утилит "lxc info" или "lxc list":
$ ssh user@10.0.4.11
Остановка контейнера:
# lxc stop ubuntu-ldap1
По умолчанию LXD-контейнеры запускаются только вручную, но следующими командами в конфигурацию таковых можно внести изменения, активирующие автозапуск:
# lxc config set ubuntu-ldap1 boot.autostart true
# lxc config set ubuntu-ldap1 boot.autostart.delay 5
# lxc config set ubuntu-ldap1 boot.autostart.delay 5
В целях снижения накладных расходов отключаем ненужную в контейнере систему синхронизации локальных часов с внешними источниками времени:
# lxc exec ubuntu-ldap1 /bin/bash
root@ubuntu-ldap1:/# systemctl disable systemd-timesyncd && systemctl stop systemd-timesyncd
root@ubuntu-ldap1:/# systemctl disable systemd-timesyncd && systemctl stop systemd-timesyncd
Также деактивируем совершенно ненужную systemd-прослойку разрешения DNS-имён:
root@ubuntu-ldap1:/# systemctl disable systemd-resolved && systemctl stop systemd-resolved
root@ubuntu-ldap1:/# rm /etc/resolv.conf && echo -e "nameserver 8.8.8.8\n" > /etc/resolv.conf
root@ubuntu-ldap1:/# rm /etc/resolv.conf && echo -e "nameserver 8.8.8.8\n" > /etc/resolv.conf
Установка приложений внутри LXD-контейнера.
Очевидно, что установка приложений, как из дистрибутивных пакетов, так и путём сборки из исходных кодов, внутри LXD-контейнеров практически не отличается от таковой в обычной операционной системе. Могут возникнуть проблемы с автоматическим созданием или доступом к уже имеющимся в несущей системе файлам-устройствам, но все они легко решаемы.
Отдельно отмечу, что при формировании списка устанавливаемых пакетов полезно следить за тем, чтобы не нахватать лишнего - суть контейнеров в их легковесности и ни к чему развешивать внутри них гроздья в действительности ненужных процессов.
Например, установим LDAP-сервер в реализации "389 Directory Server (1.3.7)":
# lxc exec ubuntu-ldap1 /bin/bash
root@ubuntu-ldap1:/# apt-get update
root@ubuntu-ldap1:/# apt-get --no-install-recommends --no-install-suggests install 389-ds-base ldap-utils net-tools
root@ubuntu-ldap1:/# apt-get update
root@ubuntu-ldap1:/# apt-get --no-install-recommends --no-install-suggests install 389-ds-base ldap-utils net-tools
Для установки были выбраны лишь необходимые APT-пакеты:
"389-ds-base" - 389 Directory Server.
"ldap-utils" - OpenLDAP utilities.
"net-tools" - Misc networking toolkit.
"ldap-utils" - OpenLDAP utilities.
"net-tools" - Misc networking toolkit.
Конфигуратор LDAP-сервера от "RedHat" очень требователен к сетевой адресации - в процессе понадобится указать действительное доменное имя (FQDN), которое используется для приёма запросов к сервису. Если нет возможности зарегистрировать символическое имя в системе DNS, то придётся объявить его связку с IP-адресом локально (внутри контейнера):
root@ubuntu-ldap1:/# vi /etc/hosts
127.0.0.1 example.net
....
....
Запускаем интерактивный конфигуратор "389 Directory Server" (написан на "Perl"):
root@ubuntu-ldap1:/# setup-ds
В процессе подготовки конфигурации поинтересуются, какого уровня детализации мы желаем (наш вариант "typical"), запросят FQDN несущего LDAP-сервис компьютер (в примере "example.net"), попросят задать пользователя и группу, в контексте которых будет работать сервис (по умолчанию "dirsrv"), предложат изменить или подтвердить TCP-порт, на котором будут приниматься подключения от пользователей (лучше бы оставить общепринятый "389"), попросят задать символический индикатор сервиса, которым группа настроек и процессы будут отделены от других в рамках одного LDAP-сервера (я предпочитаю задавать идентификаторы отталкиваясь от FQDN, в примере "example-net"), также попросят задать очень важный параметр "суффикса" виртуального дерева LDAP-данных (можно ограничиться предлагаемым одним атрибутом, но я предпочитаю привязываться к FQDN, и, если таковой двухуровневый, то и суффикс - тоже: "dc=example,dc=net"). Напоследок зададим пароль создаваемому суперпользователю сервиса (лучше оставить общепринятого "cn=Directory Manager") и получаем через пару минут готовый к работе "каталог".
Сразу после успешного прохождения этапа настройки LDAP-сервис будет запущен, прослушивая выделенный ему TCP-порт:
root@ubuntu-ldap1:/# netstat -apn | grep -i "^tcp" | grep -i listen
tcp ... 0.0.0.0:389 0.0.0.0:* LISTEN .../ns-slapd
В результате внутри LXD-контейнера оказывается запущенными несколько процессов, минимально необходимых для работы (кроме избыточного "Systemd" и "DBus", разве что):
# pstree -aT $( lxc info ubuntu-ldap1 | grep -i "^Pid:" | awk -F ':' '{print $2}' | tr -d [:blank:] )
systemd
├─agetty ...
├─cron -f
├─dbus-daemon ...
....
├─ns-slapd -D /etc/dirsrv/slapd-example-net -i /var/run/dirsrv/slapd-example-net.pid
....
└─systemd-network
├─agetty ...
├─cron -f
├─dbus-daemon ...
....
├─ns-slapd -D /etc/dirsrv/slapd-example-net -i /var/run/dirsrv/slapd-example-net.pid
....
└─systemd-network
Наш сервис управляется посредством "Systemd", но в процессе тестирования можно запускать (и останавливать) приложение "ns-slapd" вручную:
root@ubuntu-ldap1:~# systemctl status dirsrv@example-net.service
root@ubuntu-ldap1:/# start-dirsrv example-net
root@ubuntu-ldap1:/# stop-dirsrv example-net
root@ubuntu-ldap1:/# start-dirsrv example-net
root@ubuntu-ldap1:/# stop-dirsrv example-net
Взаимодействие с приложением внутри LXD-контейнера.
Первый запрос к структуре данных свежеустановленного сервера "389-DS" наверное покажет, что таковым уже поддерживается два "Directory Information Tree (DIT)" - создаваемое по умолчанию для служебных целей "netscaperoot" и наше, с параметрами, определёнными во время конфигурирования скриптом "setup-ds":
# lxc exec ubuntu-ldap1 /bin/bash
root@ubuntu-ldap1:/# ldapsearch -x -h 127.0.0.1 -p 389 -D "cn=Directory Manager" -W -s base -b "" + | grep -i "context"
root@ubuntu-ldap1:/# ldapsearch -x -h 127.0.0.1 -p 389 -D "cn=Directory Manager" -W -s base -b "" + | grep -i "context"
namingContexts: dc=example,dc=net
namingContexts: o=netscaperoot
namingContexts: o=netscaperoot
При желании можно запросить все атрибуты структуры конфигурации DIT "cn=config" (on-line configuration, OLC; они также содержатся в текстовом файле "/etc/dirsrv/slapd-example-net/dse.ldif"):
root@ubuntu-ldap1:/# ldapsearch -x -h 127.0.0.1 -p 389 -D "cn=Directory Manager" -W -b "cn=config" "(objectclass=*)"
В "OpenLDAP" часть структуры данных недоступна извне и некоторые манипуляции возможны только через "unix-сокет" в контексте системного суперпользователя, но у "RedHat" свой взгляд на баланс безопасности и удобства, и сервис "389-DS" в полном объёме доступен снаружи (разумеется с разделением уровней полномочий) - потому все дальнейшие операции с каталогом можно производить извне контейнера.
Целевой сервис, в нашем случае LDAP-сервер, запущен внутри LXD-контейнера, доступного для сетевых подключений. IP-адрес контейнера уточняем посредством утилит "lxc info" или "lxc list":
# lxc list ubuntu-ldap1 --format csv | awk -F ',' '{print $3}' | awk '{print $1}'
Запросом перечня всех объектов и атрибутов удостоверимся, что интересующее нас DIT доступно извне:
$ ldapsearch -x -LLL -h xx.xx.xx.xx -D "cn=Directory Manager" -W -b dc=example,dc=net
dn: dc=example,dc=net
objectClass: top
objectClass: domain
dc: example
....
dn: ou=People,dc=example,dc=net
objectClass: top
objectClass: organizationalunit
ou: People
....
objectClass: top
objectClass: domain
dc: example
....
dn: ou=People,dc=example,dc=net
objectClass: top
objectClass: organizationalunit
ou: People
....
Упражняясь добавим в имеющуюся группу "People" данные о пользователе:
$ vi ./add-content-example-net.ldif
dn: uid=userOne,ou=people,dc=example,dc=net
objectClass: inetOrgPerson
uid: userOne
sn: Ivanov
givenName: Ivan
cn: Ivan Ivanov
displayName: Ivan Ivanov
objectClass: inetOrgPerson
uid: userOne
sn: Ivanov
givenName: Ivan
cn: Ivan Ivanov
displayName: Ivan Ivanov
$ ldapadd -x -h xx.xx.xx.xx -D "cn=Directory Manager" -W -f ./add-content-example-net.ldif
adding new entry "uid=userOne,ou=people,dc=example,dc=net"
Проверяем, зафиксированы ли в каталоге внесённые нами данные:
$ ldapsearch -x -LLL -h xx.xx.xx.xx -D "cn=Directory Manager" -W -b dc=example,dc=net "(uid=userOne)"
dn: uid=userOne,ou=people,dc=example,dc=net
objectClass: inetOrgPerson
uid: userOne
....
objectClass: inetOrgPerson
uid: userOne
....
Важно иметь в виду, что по умолчанию к базам данных "389-DS" предоставляется возможность чтения всем обратившимся, даже с анонимным запросом. Конечно, желательно сразу озаботиться ограничением доступа, например разрешив его только прошедшим аутентификацию пользователям.
Клонирование LXD-контейнеров.
Несмотря на простоту реализации LXC/LXD-контейнера и возможность получения его дубликата простым копированием файлов корневой файловой системы в директорию с новым именем всё же, учитывая, что LXD сведения о "виртуальной аппаратуре" контейнеров хранятся в централизованной "базе данных", лучше воспользоваться предназначенной для этого утилитой из дистрибутивного набора LXD:
# lxc copy ubuntu-ldap1 ubuntu-ldap2
Новый контейнер будет файлово и конфигурационно идентичен исходному, за исключением уникальных параметров вроде MAC-адресов.
В иерархии нового LXD-контейнера "/var/lib/lxd/containers/ubuntu-ldap2/ubuntu-ldap2/rootfs" правки потребуется внести в следующие файлы:
/etc/hostname
/etc/hosts
/etc/hosts
Далее всё просто - запускаем новый контейнер и удостоверяемся, что он успешно введён в рабочий режим:
# lxc start ubuntu-ldap2
# lxc list
# lxc list
....
+--------------+---------+-------------------+−−−−
| ubuntu-ldap1 | RUNNING | 10.0.4.11 (eth0) | ...
+--------------+---------+-------------------+−−−−
| ubuntu-ldap2 | RUNNING | 10.0.4.12 (eth0) | ...
....
+--------------+---------+-------------------+−−−−
| ubuntu-ldap1 | RUNNING | 10.0.4.11 (eth0) | ...
+--------------+---------+-------------------+−−−−
| ubuntu-ldap2 | RUNNING | 10.0.4.12 (eth0) | ...
....
В дополнение к упомянутым выше файлам, изменение которых требуется при клонировании LXD-контейнеров, в сложносочинённых системах возможно придётся также обновить уникальный идентификатор "machine-id", введённый в оборот в эпоху "Systemd + DBus":
# rm -f /var/lib/lxd/containers/ubuntu-ldap2/rootfs/etc/machine-id
# rm -f /var/lib/lxd/containers/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id
# dbus-uuidgen --ensure=/var/lib/lxd/containers/ubuntu-ldap2/rootfs/etc/machine-id
# ln -s /var/lib/lxd/containers/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id /var/lib/lxd/containers/ubuntu-ldap2/rootfs/etc/machine-id
# rm -f /var/lib/lxd/containers/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id
# dbus-uuidgen --ensure=/var/lib/lxd/containers/ubuntu-ldap2/rootfs/etc/machine-id
# ln -s /var/lib/lxd/containers/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id /var/lib/lxd/containers/ubuntu-ldap2/rootfs/etc/machine-id