Свежеустановленный Redis производит впечатление идеального соседа: слушает только локальную петлю, пароля не спрашивает, памяти берёт ровно столько, сколько весят данные. Спокойствие заканчивается тремя событиями: приложение переезжает на соседнюю машину, сервер перезагружают среди ночи, а в базе внезапно обнаруживаются не только кэш, но и сессии пользователей, корзина магазина и очередь писем, для которых нет ни одной копии на диске. Ниже весь путь до состояния, при котором не страшно утром смотреть в логи: установка пакета, пароль и отдельный пользователь для приложения, два механизма записи на диск, закрытый порт и проверочное восстановление после жёсткой перезагрузки.

Почему свежий Redis из репозитория Ubuntu всегда старше релизов разработчиков

Штатные пакеты неспешны, и это не лень мейнтейнеров, а выбор в пользу предсказуемости. Выпуск Ubuntu 24.04 возит redis-server версии 7.0.15, а 22.04 довольствуется 6.0.16. Апстрим тем временем ушёл вперёд: стабильной веткой считается 8.10, точечные сборки в ней выходят регулярно, осенью 2026 года свежей была 8.10.2. Для кэша и простых очередей разница между этими ветками на практике почти не видна, зато пакет из дистрибутива ставится одной командой и обновляется вместе с системой:

sudo apt update
sudo apt install -y redis-server
redis-server --version
redis-cli ping

Ответ PONG в консоли означает, что сервер жив и отвечает. Пакет сразу регистрирует себя в systemd и стартует после установки сам, но контрольная точка не помешает:

sudo systemctl enable --now redis-server
sudo systemctl status redis-server --no-pager

Прежде чем ставить, полезно узнать, что именно предложит репозиторий:

apt-cache policy redis-server

Вывод показывает кандидата на установку и все доступные версии, по нему легко понять, на сколько поколений пакет отстал от свежего релиза. У пакета свои правила гигиены: конфиг лежит в /etc/redis/redis.conf, рабочие файлы в /var/lib/redis, журнал в /var/log/redis/. Когда нужна самая свежая ветка, исходники собираются из архива проекта за пару минут, однако на боевой сервер такую сборку отправляют уже после того, как прогнали на ней всё описанное ниже.

Как Redis остаётся открытым для чужих команд и что делает protected mode

Дефолтный конфиг свежих веток сам себя защищает двумя строками. Первая, bind 127.0.0.1, ограничивает слушающие адреса локальной петлёй. Вторая, protected-mode yes, отказывает внешним клиентам, если у сервера нет пароля. Пока эти строки нетронуты, снаружи база невидима. Проблема начинается в момент, когда порт открывают "на время теста": автоматические сканеры проверяют 6379 в первые же минуты существования адреса, и без пароля Redis честно отвечает любому. В чужих руках команда INFO выдаёт версию и сведения о системе, CONFIG GET рассказывает о настройках и путях, а FLUSHALL стирает всё содержимое одним словом.

Быстрая проверка с соседней машины расставляет всё по местам:

redis-cli -h 192.168.10.5 ping

Здесь возможны три исхода, и каждый означает свой мир. NOAUTH Authentication required говорит, что пароль уже стоит. PONG без пароля означает открытую базу. Отказ соединения или таймаут означают, что порт прикрыт сетевым экраном. Полезно добиваться того, чтобы вечер пятницы заканчивался одновременно первым и третьим вариантом: таймаут снаружи, пароль внутри.

Пароль на сервере Redis и пользователь приложения через ACL

Строка requirepass в конфиге закрывает сервер от анонимных клиентов. Пароль не придумывают руками, а генерируют утилитой из базового набора системы:

openssl rand -base64 24
sudo nano /etc/redis/redis.conf

В конфиге находят закомментированный образец requirepass foobared и вписывают рядом собственное значение, сгенерированное командой выше:

requirepass Vetr-U-Lunnyi-2026-Sklad

Перезапуск применяет настройку, но существует и живой путь без простоя, из двух команд:

redis-cli CONFIG SET requirepass Vetr-U-Lunnyi-2026-Sklad
redis-cli CONFIG REWRITE

