
Привет! Я продолжаю разрабатывать распределённые системы на основе 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 bash cp `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 операции можно запускать с любой машины, потому что все инстансы кластера могут выполнять одни и те же функции.Теперь зарегистрируем эту функции в кластере.
-- инициализация модуля admin local 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) end end
В коде есть создание таблицы прямо в 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 стало удобнее и понятнее.