# Cartridge
> Занятие 5 / 5
<br/>
<br/>
<br/>
<br/>
<br/>
> Миша Филоненко
> Продвинутый пользователь `Cartridg`-а
[TOC]
<style type="text/css">
* { tab-size: 4; }
/* Resets & overrides */
.reveal dl { display: block; }
.reveal pre { width: auto; box-shadow: none; }
.reveal pre code { width: auto; max-height: initial; }
.reveal blockquote { width: auto; }
.reveal .slides .pdf-page { pointer-events: initial; }
.reveal .slides { text-align: left; }
.reveal section img { display:block; box-shadow: none; margin: initial; padding: initial; border: none; background: none; border-width: 0; }
/* Font style defs */
html, .reveal {
font-family: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
.reveal pre code {
font-family: "PTM55F", "Monaco", monospace;
}
/* Color defs */
.reveal pre code {
background: #2d2d2d;
}
/* Backgrounds */
.reveal .slide-background-content {
background-color: #222;
}
.reveal .first .slide-background-content {
background-color: #222;
}
.reveal section {
background-color: #333;
background-size: cover;
background-image: url(https://slides.hb.bizmrg.com/tarantool/dark/page.png);
}
.reveal .slides section h1,
.reveal .slides section h2,
.reveal .slides section h3,
.reveal .slides section h4,
.reveal .slides section h5,
.reveal .slides section h6{
background: rgba(0,0,0,0.25);
}
/* Font-sizes */
html {
font-size: 32px;
}
.reveal {
font-size: 1rem;
}
.reveal .slides section h1,
.reveal .slides section h2,
.reveal .slides section h3,
.reveal .slides section h4,
.reveal .slides section h5,
.reveal .slides section h6{
font-size: 40px;
}
.reveal pre {
font-size: 24px;
line-height: 1.11;
}
.reveal pre code {
font-size: inherit;
}
.reveal blockquote {
font-size: 28px;
}
/* Paddings */
.reveal .slides section {
width: 960px;
height: 540px;
}
.reveal .slides > section,
.reveal .slides > section > section {
padding: 20px;
}
.reveal .slides > section > section {
margin: 0 -20px;
}
.reveal .slides section h1,
.reveal .slides section h2,
.reveal .slides section h3,
.reveal .slides section h4,
.reveal .slides section h5,
.reveal .slides section h6{
margin: -5px -20px 15px -20px;
padding: 5px 20px;
}
.reveal dl { margin: 0; }
.reveal dd { margin-bottom: 0.25em; }
.reveal pre {
/*margin: 10px -20px 20px -20px;*/
padding: 0;
}
.reveal pre code {
padding: 10px 20px;
}
.reveal blockquote {
margin: 20px -20px 20px -20px;
padding: 0;
}
.reveal blockquote > p {
margin: 0 0 0 40px;
padding: 0 0 0 5px;
}
.reveal section img {
margin: 0 auto;
}
/*** Helpers ***/
.reveal .slides section.center.present {
display: grid !important;
align-items: center;
}
.lr {
display:grid;
grid-gap: 0 50px;
grid-template-columns: 1fr 1fr;
}
section.only-code pre {
position: absolute;
top: 10px;
bottom: 10px;
left: 0;
right: 0;
margin: 0;
}
section.only-code pre code {
height: 100%;
}
/*** Modding ***/
.reveal .slides section .fragment.current-only {
opacity: 1;
visibility: visible;
display: none;
}
.reveal .slides section .fragment.current-only.current-fragment {
display: block;
}
.reveal .slides section li.fragment.current-only.current-fragment {
display: list-item;
}
.reveal .slides section span.fragment.current-only.current-fragment {
display: inline;
}
.reveal .slides section .fragment.visible-blurred {
opacity: 1;
visibility: visible;
filter: blur(2px);
}
.reveal .slides section .fragment.visible-blurred.visible {
filter: none;
}
.reveal .slides section .fragment.visible-blurred.visible.current-fragment {
filter: none;
text-shadow: 2px 2px #676767;
}
/*** Stylings ***/
.reveal section img.shape {
border: none;
background: none;
padding: 0;
margin: 0;
}
dd { font-style: italic; }
dd * { font-style: initial; }
.reveal pre code { overflow: hidden; }
.reveal blockquote > p { border-left: solid #333 10px; }
.reveal .slides section { overflow: hidden; }
section.first *[data-title] {
position: absolute;
left : 0;
bottom: 0px;
}
.reveal .slides section.first h1 {
margin-top: 25px;
background: none;
}
.reveal .slides section.first h2 {
background: none;
}
section.first .author {
position: absolute;
left : 20px;
bottom: 20px;
}
/***/
.reveal .slides section > div:first-child {
height: 100%;
}
.reveal img, .reveal video, .reveal iframe {
max-width: 100%;
max-height: calc(100% - 58px);
}
</style>
---
## Содержание
- Основные функции
- Роли
- Кластерная конфигурация
- Упаковка приложений
- Развертывание приложений
- Пример кластера
---
### Cartridge Cluster
- Горизонтальная масштабируемость <!-- .element: class="fragment" -->
- Балансировка нагрузки <!-- .element: class="fragment" -->
- Автоматическое переключение при сбое <!-- .element: class="fragment" -->
- Централизованное управление кластером <!-- .element: class="fragment" -->
- Автоматическая синхронизации настроек<!-- .element: class="fragment" -->
- Кластеризация бизнес функций <!-- .element: class="fragment" -->
----
### Например
```graphviz
graph {
rankdir = LR;
splines="line";
bgcolor="transparent";
color="#cccccc";
fontcolor="#cccccc";
node[color="#cccccc";
bgcolor="grey"
style="filled"
fontcolor="black";];
edge[color="#cccccc"];
subgraph cluster{
label = "Storages";
style=dashed;
node[shape=cylinder];
storage_msk[label="Moscow"];
storage_spb[label="Saint-Petersburg"];
storage_kn[label="Kazan'"];
}
subgraph cluster_1 {
label="Preprocessors"
style=dashed;
httpjson[label="HTTP API"];
}
subgraph cluster_2 {
label="Preprocessors"
style=dashed;
httpjson2[label="HTTP API"];
}
httpjson -- storage_msk;
httpjson -- storage_spb;
httpjson -- storage_kn;
httpjson2 -- storage_msk;
httpjson2 -- storage_spb;
httpjson2 -- storage_kn;
}
```
----
### GUI
- `html/js` графический интерфейс
- Управление топологией
- Авторизация
- Правка конфигурации
---
## Как устроен кластер
- Реплика — экземпляр, копия мастера, реализующий только чтение <!-- .element: class="fragment" -->
- Лидер (мастер) — экземпляр, в который можно писать <!-- .element: class="fragment" -->
- Набор реплик (`replica set`) — лидер + реплики <!-- .element: class="fragment" -->
- Фейловер — механизм восстановления после отказа <!-- .element: class="fragment" -->
---
## Как узлы кластера видят друг друга
- Пингуют друг друга по `udp brodcast` протоколу распространения слухов (`swim`)
- Управляют друг другом по `net.box` `tcp/ip` протоколу
- Имеют одинаковое значение `cluster cookie`
---
## Роль
- Роль — Lua-модуль, реализующий некоторую логику
- Роль назначается на репликасет — все реплики запустят Lua-модуль
- Роль имеет доступ ко всему `Tarantool`
- Роль может пользоваться модулем `cartridge`
- `cartridge` предоставляет «кластерные» функции
Note:
Роли это Lua-модули, которые реализуют некоторые заданные для экземпляра функции и/или логику.
----
### Жизненный цикл роли
- `init` — включение роли
- `validate_config` — проверка конфигурации
- `apply_config` — применение конфигурации
- `stop` — отключение роли
Note:
Жизненным циклом роли управляет Cartridge.
Кластер вызывает функции роли в следующих случаях:
- Функция init() обычно выполняется один раз: либо когда администратор включает роль, либо при перезапуске экземпляра.
- Функции validate_config()/apply_config(): при обновлении конфигурации.
- Функция stop() – только когда администратор отключает роль.
Администраторы кластера могут работать с ролями либо через веб-интерфейс, либо через общедоступный API кластера.
----
### net.box бизнес API роли
```lua=
-- convertor.lua
function convert()
...
end
return {
...
convert = convert
}
```
----
### Локальные зависимости от других ролей
```lua=
-- convertor.lua
return {
role_name = 'convertor',
dependencies = {
-- embedded roles
'cartridge.roles.vshard-router',
'cartridge.roles.vshard-storage',
-- custom roles
'imagemagick'
}
}
```
Note:
Роли, которые будет инициализирована автоматически для каждого экземпляра с включенной ролью test_role.
Кластер сначала инициализирует встроенные роли, а затем пользовательские в том порядке, в котором последние были перечислены в cartridge.cfg().
----
### Удаленные зависимости между ролями
- `cartridge.rpc_call` — вызов функции роли
- `vshard.router.callrw (-ro, -bro, -bre)` — вызов функции на сторадже
---
## Кластерная конфигурация
- Содержит настройки вашего приложения
- Например
- Схему данных
- Параметры подключения к Postgesql
- Настройки таймаутов
- Размеры преаллокаций
- Конфигурация всего кластера — не отдельного узла
----
### Работа с кластерной конфигурацией
- Каждый экземпляр кластера хранит копию
<!-- .element: class="fragment" -->
- Валидация в коде роли
<!-- .element: class="fragment" -->
- Двухфазный алгоритм применения
<!-- .element: class="fragment" -->
Note:
Анимация
----
### Валидация конфигурации в коде роли
```lua=
-- convertor.lua
local function validate_config(cfg)
local role = cfg['convertor'] or {}
if role.title ~= nil then
assert(type(role.title) == 'string',
'role.title must be a string')
end
return true
end
```
----
### Алгоритм применения конфигурации в кластере
- Проверка доступности всех нод <!-- .element: class="fragment" -->
- `validate_config` на всех нодах <!-- .element: class="fragment" -->
- Создание `config.prepare.yml`
- Если ошибка -> все удаляют `config.prepare.yml`
- <!-- .element: class="fragment" --> config.prepare.yml -> config.yml <!-- .element: class="fragment" -->
- `apply_config` на всех экземплярах <!-- .element: class="fragment" -->
- Если ошибка — ручное вмешательство <!-- .element: class="fragment" -->
Note:
Например функция set_secret() вызывает patch_clusterwide(), которая производит двухфазную фиксацию транзакций:
- Кластер проверяет, можно ли применить новую конфигурацию ко всем экземплярам, кроме отключенных и исключенных. Все обновляемые экземпляры должны быть исправными и рабочими.
- (Фаза подготовки) Кластер передает исправленную конфигурацию. Каждый экземпляр валидирует ее с помощью функции validate_config() каждой зарегистрированной роли. В зависимости от результата валидации:
- В случае успеха (то есть возврата значения true) экземпляр сохраняет новую конфигурацию во временный файл с именем config.prepare.yml в рабочей директории.
- (Фаза отмены) В противном случае экземпляр сообщает об ошибке, а все остальные экземпляры откатывают обновление: удаляют файл, который они, возможно, уже подготовили.
- (Фаза фиксации) После успешной подготовки всех экземпляров кластер фиксирует изменения. Каждый экземпляр:
- Создает жесткую ссылку активной конфигурации.
- Атомарно заменяет активную на подготовленную. Атомарная замена неделима, то есть она может быть либо выполнена, либо не выполнена полностью, но не частично.
- Вызывается функция apply_config() на каждой зарегистрированной роли.
----
### Кластер куки
- `<workdir>/.tarantool.cookie`
- Одинаковая для всех узлова кластера
- Используется для шифрования `udp`
- Используется для пароля `net.box` `admin`
- Вдохновлено `erlang.cookie`
---
## Цикл разработки
- Сам `tarantool`
- Фреймворк `cartridge`
- Утилита `cartridge-cli`
----
### Входная точка
- `init.lua`
- `cartridge.cfg`
- Параметры всего кластера
- Параметры текущего узла
----
### Роли
- Модули с бизнес функциями
- И колбеками для оркестрации
- Взаимодействие с другими ролями
- Локально или удаленно
- Взаимодействие с конфигурацией кластера
----
### Ручной запуск
- Одна нода
- `tarantool init.lua`
- Две и более
- `cartridge start`
----
### Автоматизированные тесты
- `luatest`
- Возможность запуска кластера
- Доступ к любому узлу по `net.box` и другим протоколам
- Возможность сценариев отказов
- Остальное как и во всех фреймворках тестирования
----
### Упаковка
- `cartridge pack`
- Файл проекта `rockspec`
- Кастомные шаги/зависимости
- `cartridge.pre-build`
- `cartridge.post-build`
- Результат `rpm`/`deb`/`targz`/`docker`
- Версионирование `semver.org` (`major.minor.patch`)
---
## Деплой
- Вручную пакетными менеджерами
- `Ansible` роль `ansible-cartridge`
----
### Конфигурация инстанса
- Аргументы командной строки
- Переменные окружения
- Конфигурационный файл формата `YAML`
- В файле `init.lua`
----
### Кластерная конфигурация
- Вкладка `Code` в админке
- Или graphql api
- Или lua api
----
### Мониторинг
- `prometheus`
- `grapfana dashboard`
---
## В заключение
- Tarantool
- Vshard
- Cartridge
- Cartridge CLI
- Ansible
---
<!-- .slide: class="center" -->
### Заполните, пожалуйста, опрос о занятии по ссылке в чате
- Мемы! <!-- .element: class="fragment" -->
---
<!-- .slide: class="center" -->
### Спасибо за проявленное внимание
{"metaMigratedAt":"2023-06-15T17:40:06.427Z","metaMigratedFrom":"YAML","title":"OTUS Cartridge","breaks":true,"slideOptions":"{\"theme\":\"dark\",\"transition\":\"none\",\"backgroundTransition\":\"none\",\"width\":960,\"height\":540,\"margin\":0,\"allottedMinutes\":60}","contributors":"[{\"id\":\"b8f98924-71ad-44a5-89d6-3de16a5bd8ab\",\"add\":15632,\"del\":2373}]"}