Первая команда действует немедленно, вторая дописывает значение в конфиг, чтобы правило пережило перезагрузку. Забытый CONFIG REWRITE входит в когорту классических ловушек: пароль работает неделю, а после планового рестарта сервер снова открыт. Существенно и то, что параметр requirepass несовместим с внешним файлом aclfile, о чём конфиг предупреждает прямо в комментариях: сложные схемы прав строят целиком на ACL, без смешивания.

Клиент авторизуется командой AUTH, а для скриптов удобнее переменная окружения, которая не оставляет секрет в истории командной строки:

export REDISCLI_AUTH=Vetr-U-Lunnyi-2026-Sklad
redis-cli ping
redis-cli ACL WHOAMI

Дальше включается ACL, появившийся в шестой ветке Redis. Он позволяет выдать приложению собственную учётную запись с правами только на нужные ключи и команды:

redis-cli ACL SETUSER appcache on >Tundra-77-Kedr ~app:* +get +set +del +expire
redis-cli ACL SAVE
redis-cli ACL LIST

Пользователь appcache видит ключи с префиксом app: и умеет ровно четыре команды, ничего сверх. Даже утечка этого пароля не даст стереть базу, переписать конфигурацию или заглянуть в чужие ключи. Оправдавший себя план прост: серверу достаётся requirepass, каждому приложению своя запись ACL с минимальным набором прав.

Снимки RDB и расписание сохранений по умолчанию

Механика снимков проста как канцелярская кнопка. По достижении порога Redis создаёт копию своего процесса, дочерняя копия пишет сжатый двоичный файл, родитель продолжает обслуживать клиентов. Пороги по умолчанию читаются как три условия: сохранить раз в час, если была хотя бы одна запись, раз в пять минут при сотне записей, раз в минуту при десяти тысячах. В конфиге свежих веток эти строки закомментированы, но действуют именно так:

save 3600 1 300 100 60 10000
dbfilename dump.rdb
dir /var/lib/redis

Плата за простоту видна сразу: между записью и снимком проходит до часа, и авария в этом окне съедает последние изменения. Поэтому в конфиге живёт ещё одна строка, stop-writes-on-bgsave-error yes: если последняя попытка сохранить файл сломалась, сервер перестаёт принимать записи и поднимает тревогу вместо того, чтобы копить ложную уверенность. Проверить здоровье снимков на живом сервере:

redis-cli BGSAVE
redis-cli LASTSAVE
redis-cli INFO persistence | grep rdb_
redis-check-rdb /var/lib/redis/dump.rdb

Строка rdb_last_bgsave_status:ok в выводе INFO означает, что последний снимок записан успешно. Сжатие rdbcompression yes включено по умолчанию, поэтому даже гигабайтные наборы превращаются в компактный файл, который удобно забирать в резервную копию целиком.

У механизма есть цена, о которой забывают под праздники. Копия процесса на большой базе расходует память: операционная система размечает страницы как разделяемые и копирует их только по факту записи, но пик нагрузки на изменение всё равно требует свободного запаса. Отсюда практическое правило: на машине со снимками держат резерв памяти сопоставимого с базой масштаба, а не только под её текущее потребление. Точки сохранения при желании отключаются директивой save с пустым аргументом, но для сервера с ценными данными это решение из разряда смелых.

Журнал AOF для сохранения каждой операции записи

Снимок теряет минуту или час изменений, журнал AOF не теряет почти ничего. При appendonly yes каждая команда записи уходит в файл, а после рестарта сервер проигрывает журнал заново. Три режима сброса на диск дают выбор между надёжностью и скоростью: always подтверждает запись после каждой команды и замедляет работу, everysec сбрасывает журнал раз в секунду и считается рабочим компромиссом, no отдаёт решение операционной системе. Включение с проверкой:

sudo nano /etc/redis/redis.conf
appendonly yes
appendfsync everysec
sudo systemctl restart redis-server
redis-cli INFO persistence | grep aof_

Начиная с седьмой ветки журнал устроен как набор файлов: базовый плюс инкрементные, со своим манифестом, в каталоге appendonlydir. Строка aof-use-rdb-preamble yes делает базовый файл двоичным снимком, а инкременты остаются текстовыми командами: база грузится быстро, хвост догоняется мелкими операциями. Совмещение RDB и AOF не конфликтует, а страхует друг друга: снимок ускоряет старт, журнал сокращает окно потерь до секунды.

