Контейнер Discourse с UnixSocket для Redis?

Интересно, использует ли кто-нибудь unixsocket /var/discourse/shared/... для Redis в своём Standalone-контейнере? Похоже, что redis-rb также поддерживает доменные сокеты с помощью:

redis = Redis.new(path: "/var/discourse/shared/standalone/redis.sock")

Согласно этой статье на Medium, подключение через Unix Domain Socket примерно на 20% быстрее, чем использование TCP-сокетов.

Кроме того, использование Unix Domain Sockets упростит запуск нескольких Standalone-контейнеров без разделения веб-контейнеров и контейнеров с данными… В противном случае, я думаю, возникнут конфликты из-за того, что Redis слушает адрес 127.0.0.1…

Сейчас я пытаюсь настроить два полностью независимых Standalone-контейнера на одном хосте, и так как оба предназначены для очень небольших сайтов, я предпочёл бы гибкость, которую дают Standalone-контейнеры… К сожалению, прослушивание Redis (и, вероятно, Postgres) на адресе 127.0.0.1 приведёт к конфликтам…

Привет, @ryanerwin

Кажется, ты не до конца понимаешь концепцию контейнеров и Docker; давай я помогу тебе разобраться.

По умолчанию Redis работает в изолированном контейнере на порту 6379:

cd /var/discourse
./launcher enter app
apt install net-tools
netstat -an | grep :6379 |wc -l 

74

Теперь выйдем из контейнера и проверим netstat для Redis:

exit
root@localhost:/var/discourse# netstat -an | grep :6379 |wc -l 
0

Как видишь, Redis слушает localhost внутри контейнера; при этом localhost внутри контейнера не был (и не является) открытым извне контейнера.

Следовательно, если у тебя запущено множество изолированных контейнеров Discourse, конфликтов между ними не возникнет, поскольку Redis не был открыт извне каждого контейнера.

Вот почему это называется «контейнером»… :slight_smile:

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

Надеюсь, это хоть немного поможет.


Обрати внимание: Unix Domain Sockets — это очень круто… Я лишь ответил на твой комментарий о предполагаемых конфликтах Redis между изолированными контейнерами и не затрагивал тему Unix Domain Sockets.

Я тоже думал, что так будет работать при запуске Discourse внутри Docker, однако при реальном запуске в процессе инициализации я увидел:

INFO -- : > cd /var/www/discourse && git reset --hard
# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
# Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=195, just started
# Configuration loaded
# Could not create server TCP listening socket *:6379: bind: Address already in use
Checking out files: 100% (27893/27893), done.

Я также нашёл эту тему про «ошибка установки из-за другого контейнера Redis», однако проблема на самом деле заключалась в нехватке места на диске… Я немного реорганизовал файлы, и теперь всё работает отлично с несколькими отдельными контейнерами.

Уважаемый @ryanerwin,

Отлично, что вы обнаружили корневую проблему и теперь понимаете, что Redis работает «внутри контейнера» и не доступен извне (с точки зрения ввода-вывода через сокет), как это настроено по умолчанию (OOTB).

Что касается использования доменных сокетов Unix и Redis, я считаю это отличной идеей. Я ещё не настраивал это и не встречал никаких материалов о том, как это сделать для Discourse, поэтому я призываю вас продолжить изучение этого варианта.

Если у меня будет время, я проведу дополнительное исследование и попробую настроить Redis для использования доменного сокета Unix в Discourse на тестовом сервере. Тем временем, если вы сможете разобраться в этом и поделиться результатами, это будет очень ценно. Я уверен, что многих других также интересует эта интересная тема, связанная с Redis.

Спасибо.

Привет @ryanerwin,

Надеюсь, вы будете рады узнать, что у меня Discourse работает с Redis через unix-сокет в Standalone-контейнере, точно так же, как вы и спрашивали.

Посмотрите на нижнюю часть этого скриншота из sidekiq:

Кроме того, вот ещё несколько скриншотов процесса сборки приложения:

Мои следующие шаги:

  1. Переместить unix-сокет в общий том, чтобы к нему можно было получить доступ вне контейнера.
  2. Провести повторное тестирование с минимальным набором переменных окружения (ENV), чтобы реализовать «базовые» изменения для запуска системы.

В целом, я создал новый шаблон:

-rw-r–r-- 1 root root 2028 Jun 6 08:13 redis.socketed.template.yml

Screen Shot 2020-06-06 at 4.18.55 PM

