# Управляем кластером на Tarantool из командной строки Два года назад мы уже [рассказывали](https://habr.com/ru/company/mailru/blog/465503/) вам, что такое Cartridge и как с его помощью разрабатывать распределенные приложения. Это полноценный фреймворк, в который входит CLI-интерфейс, который сильно упрощает разработку и эксплуатацию приложений на Tarantool Cartridge. Я расскажу вам, как можно использовать Cartridge CLI для эффективного использования ваших локальных приложений, и об интересных фичах самого CLI. Статья больше ориентирована на тех, кто уже использует Cartridge или хочет начать им пользоваться. Поехали! ## Какие проблемы решает Cartridge CLI? У нас есть Tarantool Cartridge, который решает проблемы взаимодействия и масштабирования нескольких микросервисов в рамках одного приложения. Поговорим о трудностях, которые возникают в процессе разработки. #### С чего начать? Вы захотели использовать Cartridge. Первая мысль, которая возникает в вашей голове: «Как мне запустить мое приложение?» Как минимум, вам нужно реализовать точку входа. Но при этом вы решите лишь часть проблемы — дальше нужно будет понять, как вообще запускать приложение. Cartridge CLI содержит готовый шаблон приложения. В этом шаблоне есть все необходимые файлы (и не только) для того, чтобы запустить и сконфигурировать ваш кластер. Вам не нужно думать, какие файлы создать в проекте, что они должны из себя представлять и так далее. Кроме того, в стандартный шаблон легко вносить изменения в соответствии с вашими нуждами. #### Сборка, запуск и настройка приложения Приложение нужно собрать: как минимум — поставить сам Cartridge (он устанавливается как отдельный Lua-пакет), а как максимум — еще десяток зависимостей в виде Lua-пакетов (и не только), которые используются в вашем приложении. Конечно, вы можете написать свой скрипт, который будет собирать приложение за вас, и использовать его в каждом вашем приложении. Но зачем всякий раз придумывать велосипед? Допустим, приложение вы успешно собрали. Теперь вы хотите локально запустить экземпляры и сконфигурировать приложение: создать наборы реплик, выставить им роли и так далее... В Cartridge CLI есть всего три команды, с помощью которых можно выполнить вышеуказанные действия в одну строчку: собрать, запустить и настроить ваше приложение. #### Настройка набора реплик и failover Да, управление через GUI нельзя называть проблемой. Для кого-то, может быть, это будет плюсом. Но я всё равно решил выделить как отдельное преимущество Cartridge CLI, и сейчас объясню почему: * Для того, чтобы сконфигурировать кластер через GUI, нужно зайти в браузер и сделать N кликов. Чтобы сделать то же самое с помощью CLI, достаточно ввести одну команду. Как минимум, это экономия времени. * Если вы решите сбросить конфигурацию кластера в GUI и заново настроить его, то всё придется повторить — сделать еще N кликов вместо вызова одной команды. * Вы можете однажды собрать минимальную конфигурацию кластера, сохранить её в файл и закоммитить в репозиторий. После этого кто угодно (в том числе и вы при каждом перезапуске кластера) сможет поднимать настроенный кластер одной командой. * Может быть, вам просто совсем не нравится пользоваться GUI. #### Упаковка приложения Представьте, что вы написали приложение и хотите отправить его клиенту в формате rpm-пакета. Сначала вам нужно будет описать файл *.spec*, установить (например) утилиту ``rpmbuild``, и только после этого начать сборку пакета. Утомительно, не правда ли? В Cartridge CLI процесс упаковки приложения унифицирован, содержит основные формы упаковки приложения (``deb``, ``rpm``, ``tgz`` и ``docker image``) и много упрощающих этот процесс опций. Всё это позволяет не думать об упаковке в принципе, а просто вызвать одну команду с удобным интерфейсом. ## Создаем и запускаем первое приложение Для начала нам потребуется установить Cartridge CLI. Установка на Debian или Ubuntu: ```bash curl -L https://tarantool.io/IMYxqhi/release/2.7/installer.sh | bash sudo apt install cartridge-cli ``` Установка на CentOS, Fedora или ALT Linux: ```bash curl -L https://tarantool.io/IMYxqhi/release/2.7/installer.sh | bash sudo yum install cartridge-cli ``` Установка на MacOS: ```bash brew install cartridge-cli ``` Чтобы убедиться в успешной установке введите команду: ```bash cartridge version ``` В случае, если всеё прошло успешно, вы увидите следующее сообщение: ![cartridge-version](https://sun9-18.userapi.com/impg/LmO7iWWEP_4mUTo9kxaHayihitWybO2fMpRW2g/DhzZlgE8Vv4.jpg?size=2560x348&quality=96&sign=2fb3d5c0444cb280c362a738ab9e48e7&type=album) Не обращайте внимание на предупреждение, ведь проект мы еще не создали (и, соотвественно, не установили сам Cartridge). Команда ``cartridge create`` создает приложение с использованием стандартного шаблона приложения на Cartridge: ```bash cartridge create --name myapp && cd myapp ``` ![cartridge-create](https://sun9-11.userapi.com/impg/osv08fJkwbW6flQ4m_81zhV0LVnkWY3_SqSkWw/2_otV6NiM28.jpg?size=856x240&quality=96&sign=de3b2669ca02bdc4c91555972a0af150&type=album) Стандартный шаблон приложения содержит файлы: ``` ### Файлы приложения ├── README.md ├── init.lua ├── stateboard.init.lua ├── myapp-scm-1.rockspec ├── app │ ├── admin.lua │ └── roles ### Сборка и упаковка ├── cartridge.pre-build ├── cartridge.post-build ├── Dockerfile.build.cartridge ├── Dockerfile.cartridge ├── package-deps.txt ├── systemd-unit-params.yml ### Локальный запуск приложения ├── instances.yml ├── replicasets.yml ├── failover.yml ├── .cartridge.yml ├── tmp ### Тестирование ├── deps.sh ├── test │ ├── helper.lua │ ├── integration │ └── unit ``` Если вам не нравится стадартный шаблон приложения, то можете создать [свой шаблон](https://github.com/tarantool/cartridge-cli/#creating-an-application-from-template) и использовать его: ```bash cartridge create --from mytemplate --name mycustomapp && cd mycustomapp ``` В корне проекта сразу инициализируется локальный Git-репозиторий, который уже содержит первый коммит: ![git-log](https://sun9-54.userapi.com/impg/SXAeEkphrADkMdTiDsd6F1-nVcE_3SSkqVcCsA/aPfdPBmwRuI.jpg?size=1384x194&quality=96&sign=a0bbac0484fca15e20efb96c64728a75&type=album) Чтобы запустить экземпляры, соберем наше приложение: ```bash cartridge build ``` Сборка выполняется с помощью утилиты [tarantoolctl](https://www.tarantool.io/en/doc/latest/reference/tarantoolctl/): она устанавливает все необходимые зависимости, в том числе Cartridge. Зависимости описаны в файле *myapp-scm-1.rockspec*. Если в вашем проекте понадобилась еще какая-либо зависимость, отредактируйте файл *.rockspec*, поместив зависимость туда, и введите команду ``cartridge build``. Проект содержит файл ``cartridge.pre-build``: он запускается перед установкой зависимостей. Например, вы можете установить нестандартные модули ``rocks`` с помощью того же ``tarantoolctl``: ```bash #!/bin/sh tarantoolctl rocks make --chdir ./third_party/my-custom-rock-module ``` Проверим, что проект был успешно собран и все зависимости установлены: ![cartridge-version-rocks](https://sun9-18.userapi.com/impg/dU4ndfC3pQiOdPAUNcFSTdunZ9DSSYU9vep3Ag/C0OtuogL3BY.jpg?size=1102x1032&quality=96&sign=bd6f04074b976a1843255337a5204542&type=album) На экране появилась информация о версии Cartridge и список rocks проекта — информация о них выводится на экран, если указан флаг ``--rocks``. В файле *instances.yml* можно сконфигурировать ваши экземпляры (имя, порт и так далее). Описание должно быть в формате ``<имя-приложения>.<имя экземпляра>``. Стандартый шаблон приложения уже содержит этот файл: ```yaml myapp.router: advertise_uri: localhost:3301 http_port: 8081 myapp.s1-master: advertise_uri: localhost:3302 http_port: 8082 myapp.s1-replica: advertise_uri: localhost:3303 http_port: 8083 myapp.s2-master: advertise_uri: localhost:3304 http_port: 8084 myapp.s2-replica: advertise_uri: localhost:3305 http_port: 8085 ``` Чтобы запустить описанные в этом файле экземпляры, используйте команду ``cartridge start``: ```bash cartridge start -d # убедимся, что все инстансы приложения были успешно запущены cartridge status ``` ![cartridge-start-status](https://sun9-62.userapi.com/impg/9xzKLpz2DGBfRp54FZrLMnEdeeWLNdRNxakwyw/qY1KKf9FeL4.jpg?size=1978x1424&quality=96&sign=e5513dd63b9c3f4126f88fa747a8fa17&type=album) Не обязательно запускать сразу все экземпляры. Можно, например, запустить лишь один ``s1-master``: ```bash cartridge start s1-master -d cartridge status s1-master ``` Точкой входа в наше приложение является файл с именем ``init.lua``. Именно он под капотом запускает ``cartridge start``. Стандартый шаблон приложения в своём корне содержит папку ``tmp``. * ``tmp/run`` — директория, хранящая PID процессов-экземпляров и socket-файлы; * ``tmp/data`` — директория, хранящая данные экземпляров; * ``tmp/log`` — директория, хранящая логики экземпляров. Вы можете изменить стандартные пути к вышеописанным директориям, указав новые пути в файле *.cartridge.yml* или с помощью [флагов](https://github.com/tarantool/cartridge-cli/#options) команды ``cartridge start``. Чтобы запустить экземпляры в фоновом режиме, используйте флаг ``-d``. В таком случае логи будут сохраняться в файл и мы сможем их посмотреть с помощью команды ``cartridge log``: ![cartridge-log](https://sun9-16.userapi.com/impg/wTba3ySQpAxHdzPdSKD3eSkF1uz3II4jh4-JYw/0myAjxRC3uE.jpg?size=2560x631&quality=96&sign=d61b2b03b24f59deb8bd294dddca0249&type=album) С помощью флага ``--stateboard`` или настройки ``stateboard: true`` в файле конфигурации *.cartridge.yml* вы можете также запустить изолированный экземпляр Tarantool, который можно будет использовать в качестве поставщика состояний для failover. Я предлагаю всегда запускать экземпляр ``stateboard`` (в дальнейшем его можно использовать для настройки failover), поэтому стандартный шаблон приложения уже содержит флаг ``stateboard: true``. При необходимости вы можете безболезненно убрать этот флаг из файла конфигурации. ## Настраиваем топологию Команда ``cartridge replicasets`` позволяет выполнять различные действия по изменению топологии кластера (например, из конфигурационного файла) и сохранять конфигурацию кластера в файл. Созданный командой ``cartridge create`` шаблон приложения содержит файл *replicasets.yml*, с помощью которого мы можем сконфигурировать базовую топологию нашего кластера: ```bash cartridge replicasets setup --bootstrap-vshard # убедимся, что топология была успешно настроена cartridge replicasets list ``` ![cartridge-replicasets](https://sun9-80.userapi.com/impg/ku2l8EvfWbnvygFMK81Ma_e8pgsnrFngHWdbuQ/pT9WBPjzcrg.jpg?size=1828x944&quality=96&sign=24d6a05d50f64fda54b93a9fc7dd6696&type=album) Всё! Одной командой мы настроили топологию, а также включили [шардирование](https://www.tarantool.io/ru/doc/latest/reference/reference_rock/vshard/). Круто, не правда ли? Рассмотрим файл *replicasets.yml* подробнее: ```yaml router: instances: - router roles: - failover-coordinator - vshard-router - app.roles.custom all_rw: false s-1: instances: - s1-master - s1-replica roles: - vshard-storage weight: 1 all_rw: false vshard_group: default s-2: instances: - s2-master - s2-replica roles: - vshard-storage weight: 1 all_rw: false vshard_group: default ``` В нем содержится три набора реплик с именами ``router``, ``s-1`` и ``s-2``. * ``instances`` — в этом блоке описаны экземпляры, которые содержит каждый из набора реплик. Имена экземпляров должны совпадать с именами, которые описаны в *instances.yml*. * В блоке ``roles`` описаны [роли](https://www.tarantool.io/ru/doc/latest/book/cartridge/cartridge_dev/#cluster-roles) для каждого набора реплик; * ``weight`` — vshard-вес набора реплик. * ``all_rw`` — флаг, указывающий, что все экземпляры в наборе реплик должны быть доступны как для чтения, так и для записи. * ``vshard_group`` — имя группы vshard, к которой принадлежит набор реплик. Если вдруг вам захотелось настроить всё это в ручную (без использования конфига *replicasets.yml*), то можете воспользоваться другими опциями ``cartridge replicasets``: ```bash # объединим экземпляры s1-master и s1-replica в набор реплик s-1: cartridge replicasets join --replicaset s-1 s1-master s1-replica # добавим реплику router: cartridge replicasets join --replicaset router router # посмотрим текущие доступные роли и выберем из них подходящие для каждой из реплик: cartridge replicasets list-roles # добавим роль vshard-storage для набора реплик s-1: cartridge replicasets add-roles --replicaset s-1 vshard-storage # также добавим роли для реплики router: cartridge replicasets add-roles \ --replicaset router \ vshard-router app.roles.custom failover-coordinator metrics # и наконец забутстрапим vshard: cartridge replicasets bootstrap-vshard # посмотрим конфигурацию набора реплик: cartridge replicasets list ``` ![cartridge-replicasets-2](https://sun9-61.userapi.com/impg/1BDhLIByeNTXX6kDAu_DstIf19KHZwao1bPvIQ/UEgGdSx31KE.jpg?size=2062x1532&quality=96&sign=3c797e1902caade6f73ba55fc1fe01af&type=album) Сбросить заданную конфигурацию кластера можно с помощью следующих команд: ```bash cartridge stop cartridge clean ``` ## Настраиваем failover После конфигурации топологии кластера, можно настроить failover: ```bash cartridge failover setup # посмотрим состояние failover cartridge failover status ``` ![cartridge-failover](https://sun9-12.userapi.com/impg/9hvA-BeYoJh3C_WQJfkgqh_HYTwbNeJmUFA3oA/CP-z9k1vty4.jpg?size=2092x772&quality=96&sign=a4477499d68c6da866e925cb4e89d23a&type=album) Команда ``cartridge failover setup`` использует конфигурацию, описанную в файле *failover.yml*, который находится в корне созданного приложения. ```yaml mode: stateful state_provider: stateboard stateboard_params: uri: localhost:4401 password: passwd ``` У failover может быть три состояния: ``eventual``, ``stateful`` и ``disabled``: * ``eventual`` и ``disabled`` не требуют никаких дополнительных настроек; * ``stateful`` требует указания поставщика состояний (поле ``state_provider``) и указания параметров для этого поставщика. На данный момент поддерживаются поставщики ``stateboard`` и ``etcd2``. Подробнее об архитектуре failover вы можете прочитать [здесь](https://www.tarantool.io/en/doc/1.10/book/cartridge/topics/failover/). А [здесь](https://github.com/tarantool/cartridge-cli/blob/master/doc/failover.rst) вы можете прочитать о всех параметрах, которые вы можете указать при его конфигурации. Вы также можете использовать команду ``cartridge failover set`` для ввода настроек failover прямо в командной строке: ```bash cartridge failover set stateful \ --state-provider etcd2 \ --provider-params '{"lock_delay": 15}' ``` Для отключения failover используйте следующие команды: ```bash cartridge failover disable # или cartridge failover set disabled ``` ## Подключаемся к экземплярам Вам вдруг понадобилось подключиться экземпляру и ввести там интересующие вас команды, например, выполнить [``cartridge.reload_roles()``](https://www.tarantool.io/en/doc/latest/book/cartridge/cartridge_api/modules/cartridge.roles/#reload)? Легко! С помощью ``cartridge enter`` вы можете подключиться к экземпляру через консольный сокет, размещенный в ``run-dir``. Никаких дополнительных параметров не нужно, достаточно ввести имя экземпляра, указанного в ``instances.yml``: ```bash cartridge enter instance-name ``` ![cartridge-enter](https://sun9-2.userapi.com/impg/ijSv1HCN0vX2UHHaYcxUAPD1aMwMZFbIudS7yg/igxcrv1n4NU.jpg?size=1566x746&quality=96&sign=f9b2e9e37fe23e200e92d1a8cbce671b&type=album) Вы также можете использовать ``cartridge connect`` для подключения к интересующему вас экземпляру. Отличие этого подхода в том, что вы можете указать адрес экземпляра или путь к UNIX-сокету. ```bash cartridge connect localhost:3301 \ --username admin \ --password secret-cluster-cookie # либо cartridge connect admin:secret-cluster-cookie@localhost:3301 ``` ## Упаковываем приложение Для упаковки приложения есть команда ``cartridge pack <type>``. На данный момент поддерживается четыре варианта упаковки: * ``deb`` — deb-пакет; * ``rpm`` — rpm-пакет; * ``tgz`` — tgz-архив; * ``docker`` — Docker-образ. Например, для упаковки вашего приложения в tgz-архив используйте следующую команду: ```bash cartridge pack tgz ``` ![cartridge-pack-tgz](https://sun9-14.userapi.com/impg/UvMD84wNoctqf81M2PtvlRThFch5oPkF7lYQjA/njfNLCmQNzw.jpg?size=2560x780&quality=96&sign=032bd05f2b65339aed1f6f31e0b6a0ad&type=album) Хотите собрать rpm- или deb-пакет, но при этом используете MacOS? Вы не можете сделать это просто так: в упакованном приложении будут ``rocks`` и исполняемые файлы, которые нельзя использовать в Linux. Специально для такого случая существует флаг ``--use-docker``, который собирает в Docker: ```bash cartridge pack deb --use-docker ``` Помимо ``--use-docker``, команда ``cartridge pack`` имеет множество других полезных опций. Рассмотрим самые интересные из них. ### Добавляем зависимости в пакет Добавим в наш rpm- или deb-пакет какую-нибудь зависимость. Например, ``unzip``: ```bash cartridge pack deb --deps unzip>=6.0 ``` Либо вы можете описать зависимости для вашего пакета в файле *package-deps.txt*, который уже находится в корне созданного приложения: ``` unzip==6.0 neofetch>=6,<7 gcc>8 ``` Теперь, упаковав приложение с помощью команды ``cartridge pack deb``, ваш пакет будет содержать зависимости ``unzip``, ``neofetch`` и ``gcc``. Вы можете использовать файл с другим именем для указания ваших зависимостей с помощью флага ``--deps-file``: ```bash cartridge pack rpm --deps-file=path-to-deps-file ``` ### Добавляем сборочные сценарии до (и после) А что если во время упаковки вам нужно создать файл, папку, поставить какую-либо утилиту — то есть внести какие-либо изменения в сценарий упаковки приложения? Для этого используются файлы *preinst.sh* и *postinst.sh*. Все пути к исполняемым файлам в сценариях до и после установки должны быть абсолютными. Либо используйте ``/bin/sh -c ''``: ```bash /bin/sh -c 'touch file-path' /bin/sh -c 'mkdir dir-path' ``` С помощью флагов ``--preinst`` и ``--postinst`` вы можете использовать файлы с любым именем: ```bash cartridge pack rpm \ --preinst=path-to-preinst-script \ --posints=path-to-posinst-script ``` Сценарии работают только для сборки rpm- и deb-пакетов. ### Кешируем пути При каждом запуске упаковки приложение собирается с нуля. Например, сборка всех зависимостей (т.е. ``rocks``) начинается заново. Чтобы этого избежать (и уменьшить время переупаковки приложения), существует опция кеширования путей и файл *pack-cache-config.yml*: ```yaml - path: '.rocks': key-path: 'myapp-scm-1.rockspec' - path: 'node_modules': always-cache: true - path: 'third_party/custom_module': key: 'simple-hash-key' ``` Рассмотрим подробнее параметры конфигурационного файла: * ``path`` — путь от корня проекта до кешируемого пути; * ``key-path`` — путь до файла, содержание которого будет ключом кеширования. В примере выше для пути ``.rocks`` ключом кширования является файл ``myapp-scm-1.rockspec`` — если изменить его, то ``cache hit`` не произойдет и все ``rocks`` приложения будут собираться заново; * ``always-cache`` — кеширование указанного пути всегда, независимо от каких-либо ключей; * ``key`` — простой ключ кеширования в виде строки. В стандартном шаблоне приложения уже содержится один кешируемый путь: ```yaml - path: '.rocks' key-path: myapp-scm-1.rockspec ``` Я предлагаю всегда кешировать содержимое папки ``.rocks`` опираясь на содержимое файла *.rockspec*. Для одного пути может быть только один кеш. Например, у вас в кеше находится папка ``.rocks``, вы меняете ключ и запускаете упаковку приложения. В этот момент старый кеш ``.rocks`` удаляется и заменяется новым на основе нового ключа. Чтобы отключить кеширование путей, используйте флаг ``--no-cache``. С полным списком опций команды ``cartridge pack`` вы можете ознакомиться [здесь](https://github.com/tarantool/cartridge-cli#packing-an-application). ### Подробнее о процессе упаковки Команда ``cartridge pack`` помимо упаковки приложения в пакет ещё и собирает его (аналогично команде ``cartridge build``). По умолчанию сборка выполняется во временной директории ``~/.cartridge/tmp``. Вы можете изменить её на свою, установив значение переменной окружения ``CARTRIDGE_TEMPDIR``: * Если эта директория не существует, она будет создана и использована для сборки приложения, а затем удалена. * В противном случае сборка будет выполнена в директории ``CARTRIDGE_TEMPDIR/cartridge.tmp``. Создание временной директории (в которой будет выполняться сборка) с исходными файлами вашего приложения происходит в три этапа: 1. Копирование файлов во временную директорию и её очистка. Папка с приложением копируется во временную директорию, выполняется команда ``git clean -X -d -f`` для удаления неотслеживаемых файлов и удаляются папки *.rocks* и *.git*. 2. Сборка приложения в очищенной директории. 3. Запуск скрипта *cartridge.post-build* (если он существует). В корне проекта находится файл *cartridge.post-build*. Это скрипт, основная цель которого — удаление артефактов сборки из результирующего пакета. После сборки приложения во временной директории генерируются специальные файлы, такие как ``VERSION`` и ``VERSION.lua``, которые содержат версию приложения. В случае сборки в rpm и deb инициализируются директории ``systemd`` и ``tmpfiles``. Далее приложение упаковывается. Подробнее о структуре и дальнейшей работе с полученными rpm- и deb-пакетами вы можете прочитать [здесь](https://github.com/tarantool/cartridge-cli/#usage-example). А [здесь](https://github.com/tarantool/cartridge-cli/#id5) — про работу с Docker-образами. ## Итоги Cartridge CLI содержит удобный и унифицированный интерфейс для управления приложением и позволяет не придумывать велосипед. В этой статье я рассказал о том, как можно из командной строки максимально эффективно и удобно управлять вашим локальным приложением, написанным на Tarantool Cartridge: запускать, настраивать топологию и failover, упаковывать приложение и подключаться к его экземплярам. Cartridge CLI имеет еще одну команду, о которой я не рассказал в этой статье. Команда ``cartridge admin`` призвана упростить разработчикам написание и поддержку эксплуатационных кейсов, повысить переиспользование операций, оптимизировать поставку в эксплуатацию. Почитать подробнее об этом вы можете в [этой статье](https://habr.com/ru/company/mailru/blog/548228/). Если у вас что-то вдруг пошло не так, или вы знаете, как можно улучшить продукт, то всегда можете завести [тикет в нашем GitHub-репозитории](https://github.com/tarantool/cartridge-cli/issues/new). Мы всегда поможем с решением вашей проблемы и будем рады интересным предложениям!