Apps: "LXC v.3.0.3", "OpenLDAP v.2.4.45".
Задача: запустить приложение в изолированной среде LXC-контейнера, на примере установки, минимальной предварительной настройки и проверки функционирования LDAP-сервиса в реализации "OpenLDAP standalone server".
Последовательность дальнейших действий такова:
1. Подготовка системного окружения и установка сопутствующего ПО;
2. Преднастройка компонентов LXC;
3. Создание LXC-контейнера желаемого типа;
4. Установка и запуск LDAP-сервиса внутри LXC-контейнера;
5. Проверка функциональности LDAP-сервиса и создание своего DIT;
6. Клонирование LXC-контейнера.
2. Преднастройка компонентов LXC;
3. Создание LXC-контейнера желаемого типа;
4. Установка и запуск LDAP-сервиса внутри LXC-контейнера;
5. Проверка функциональности LDAP-сервиса и создание своего DIT;
6. Клонирование LXC-контейнера.
Установка компонентов LXC.
Прежде всего необходимо установить утилиты управления подсистемой LXC (функционал LXC уже в ядре с "Linux 2.6.29" и отдельно его устанавливать не требуется) и создания сетевых интерфейсов для пропуска трафика к контейнерам:
# apt install lxc-utils lxcfs bridge-utils
Сразу после установки дистрибутивных пакетов проверяем успешность активации всех компонентов LXC (роль каждого параметра вполне легко читается):
# lxc-checkconfig
# lxc-start --version
# lxc-start --version
Преднастройка сетевой подсистемы.
В современном Linux-е сетевая подсистема LXC активируется сразу после установки его компонентов их дистрибутивных пакетов.
По умолчанию на этапе запуска несущей операционной системы службой "lxc-net" создаётся виртуальный "мостовой" интерфейс "lxcbr0", который обслуживает изолированную виртуальную локальную сеть LXC-контейнеров, для каждого из которых в свою очередь создаются виртуальные интерфейсы "veth*"; при этом виртуальные интерфейсы контейнеров прозрачно обмениваются пакетами данных между собой и несущей машиной через "мостовой" интерфейс "lxcbr0" на уровне L2, а за пределы несущей операционной системы трафик выпускается через подсистему трансляции адресов "iptables" (NAT) на уровне L3 - эта способ виртуализации сети в LXC называется "veth" (кроме него ещё поддерживаются "phys", "macvlan" и "vlan").
Для понимания общей картины можно проверяем и активируем конфигурацию заново:
# vi /etc/default/lxc-net
# Разрешаем использование выделенного для LXC "мостового" виртуального интерфейса
# (по умолчанию именуется как "lxcbr0")
USE_LXC_BRIDGE="true"
# Задаём сетевую конфигурацию "моста" для LXC и параметры встроенного в LXC DHCP-сервера
LXC_BRIDGE="lxcbr0"
LXC_ADDR="10.0.3.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="10.0.3.0/24"
LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"
LXC_DHCP_MAX="253"
# (по умолчанию именуется как "lxcbr0")
USE_LXC_BRIDGE="true"
# Задаём сетевую конфигурацию "моста" для LXC и параметры встроенного в LXC DHCP-сервера
LXC_BRIDGE="lxcbr0"
LXC_ADDR="10.0.3.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="10.0.3.0/24"
LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"
LXC_DHCP_MAX="253"
Сверяемся с содержимым исходного конфигурационного файла, используемого при создании LXC-контейнеров:
# vi /etc/lxc/default.conf
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx
Профилактически активируем сервис виртуальной сети LXC и запускаем его:
# systemctl enable lxc-net
# systemctl start lxc-net
# journalctl -xe
# systemctl start lxc-net
# journalctl -xe
Надо понимать, что вышеописанная сетевая конфигурация хорошо подходит для быстрого развёртывания локальных тестовых площадок, в силу своей простоты и предустановленности. В случае запуска группы LXC-контейнеров на выделенном сервере виртуализации лучше воспользоваться способом виртуализации сети "veth" или "macvlan" с явно созданным фиксированной конфигурации "мостом" и виртуальными интерфейсами LXC-контейнеров со статически заданными IP-адресами, не спрятанными при этом за прослойкой NAT.
Создание LXC-контейнера желаемого типа.
При желании можно создать LXC-контейнер самостоятельно, но проще и быстрее воспользоваться набором готовых шаблонов, воссоздающих минимально достаточное файловое окружение большинства распространённых Linux-систем, размещённых на web-ресурсе "https://images.linuxcontainers.org". Шаблоны скачиваются и применяются специальной дистрибутивной LXC-утилитой, параметры которой можно посмотреть во встроенной справке:
$ lxc-create --template download --help
Выбрав из списка доступных в вышеуказанном каталоге загружаем требуемый шаблон, на основе которого в указанной точке локальной файловой системы (по умолчанию "/var/lib/lxc") формируется корневая файловая LXC-контейнера:
# lxc-create -t download -n ubuntu-ldap1 -- --dist ubuntu --release bionic --arch amd64
Downloading the image index
Downloading the rootfs
....
Downloading the rootfs
....
В результате в директории "/var/lib/lxc" будет создана структура с корневой файловой системой заказанного контейнера "ubuntu-ldap1" и файлом конфигурации LXC-контейнера ("config"):
# tree -L 2 /var/lib/lxc/ubuntu-ldap1
/var/lib/lxc/ubuntu-ldap1/
├── config
└── rootfs
├── bin
├── boot
├── dev
....
├── tmp
├── usr
└── var
├── config
└── rootfs
├── bin
├── boot
├── dev
....
├── tmp
├── usr
└── var
Загруженный шаблон сохраняется в директории "/var/cache/lxc/download/ubuntu/bionic/amd64/default" для повторного использования.
Запуск и преднастройка LXC-контейнера.
Запускаем свежесозданный контейнер:
# lxc-start -n ubuntu-ldap1
Проверяем его статус встроенными средствами LXC:
# lxc-ls -f
NAME STATE ... IPV4
ubuntu-ldap1 RUNNING ... 10.0.3.11
ubuntu-ldap1 RUNNING ... 10.0.3.11
Проверим потребление контейнером ресурсов несущей системы:
# lxc-top
Ещё один способ узнать текущее состояние и отчасти параметры конфигурации LXC-контейнера:
# lxc-info -n ubuntu-ldap1
Name: ubuntu-ldap1
State: RUNNING
PID: ...
IP: 10.0.3.11
....
State: RUNNING
PID: ...
IP: 10.0.3.11
....
Если контейнер не стартует или что-то ещё на этапе запуска пошло не так, то включаем детализированное журналирование и пробуем снова:
# lxc-start -n ubuntu-ldap1 -F -l debug -o ubuntu-ldap1.log ; cat ubuntu-ldap1.log
Прежде всего после первого запуска нового контейнера можно подключиться к нему средствами LXC (без запроса логина и пароля, сразу в контексте локального суперпользователя) и задать пароль какому-то из имеющихся пользователей или создать нового (в примере заодно дав ему возможность переключаться в роль суперпользователя, введя в группу "sudo"):
# lxc-attach -n ubuntu-ldap1 --clear-env
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-сеансам LXC-контейнера:
# lxc-console -n ubuntu-ldap1
Для того, чтобы в контейнер можно было входить путём подключения с удалённых сетевых узлов по протоколу SSH, внутри такового потребуется установить OpenSSH-сервер:
root@ubuntu-ldap1:/# apt-get update && apt-get install openssh-server
Обращаю внимание на то, что использование "SSH" для подключения к локально эксплуатируемому LXC-контейнеру возможно излишне. Если запускаемый сервис не нужен за пределами несущей системы виртуализации, то проще и дешевле, с точки зрения расхода ресурсов, воспользоваться для входа внутрь контейнера утилитой "lxc-console". Даже отдельного пользователя для входа в контейнер создавать иногда излишне - достаточно включения в контекст утилитой "lxc-attach".
После этого подключаемся к SSH-серверу в контейнере, используя для этого IP-адрес, полученный из вывода утилит "lxc-info" или "lxc-ls":
$ ssh user@10.0.3.11
Остановка контейнера:
# lxc-stop -n ubuntu-ldap1
Остановке есть удобная альтернатива - "заморозка" путём блокирования всех процессов LXC-контейнера на уровне системы виртуализации - последующая "разморозка" будет существенно быстрее обычного запуска:
# lxc-freeze -n ubuntu-ldap1
# lxc-unfreeze -n ubuntu-ldap1
# lxc-unfreeze -n ubuntu-ldap1
Надо понимать, что "заморозка" LXC-контейнера - это не "быстрая замена" полной остановки такового. Следует учитывать, что в момент блокирования процессов они могут вносить изменения в критически важные файлы данных, которые до завершения транзакции будут неконсистентны - так что для резервного копирования этот режим уже не подходит. Для изменения конфигурации контейнера и его сервисов "заморозка" тоже не подходит - сервисы не стартуют после "разморозки" заново и не считывают конфигурации, просто возобновляя работу в неизменном состоянии.
Автозапуск LXC-контейнера включается соответствующих опций в его конфигурационном файле:
# vi /var/lib/lxc/ubuntu-ldap1/config
....
lxc.start.auto = 1
lxc.start.delay = 5
lxc.start.auto = 1
lxc.start.delay = 5
В целях снижения накладных расходов отключаем ненужную в контейнере систему синхронизации локальных часов с внешними источниками времени:
# systemctl disable systemd-timesyncd && systemctl stop systemd-timesyncd
Также деактивируем совершенно ненужную systemd-прослойку разрешения DNS-имён:
# systemctl disable systemd-resolved && systemctl stop systemd-resolved
# rm /etc/resolv.conf && echo -e "nameserver 8.8.8.8\n" > /etc/resolv.conf
# rm /etc/resolv.conf && echo -e "nameserver 8.8.8.8\n" > /etc/resolv.conf
Установка приложений внутри LXC-контейнера.
Очевидно, что установка приложений, как из дистрибутивных пакетов, так и путём сборки из исходных кодов, внутри LXC-контейнеров практически не отличается от таковой в обычной операционной системе. Могут возникнуть проблемы с автоматическим созданием или доступом к уже имеющимся в несущей системе файлам-устройствам, но все они легко решаемы.
Отдельно отмечу, что при формировании списка устанавливаемых пакетов полезно следить за тем, чтобы не нахватать лишнего - суть контейнеров в их легковесности и ни к чему развешивать внутри них гроздья в действительности ненужных процессов.
Например, установим LDAP-сервер в реализации "OpenLDAP standalone server (2.4.45)":
# lxc-attach -n ubuntu-ldap1 --clear-env
root@ubuntu-ldap1:/# apt-get update
root@ubuntu-ldap1:/# apt-get --no-install-recommends --no-install-suggests install slapd ldap-utils net-tools
root@ubuntu-ldap1:/# apt-get update
root@ubuntu-ldap1:/# apt-get --no-install-recommends --no-install-suggests install slapd ldap-utils net-tools
Выбраны лишь необходимые APT-пакеты:
"slapd" - OpenLDAP Server.
"ldap-utils" - OpenLDAP utilities.
"net-tools" - Misc networking toolkit.
"ldap-utils" - OpenLDAP utilities.
"net-tools" - Misc networking toolkit.
При развёртывании APT-пакета "slapd" автоматический настройщик как минимум попросит придумать пароль администратора LDAP-сервера и сразу после этого сервис будет запущен, прослушивая выделенный ему TCP-порт:
root@ubuntu-ldap1:/# netstat -apn | grep -i "^tcp\b" | grep -i listen
tcp ... 0 0.0.0.0:389 0.0.0.0:* LISTEN .../slapd
В результате внутри LXC-контейнера оказывается запущенными несколько процессов, минимально необходимых для работы (кроме избыточного "Systemd" и "DBus", разве что):
# pstree -aT $( lxc-info -n ubuntu-ldap1 | grep -i "^PID:" | awk '{print $2}' )
systemd
├─agetty ...
├─cron -f
├─dbus-daemon
├─rsyslogd -n
├─slapd -h ldap:/// ... -u openldap -F /etc/ldap/slapd.d
....
└─systemd-network
├─agetty ...
├─cron -f
├─dbus-daemon
├─rsyslogd -n
├─slapd -h ldap:/// ... -u openldap -F /etc/ldap/slapd.d
....
└─systemd-network
Взаимодействие с приложением внутри LXC-контейнера.
Прежде всего желательно найти внутри LDAP-сервера учётные записи, позволяющие подключаться к нему с неограниченными привилегиями. Такого рода сведения хранятся в специальной структуре DIT "cn=config" (on-line configuration, OLC), основанной на LDIF-файлах (в "OpenLDAP" по умолчанию размещаемых в директории "/etc/ldap/slapd.d") и формируемой динамически (помним, что современный LDAP - от "v.2.3" для "OpenLDAP" - не настраивается привычными "plain-text" файлами конфигурации).
Структура "cn=config" из соображений безопасности недоступна извне и подключение к ней возможно только через "unix-сокет" в контексте системного суперпользователя:
# lxc-attach -n ubuntu-ldap1 --clear-env
root@ubuntu-ldap1:/# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config "(olcRootDN=*)"
root@ubuntu-ldap1:/# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config "(olcRootDN=*)"
Заодно можно ознакомится с перечнем всех объектов схемы конфигурации:
root@ubuntu-ldap1:/# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config dn
Также не помешает просмотреть действующие правила "Access Control List (ACL)":
root@ubuntu-ldap1:/# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config olcAccess
Кроме того полезно выяснить, в каком месте файловой системы хранятся данные (не структура!):
root@ubuntu-ldap1:/# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config olcDbDirectory
Первый запрос к структуре данных свежеустановленного LDAP-сервера скорее всего покажет, что таковая пуста - за исключением служебных "cn=config" и "dc=nodomain" нет ни одного поддерживаемого "Directory Information Tree (DIT)":
root@ubuntu-ldap1:/# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -s base -b "" + | grep -i "context"
configContext: cn=config
namingContexts: dc=nodomain
namingContexts: dc=nodomain
Как бы то ни было, наверняка нам понадобится создать свою собственную, наиболее подходящую для наших производственных процессов, структуру данных.
Прежде всего создаём директорию для файлов "базы данных" нашего дерева LDAP:
root@ubuntu-ldap1:/# mkdir -p /var/lib/ldap/example.net
root@ubuntu-ldap1:/# chown openldap:openldap /var/lib/ldap/example.net
root@ubuntu-ldap1:/# chmod -R o-rw /var/lib/ldap/example.net
root@ubuntu-ldap1:/# chown openldap:openldap /var/lib/ldap/example.net
root@ubuntu-ldap1:/# chmod -R o-rw /var/lib/ldap/example.net
Приготовим для последующего применения LDIF-файл с инструкциями инициирования структуры DIT:
root@ubuntu-ldap1:/# vi ./add-dit-example-net.ldif
# Задаём параметры места хранения структуры DIT
dn: olcDatabase=mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: mdb
olcDbDirectory: /var/lib/ldap/example.net
olcSuffix: dc=example,dc=net
# Задаём базовые параметры описание DIT как такового
dn: dc=example,dc=net
objectClass: dcObject
objectClass: organization
o: Example, Inc.
dc: example
dn: olcDatabase=mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: mdb
olcDbDirectory: /var/lib/ldap/example.net
olcSuffix: dc=example,dc=net
# Задаём базовые параметры описание DIT как такового
dn: dc=example,dc=net
objectClass: dcObject
objectClass: organization
o: Example, Inc.
dc: example
Даём команду LDAP-серверу применить передаваемую ему в LDIF-файле конфигурацию:
root@ubuntu-ldap1:/# ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ./add-dit-example-net.ldif
adding new entry "olcDatabase=mdb,cn=config"
adding new entry "dc=example,dc=net"
adding new entry "dc=example,dc=net"
После успешного создания нового корневого DIT в директории конфигурации "/etc/ldap/sldap.d/cn=config" появится дополнительный файл описания структуры "базы данных", его можно найти по ключевому содержимому:
root@ubuntu-ldap1:/# grep -rnw '/etc/ldap/slapd.d/cn=config' -e 'dc=example,dc=net'
Также в выхлопе запроса сведений о корневой конфигурации появится упоминание нового описания имён:
root@ubuntu-ldap1:/# ldapsearch -Q -Y EXTERNAL -H ldapi:/// -s base -b "" + | grep -i "namingContexts"
namingContexts: dc=nodomain
namingContexts: dc=example,dc=net
namingContexts: dc=example,dc=net
Первым делом есть смысл для новой DIT создать суперпользователя "RootDN" (на пользователя этого типа не распространяется действие ACL и он может изменять свою ветвь структуры без ограничений).
Получаем "хеш" от желаемого пароля, воспользовавшись для этого дистрибутивной утилитой:
root@ubuntu-ldap1:/# slappasswd -h '{SSHA}' -s 'strongPassword'
В интерактивном режиме передаём LDAP-серверу команды дополнения конфигурации DIT:
root@ubuntu-ldap1:/# ldapmodify -Q -Y EXTERNAL -H ldapi:///
# Создаём суперпользователя новой структуры DIT
dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcRootDN
olcRootDN: cn=admin,dc=example,dc=net
# Задаём пароль суперпользователя новой структуры DIT
dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}oAY...hQU
dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcRootDN
olcRootDN: cn=admin,dc=example,dc=net
# Задаём пароль суперпользователя новой структуры DIT
dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}oAY...hQU
Дальнейшие действия с DIT, не требующие доступа к глобальной конфигурации "cn=config", уже возможно и удобно производить посредством удалённого сетевого подключения.
Целевой сервис, в нашем случае LDAP-сервер, запущен внутри LXC-контейнера, доступного для сетевых подключений. IP-адрес контейнера уточняем посредством утилит "lxc-info" или "lxc-ls":
# lxc-info -n ubuntu-ldap1 | grep -i "^IP:" | awk '{print $2}'
Удостоверимся, что новый DIT успешно создан и доступен извне его суперпользователю:
$ ldapsearch -x -LLL -h xx.xx.xx.xx -D cn=admin,dc=example,dc=net -W -b dc=example,dc=net
dn: dc=example,dc=net
objectClass: dcObject
objectClass: organization
o: Example, Inc.
dc: example
objectClass: dcObject
objectClass: organization
o: Example, Inc.
dc: example
Упражняясь, создадим LDIF-файл описания ветви структуры, например для группировки данных о пользователях сервиса, и применим его:
$ vi ./add-ou-example-net.ldif
# Создаём одну из ветвей DIT
dn: ou=people,dc=example,dc=net
ou: people
description: All people in organisation
objectClass: organizationalUnit
dn: ou=people,dc=example,dc=net
ou: people
description: All people in organisation
objectClass: organizationalUnit
$ ldapadd -x -h xx.xx.xx.xx -D cn=admin,dc=example,dc=net -W -f ./add-ou-example-net.ldif
adding new entry "ou=people,dc=example,dc=net"
Добавим в созданную группу данные о пользователе:
$ 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=admin,dc=example,dc=net -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=admin,dc=example,dc=net -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
....
Важно иметь в виду, что по умолчанию к базам данных "OpenLDAP" предоставляется возможность чтения всем обратившимся, даже с анонимным запросом. Конечно, желательно сразу озаботиться ограничением доступа, например разрешив его только прошедшим аутентификацию пользователям.
Клонирование LXC-контейнеров.
Учитывая простоту реализации файловой системы LXC-контейнера получить его копию можно копированием файлов корневой файловой системы в директорию с новым именем и последующей корректировкой конфигурации в части файловых путей, доменных имён и IP/MAC-адресов (очевидно, что на время копирования для сохранения консистентности данных контейнер очень желательно остановить):
# lxc-stop -n ubuntu-ldap1
# cp -RP --preserve=all /var/lib/lxc/ubuntu-ldap1 /var/lib/lxc/ubuntu-ldap2
# lxc-start -n ubuntu-ldap1
# cp -RP --preserve=all /var/lib/lxc/ubuntu-ldap1 /var/lib/lxc/ubuntu-ldap2
# lxc-start -n ubuntu-ldap1
В конфигурационном файле контейнера-копии "/var/lib/lxc/ubuntu-ldap2/config" как минимум потребуется изменить следующие параметры:
lxc.rootfs.path
lxc.uts.path
lxc.net.0.hwaddr
lxc.uts.path
lxc.net.0.hwaddr
Также в иерархии нового LXC-контейнера "/var/lib/lxc/ubuntu-ldap2/rootfs" правки потребуется внести в следующие файлы:
/etc/hostname
/etc/hosts
/etc/hosts
Далее всё просто - запускаем новый контейнер и удостоверяемся, что он успешно введён в рабочий режим:
# lxc-start -F -n ubuntu-ldap2
# lxc-ls -f
# lxc-ls -f
NAME STATE ... IPV4
ubuntu-ldap1 RUNNING ... 10.0.3.11
ubuntu-ldap2 RUNNING ... 10.0.3.12
ubuntu-ldap1 RUNNING ... 10.0.3.11
ubuntu-ldap2 RUNNING ... 10.0.3.12
В дополнение к упомянутым выше файлам, изменение которых требуется при клонировании LXC-контейнеров, в сложносочинённых системах возможно придётся также обновить уникальный идентификатор "machine-id", введённый в оборот в эпоху "Systemd + DBus":
# rm -f /var/lib/lxc/ubuntu-ldap2/rootfs/etc/machine-id
# rm -f /var/lib/lxc/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id
# dbus-uuidgen --ensure=/var/lib/lxc/ubuntu-ldap2/rootfs/etc/machine-id
# ln -s /var/lib/lxc/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id /var/lib/lxc/ubuntu-ldap2/rootfs/etc/machine-id
# rm -f /var/lib/lxc/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id
# dbus-uuidgen --ensure=/var/lib/lxc/ubuntu-ldap2/rootfs/etc/machine-id
# ln -s /var/lib/lxc/ubuntu-ldap2/rootfs/var/lib/dbus/machine-id /var/lib/lxc/ubuntu-ldap2/rootfs/etc/machine-id