# Управляем кластером на 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).
Команда ``cartridge create`` создает приложение с использованием стандартного шаблона приложения на Cartridge:
```bash
cartridge create --name myapp && cd myapp
```

Стандартный шаблон приложения содержит файлы:
```
### Файлы приложения
├── 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-репозиторий, который уже содержит первый коммит:

Чтобы запустить экземпляры, соберем наше приложение:
```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 и список 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
```

Не обязательно запускать сразу все экземпляры. Можно, например, запустить лишь один ``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``:

С помощью флага ``--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
```

Всё! Одной командой мы настроили топологию, а также включили [шардирование](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
```

Сбросить заданную конфигурацию кластера можно с помощью следующих команд:
```bash
cartridge stop
cartridge clean
```
## Настраиваем failover
После конфигурации топологии кластера, можно настроить failover:
```bash
cartridge failover setup
# посмотрим состояние failover
cartridge failover status
```

Команда ``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 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
```

Хотите собрать 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). Мы всегда поможем с решением вашей проблемы и будем рады интересным предложениям!