Статья 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. Клонирование контейнеров и создание снимков.