Привет! Я продолжаю разрабатывать распределённые системы на основе 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 стало удобнее и понятнее.