Контроль журнала на практике:

redis-cli BGREWRITEAOF
redis-check-aof /var/lib/redis/appendonlydir/appendonly.aof.1.incr.aof

Утилита redis-check-aof с ключом --fix подрезает оборванный хвост после сбоя питания, потому что обрыв посередине команды иначе остановил бы загрузку. BGREWRITEAOF сжимает журнал, убирая накопившиеся повторы, его запускают по расписанию. Место на диске для AOF планируют заранее: журнал пишется постоянно, и переполненный раздел останавливает записи так же надёжно, как остановленная служба.

В выводе INFO persistence журнал описывают несколько полей, и первыми смотрят на два: aof_last_write_status отражает последнюю попытку сброса, aof_last_bgrewrite_status говорит о пересборке базовой части. Значение ok у обоих означает, что механизм здоров. Любая ошибка в этих полях требует разбора до того, как в неё упрёт первый рестарт.

Ограничение сетевого доступа через bind и правила firewall

Строка bind перечисляет адреса, на которых сервер слушает. Для машины с одним приложением localhost закрывает вопрос полностью. Когда клиенты живут в локальной сети, адреса перечисляют явно, через пробел:

bind 127.0.0.1 192.168.10.5
sudo systemctl restart redis-server
ss -ltnp | grep 6379

Прослушка 0.0.0.0 допустима только в паре с паролем и сетевым экраном, иначе это дверь с табличкой "входи кто хочет". Отдельная засада прячется в контейнерах: флажок -p 6379:6379 у Docker публикует порт наружу, и Redis, который внутри контейнера слышал только собственную петлю, внезапно становится виден из внешней сети.

Сетевой экран на Ubuntu ограничивает порт точнее, чем bind, потому что работает на границе машины. Порядок закрытия Redis от лишних адресов выглядит так:

  1. Составить список хостов и подсетей, которым действительно нужен доступ к порту 6379;
  2. Разрешить соединения с адресов из списка и явно запретить остальные входящие;
  3. Включить экран с политикой запрета входящих по умолчанию;
  4. Проверить с посторонней машины, что соединение отклоняется, а с разрешённой работает.

Правила для одного клиента и общий вид состояния:

sudo ufw allow from 192.168.10.20 to any port 6379 proto tcp
sudo ufw enable
sudo ufw status verbose

Проверка снаружи уже знакомой командой redis-cli с адресом сервера должна теперь давать таймаут. Двойной барьер из bind и сетевого экрана переживает и ошибку в конфиге, и временно отключённый пароль, и чужой контейнер с проброшенным портом.

Проверка отказа и восстановления Redis перед боевым режимом

Настоящую уверенность даёт только репетиция аварии. Мягкий вариант: остановить сервер с сохранением, поднять заново, убедиться, что ключи вернулись:

redis-cli SHUTDOWN SAVE
sudo systemctl start redis-server
redis-cli GET app:session:1

Жёсткий вариант честнее: сигналу kill -9 всё равно, сохранен ли буфер, и сервер умирает без церемоний. С включённым AOF при загрузке проигрывается журнал, в /var/log/redis/ появляются строки о чтении файлов, а утерянным оказывается максимум одна секунда записей. Перед экспериментами снимок dump.rdb и каталог appendonlydir копируют в сторону, это заодно готовый шаблон резервного копирования:

sudo cp /var/lib/redis/dump.rdb /backup/redis/dump-$(date +%F).rdb

Четыре ловушки находят почти все. Первая это CONFIG SET без CONFIG REWRITE, после рестарта сервер снова открыт. Вторая это пароль в командной строке, который остаётся в истории интерпретатора, поэтому скрипты переводят на REDISCLI_AUTH. Третья это память без ограничений: параметр maxmemory не задан, политика по умолчанию noeviction запрещает вытеснение, и при исчерпании памяти записи начинают падать с ошибкой OOM, а для чистого кэша разумнее allkeys-lru. Четвёртая это строки rename-command, которыми опасные команды прячут от приложений, но об этом легко забыть при разборе инцидента.

Сервер, который молча пережил жёсткий сигнал, вернул ключи из журнала, отказал чужому адресу и спросил пароль у своего, готов к работе. Всё остальное уже тюнинг под нагрузку.