Статья 8 из 10, в которой речь пойдёт об использовании API и об автоматизации действий с помощью языков программирования и LXC API.
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, если это нужно.
Примеры на Python 3.
Возможно, управлять контейнерами через 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. Клонирование контейнеров и создание снимков.
Немає коментарів:
Дописати коментар