Redis에 UnixSocket를 사용하는 Discourse 컨테이너?

누군가 Standalone 컨테이너에서 Redis에 unixsocket /var/discourse/shared/...를 사용 중인지 궁금합니다. redis-rb도 다음처럼 도메인 소켓을 지원하는 것 같습니다:

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

이 Medium 기사에 따르면, Unix 도메인 소켓을 통해 연결하는 것이 TCP 소켓을 사용하는 것보다 약 20% 빠르다고 합니다…

또한 Unix 도메인 소켓을 사용하면 웹 컨테이너와 데이터 컨테이너를 분리하지 않고도 여러 Standalone 컨테이너를 실행하기가 더 쉬워집니다… 그렇지 않으면 Redis가 127.0.0.1에서 리스닝하는 문제로 충돌이 발생할 것 같습니다…

현재 하나의 호스트에서 완전히 독립적인 Standalone 컨테이너 두 개를 설정하고 있습니다. 둘 다 아주 작은 사이트용이므로, Standalone 컨테이너로 유지하는 유연성을 선호합니다… 불행히도 Redis가 127.0.0.1에서 리스닝하는 것(그리고 아마도 Postgres도)이 충돌을 일으킬 것입니다…

@ryanerwin 안녕하세요

컨테이너와 도커에 대해 아직 완전히 이해하지 못하신 것 같으니, 도움을 드리고자 합니다.

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는(아직도) 컨테이너 외부로 노출되지 않았습니다.

따라서 많은 수의 독립 실행형 discourse 컨테이너가 실행 중이라 하더라도, Redis가 각 컨테이너 외부로 노출되지 않았으므로 컨테이너 간 Redis 충돌은 발생하지 않습니다.

그래서 이를 "컨테이너"라고 부르는 것입니다… :slight_smile:

컨테이너 내부의 모든 소켓은 컨테이너 외부에서 사용 가능하도록 명시적으로 노출되어야 합니다.

조금이나마 도움이 되길 바랍니다.


유닉스 도메인 소켓은 매우 훌륭한 기술입니다… 저는 독립 실행형 컨테이너 간의 Redis 충돌에 대한 우려를 언급하신 부분만 답변했으며, 유닉스 도메인 소켓 주제 자체에 대해서는 다루지 않았습니다.

Docker 내부에서 Discourse를 실행하는 경우에도 그렇게 작동할 거라고 생각했지만, 실제로 실행해 보니 부트스트래핑 과정에서 다음과 같은 내용을 확인했습니다:

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가 "컨테이너 내부"에서 실행되며 컨테이너 외부에는 노출되지 않음(소켓 I/O 관점에서, 기본 설정 상태)을 이해하게 되셨다니 기쁩니다.

Unix 도메인 소켓과 Redis를 사용하는 것에 대해, 이는 훌륭한 아이디어라고 생각하며 아직 설정해 보지 않았고, Discourse에서 이를 설정하는 방법에 대한 문서도 읽어 본 적이 없습니다. 따라서 이 옵션을 계속 탐구해 주시길 권장합니다.

시간이 나면 이를 더 조사하여 스테이징 서버의 Discourse에서 Redis가 Unix 도메인 소켓을 사용하도록 설정해 보겠습니다. 그 사이에 이 문제를 해결하여 결과를 공유해 주시면 매우 감사하겠습니다. 이 흥미로운 Redis 주제에 대해 다른 많은 분들도 관심을 가지고 있을 것입니다.

감사합니다.

안녕하세요 @ryanerwin,

질문하신 대로, 스탠드얼론 컨테이너에서 유닉스 소켓을 사용하는 Redis와 함께 Discourse를 실행하고 있다는 소식을 전하게 되어 기쁩니다.

sidekiq 화면 캡처의 하단을 확인해 보세요:

또한, 앱 빌드 과정의 추가 스크린샷도 공유합니다:

다음 단계로 할 일은 다음과 같습니다:

  1. 유닉스 소켓을 컨테이너 외부에서 접근할 수 있도록 공유 볼륨으로 이동합니다.
  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에 작은 변경 사항을 가했습니다.

오늘 이 작업을 시작했으므로, 세부 사항을 게시하기 전에 조금 더 테스트할 계획입니다.

도움이 되길 바랍니다.


업데이트


유닉스 소켓이 공유 볼륨에 있을 때, 컨테이너 외부에서도 예상대로 작동합니다:

관련 PR:

참고:

구현 방법:

  • 컨테이너 yml 파일의 redis 템플릿을 변경
  • 동일한 컨테이너 yml 파일에 한 줄 추가
 ## REDIS_URL을 설정하고 redis.socketed.template.yml을 사용하여 
 ## Redis에 유닉스 도메인 소켓을 사용
 REDIS_URL: unix:///shared/tmp/redis.sock

구현 사항:

  1. 호스트의 Redis DB 보안이 걱정된다면, 이 유닉스 소켓을 공유 볼륨에 노출할 필요가 없습니다.

  2. 유닉스 소켓의 권한을 777 대신 770으로 설정하려면, 유닉스 소켓의 그룹을 www-data로 변경하십시오.

누군가 REDIS_URL이 더 이상 효과가 없음을 알아채셨나요? (재)빌드 시와 컨테이너 시작 시 모두, REDIS_URL이 UNIX 소켓을 통해 연결하도록 설정되어 있음에도 불구하고 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
따라서 REDIS_URL이 Redis gem에서(다른 옵션을 더 이상 오버라이드하지 않아) 깨진 것이며, Discourse(관련 코드가 변경되지 않음) 때문은 아닌 것 같습니다.

아마도 이 커밋이 원인이었을 것입니다: Use redis-client as transport · redis/redis-rb@08a2100 · GitHub
Redis gem 저장소에 보고해야 할까요, 아니면 이것이 Discourse의 구현 방식에 문제가 있다는 것을 아는 사람이 있을까요?

참고로 환경 변수는 올바르게 전달되고 있습니다. UNIX 소켓에서 경청하지 않도록 Redis만 설정하고 나머지는 그대로 두었더니, 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'

오류 추적을 보면, global_setting.rb이 sidekiq의 경우 호스트와 포트를 적용하지 않는다는 것이 알 수 있습니다. unicorn에서는 적용하고 있는데 말이죠. 이는 REDIS_URL이 여전히 작동하지만, 다른 곳에서 연결 설정이 정의되지 않은 경우에만 작동한다는 사실과 일치합니다.