Centrifugo
Getting Started
Взять можно тут https://github.com/centrifugal/centrifugo/releases
Я взял deb пакет и установил через dpkg -i
Он предоставляет и systemd-unit, поэтому вдвойне удобно
Далее нужно сгенерить конфиг:
root@centrifugo:~# centrifugo genconfig -c /etc/centrifugo/config.json
root@centrifugo:~# cat /etc/centrifugo/config.json
{
"token_hmac_secret_key": "b06abc67-5044-415e-afff-843be892519c",
"admin_password": "9a722c80-6f7a-4f8f-b5a5-a343e6e4c235",
"admin_secret": "e01a905e-220d-4d6f-84a4-d6d33ada1105",
"api_key": "f71f267a-423b-4d10-a080-8f00bf9bc811",
"allowed_origins": []
}
Если не указывать путь куда генерить то сгенерить туда где ты находишься
Добавляем в конфиг "admin": true
и рестартим (после просто релоада админка не заработает, надо рестартить)
Становится доступна админка:
Вводим admin_password
из конфига и можно тыкать кнопки
Для того чтобы клиент мог подключиться, нужно сгенерировать ему токен:
centrifugo gentoken -u 123722 # число это айдишник юзера (тут случайный)
А так же нужно прописать в конфиге в allowed_origins
разрешенные сайты
(Когда клиент запрашивает апгрейд протокола на websocket, то передает http-заголовок Origin, этот ориджин и должен быть в конфиге)
vandud@centrifugo:~$ cat /etc/centrifugo/config.json
{
"token_hmac_secret_key": "b06abc67-5044-415e-afff-843be892519c",
"admin_password": "9a722c80-6f7a-4f8f-b5a5-a343e6e4c235",
"admin_secret": "e01a905e-220d-4d6f-84a4-d6d33ada1105",
"api_key": "f71f267a-423b-4d10-a080-8f00bf9bc811",
"allowed_origins": [
"http://51.250.23.143"
],
"admin": true
}
Server Guide
Configure Centrifugo
Центрифугу можно конфигурировать разными способами:
- Command-line flags - флаги - самый приоритетный источник настроек, но их не рекомендуется использовать
-
OS environment variables - все опции могут быть представлены в виде переменных окружения с префиксом
CENTRIFUGO_
- Configuration file - конфиг поддерживает все возможные опции и может быть представлен в трех форматах: json, yaml, toml.
Существуют internal endpoints которые не должны быть доступны через интернет
Они выделены в отдельную группу и все вместе могут быть вынесены на отдельный порт с помощью опции internal_port
Эндпоинты можно переопределять в конфиге:
Переопределим endpoint для метрик
root@centrifugo:/etc/centrifugo# tail -n 5 config.yaml
health: true
log_level: debug
prometheus: true
debug: true
prometheus_handler_prefix: /metricosy
И вот результат
(yc-zeen-pro-00:default) vandud@macbook: ~ [0] ? curl -I http://51.250.23.143:8000/metrics
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Thu, 26 May 2022 16:37:21 GMT
Content-Length: 19
(yc-zeen-pro-00:default) vandud@macbook: ~ [0] ? curl -I http://51.250.23.143:8000/metricosy
HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4; charset=utf-8
Date: Thu, 26 May 2022 16:37:23 GMT
Console Commands
Есть команда для валидации конфига
centrifugo checkconfig --config=config.json
Результат работы это exit-code 1 или 0
genconfig
может генерить базовый конфиг в трех форматах (json, toml, yaml)
Для этого нужно указать расширение файла конфига
root@centrifugo:~# centrifugo genconfig -c config.json
root@centrifugo:~# centrifugo genconfig -c config.toml
root@centrifugo:~# centrifugo genconfig -c config.yaml
Результат три файла в разных форматах:
root@centrifugo:~# bat *
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: config.json
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ {
2 │ "token_hmac_secret_key": "74dbbf71-d05b-43ec-8f32-4d8f94a6ed76",
3 │ "admin_password": "152232c9-4b97-4f3d-aba6-192af509c629",
4 │ "admin_secret": "2693d15d-cfbb-4129-be4f-30c5a9836b22",
5 │ "api_key": "8fa3f035-72c1-4d93-8fb6-cf2054792b91",
6 │ "allowed_origins": []
7 │ }
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: config.toml
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ token_hmac_secret_key = "0b32b520-b7c5-48ab-92b0-a9e0f26551f7"
2 │ admin_password = "1196c810-1288-404a-8ab8-571149a59bb0"
3 │ admin_secret = "aeeae78e-f826-40a3-9e89-b9d4d24a6d92"
4 │ api_key = "ee59c24a-fc54-439d-b0ee-d58b7bde9c3f"
5 │ allowed_origins = []
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: config.yaml
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ token_hmac_secret_key: 5e951b11-4a68-4970-bb1c-dd010eca21f3
2 │ admin_password: 95dbf047-7cb0-4310-9c4a-ecc5c79df0c0
3 │ admin_secret: 47f63e69-f48f-4fce-8cea-2f48921a872a
4 │ api_key: da653ff6-f3d9-4cc2-9ef7-f8851f9d9f2f
5 │ allowed_origins: []
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gentoken
/checktoken
позволяют генерить и проверять токены
root@centrifugo:~# centrifugo gentoken -c /etc/centrifugo/config.yaml -t 3600 -u 2
HMAC SHA-256 JWT for user 2 with expiration TTL 1h0m0s:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiZXhwIjoxNjUzNTg3OTI4fQ.Qp1RPsIU-bQTnLr3lec0o34oJ7Hm42OOV5ctgAtXCIc
root@centrifugo:~# centrifugo checktoken -c /etc/centrifugo/config.yaml eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiZXhwIjoxNjUzNTg3OTI4fQ.Qp1RPsIU-bQTnLr3lec0o34oJ7Hm42OOV5ctgAtXCIc
{"level":"info","algorithms":"HS256, HS384, HS512","time":"2022-05-26T16:59:12Z","message":"enabled JWT verifiers"}
valid token for user 2
payload: {"sub":"2","exp":1653587928}
Server API
У центрифуги есть два варианта работы с API: HTTP и GRPC
Будет рассмотрен только HTTP вариант
Чтобы использовать апи нужно в нем аутентифицироваться
Для этого в заголовке нужно передать api_key из конфига
Authorization: apikey <KEY>
В теле запроса нужно передать json с двумя обязательными сущностями:
-
method
- имя вызываемой команды -
params
- опции этой команды (у каждой свои)
{
"method": "info",
"params": {}
}
Получается такая штука:
? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" \
--data '{"method": "info", "params": {}}' http://51.250.23.143:8000/api | jq | head -n 20
{
"result": {
"nodes": [
{
"uid": "b1499040-5927-4ffc-af9c-f6111b71ec53",
"name": "centrifugo_8000",
"version": "3.2.0",
"num_clients": 2,
"num_users": 1,
"num_channels": 1,
"uptime": 10793,
"metrics": {
"interval": 60,
"items": {
"centrifugo.api.command_duration_seconds.count.method.info.protocol.http": 10,
"centrifugo.api.command_duration_seconds.quantile.50.method.info.protocol.http": 5.174e-06,
"centrifugo.api.command_duration_seconds.quantile.99.method.info.protocol.http": 1.6736e-05,
"centrifugo.api.command_duration_seconds.quantile.999.method.info.protocol.http": 1.6736e-05,
"centrifugo.api.command_duration_seconds.sum.method.info.protocol.http": 6.281499999999905e-05,
"centrifugo.client.command_duration_seconds.count.method.connect": 0,
...
Методы
-
publish - позволяет заслать данные в канал (самый частоиспользуемый)
{ "method": "publish", "params": { "channel": "channel", "data": { /* в data можно положить json */ "value": "hello" } } }
- broadcast - то же что и publish, но можно передать список каналов, а не один
- subscribe/unsubscribe - подписывание/отписываение пользователей на каналы
-
disconnect - гасит все сессии пользователя (пользователь - токен - айди)
? cat /tmp/centrifugo.data { "method": "disconnect", "params": { "user": "1" } } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '@/tmp/centrifugo.data' http://51.250.23.143:8000/api
- refresh - (не понял прикола)
-
presence - показывает стату по каналу (всех текущих подписчиков канала)
По умолчанию эта функция выключена, потому что грузит центрифугу, чтобы включить, надо в конфиг прописатьpresence: true
, после этого начинает работать такая штука:? cat /tmp/centrifugo.data { "method": "presence", "params": { "channel": "channel" } } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '@/tmp/centrifugo.data' http://51.250.23.143:8000/api | jq { "result": { "presence": { "3eb68cd6-1194-4009-944a-917a0b58c897": { "user": "1", "client": "3eb68cd6-1194-4009-944a-917a0b58c897" }, "7097cc0e-8d07-466c-aa69-7bee6ef14d08": { "user": "1", "client": "7097cc0e-8d07-466c-aa69-7bee6ef14d08" } } } }
-
presence_stats - то же что выше но кратко (чисто числа)(так же требуется опция в конфиге)
? cat /tmp/centrifugo.data { "method": "presence_stats", "params": { "channel": "channel" } } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '@/tmp/centrifugo.data' http://51.250.23.143:8000/api | jq { "result": { "num_clients": 2, "num_users": 1 } }
-
history - история
В конфиге делаем так:
В апи делаем так:root@centrifugo:~# tail -n 2 /etc/centrifugo/config.yaml history_size: 10 history_ttl: 60s
? cat /tmp/centrifugo.data { "method": "history", "params": { "channel": "channel", "limit": 3 } } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '@/tmp/centrifugo.data' http://51.250.23.143:8000/api | jq { "result": { "publications": [ { "data": { "value": "07fbacc688715758ebe21b8f71bfde---ty pidor---" }, "offset": 85 }, { "data": { "value": "62b5942b6aabb2aa53df28074c1b83---ty pidor---" }, "offset": 86 }, { "data": { "value": "5382002b3f86adf92d9c32cc605cb7---ty pidor---" }, "offset": 87 } ], "epoch": "txQb", "offset": 94 } }
-
history_remove - позволяет удалять историю
Проверяем текущую историю:
Удаляем историю:? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '{"method": "history","params": {"channel": "channel","limit": 2}}' http://51.250.23.143:8000/api | jq { "result": { "publications": [ { "data": { "value": "98122dbb14af6a6655642e0dbdd040---ty pidor---" }, "offset": 496 }, { "data": { "value": "927a91158d3458a10b0bb56cd1c156---ty pidor---" }, "offset": 497 } ], "epoch": "txQb", "offset": 503 } }
Начинаем проверять (два раза, сразу и спустя время, потому что значения в канале обновляются раз в несколько секунд)? cat /tmp/centrifugo.data { "method": "history_remove", "params": { "channel": "channel" } } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '@/tmp/centrifugo.data' http://51.250.23.143:8000/api
? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data{"method": "history","params": {"channel": "channel","limit": 2}}' http://51.250.23.143:8000/api | jq { "result": { "publications": [], "epoch": "txQb", "offset": 504 } } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '{"method": "history","params": {"channel": "channel","limit": 2}}' http://51.250.23.143:8000/api | jq { "result": { "publications": [ { "data": { "value": "2033bac26a0c3a6ec00b0cb717143f---ty pidor---" /* Опа! Появилось! */ }, "offset": 505 } ], "epoch": "txQb", "offset": 505 } }
-
channels - показывает активные каналы (с 1+ подписчиком)
? cat /tmp/centrifugo.data { "method": "channels", "params": {} /* тут правда пусто */ } ? curl -s -X POST --header "Authorization: apikey f71f267a-423b-4d10-a080-8f00bf9bc811" --data '@/tmp/centrifugo.data' http://51.250.23.143:8000/api | jq { "result": { "channels": { "channel": { "num_clients": 2 } } } }
- info - не имеет параметров как предыдущий метод и выдает инфу про ноды
Client Authentication
Клиенты могут аутентифицироваться по JWT
Центрифуга использует следующие ключи из JWT:
-
sub
- user id (остается пустой если используется анонимный режим) -
exp
- standard JWT claim -
iat
- standard JWT claim (используется в Centrifugo PRO) -
jti
- standard JWT claim (используется в Centrifugo PRO) -
info
- Доп инфа о клиентском подключении (This information will be included in presence information, join/leave events, and channel publication if it was published from a client-side) -
b64info
- Для Protobuf -
channels
- Список каналов на которые пользователь будет подписан сразу при установлении подключения без вызова метода subscribe по каждому каналу отдельно (server-side subscriptions) -
subs
- То же что channels но с большим контролем
Channels
Channel name rules
Канал это маршрут для публикаций
Клиенты подписываются на канал и получают сообщения опубликованные в него
Каналы эфемерны, их не надо создавать или удалять явно, они существуют пока существуют подписчики (подписчик подписался - канал создался, подписчиков не осталось - канал удалился)
Соответственно чтобы было куда публиковать сообщения, нужно чтобы были подписчики
Неймспейсы позволяют определять кастомные опции для групп каналов
Имя канала может содержать только ASCII символы и быть не длиннее 255 символов
Несколько символов зарезервированы:
-
:
– for namespace channel boundary. Неймспейс это часть имени канала, поэтомуpublic:chat
иchat
это два разных канала -
$
– for private channel prefix. Если имя канала начинается с доллара, то это приватный канал. На такой канал клиента должен подписать бэкенд (ex:$public:chat
) -
#
– for user channel boundary. После решетки через запятую указываются user id юзеров которые могут подписываться на этот канал (ex:dialog#42,43
) -
*
– for the future Centrifugo needs -
&
– for the future Centrifugo needs -
/
– for the future Centrifugo needs
Channel options
Используя опции можно изменять поведение каналов глобально или per namespace
-
publish
- позволяет клиентам публиковать сообщения. Не рекомендуется, но иногда полезно -
subscribe_to_publish
- заставляет клиента быть подписанным на канал куда он хочет публиковать -
anonymous
- влючает анонимный доступ к каналу -
presence
- включает инфу по каналам (подписчики, пользователи, и тд) -
presence_disable_for_client
- запрещает пользователям смотреть presence инфу -
join_leave
- включает/выключает join/leave messages -
history_size
- размер истории для каналов -
history_ttl
- длительность истории (чтобы история работала нужно указывать обе опции одновременно) -
position
- позволяет центрифуге следить за положением пользователя в стриме (не догнал как это полезно) -
recover
- связано с предыдущей опцией -
history_disable_for_client
- позволяет оставить доступ к истории только для server-side api -
protected
- управляет возможностью пользователей подписываться на любые каналы или только на user-limited -
proxy_subscribe
- проксевая опция -
proxy_publish
- проксевая опция -
subscribe_proxy_name
- проксевая опция -
publish_proxy_name
- проксевая опция
Опции можно прописывать в корне конфига (тогда они будут глобальными), а можно описать для каждого неймспейса отдельно
{
"token_hmac_secret_key": "very-long-secret-key",
"api_key": "secret-api-key",
"anonymous": true,
"publish": true,
"presence": true,
"join_leave": true,
"history_size": 10,
"history_ttl": "30s",
"namespaces": [
{
"name": "public",
"publish": true,
"anonymous": true,
"history_size": 10,
"history_ttl": "300s",
"recover": true
},
{
"name": "gossips",
"presence": true,
"join_leave": true
}
]
}
Engines, scalability
Engine отвечает за паблишинг между нодами, хранение presence и истории
По умолчанию используется движок Memory. Доступны еще Redis, KeyDB и Tarantool
Memory engine позволяет использовать только одну ноду, Редис позволяет запустить несколько нод на разных машинах с подключением через pub/sub, все ноды будут знать друг про друга, будут хранить историю и presence в редисе, а не в памяти, и данные будут доступны на каждой ноде и не будут теряться при рестартах
Движок задается опцией engine
в конфиге, возможные значения: memory
, redis
и tarantool
(по умолчанию memory
)
Memory Engine
Все данные хранятся в памяти процесса. Это работает быстро но это невозможно масштабировать
Для memory enginge в конфиге доступна опция history_meta_ttl
, ее можно задать чтобы не было утечек памяти
Redis Engine
Redis engine позволяет масштабировать центрифугу (потому что данные хранятся во внешнем хранилище)
Для redis engine в конфиге доступно несколько опций:
- redis_address - строка с адресом и портом редиса
- redis_password - строка с паролем (если используется)
- redis_user - строка с именем пользователя (если используется)
- redis_db - число с номером базы
- redis_tls - true/false для redis tls
- redis_tls_skip_verify - true/false для tls host verification
- redis_prefix - префикс для каналов и ключей в редисе
- redis_use_lists - старая опция оставленная для совместимости
- history_meta_ttl - то что и у memory engine
Центрифуга имеет встроенную поддержку шардинга редисов (но шардятся только данные, общего pub/sub у них не будет)
Пример конфига:
{
...
"engine": "redis",
"redis_address": [
"192.168.1.34:6379",
"192.168.1.35:6379",
]
}
Proxy
Можно проксировать некоторые запросы от клиентов к бэкенду сквозь центрифугу
Подробно тут https://centrifugal.dev/docs/server/proxy
History and recovery
Центрифуга может хранить историю публикаций
История конфигурируется на уровне неймспейса
Чтобы она была включена, нужно чтобы опции history_size
и history_ttl
имели значения больше нуля
Каждая публикация в истории имеет поле offset
- это инкрементное uint64
поле
Каждый стрим (redis stream) идентифицируется по полю epoch
Центрифуга предоставляет механизм восстановления данных из истории, это полезно когда клиент находился какое-то небольшое время в автономном режиме (например мигнула сеть), вместо того чтобы клиент шел к бэкенду и получал последние данные от него, он получает их от центрифуги
Таким образом если мигнула сеть на продакшене или мигнул балансер, то все наши клиенты не ломанутся одновременно к бэкенду, а получат данные от центрифуги
Чтобы включить этот механизм нужно в конфиге проставить опцию recover
в true
(глобально или для неймспейса)
Admin web UI
У центрифуги есть встроенный web ui, он позволяет просматривать инфу и статистику по нодам и выполнять команды (паблишить, подписывать итд)
В конфиге есть соответствующие опции:
{
"admin": true,
"admin_password": "<PASSWORD>",
"admin_secret": "<SECRET>"
}
-
admin (boolean, default:
false
) - вкл/выкл web ui -
admin_password (string, default:
""
) - пароль для вебни -
admin_secret (string, default:
""
) - this is a secret key for authentication token set on successful login
Можно использовать кастомную вебню
Например отредактировать стандартную https://github.com/centrifugal/web и заставить центрифугу использовать ее
Для этого в конфиге нужно в опции admin_web_path
указать путь до новой вебни
Можно указать опцию admin_insecure
со значением true
и тогда админка не будет просить пароль (но так делать не надо)
Configure TLS
Лучше переложить задачу терминирования tls на прокси или балансировщик, но при необходимости центрифуга может делать это и самостоятельно
Для этого нужно указать в конфиге пути до серта и ключа
{
...
"tls": true,
"tls_key": "server.key",
"tls_cert": "server.crt"
}
Еще можно заставить центрифугу выпускать серты самостоятельно (через LE)
{
...
"tls_autocert": true,
"tls_autocert_host_whitelist": "www.example.com",
"tls_autocert_cache_dir": "/tmp/certs",
"tls_autocert_email": "user@example.com",
"tls_autocert_http": true,
"tls_autocert_http_addr": ":80"
}