Discourse コンテナの Redis 用 UnixSocket は?

Standalone コンテナで Redis に unixsocket /var/discourse/shared/... を使用している方がいらっしゃるかどうか気になっています。redis-rb も以下のようにドメインソケットをサポートしているようです:

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

Medium のこの記事によると、Unix ドメインソケット経由で接続すると、TCP ソケットを使用する場合に比べて約 20% 高速になるそうです。

また、Unix ドメインソケットを使用すれば、Web コンテナとデータコンテナを分離せずに複数の Standalone コンテナを実行しやすくなるでしょう。そうしないと、Redis が 127.0.0.1 でリスニングしていることで競合が発生する可能性があります。

現在、1 台のホスト上で 2 つの完全に独立した Standalone コンテナをセットアップしようとしています。どちらも非常に小規模なサイト向けなので、できれば Standalone コンテナのまま柔軟性を保ちたいと考えています。しかし、残念ながら Redis(おそらく Postgres も同様)が 127.0.0.1 でリスニングすると競合してしまいます。

「いいね!」 1

@ryanerwin さん、こんにちは

どうやら、コンテナと Docker について完全に理解されていないようなので、お手伝いしましょう。

Redis はデフォルトでポート 6379 のスタンドアロンコンテナ内で実行されます。

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

74

次に、コンテナから退出して、Redis について netstat を確認します。

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

ご覧の通り、Redis はコンテナ内の localhost でリスニングしていますが、コンテナ内の localhost は(コンテナ外には)公開されていません。

したがって、複数のスタンドアロン Discourse コンテナを実行していても、Redis が各コンテナの外に公開されていないため、コンテナ間で Redis の競合は発生しません。

これが「コンテナ」と呼ばれる所以です… :slight_smile:

コンテナ内のすべてのソケットは、コンテナ外から利用可能にするために明示的に公開する必要があります。

少しでもお役に立てれば幸いです。


Unix ドメインソケットは非常に優れています。私は、スタンドアロンコンテナ間の Redis 競合に関するあなたのコメントに対応したものであり、Unix ドメインソケットのトピックについては触れていません。

「いいね!」 5

Docker 内で Discourse を実行する場合も同様に動作すると思っていましたが、実際に実行してみると、ブートストラップ中に以下のメッセージが表示されました。

INFO -- : > cd /var/www/discourse && git reset --hard
# oO0OoO0OoO0Oo Redis は起動中です oO0OoO0OoO0Oo
# Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=195, just started
# 設定を読み込み中
# サーバーの TCP リッスンソケット *:6379 を作成できませんでした: bind: アドレスは既に使用されています
Checking out files: 100% (27893/27893), done.

そして、「他の Redis コンテナのためにインストールが失敗する」というスレッドを見つけましたが、実際の問題はディスク容量不足でした……いくつかの整理を行った結果、複数のスタンドアロンコンテナでも問題なく動作することが確認できました。

「いいね!」 2

@ryanerwin

根本的な問題を見つけて、Redis が「コンテナ内で動作し」、コンテナの外(ソケット I/O の観点から)には露出していない(デフォルト設定では)ことを理解されたことを嬉しく思います。

Unix ドメインソケットと Redis の併用についてですが、これは素晴らしいアイデアだと思います。現時点ではこの設定を行ったことはなく、Discourse での設定方法について解説された記事も見当たりません。そのため、このオプションの探索を続けていただくことをお勧めします。

時間があれば、さらに調査を行い、ステージングサーバー上で Discourse が Unix ドメインソケットを使用するように Redis を設定してみます。それまでの間、もしご自身で解決して結果を投稿していただければ、大変感謝いたします。この興味深い Redis のトピックに関心を持っている方は他にも多いはずです。

ありがとうございます。

「いいね!」 3

こんにちは @ryanerwin さん、

ご質問いただいた通り、Standalone コンテナ内で Unix ソケットを使用して Redis を動作させている Discourse が稼働していることをお伝えできて嬉しいです。

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 ソケットが共有ボリューム上にある場合、コンテナ外でも期待通りに動作します:

「いいね!」 4

この PR について:

注:

実装方法:

  • コンテナの yml ファイル内の Redis テンプレートを変更する
  • 同じコンテナの yml ファイルに 1 行追加する
 ## REDIS_URL を設定し、redis.socketed.template.yml を使用して Redis の Unix ドメインソケットを利用します
 REDIS_URL: unix:///shared/tmp/redis.sock

実装上の注意点:

  1. ホスト上の Redis データベースのセキュリティが気になる場合は、共有ボリューム内でこの Unix ソケットを公開する必要はありません。

  2. Unix ソケットのパーミッションを 777 ではなく 770 に設定したい場合は、Unix ソケットのグループを www-data に変更してください。

「いいね!」 4

REDIS_URL がもう効果を持たないことに気づいた人はいますか? (再)ビルド中およびコンテナ起動時にも、UNIX ソケット経由で接続するように REDIS_URL を設定しているにもかかわらず、redis://localhost:6379 経由で接続しようとします。

一方で、Discourse はホストとポートのみを設定して適用しているため、理にかなっています: discourse/app/models/global_setting.rb at main · discourse/discourse · GitHub
UNIX ソケットパスを定義するための path パラメータがあり、ホストとポートをオーバーライドします。また、unix:///shared/redis_data/redis.sock のような完全な URL を定義するための url パラメータもあります。

Redis gem は、REDIS_URL 環境変数が他のすべての設定をオーバーライドすると文書化しており、特定の Discourse/Redis gem バージョンまでは機能していました: redis-rb/lib/redis.rb at master · redis/redis-rb · GitHub

Discourse が Redis gem バージョン 5 に移行したときに UNIX ソケットの使用が壊れました: DEV: Upgrade the Redis gem to v5.4 · discourse/discourse@2ed31fe · GitHub
したがって、Discourse (関連コードは変更されていません) ではなく、Redis gem で REDIS_URL が壊れた (他のオプションをオーバーライドしなくなった) と推測されますか?

おそらくこのコミットが原因で壊れました: Use redis-client as transport · redis/redis-rb@08a2100 · GitHub
Redis gem のリポジトリに報告すべきですか、それとも Discourse の実装方法に問題があることを誰か知っていますか?

ちなみに環境変数は正しく渡されています。そのままにして、Redis が UNIX ソケットでリッスンしないように設定したところ、ユニコーンは正常に起動しましたが、サイドキックは 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 はユニコーンの場合と同様に、サイドキックにホストとポートを適用していません。