и внёс незначительные изменения в старый добрый app.yml

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

Надеюсь, это будет полезно.


Обновление


Работает как положено вне контейнера, когда unix-сокет находится на общем томе:

PR по этой теме:

Примечание:

Для реализации:

  • Измените шаблон Redis в файле yml контейнера
  • Добавьте одну дополнительную строку в тот же файл yml контейнера
 ## Установите REDIS_URL и используйте redis.socketed.template.yml для использования 
 ## Unix-сокета для Redis
 REDIS_URL: unix:///shared/tmp/redis.sock

Примечания к реализации:

  1. Если вас беспокоит безопасность базы данных Redis на хосте, нет необходимости экспонировать этот Unix-сокет в общем томе.

  2. Если вы хотите установить права доступа Unix-сокета на 770 (вместо 777), измените группу Unix-сокета на www-data.

Заметил ли кто-нибудь ещё, что REDIS_URL больше не имеет эффекта? Во время (пере)сборки, а также при запуске контейнера, несмотря на то, что REDIS_URL установлен для подключения через UNIX-сокет, система пытается подключиться по адресу redis://localhost:6379.

С одной стороны, это логично, поскольку в Discourse конфигурация учитывает и применяет только хост и порт: discourse/app/models/global_setting.rb at main · discourse/discourse · GitHub
Существует параметр path для определения пути к UNIX-сокету, который должен переопределять хост и порт. Также есть параметр url для указания полного URL, например unix:///shared/redis_data/redis.sock.

Документация Redis-библиотеки указывает, что переменная окружения REDIS_URL должна переопределять любые другие настройки, и до определённой версии Discourse/Redis gem это работало: redis-rb/lib/redis.rb at master · redis/redis-rb · GitHub

Использование UNIX-сокетов перестало работать, когда Discourse перешёл на версию 5 библиотеки Redis: DEV: Upgrade the Redis gem to v5.4 · discourse/discourse@2ed31fe · GitHub
Похоже, что проблема возникла в самой Redis-библиотеке (она больше не переопределяет другие опции), а не в Discourse (где соответствующий код не менялся)?

Скорее всего, это сломал коммит: Use redis-client as transport · redis/redis-rb@08a2100 · GitHub
Стоит ли мне сообщить об этом в репозиторий Redis gem, или кто-то знает, что проблема в реализации Discourse?

Кстати, переменная окружения передаётся корректно. Я оставил её без изменений и лишь настроил Redis так, чтобы он не слушал UNIX-сокет. В результате unicorn запускается нормально, а sidekiq падает с ошибкой, не находя UNIX-сокет :sweat_smile::

Error in demon processes heartbeat check: No such file or directory - connect(2) for /shared/redis_data/redis.sock
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client/ruby_connection.rb:116:in `initialize'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client/ruby_connection.rb:116:in `new'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client/ruby_connection.rb:116:in `connect'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client/ruby_connection.rb:51:in `initialize'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client.rb:759:in `new'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client.rb:759:in `block in connect'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client/middlewares.rb:12:in `connect'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client.rb:758:in `connect'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client.rb:745:in `raw_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client.rb:705:in `ensure_connected'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/redis-client-0.24.0/lib/redis_client.rb:285:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/redis_client_adapter.rb:36:in `block (2 levels) in <module:CompatMethods>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/api.rb:912:in `block in cleanup'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/config.rb:175:in `block in redis'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/connection_pool-2.5.3/lib/connection_pool.rb:110:in `block (2 levels) in with'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/connection_pool-2.5.3/lib/connection_pool.rb:109:in `handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/connection_pool-2.5.3/lib/connection_pool.rb:109:in `block in with'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/connection_pool-2.5.3/lib/connection_pool.rb:106:in `handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/connection_pool-2.5.3/lib/connection_pool.rb:106:in `with'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/config.rb:172:in `redis'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq.rb:74:in `redis'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/api.rb:912:in `cleanup'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/api.rb:903:in `initialize'
/var/www/discourse/lib/demon/sidekiq.rb:25:in `new'
/var/www/discourse/lib/demon/sidekiq.rb:25:in `heartbeat_check'
config/unicorn.conf.rb:131:in `block (2 levels) in reload'

Это согласуется с тем, что REDIS_URL всё ещё работает, но только если не заданы другие настройки подключения. Судя по трассировке ошибки, global_setting.rb не применяет хост и порт для sidekiq, в отличие от unicorn.