LXD — это системный менеджер контейнеров следующего поколения, так гласит источник. Он предлагает пользовательский интерфейс, похожий на виртуальные машины, но использующий вместо этого контейнеры Linux.
Ядро LXD — это привелегированный демон (сервис запущенный с правами root), который предоставляет REST API через локальный unix сокет, а также через сеть, если установлена соответствующая конфигурация. Клиенты, такие как инструмент командной строки поставляемый с LXD посылают запросы через этот REST API. Это означает, что независимо от того, обращаетесь ли вы к локальному хосту или к удаленному, все работает одинаково.
В этой статье мы не будем подробно останавливаться на концепциях LXD, не будем рассматривать все доступные возможности изложенные в документации в том числе недавнюю реализацию в последних версиях LXD поддержки виртуальных машин QEMU параллельно с контейнерами. Вместо этого мы узнаем только базовые возможности управления контейнерами — настроим пулы хранилищ, сеть, запустим контейнер, применим лимиты на ресурсы, а также рассмотрим как использовать снепшоты, чтобы вы смогли получить базовое представление о LXD и использовать контейнеры в Linux.
Для получения полной информации следует обратиться к официальному источнику:
Инсталляция LXD
Инсталляция LXD в дистрибутивах Ubuntu
В дистрибутиве Ubuntu 19.10 пакет lxd
имеет трансляцию на snap-пакет:
apt search lxd
lxd/eoan 1:0.7 all
Transitional package - lxd -> snap (lxd)
Это значит, что будут установлены сразу два пакета, один системный, а другой как snap-пакет. Установка двух пакетов в системе может создать некоторую проблему, при которой системный пакет может стать сиротой, если удалить snap-пакет менеджером snap-пакетов.
Найти пакет lxd
в snap-репозитории можно следующей командой:
snap find lxd
Name Version Publisher Notes Summary
lxd 3.21 canonical✓ - System container manager and API
lxd-demo-server 0+git.6d54658 stgraber - Online software demo sessions using LXD
nova ocata james-page - OpenStack Compute Service (nova)
nova-hypervisor ocata james-page - OpenStack Compute Service - KVM Hypervisor (nova)
distrobuilder 1.0 stgraber classic Image builder for LXC and LXD
fabrica 0.1 ogra - Build snaps by simply pointing a web form to a git tree
satellite 0.1.2 alanzanattadev - Advanced scalable Open source intelligence platform
Запустив команду list
можно убедится, что пакет lxd
еще не установлен:
snap list
Name Version Rev Tracking Publisher Notes
core 16-2.43.3 8689 stable canonical✓ core
Не смотря на то, что LXD является snap-пакетом, устанавливать его нужно через системный пакет lxd
, который создаст в системе соответствующую группу, необходимые утилиты в /usr/bin
и т.д.
sudo apt update
sudo apt install lxd
Убедимся, что пакет установлен как snap-пакет:
snap list
Name Version Rev Tracking Publisher Notes
core 16-2.43.3 8689 stable canonical✓ core
lxd 3.21 13474 stable/… canonical✓ -
Инсталляция LXD в дистрибутивах Arch Linux
Для установки пакета LXD в системе необходимо запустить следующие команды, первая — актуализирует список пакетов в системе доступных в репозитории, вторая — непосредственно установит пакет:
sudo pacman -Syyu && sudo pacman -S lxd
После установки пакета, для управления LXD обычным пользователем, его необходимо добавить в системную группу lxd
:
sudo usermod -a -G lxd user1
Убедимся, что пользователь user1
добавлен в группу lxd
:
id -Gn user1
user1 adm dialout cdrom floppy sudo audio dip video plugdev netdev lxd
Если группа lxd
не видна в списке, тогда нужно активировать сессию пользователя заново. Для этого нужно выйти и зайти в систему под этим же пользователем.
Активируем в systemd
загрузку сервиса LXD при старте системы:
sudo systemctl enable lxd
Запускаем сервис:
sudo systemctl start lxd
Проверяем статус сервиса:
sudo systemctl status lxd
Хранилище LXD (Storage)
До начала инициализации нам необходимо разобраться как логически устроено хранилище в LXD.
Хранилище (Storage) состоит из одного или нескольких Storage Pool который использует одну из поддерживаемых файловых систем такие как ZFS, BTRFS, LVM или обычные директории. Каждый Storage Pool разделяется на тома (Storage Volume) которые содержат образы, контейнеры или данные для других целей.
- Образы — это специально собранные дистрибутивы без ядра Linux и доступные из внешних источников
- Контейнеры — это развернутые дистрибутивы из образов, готовые к эксплуатации
- Снепшоты — это снимки состояния контейнеров к которым можно возвращаться
Для управления хранилищем в LXD служит команда lxc storage
справку по которой можно получить указав ключ — lxc storage --help
Следующая команда выводит на экран список всех Storage Pool в LXD хранилище:
lxc storage list
+---------+-------------+--------+--------------------------------+---------+
| NAME | DESCRIPTION | DRIVER | SOURCE | USED BY |
+---------+-------------+--------+--------------------------------+---------+
| hddpool | | btrfs | /dev/loop1 | 2 |
+---------+-------------+--------+--------------------------------+---------+
| ssdpool | | btrfs | /var/lib/lxd/disks/ssdpool.img | 4 |
+---------+-------------+--------+--------------------------------+---------+
Для просмотра списка всех Storage Volume в выбранном Storage Pool служит команда lxc storage volume list
:
lxc storage volume list hddpool
+-------+------------------------------------------------------------------+-------------+---------+
| TYPE | NAME | DESCRIPTION | USED BY |
+-------+------------------------------------------------------------------+-------------+---------+
| image | ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3 | | 1 |
+-------+------------------------------------------------------------------+-------------+---------+
lxc storage volume list ssdpool
+-----------+------------------------------------------------------------------+-------------+---------+
| TYPE | NAME | DESCRIPTION | USED BY |
+-----------+------------------------------------------------------------------+-------------+---------+
| container | alp3 | | 1 |
+-----------+------------------------------------------------------------------+-------------+---------+
| container | jupyter | | 1 |
+-----------+------------------------------------------------------------------+-------------+---------+
| image | ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3 | | 1 |
+-----------+------------------------------------------------------------------+-------------+---------+
Также, если для Storage Pool при создании была выбрана файловая система BTRFS, то получить список Storage Volume или subvolumes в интерпретации BTRFS можно с помощью инструментария этой файловой системы:
sudo btrfs subvolume list -p /var/lib/lxd/storage-pools/hddpool
ID 257 gen 818 parent 5 top level 5 path images/ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
sudo btrfs subvolume list -p /var/lib/lxd/storage-pools/ssdpool
ID 257 gen 1820 parent 5 top level 5 path images/ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
ID 260 gen 1819 parent 5 top level 5 path containers/jupyter
ID 263 gen 1820 parent 5 top level 5 path containers/alp3
Инициализация LXD
Перед созданием и использованием контейнеров необходимо выполнить общую инициализацию LXD которая создает и настраивает сеть, а также хранилище. Это можно произвести вручную с помощью стандартных команд клиента которые доступны в списке по вызову команды lxc --help
или с помощью мастера инициализации lxd init
ответив на несколько вопросов.
Выбор файловой системы для Storage Pool
Во время инициализации LXD задаёт несколько вопросов, среди которых будет определение типа файловой системы для дефолтного Storage Pool. По умолчанию для него выбирается файловая система BTRFS. Поменять на другую ФС после создания будет невозможно. Для выбора ФС предлагается таблица сравнения возможностей:
Feature | Directory | Btrfs | LVM | ZFS | CEPH |
---|---|---|---|---|---|
Optimized image storage | no | yes | yes | yes | yes |
Optimized instance creation | no | yes | yes | yes | yes |
Optimized snapshot creation | no | yes | yes | yes | yes |
Optimized image transfer | no | yes | no | yes | yes |
Optimized instance transfer | no | yes | no | yes | yes |
Copy on write | no | yes | yes | yes | yes |
Block based | no | no | yes | no | yes |
Instant cloning | no | yes | yes | yes | yes |
Storage driver usable inside a container | yes | yes | no | no | no |
Restore from older snapshots (not latest) | yes | yes | yes | no | yes |
Storage quotas | yes(*) | yes | yes | yes | no |
Инициализация сети и Storage Pool с помощью мастера
Следующая команада которую мы рассмотрим, предлагает настроить основные компоненты LXD ответами на простые вопросы с помощью мастера инициализации.
Запустите команду lxc init
и введите ответы на вопросы после знака двоеточия так как показано в примере ниже или измените их согласно вашим условиям:
lxd init
Would you like to use LXD clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]: ssdpool
Name of the storage backend to use (lvm, btrfs, dir) [default=btrfs]:
Create a new BTRFS pool? (yes/no) [default=yes]:
Would you like to use an existing block device? (yes/no) [default=no]:
Size in GB of the new loop device (1GB minimum) [default=15GB]: 10GB
Would you like to connect to a MAAS server? (yes/no) [default=no]:
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=lxdbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: 10.0.5.1/24
Would you like LXD to NAT IPv4 traffic on your bridge? [default=yes]:
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]: none
Would you like LXD to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes] no
Would you like a YAML "lxd init" preseed to be printed? (yes/no) [default=no]:
Создание дополнительного Storage Pool
В предыдущем шаге мы создали Storage Pool которому дали название ssdpool
и файл которого расположился в моей системе по адресу /var/lib/lxd/disks/ssdpool.img
. Этот адрес файловой системы соответствует физическому SSD диску в моём ПК.
Следующими действиями, для расширения понимания того, какую роль играет Storage Pool в хранилище, мы создадим второй Storage Pool который будет физически располагаться на другом типе диске, на HDD. Проблема заключается в том, что LXD не позволяет создавать Storage Pool вне адреса /var/lib/lxd/disks/
и даже символические ссылки не будут работать, смотрите ответ разработчика. Обойти это ограничение мы можем при инициализации/форматировании Storage Pool указав значение как блочное устройство вместо пути к loopback-файлу указав это в ключе source
.
Итак, до создания Storage Pool необходимо определить loopback-файл или существующий раздел в вашей файловой системе который он будет использовать. Для этого, мы создадим и будем использовать файл который ограничим размером в 10GB:
dd if=/dev/zero of=/mnt/work/lxd/hddpool.img bs=1MB count=10000
10000+0 records in
10000+0 records out
10000000000 bytes (10 GB, 9,3 GiB) copied, 38,4414 s, 260 MB/s
Подключим loopback-файл в свободное loopback-устройство:
sudo losetup --find --show /mnt/work/lxd/hddpool.img
/dev/loop1
Благодаря ключу --show
выполнение команды возвращает на экран имя устройства в которое подключился наш loopback-файл. При необходимости, мы можем вывести на экран список всех занятых устройств этого типа, чтобы убедится в корректности наших действий:
losetup -l
NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC
/dev/loop1 0 0 0 0 /mnt/work/lxd/hddpool.img 0 512
/dev/loop0 0 0 1 0 /var/lib/lxd/disks/ssdpool.img 0 512
Из списка можно обнаружить, что в устройстве /dev/loop1
подключен loopback-файл /mnt/work/lxd/hddpool.img
, а в устройстве /dev/loop0
подключен loopback-файл /var/lib/lxd/disks/ssdpool.img
который соответствует дефолтному Storage Pool.
Следующая команда создает новый Storage Pool в LXD на основе только что подготовленного loopback-файла. LXD отформатирует loopback-файл /mnt/work/lxd/hddpool.img
в устройстве /dev/loop1
под файловую систему BTRFS:
lxc storage create hddpool btrfs size=10GB source=/dev/loop1
Выведем список всех Storage Pool на экран:
lxc storage list
+---------+-------------+--------+--------------------------------+---------+
| NAME | DESCRIPTION | DRIVER | SOURCE | USED BY |
+---------+-------------+--------+--------------------------------+---------+
| hddpool | | btrfs | /dev/loop1 | 0 |
+---------+-------------+--------+--------------------------------+---------+
| ssdpool | | btrfs | /var/lib/lxd/disks/ssdpool.img | 0 |
+---------+-------------+--------+--------------------------------+---------+
Увеличение размера Storage Pool
После создания Storage Pool, при необходимости, его можно расширить. Для Storage Pool основанном на файловой системе BTRFS выполните следующие команды:
sudo truncate -s +5G /mnt/work/lxd/hddpool.img
sudo losetup -c /dev/loop1
sudo btrfs filesystem resize max /var/lib/lxd/storage-pools/hddpool
Автовставка loopback-файла в слот loopback-устройства
У нас есть одна небольшая проблема, при перезагруке системы хоста, файл /mnt/work/lxd/hddpool.img
"вылетит" из устройства /dev/loop1
и сервис LXD упадет при загрузке так как не увидит его в этом устройстве. Для решения этой проблемы нужно создать системный сервис который будет вставлять этот файл в устройство /dev/loop1
при загрузке системы хоста.
Создадим unit файл типа service в /etc/systemd/system/
для системы инициализации SystemD:
cat << EOF | sudo tee -a /etc/systemd/system/lxd-hddpool.service
[Unit]
Description=Losetup LXD Storage Pool (hddpool)
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/sbin/losetup /dev/loop1 /mnt/work/lxd/hddpool.img
RemainAfterExit=true
[Install]
WantedBy=local-fs.target
EOF
Активируем сервис:
sudo systemctl enable lxd-hddpool
Created symlink /etc/systemd/system/local-fs.target.wants/lxd-hddpool.service → /etc/systemd/system/lxd-hddpool.service.
После рестарта хостовой системы проверяем статус сервиса:
systemctl status lxd-hddpool.service
● lxd-hddpool.service - Losetup LXD Storage Pool (hddpool)
Loaded: loaded (/etc/systemd/system/lxd-hddpool.service; enabled; vendor preset: disabled)
Active: active (exited) since Wed 2020-04-08 03:43:53 MSK; 1min 37s ago
Process: 711 ExecStart=/sbin/losetup /dev/loop1 /mnt/work/lxd/hddpool.img (code=exited, status=0/SUCCESS)
Main PID: 711 (code=exited, status=0/SUCCESS)
апр 08 03:43:52 manjaro systemd[1]: Starting Losetup LXD Storage Pool (hddpool)...
апр 08 03:43:53 manjaro systemd[1]: Finished Losetup LXD Storage Pool (hddpool).
Из вывод мы можем убедиться, что состояние сервиса равно active, несмотря на то, что выполнение нашего скрипта из одной команды завершилось, это позволила нам сделать опция RemainAfterExit=true
.
Безопасность. Привелегии контейнеров
Так как все процессы контейнера фактически исполняются в изоляции на хостовой системе используя его ядро, то для дополнительной защиты доступа процессов контейнера к системе хоста LXD предлагает привелегированность процессов, где:
-
Привелегированные контейнеры — это контейнеры в которых процессы с UID и GID соответствуют тому же владельцу, что и на хостовой системе. Например, процесс запущенный в контейнере с UID равным 0 имеет все те же права доступа, что и процесс хостовой системы с UID равным 0. Другими словами, root пользователь в контейнере обладает всеми правами не только в контейнере, но и на хостовой системе если он сможет выйти за пределы изолированного пространства имен контейнера.
-
Непривелегированные контейнеры — это контейнеры в которых процессы принадлежат владельцу UID и GID с номером от 0 до 65535, но для хостовой системы владелец маскируется с помощью добавляемого бита SubUID и SubGID соответственно. Например, пользователь с UID=0 в контейнере будет замечен в хостовой системе как
SubUID + UID
. Это защищает хост-систему, поскольку, если какой-либо процесс в контейнере сможет выйти из своего изолированного пространства имен, он может взаимодействовать с хост-системой только как процесс с неизвестным, очень высоким UID/GID.
По умолчанию, вновь создаваемые контейнеры имеют статус непривелегированных и поэтому мы должны определить SubUID и SubGID.
Создадим два конфигурационных файла в которых зададим маску для SubUID и SubGID соответственно:
sudo touch /etc{/subuid,/subgid}
sudo usermod --add-subuids 1000000-1065535 root
sudo usermod --add-subgids 1000000-1065535 root
Для применения изменений, сервис LXD должен быть рестартован:
sudo systemctl restart lxd
Создание виртуального коммутатора сети
Так как мы ранее инициализировали сеть с помощью мастера инициализации lxd init
и создали сетевое устройство lxdbr0, то в этом разделе мы просто ознакомимся с сетью в LXD и с тем, как создавать виртуальный коммутатор (сетевой мост, bridge) с помощью команды клиента.
Следующая схема демонстрирует как коммутатор (сетевой мост, bridge) объединяет хост и контейнеры в сеть:
Контейнеры могут взаимодействовать по средством сети с другими контейнерами или хостом на котором эти контейнеры обслуживаются. Для этого необходимо слинковать виртуальные сетевые карты контейнеров с виртуальным коммутатором. В начале создадим коммутатор, а сетевые интерфейсы контейнера будут слинкованы в последующих главах, после того как будет создан сам контейнер.
Следующая команда создаёт коммутатор с подсетью 10.0.5.0/24
и IPv4 адресом 10.0.5.1/24
, а также включает ipv4.nat
чтобы контейнеры смогли получать интернет через хост с помощью службы NAT:
lxc network create lxdbr0 ipv4.address=10.0.5.1/24 ipv4.nat=true ipv6.address=none
Проверяем список сетевых устройств доступных LXD:
lxc network list
+--------+----------+---------+-------------+---------+
| NAME | TYPE | MANAGED | DESCRIPTION | USED BY |
+--------+----------+---------+-------------+---------+
| eno1 | physical | NO | | 0 |
+--------+----------+---------+-------------+---------+
| lxdbr0 | bridge | YES | | 0 |
+--------+----------+---------+-------------+---------+
Также, убедится в создании сетевого устройства можно с помощью штатного стредства Linux-дистрибутива — ip link
или ip addr
:
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:ee:7b:5a:6b:44 brd ff:ff:ff:ff:ff:ff
altname enp0s25
inet6 fe80::9571:11f3:6e0c:c07b/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: lxdbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether c2:38:90:df:cb:59 brd ff:ff:ff:ff:ff:ff
inet 10.0.5.1/24 scope global lxdbr0
valid_lft forever preferred_lft forever
inet6 fe80::c038:90ff:fedf:cb59/64 scope link
valid_lft forever preferred_lft forever
5: veth3ddab174@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master lxdbr0 state UP group default qlen 1000
link/ether ca:c3:5c:1d:22:26 brd ff:ff:ff:ff:ff:ff link-netnsid 0
Профиль конфигурации
Каждый контейнер в LXD имеет свою собственную конфигурацию и может расширять её с помощью глобально декларируемых конфигураций которые называются профилями конфигурации. Применение профилей конфигурации к контейнеру имеет каскадную модель, следующий пример демонстрирует это:
В этом примере, в системе LXD созданы три профиля: default
, hddpool
и hostfs
. Все три профиля применены к контейнеру у которого имеется локальная конфигурация (серая зона). Профиль default
имеет устройство root
у которого параметр pool
равен ssdpool
, но благодаря каскадной модели применения конфигурации мы можем применить для контейнера профиль hddpool
у которого параметр pool
перекроет этот же параметр из профиля default
и контейнер получит конфигурацию устройства root
с параметром pool
равным hddpool
, а профиль hostfs
просто добавляет новое устройство в контейнер.
Для того чтобы увидеть список доступных профилей конфигурации служит следующая команда:
lxc profile list
+---------+---------+
| NAME | USED BY |
+---------+---------+
| default | 1 |
+---------+---------+
| hddroot | 0 |
+---------+---------+
| ssdroot | 1 |
+---------+---------+
Полный список доступных команд для работы с профилем можно получить добавив ключ --help
:
lxc profile --help
Description:
Manage profiles
Usage:
lxc profile [command]
Available Commands:
add Add profiles to instances
assign Assign sets of profiles to instances
copy Copy profiles
create Create profiles
delete Delete profiles
device Manage instance devices
edit Edit profile configurations as YAML
get Get values for profile configuration keys
list List profiles
remove Remove profiles from instances
rename Rename profiles
set Set profile configuration keys
show Show profile configurations
unset Unset profile configuration keys
Редактирование профиля
Профиль конфигурации по умолчанию default
не имеет конфигурации сетевой карты для контейнера и все вновь создаваемые контейнеры не имеют сеть, для них необходимо создавать локальные (выделенные) устройства сети отдельной командой, но мы можем создать в профиле конфигурации глобальное сетевое устройство которое будет разделяться между всеми контейнерами использующими этот профиль. Таким образом, сразу после команды создания нового контейнера у них будет сеть с доступом в сеть. При этом, нет ограничений, мы всегда можем позже создать локальное сетевое устройство если будет в этом необходимость.
Следующая команда добавит в профиль конфигурации устройство eth0
типа nic
присоединяемого к сети lxdbr0
:
lxc profile device add default eth0 nic network=lxdbr0 name=eth0
Важно отметить, что так как мы фактически добавили устройство в профиль конфигурации, то если бы мы указали в устройстве статический IP адрес, то все контейнеры которые будут применять этот профиль разделят один и тот же IP адрес. Если есть необходимость создать контейнер с выделенным для контейнера статическим IP адерсом, тогда следует создать конфигурацию сетевого устройства на уровне контейнера (локальная конфигурация) с параметром IP адреса, а не на уровне профиля.
Проверим профиль:
lxc profile show default
config: {}
description: Default LXD profile
devices:
eth0:
name: eth0
network: lxdbr0
type: nic
root:
path: /
pool: ssdpool
type: disk
name: default
used_by: []
В этом профиле мы можем увидеть, что для всех вновь создаваемых контейнеров будут созданы два устройства (devices):
eth0
— Устройство типаnic
соединенное с коммутатором (сетевым мостом)lxdbr0
root
— Устройство типаdisk
которое использует пул хранилищаssdpool
Создание новых профилей
Для использования ранее созданных Storage Pool контейнерами, создадим профиль конфигурации ssdroot
в котором добавим устройство типа disk
с точкой монтирования /
(root) использующее ранее созданное Storage Pool — ssdpool
:
lxc profile create ssdroot
lxc profile device add ssdroot root disk path=/ pool=ssdpool
Аналогично, создаем устройство типа disk
, но в этом случае использующее Storage Pool — hddpool
:
lxc profile create hddroot
lxc profile device add hddroot root disk path=/ pool=hddpool
Проверяем профили конфигурации:
lxc profile show ssdroot
config: {}
description: ""
devices:
root:
path: /
pool: ssdpool
type: disk
name: ssdroot
used_by: []
lxc profile show hddroot
config: {}
description: ""
devices:
root:
path: /
pool: hddpool
type: disk
name: hddroot
used_by: []
Репозиторий образов
Контейнеры создаются из образов которые являются специально собранными дистрибутивами не имеющие ядра Linux. Поэтому, прежде чем запустить контейнер, он должен быть развернут из этого образа. Источником образов служит локальный репозиторий в который образы загружаются из внешних репозиториев.
Удаленные репозитории образов
По умолчанию LXD настроен на получение образов из трёх удаленных источников:
- ubuntu: (for stable Ubuntu images)
- ubuntu-daily: (for daily Ubuntu images)
- images: (for a bunch of other distros)
lxc remote list
+-----------------+------------------------------------------+---------------+-------------+--------+--------+
| NAME | URL | PROTOCOL | AUTH TYPE | PUBLIC | STATIC |
+-----------------+------------------------------------------+---------------+-------------+--------+--------+
| images | https://images.linuxcontainers.org | simplestreams | none | YES | NO |
+-----------------+------------------------------------------+---------------+-------------+--------+--------+
| local (default) | unix:// | lxd | file access | NO | YES |
+-----------------+------------------------------------------+---------------+-------------+--------+--------+
| ubuntu | https://cloud-images.ubuntu.com/releases | simplestreams | none | YES | YES |
+-----------------+------------------------------------------+---------------+-------------+--------+--------+
| ubuntu-daily | https://cloud-images.ubuntu.com/daily | simplestreams | none | YES | YES |
+-----------------+------------------------------------------+---------------+-------------+--------+--------+
Например, репозиторий ubuntu:
имеет следующие образы:
lxc image -c dasut list ubuntu: | head -n 11
+-------------------------------------------------+--------------+----------+-----------------+
| DESCRIPTION | ARCHITECTURE | SIZE | TYPE |
+-------------------------------------------------+--------------+----------+-----------------+
| ubuntu 12.04 LTS amd64 (release) (20150728) | x86_64 | 153.72MB | CONTAINER |
+-------------------------------------------------+--------------+----------+-----------------+
| ubuntu 12.04 LTS amd64 (release) (20150819) | x86_64 | 152.91MB | CONTAINER |
+-------------------------------------------------+--------------+----------+-----------------+
| ubuntu 12.04 LTS amd64 (release) (20150906) | x86_64 | 154.69MB | CONTAINER |
+-------------------------------------------------+--------------+----------+-----------------+
| ubuntu 12.04 LTS amd64 (release) (20150930) | x86_64 | 153.86MB | CONTAINER |
+-------------------------------------------------+--------------+----------+-----------------+
Чтобы вывести ограниченное количество колонок мы использовали опцию -c
с параметрами dasut
, а также ограничили длину списка командой head
.
Для вывода списка образов доступна фильтрация. Следующая команда выведет список всех доступных архитектур дистрибутива AlpineLinux:
lxc image -c ldast list images:alpine/3.11
+------------------------------+--------------------------------------+--------------+--------+-----------+
| ALIAS | DESCRIPTION | ARCHITECTURE | SIZE | TYPE |
+------------------------------+--------------------------------------+--------------+--------+-----------+
| alpine/3.11 (3 more) | Alpine 3.11 amd64 (20200220_13:00) | x86_64 | 2.40MB | CONTAINER |
+------------------------------+--------------------------------------+--------------+--------+-----------+
| alpine/3.11/arm64 (1 more) | Alpine 3.11 arm64 (20200220_13:00) | aarch64 | 2.22MB | CONTAINER |
+------------------------------+--------------------------------------+--------------+--------+-----------+
| alpine/3.11/armhf (1 more) | Alpine 3.11 armhf (20200220_13:00) | armv7l | 2.13MB | CONTAINER |
+------------------------------+--------------------------------------+--------------+--------+-----------+
| alpine/3.11/i386 (1 more) | Alpine 3.11 i386 (20200220_13:01) | i686 | 2.42MB | CONTAINER |
+------------------------------+--------------------------------------+--------------+--------+-----------+
| alpine/3.11/ppc64el (1 more) | Alpine 3.11 ppc64el (20200220_13:00) | ppc64le | 2.31MB | CONTAINER |
+------------------------------+--------------------------------------+--------------+--------+-----------+
| alpine/3.11/s390x (1 more) | Alpine 3.11 s390x (20200220_13:00) | s390x | 2.09MB | CONTAINER |
+------------------------------+--------------------------------------+--------------+--------+-----------+
Локальный репозиторий образов
Для начала эксплуатации контейнера необходимо добавить образ из глобального репозитория в локальный local:
. Сейчас локальный репозиторий пуст, убедится в этом нам даст команда lxc image list
. Если методу list
не указать репозиторий, то по умолчанию будет использоваться локальный репозиторий — local:
lxc image list local:
+-------+-------------+--------+-------------+--------------+------+------+-------------+
| ALIAS | FINGERPRINT | PUBLIC | DESCRIPTION | ARCHITECTURE | TYPE | SIZE | UPLOAD DATE |
+-------+-------------+--------+-------------+--------------+------+------+-------------+
Управление образами в репозитории производится следующими методами:
Команда | Описание |
---|---|
lxc image alias | Manage image aliases |
lxc image copy | Copy images between servers |
lxc image delete | Delete images |
lxc image edit | Edit image properties |
lxc image export | Export and download images |
lxc image import | Import images into the image store |
lxc image info | Show useful information about images |
lxc image list | List images |
lxc image refresh | Refresh images |
lxc image show | Show image properties |
Копируем образ в локальный репозиторий из глобального images:
:
lxc image copy images:alpine/3.11/amd64 local: --alias=alpine3
Image copied successfully!
Выведем список всех образов доступных сейчас в локальном репозитории local:
:
lxc image -c lfdatsu list local:
+---------+--------------+------------------------------------+--------------+-----------+--------+------------------------------+
| ALIAS | FINGERPRINT | DESCRIPTION | ARCHITECTURE | TYPE | SIZE | UPLOAD DATE |
+---------+--------------+------------------------------------+--------------+-----------+--------+------------------------------+
| alpine3 | 73a3093d4a5c | Alpine 3.11 amd64 (20200220_13:00) | x86_64 | CONTAINER | 2.40MB | Feb 21, 2020 at 1:36pm (UTC) |
+---------+--------------+------------------------------------+--------------+-----------+--------+------------------------------+
Конфигурация LXD
Кроме интерактивного режима, LXD поддерживает также неинтерактивный режим установки конфигурации, это когда конфигурация задается в виде YAML-файла, специального формата, который позволяет установить всю конфигурацию за один раз, минуя выполнения множества интерактивных команд которые были рассмотрены выше в этой статье, в том числе конфигурацию сети, создание профилей конфигурации и т.д. Здесь мы не будем рассматривать эту область, вы можете самостоятельно ознакомится с этим в документации.
Следующая интерактивная команда lxc config
которую мы рассмотрим, позволяет устанавливать конфигурацию. Например, для того, чтобы загруженные образы в локальный репозиторий не обновлялись автоматически из глобальных репозиториев, мы можем включить это поведение следующей командой:
lxc config set images.auto_update_cached=false
Создание и управление контейнером
Для создания контейнера служит команда lxc init
которой передаются значения репозиторий:образ
и затем желаемый идентификатор для контейнера. Репозиторий может быть указан как локальный local:
так и любой глобальный. Если репозиторий не указан, то по умолчанию, для поиска образа используется локальный репозиторий. Если образ указан из глобального репозитория, то в начале образ будет загружен в локальный репозиторий, а затем использован для создания контейнера.
Выполним следующую команду чтобы создать наш первый контейнер:
lxc init alpine3 alp --storage=hddpool --profile=default --profile=hddroot
Разберем по порядку ключи команды которые мы здесь используем:
alpine3
— Указывается альяс (псевдоним) для образа который ранее был загружен в локальный репозиторий. Если бы альяс был не создан для этого образа, то всегда можно сослаться на образ по его Fingerprint который выводится в таблице.alp
— Задаётся идентификатор для контейнера--storage
— Этот ключ указывает в каком Storage Pool будет создан контейнер--profile
— Эти ключи каскадно применяют к контейнеру конфигурацию из ранее созданных профилей конфигурации
Запускаем контейнер, который начинает запускать init-систему дистрибутива:
lxc start alp
Также, можно воспользоваться командой lxc launch
которая позволяет объединить команды lxc init
и lxc start
в одну операцию.
Проверяем состояние контейнера:
lxc list -c ns46tb
+------+---------+------------------+------+-----------+--------------+
| NAME | STATE | IPV4 | IPV6 | TYPE | STORAGE POOL |
+------+---------+------------------+------+-----------+--------------+
| alp | RUNNING | 10.0.5.46 (eth0) | | CONTAINER | hddpool |
+------+---------+------------------+------+-----------+--------------+
Проверяем конфигурацию контейнера:
lxc config show alp
architecture: x86_64
config:
image.architecture: amd64
image.description: Alpine 3.11 amd64 (20200326_13:39)
image.os: Alpine
image.release: "3.11"
image.serial: "20200326_13:39"
image.type: squashfs
volatile.base_image: ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
volatile.eth0.host_name: vethb1fe71d8
volatile.eth0.hwaddr: 00:16:3e:5f:73:3e
volatile.idmap.base: "0"
volatile.idmap.current: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.idmap.next: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.last_state.idmap: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.last_state.power: RUNNING
devices:
root:
path: /
pool: hddpool
type: disk
ephemeral: false
profiles:
- default
- hddroot
stateful: false
description: ""
В секции profiles
мы можем убедиться, что этот контейнер использует два профиля конфигурации — default
и hddroot
. В секции devices
мы можем обнаружить только одно устройство, так как сетевое устройство было создано на уровне профиля default
. Для того, чтобы увидеть все устройства используемые контейнером необходимо добавить ключ --expanded
:
lxc config show alp --expanded
architecture: x86_64
config:
image.architecture: amd64
image.description: Alpine 3.11 amd64 (20200326_13:39)
image.os: Alpine
image.release: "3.11"
image.serial: "20200326_13:39"
image.type: squashfs
volatile.base_image: ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
volatile.eth0.host_name: vethb1fe71d8
volatile.eth0.hwaddr: 00:16:3e:5f:73:3e
volatile.idmap.base: "0"
volatile.idmap.current: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.idmap.next: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.last_state.idmap: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.last_state.power: RUNNING
devices:
eth0:
name: eth0
network: lxdbr0
type: nic
root:
path: /
pool: hddpool
type: disk
ephemeral: false
profiles:
- default
- hddroot
stateful: false
description: ""
Установка статического IP адреса
Если мы попытаемся установить IP адрес для сетевого устройства eth0
командой lxc config device set alp
предназначенной для конфигурации контейнера, то мы получим ошибку которая сообщит, что устройство не существует потому что устройство eth0
которое используется контейнером принадлежит профилю default
:
lxc config device set alp eth0 ipv4.address 10.0.5.5
Error: The device doesn't exist
Мы можем конечно установить статический IP адрес для eth0
устройства в профиле, но он будет един для всех контейнеров которые этот профиль будут использовать. Поэтому, добавим выделенное для контейнера устройство:
lxc config device add alp eth0 nic name=eth0 nictype=bridged parent=lxdbr0 ipv4.address=10.0.5.5
Затем нужно рестартовать контейнер:
lxc restart alp
Если мы сейчас посмотрим на конфигурацию контейнера, то нам не нужно применять опцию --expanded
чтобы увидеть сетевое устройство eth0
, так как мы создали его на уровне контейнера и оно каскадно перекрыло это же устройство из профиля default
:
lxc config show alp
architecture: x86_64
config:
image.architecture: amd64
image.description: Alpine 3.11 amd64 (20200326_13:39)
image.os: Alpine
image.release: "3.11"
image.serial: "20200326_13:39"
image.type: squashfs
volatile.base_image: ebd565585223487526ddb3607f5156e875c15a89e21b61ef004132196da6a0a3
volatile.eth0.host_name: veth2a1dc59d
volatile.eth0.hwaddr: 00:16:3e:0e:e2:71
volatile.idmap.base: "0"
volatile.idmap.current: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.idmap.next: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.last_state.idmap: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":65536},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":65536}]'
volatile.last_state.power: RUNNING
devices:
eth0:
ipv4.address: 10.0.5.5
name: eth0
nictype: bridged
parent: lxdbr0
type: nic
root:
path: /
pool: hddpool
type: disk
ephemeral: false
profiles:
- default
- hddroot
stateful: false
description: ""
Удаление контейнера
Для удаления контейнера служит команда lxc delete
, но прежде чем удалить контейнер, он должен быть остановлен с помощью команды lxc stop
:
lxc stop alp
lxc list
+------+---------+-------------------+------+-----------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+---------+-------------------+------+-----------+-----------+
| alp | STOPPED | 10.0.5.10 (eth0) | | CONTAINER | 0 |
+------+---------+-------------------+------+-----------+-----------+
После того как мы убедились, что состояние контейнера стало STOPPED, его можно удалить из Storage Pool:
lxc delete alp
Доступ к контейнеру
Для выполнения команд в контейнере, напрямую, минуя сетевые соединения, служит команда lxc exec
которая выполняет команды в контейнере без запуска системной оболочки. Если вам нужно выполнить команду в оболочке, используя shell паттерны, такие как переменные, файловые перенаправления (pipe) и т.д., то необходимо явно запускать оболочку и передавать команду в качестве ключа, например:
lxc exec alp -- /bin/sh -c "echo $HOME"
В команде был задействован спецсимвол экранирования для спецсимвола
$
чтобы переменная $HOME
не интерпретировалась на хостовой машине, а была интерпретирована только внутри контейнера.
Также, возможно запустить интерактивный режим оболочки, а после завершить сеанс выполнив hotkey CTRL+D
:
lxc exec alp -- /bin/sh
Управление ресурсами контейнера
В LXD можно управлять ресурсами контейнера при помощи специального набора конфигурации. Полный список конфигурационных параметров контейнера можно найти в документации.
Ограничение ресурсов RAM (ОЗУ)
Параметр limits.memory
ограничивает объём RAM доступный для контейнера. В качестве значения указывается число и один из доступных суффиксов.
Зададим контейнеру ограничение на объём RAM равный 256 MB:
lxc config set alp limits.memory 256MB
Также, существуют другие параметры для ограничения памяти:
limits.memory.enforce
limits.memory.hugepages
limits.memory.swap
limits.memory.swap.priority
Команда lxc config show
позволяет вывести на экран всю конфигурацию контейнера, в том числе примененное ограничение ресурсов которое было установлено:
lxc config show alp
architecture: x86_64
config:
image.architecture: amd64
image.description: Alpine 3.11 amd64 (20200220_13:00)
image.os: Alpine
image.release: "3.11"
image.serial: "20200220_13:00"
image.type: squashfs
limits.memory: 256MB
volatile.base_image: 73a3093d4a5ce0148fd84b95369b3fbecd19a537ddfd2e2d20caa2eef0e8fd60
volatile.eth0.host_name: veth75b6df07
volatile.eth0.hwaddr: 00:16:3e:a1:e7:46
volatile.idmap.base: "0"
volatile.idmap.current: '[]'
volatile.idmap.next: '[]'
volatile.last_state.idmap: '[]'
volatile.last_state.power: RUNNING
devices: {}
ephemeral: false
profiles:
- default
stateful: false
description: ""
Ограничение ресурсов CPU (ЦП)
Для ограничения ресурсов ЦП существует несколько типов ограничений:
limit.cpu
— привязывает контейнер к одному или нескольким ядрам ЦПlimits.cpu.allowance
— управляет либо квотами планировщика CFS, когда прошло ограничение по времени, либо универсальным механизмом совместного использования ресурсов CPU, когда прошло процентное значениеlimits.cpu.priority
— приоритет планировщика, когда для нескольких экземпляров, совместно использующих набор процессоров, назначен одинаковый процент процессоров
lxc config set alp limits.cpu.allowance 40%
lxc config show alp
architecture: x86_64
config:
image.architecture: amd64
image.description: Alpine 3.11 amd64 (20200220_13:00)
image.os: Alpine
image.release: "3.11"
image.serial: "20200220_13:00"
image.type: squashfs
limits.cpu.allowance: 40%
limits.memory: 256MB
volatile.base_image: 73a3093d4a5ce0148fd84b95369b3fbecd19a537ddfd2e2d20caa2eef0e8fd60
volatile.eth0.host_name: veth75b6df07
volatile.eth0.hwaddr: 00:16:3e:a1:e7:46
volatile.idmap.base: "0"
volatile.idmap.current: '[]'
volatile.idmap.next: '[]'
volatile.last_state.idmap: '[]'
volatile.last_state.power: RUNNING
devices: {}
ephemeral: false
profiles:
- default
stateful: false
description: ""
Ограничение дискового пространства
Кроме ограничений таких как limits.read
, limits.write
мы также можем ограничить объём потребления контейнером дискового пространства (работает только с ZFS или BTRFS):
lxc config device set alp root size=2GB
После установки, в параметре devices.root.size
мы можем убедится в установленном ограничении:
lxc config show alp
...
devices:
root:
path: /
pool: hddpool
size: 2GB
type: disk
ephemeral: false
profiles:
- default
- hddroot
stateful: false
description: ""
Для просмотра используемых квот на диск мы можем получить из команды lxc info
:
lxc info alp
...
Resources:
Processes: 5
Disk usage:
root: 1.05GB
CPU usage:
CPU usage (in seconds): 1
Memory usage:
Memory (current): 5.46MB
Network usage:
eth0:
Bytes received: 802B
Bytes sent: 1.59kB
Packets received: 4
Packets sent: 14
lo:
Bytes received: 0B
Bytes sent: 0B
Packets received: 0
Packets sent: 0
Не смотря на то, что мы установили ограничение для корневого устройства контейнера в 2GB, системные утилиты такие как df
не будут видеть это ограничение. Для этого мы проведем небольшой тест и выясним как это работает.
Создадим 2 новых одинаковых контейнера в одном и том же Storage Pool (hddpool):
lxc init alpine3 alp1 --storage=hddpool --profile=default --profile=hddroot
lxc init alpine3 alp2 --storage=hddpool --profile=default --profile=hddroot
lxc list
+------+---------+------------------+------+-----------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+---------+------------------+------+-----------+-----------+
| alp1 | RUNNING | 10.0.5.46 (eth0) | | CONTAINER | 0 |
+------+---------+------------------+------+-----------+-----------+
| alp2 | RUNNING | 10.0.5.30 (eth0) | | CONTAINER | 0 |
+------+---------+------------------+------+-----------+-----------+
В одном из контейнеров создадим файл размером 1GB:
lxc exec alp1 -- dd if=/dev/urandom of=file.img bs=1M count=1000
Убедимся, что файл создан:
lxc exec alp1 -- ls -lh
total 1000M
-rw-r--r-- 1 root root 1000.0M Mar 27 10:16 file.img
Если мы посмотрим во втором контейнере, проверим существование файла в том же самом месте, то этого файла не будет, что ожидаемо, так как контейнеры создаются в своих собственных Storage Volume в этом же Storage Pool:
lxc exec alp2 -- ls -lh
total 0
Но давайте сравним значения которые выдает df
на одном и другом контейнерах:
lxc exec alp1 -- df -hT
Filesystem Type Size Used Available Use% Mounted on
/dev/loop1 btrfs 9.3G 1016.4M 7.8G 11% /
...
lxc exec alp2 -- df -hT
Filesystem Type Size Used Available Use% Mounted on
/dev/loop1 btrfs 9.3G 1016.4M 7.8G 11% /
...
Устройство /dev/loop1
смонтированное как корневой раздел является Storage Pool которое эти контейнеры используют, поэтому они разделяют его объём на двоих.
Статистика потребления ресурсов
Просмотреть статистику потребления ресурсов для контейнера можно с помощью команды:
lxc info alp
Name: alp
Location: none
Remote: unix://
Architecture: x86_64
Created: 2020/04/08 18:05 UTC
Status: Running
Type: container
Profiles: default, hddroot
Pid: 19219
Ips:
eth0: inet 10.0.5.5 veth2a1dc59d
eth0: inet6 fe80::216:3eff:fe0e:e271 veth2a1dc59d
lo: inet 127.0.0.1
lo: inet6 ::1
Resources:
Processes: 5
Disk usage:
root: 495.62kB
CPU usage:
CPU usage (in seconds): 1
Memory usage:
Memory (current): 4.79MB
Network usage:
eth0:
Bytes received: 730B
Bytes sent: 1.59kB
Packets received: 3
Packets sent: 14
lo:
Bytes received: 0B
Bytes sent: 0B
Packets received: 0
Packets sent: 0
Работа со снепшотами
В LXD имеется возможность создания снапшотов и восстановления из них состояния контейнера.
Чтобы создать снепшот, выполните следующую команду:
lxc snapshot alp snapshot1
У команды lxc snapshot
не имеется ключа list
, поэтому, чтобы просмотреть список снепшотов нужно воспользоваться командой выводящей общую информацию о контейнере:
lxc info alp
...
...
Snapshots:
snapshot1 (taken at 2020/04/08 18:18 UTC) (stateless)
Восстановить контейнер из снепшота можно командой lxc restore
указав контейнер для которого будет произведено восстановление и псевдоним снепшота:
lxc restore alp snapshot1
Следующая команда служит для удаления снепшота. Обратите внимание, что синтаксис команды не похож на все остальные, здесь необходимо указать прямой слеш после имени контейнера. Если слеш опустить, то команда удаления снепшота интерпретируется как команда удаления контейнера!
lxc delete alp/snapshot1
В приведённом выше примере мы рассмотрели так называемые stateless-снапшоты. В LXD есть и другой тип снапшотов — stateful, в которых сохраняется текущее состояние всех процессов в контейнере. Со stateful-снапшотами связаны ряд интересных и полезных функций.
Что ещё?
- Для Python разработчиков доступен модуль PyLXD который предоставляет API к LXD
Автор: microcoder