Привет! Я продолжаю разрабатывать распределённые системы на основе Tarantool. За последний год наша команда вывела в прод 17 новых систем. В прошлый раз я рассказал, как мы наладили автоматический деплой. В этой статье я покажу, как упростить обслуживание приложений на Tarantool Cartridge.
У одного крупного заказчика процесс выхода в прод сложный. Софт прогоняют через длинный чек-лист и, если система чему-то не соответствует, то никакого прода. Вместе с системой мы должны предоставить документацию по эксплуатации в штатном режиме и траблшутингу на случай аварий. Одной только документации мало, должно быть обучение! Взять хотя бы общий сценарий как восстановить упавшую реплику Tarantool мы должны показать команде эксплуатации последовательность шагов и ответить на вопросы.
Общетарантульные сценарии не меняются от приложения к приложению. А есть специфичные операции: перезагрузить данные кэша, сформировать отчёт по рекламной кампании и т. д. Каждая система будет иметь свой набор. Запуском этих сценариев тоже занимается команда эксплуатации, и тоже необходимо обучение. Однако у нас возникла проблема с доставкой таких операций до конечного исполнителя.
Мы испробовали разные способы:
- Скрипт. Можно подложить в поставку с приложением. Плохая масштабируемость между разными приложениями и командами. Каждый разработчик захочет написать свой скрипт для вкручивания лампочки.
- HTTP API. По сути удалённый способ вызова встроенной функции. Требует расширенной документации и способов использования. Сойдёт для первой реализации.
- Вместе с инструментом автоматизации. Например, отдельные ansible-playbook для работы с теми же HTTP-вызовами. Это удобнее с точки зрения эксплуатации, но требует повышенной осторожности и документации. Кажется слишком громоздким решением.
- Утилита для управления приложением. Единая
точка входа для набора скриптов со встроенной документацией,
возможными аргументами и сценариями использования. Можно
реализовать
--help
для получения справки и всех доступных команд. Лучше переиспользуется, однако не защищает от копирования и создания по одной утилите на каждый проект.
Все методы вскрывали одни и те же проблемы. Каждый разработчик захочет написать свой велосипед, и каждый раз придётся выдумывать на чём ехать. Не забыть бы ещё про экплуатационную документацию, чтобы этим пользоваться. Затем провести обучение по взаимодействию с каждым из велосипедов. С таким подходом на внедрение одного скрипта уйдёт в лучшем случае месяц.
Тут появляется
cartridge admin
, который призван
упростить для разработчика написание и поддержку эксплуатационных
кейсов, повысить переиспользование операций, оптимизировать
доставку до эксплуатации. Больше не нужно выдумывать, куда
приложить кейс и как описать его в документации. cartidge
admin
становится единым местом описания всех процедур
обслуживания. Эксплуатация становится частью кода,
её можно обложить тестами и гарантировать работоспособность.С точки зрения команды эксплуатации ситуация аналогична. Есть 17 проектов: каждый имеет свою специфику, документацию и скрипты. Нужно постоянно переключаться между контекстами: WebUI, tarantoolctl, cartridge, HTTP API и т.д. Чем больше разных вызовов, тем больше вероятность ошибки.
И здесь
cartridge admin
становится единой точкой
взаимодействия. Утилита имеет простой консольный интерфейс и
обратную связь. При обслуживании приложений на Tarantool Cartridge
не придётся переключать контекст. Вызов справки позволяет
ознакомиться со всеми операциями, доступными для конкретной
системы. Риск человеческой ошибки минимизируется.Далее опишу как работать с
cartridge admin
.Необходимые компоненты
- cartridge-cli v2.7.0 https://github.com/tarantool/cartridge-cli
- cartridge-cli-extensions v1.1.0 https://github.com/tarantool/cartridge-cli-extensions
- Tarantool Cartridge v2.4.0 https://github.com/tarantool/cartridge
Установим
cartridge-cli
.Для Centos:
$ curl -L https://tarantool.io/installer.sh | sudo bash -s -- --repo-only$ sudo yum install cartridge-cli
Для MacOS:
$ brew install cartridge-cli
Альтернативно, можно использовать наше энтерпрайз-решение Tarantool Enterprise SDK, который уже содержит нужные версии компонентов и сам Tarantool. Также в составе идет скрипт для настройки окружения
env.sh
.Создаем приложение
Для простоты возьмём проект, созданный с помощью
cartridge
create
(инструкция).
$ cartridge create --name tarantool-demo$ cd tarantool-demo$ cartridge build
Настраиваем сборку артефакта
- В файле проекта tarantool-demo-scm-1.rockspec
нужно добавить зависимость
cartridge-cli-extensions
.
dependencies = { ... 'cartridge-cli-extensions == 1.1.0-1', ...}
- В cartridge.pre-build добавить копирование
бинаря cartridge, так как он не подкладывается в сборку
автоматически. Например, если SDK был распакован в одноименную
директорию:
#!/usr/bin/env bashcp `which cartridge` .
Теперь артефакт будет содержать все нужные модули.
$ cartridge pack tgz --version 1
Пишем admin функцию
Вначале предлагаю ознакомиться с документацией README, которая находится в репозитории
cartridge-cli-extension
. Там пошагово описан процесс
инициализации модуля и регистрации функций.Из интересного:
- В функции можно использовать команду
print()
. Сообщение сразу же будет напечатано в консоль. Удобно для промежуточных результатов выполнения команды, ошибок, прогресса.
Cleanup: ready to proceed on 2 storages Cleanup [1/2 tnt-storage_2]: deleted {products:100} Cleanup [2/2 tnt-storage_1]: deleted {products:50} Deleted total {products:150}------------------- Cleanup: ready to proceed on 2 storages Cleanup [1/2 tnt-storage_2]: error NetboxEvalError: Peer closed
- Консольный вывод поддерживает смайлики (utf-8)
Пример
Я хочу организовать чистку хранилища, а именно таблицы некоторых продуктов. Ниже описана admin функция, которая вызывает
box.space.products:truncate()
на подмножестве серверов
(в данном случае стораджей-мастеров) и возвращает количество
удаленных записей.Для этого добавим в конец файла
init.lua
следующий
код. В коде есть пояснения к каждой секции.
local admin_tasks = {}local json = require('json')local pool = require('cartridge.pool')admin_tasks.cleanup = { -- описываем новую функцию usage = 'Clean old products from cache', -- конкретно cleanup не будет принимать аргументов. -- с форматом аргументов можно ознакомиться в примере на GitHub -- https://github.com/tarantool/cartridge-cli-extensions/blob/master/doc/admin.md#example args = nil, call = function(opts) opts = opts or {} local servers = {} local total = { products = 0 } -- функция для проверки имеет ли репликасет определённую роль local function replicaset_has_role(replicaset, role) for _, name in ipairs(replicaset.roles) do if name == role then return true end end return false end -- обходим все сервера в кластере и выбираем только те, которые: -- 1) имеют роль vshard-storage -- 2) являются мастером в своем репликасете for _, rs in pairs(cartridge.admin_get_replicasets()) do if replicaset_has_role(rs, 'vshard-storage') then local server = rs.active_master if server ~= nil and server ~= "expelled" then if server.status == 'unreachable' then return nil, ("Server %s status is %s"):format(server.alias, server.status) else servers[server.alias] = server.uri end end end end -- выполняем очистку данных по очереди на каждом из стораджей for alias, uri in pairs(servers) do -- префикс можно задать любой, например добавить порядковый номер сервера local prefix = alias local conn, err = pool.connect(uri) if err ~= nil then print(("Cleanup [%s]: error %s"):format(prefix, err)) else -- собственно вызов функции -- здесь есть небольшой хак, создадим таблицы прямо из admin функции -- на реальном примере таблица должна существовать изначально local res = conn:eval([[ box.schema.create_space('products', { if_not_exists = true }) box.space.products:create_index('primary', { if_not_exists = true }) local len = box.space.products:len() box.space.products:truncate() return { products = len } ]]) -- выводим промежуточный результат в консоль print(("Cleanup [%s]: deleted %s"):format(prefix, json.encode(res))) total.products = total.products + res.products end end return ("Deleted total %s"):format(json.encode(total)) end}
Я рекомендую добавлять обход по всем инстансам, как это сделано в примере. Тогда
admin
операции можно запускать с любой
машины, потому что все инстансы кластера могут выполнять одни и те
же функции.Теперь зарегистрируем эту функции в кластере.
-- инициализация модуля adminlocal cli_admin = require('cartridge-cli-extensions.admin')cli_admin.init()-- регистрируем все функции таблицы admin_tasks из примера вышеfor name, task in pairs(admin_tasks) do local ok, err = cli_admin.register(name, task.usage, task.args, task.call) if ok ~= true then error(err) endend
В коде есть создание таблицы прямо в admin-функции. Конечно на реальном примере так делать не нужно. Если запустить функцию без этих строк
box.schema.create_space('products', { if_not_exists = true })box.space.products:create_index('primary', { if_not_exists = true })
то получим ошибку, так как таблицы
products
не
существует.
Failed to call "cleanup": eval:1: attempt to index field 'products' (a nil value)
Запускаем приложение
$ cartridge start -d$ cartridge replicasets setup
Запускаем admin функцию
Все вызовы необходимо производить из директории с бинарем cartridge-cli.
Начиная с
cartridge-cli v2.6
есть два способа
подключиться к инстансу Tarantool.- Через socket-файл
./cartridge admin \ --name tarantool-demo \ --run-dir ./tmp/run \ --instance router \ --list
- С помощью логин-пароля по сети
./cartridge admin \ --conn admin:<cluster-cookie>@localhost:3301 --list
Далее буду рассматривать работу с socket-файлами. Оба способа идентичны в плане запуска команд.
Сначала проверим, какие функции определены в кластере. Используем директиву
--list
:
./cartridge admin \ --name tarantool-demo \ --run-dir ./tmp/run \ --instance router \ --list
- Обязательно задать имя приложения через
--name
. - Директория для поиска socket-файлов
--run-dir
. - Имя инстанса в кластере задается с помощью
--instance
. - При указанных параметрах cartridge-cli будет работать с
кластером через сокет
tarantool-demo.router.control
. Его наличие вrun-dir
обязательно. Для Tarantool Cartridge путь к сокету можно задать, например, через переменную средыTARANTOOL_CONSOLE_SOCK
.
Получаем в ответ:
Available admin functions:cleanup Clean old products from cache
Теперь запустим желаемую операцию. Используя упомянутый выше пример, запущу чистку продуктов:
./cartridge admin \ --name tarantool-demo \ --run-dir ./tmp/run \ --instance router \ cleanup
- Название команды (
cleanup
) и любые параметры, которые были определены для этой команды, указываются в конце.
И всё, по завершению операции кластер будет чист!
Итоги
cartridge admin
оказался неплохой заменой разрозненной
кучке скриптов в нашем окружении. Мы перевели часть операций на его
рельсы и не собираемся останавливаться.Появилась единая точка сбора всех функций по управлению системой. Упростились как технические, так и административные процессы. Меньше затрат на разработку и поддержку эксплуатационных сценариев. С помощью
cartridge admin
обслуживание приложения на
Tarantool Cartridge стало удобнее и понятнее.