Статья 8 из 10, в которой речь пойдёт об использовании API и об автоматизации действий с помощью языков программирования и LXC API.
Первая версия liblxc появилась в LXC 0.9, но в экспериментальном состоянии. В LXC 1.0 будет гораздо полнее API, который охватывает все возможности LXC. Даже инструменты lxc-* теперь используют вызовы API, а не прямые вызовы внутренних функций.
API идёт с рядом тестов, которые являются неотъемлемой частью цикла разработки. Кто не хочет писать на языке С, то есть привязки (bindings) для языков LUA, Python3, Go и Ruby.
Документация по API доступна по адресу https://qa.linuxcontainers.org/master/current/doc/api/
Данная документация не есть верх совершенства, не хватает примеров, особенно для привязок, но документация покрывает все функции API и это главное. Любая помощь по улучшению документации API только приветствуется.
Лучше начать с очень простого примера на языке С, используя LXC API. В примере создаётся структура apicontainer, корневая файловая система (rootfs), используя шаблон download. Затем контейнер запускается, печатается его состояние, PID и производится попытка его корректно остановить до уничтожения.
#include <stdio.h>
#include <lxc/lxccontainer.h>
int main() {
struct lxc_container *c;
int ret = 1;
/* Настройка структуры */
c = lxc_container_new("apicontainer", NULL);
if (!c) {
fprintf(stderr, "Failed to setup lxc_container struct\n");
goto out;
}
if (c->is_defined(c)) {
fprintf(stderr, "Container already exists\n");
goto out;
}
/* Создание контейнера */
if (!c->createl(c, "download", NULL, NULL, LXC_CREATE_QUIET,
"-d", "ubuntu", "-r", "trusty", "-a", "i386", NULL)) {
fprintf(stderr, "Failed to create container rootfs\n");
goto out;
}
/* Старт контейнера */
if (!c->start(c, 0, NULL)) {
fprintf(stderr, "Failed to start the container\n");
goto out;
}
/* Запрос информации */
printf("Container state: %s\n", c->state(c));
printf("Container PID: %d\n", c->init_pid(c));
/* Остановка контейнера */
if (!c->shutdown(c, 30)) {
printf("Failed to cleanly shutdown the container, forcing.\n");
if (!c->stop(c)) {
fprintf(stderr, "Failed to kill the container.\n");
goto out;
}
}
/* Уничтожение контейнера */
if (!c->destroy(c)) {
fprintf(stderr, "Failed to destroy the container.\n");
goto out;
}
ret = 0;
out:
lxc_container_put(c);
return ret;
}
Как видно из примера, работать с API LXC в языке программирования С несложно. Большинство функций достаточно понятны и проверка ошибок проста, так как возвращаемое значение булево, легко его проверить и вывести ошибку на stderr, если это нужно.
Возможно, управлять контейнерами через API вы захотите на скриптовых языках. Попробуем предыдущий пример написать, используя ЯП Python 3.
import lxc
import sys
# Настройка объекта
c = lxc.Container("apicontainer")
if c.defined:
print("Container already exists", file=sys.stderr)
sys.exit(1)
# Создание rootfs
if not c.create("download", lxc.LXC_CREATE_QUIET, {"dist": "ubuntu",
"release": "trusty",
"arch": "i386"}):
print("Failed to create the container rootfs", file=sys.stderr)
sys.exit(1)
# Старт контейнера
if not c.start():
print("Failed to start the container", file=sys.stderr)
sys.exit(1)
# Остановка контейнера
print("Container state: %s" % c.state)
print("Container PID: %s" % c.init_pid)
# Остановка контейнера
if not c.shutdown(30):
print("Failed to cleanly shutdown the container, forcing.")
if not c.stop():
print("Failed to kill the container", file=sys.stderr)
sys.exit(1)
# Уничтожение контейнера
if not c.destroy():
print("Failed to destroy the container.", file=sys.stderr)
sys.exit(1)
Для данного примера код на Питоне 3 получился нисколько не проще, чем на С. Но что, если сделать пример чуть более функциональнее? Перебрать имена существующих контейнеров и запустить их, если они не запущены. Подождать когда они запустятся и станут доступны по сети, приказать им обновиться и остановить их.
import lxc
import sys
for container in lxc.list_containers(as_object=True):
# Старт контейнера (если он не запущен)
started=False
if not container.running:
if not container.start():
continue
started=True
if not container.state == "RUNNING":
continue
# ждать соединения с контейнером
if not container.get_ips(timeout=30):
continue
# выполнить команду обновления
container.attach_wait(lxc.attach_run_command,
["apt-get", "update"])
container.attach_wait(lxc.attach_run_command,
["apt-get", "dist-upgrade", "-y"])
# завершить работу контейнера
if started:
if not container.shutdown(30):
container.stop()
Наиболее интересное в примере это attach_wait, благодаря которой можно вызвать в пространстве имён контейнера нужную команду. Приведённый ниже пример это ясно доказывает. Если пример сохранить под именем lxc-api.py
import lxc
c = lxc.Container("p1")
if not c.running:
c.start()
def print_hostname():
with open("/etc/hostname", "r") as fd:
print("Hostname: %s" % fd.read().strip())
# первый запуск в хостовой системе
print_hostname()
# запуск в контейнере - гостевая система
c.attach_wait(print_hostname)
if not c.shutdown(30):
c.stop()
и вызвать python3 lxc-api.py, то можно будет увидеть что-то типа
Hostname: castiana
Hostname: p1
Функции, которые даёт API, нужно "умножить" на доступные флаги типа LXC_ATTACH_* в C API, позволяя контролировать подключаемые пространства имён, AppArmor, обход ограничений cgroups и так далее. Такая гибкость позволит вам реализовать любые задумки по автоматизации ваших контейнеров. API так же позволяет управлять клонированием контейнера и созданием снимков.
import lxc
import os
import sys
if not os.geteuid() == 0:
print("The use of overlayfs requires privileged containers.")
sys.exit(1)
# Create a base container (if missing) using an Ubuntu 14.04 image
base = lxc.Container("base")
if not base.defined:
base.create("download", lxc.LXC_CREATE_QUIET, {"dist": "ubuntu",
"release": "precise",
"arch": "i386"})
# Customize it a bit
base.start()
base.get_ips(timeout=30)
base.attach_wait(lxc.attach_run_command, ["apt-get", "update"])
base.attach_wait(lxc.attach_run_command, ["apt-get", "dist-upgrade", "-y"])
if not base.shutdown(30):
base.stop()
# Clone it as web (if not already existing)
web = lxc.Container("web")
if not web.defined:
# Clone base using an overlayfs overlay
web = base.clone("web", bdevtype="overlayfs",
flags=lxc.LXC_CLONE_SNAPSHOT)
# Install apache
web.start()
web.get_ips(timeout=30)
web.attach_wait(lxc.attach_run_command, ["apt-get", "update"])
web.attach_wait(lxc.attach_run_command, ["apt-get", "install",
"apache2", "-y"])
if not web.shutdown(30):
web.stop()
# Create a website container based on the web container
mysite = web.clone("mysite", bdevtype="overlayfs",
flags=lxc.LXC_CLONE_SNAPSHOT)
mysite.start()
ips = mysite.get_ips(family="inet", timeout=30)
if ips:
print("Website running at: http://%s" % ips[0])
else:
if not mysite.shutdown(30):
mysite.stop()
Пример создаёт базовый контейнер с именем base с помощью шаблона download, клонирует его с помощью overlayfs под именем web, устанавливает apache2 и клонирует снова под именем mysite.
Данные примеры не показали всё что можно сделать через API. К примеру, не дан пример создания снимков, которые временно ограничены системными контейнерами и поверхностно сказано о возможностях функции attach.
LXC 1.0 выйдет со стабильной версией API. Новые возможности будут добавляться в 1.x версию, а в 1.0.х будут добавляться только исправления ошибок.
Предыдущая статья LXC 1.0: Непривилегированные контейнеры.
Следующая статья LXC 1.0: GUI в контейнере.
Дополнительные материалы:
Серия статей LXC 1.0. от Стефана Грабера.
Хранилище overlayfs в LXC. Клонирование контейнеров и создание снимков.