3.0.2로 업그레이드: NameError: `Redis::Client` 클래스에 `call` 메서드 없음 (수정 방법)

방금 3.0.1에서 3.0.2로 업그레이드를 시도했는데, rake db:migrate 단계에서 다음 오류가 발생했습니다:

rake aborted!
NameError: undefined method `call' for class `Redis::Client'
Did you mean?  caller
/var/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/mini_profiler/profiling_methods.rb:83:in `alias_method'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/mini_profiler/profiling_methods.rb:83:in `profile_method'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/rack-mini-profiler-3.0.0/lib/mini_profiler/profiling_methods.rb:65:in `counter_method'
/var/discourse/config/initializers/006-mini_profiler.rb:90:in `<main>'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:667:in `load'

긴 이야기를 짧게 요약하면, Gemfile에서 redis 버전을 지정하지 않고 두는 대신 redis 버전을 4.8.0으로 고정하면 문제가 해결되었습니다. 즉, Gemfile의 경우 다음과 같습니다:

-gem "redis"
+gem "redis", "4.8.0"

이렇게 직접 건드리는 것은 좀 불안하지만, 제 생각에는 이전 업그레이드(3.0.1로) 이후 다른 곳에서 redis가 업그레이드된 것의 의도치 않은 부수적 효과인 것 같고, 이제 Discourse 3.0.1을 재설치하면 같은 문제가 발생할 것 같습니다.

누군가에게 도움이 되길 바라며, 이 방법이 시스템에 취약점을 남기는 상태인지 알려주시면 좋겠습니다 :slight_smile:

Gemfile.lock 파일에서 gem 버전을 설정하고 있으며, 이미 해당 버전으로 설정되어 있습니다.

정말이죠! Gemfile이나 Gemfile.lock에 대해 이전에 들어본 적이 없었기 때문에 의외였습니다. 하지만 다른 20개의 포럼에서 설치를 반복해야 하는 상황이니(한숨), 결국 이 모든 것이 설치 중 발생한 별개의 문제에서 시작되었음이 드러났습니다. 당시 불평의 끝에는 Gemfile.lock을 삭제해 보라는 권고가 있었습니다:

Bundler found conflicting requirements for the Ruby version:
  In Gemfile:
    actionmailer (= 7.0.4.3) was resolved to 7.0.4.3, which depends on
      Ruby (>= 2.7.0)

    sassc-rails was resolved to 2.1.2, which depends on
      sprockets-rails was resolved to 3.4.2, which depends on
        Ruby (>= 2.5)

    json was resolved to 2.6.3, which depends on
      Ruby (>= 2.3)
:
:
:
    json_schemer was resolved to 0.2.23, which depends on
      ecma-re-validator (~> 0.3) was resolved to 0.4.0, which depends on
        Ruby (>= 2.6, < 4.0)

    rspec was resolved to 3.12.0, which depends on
      rspec-expectations (~> 3.12.0) was resolved to 3.12.2, which depends on
        diff-lcs (>= 1.2.0, < 2.0) was resolved to 1.5.0, which depends on
          Ruby (>= 1.8)

    web-push was resolved to 3.0.0, which depends on
      Ruby (>= 3.0)

  Current Ruby version:
    Ruby (= 2.7.6)

Bundler could not find compatible versions for gem "hkdf":
  In snapshot (Gemfile.lock):
    hkdf (= 1.0.0)

  In Gemfile:
    web-push was resolved to 1.0.0, which depends on
      hkdf (~> 0.2)

Deleting your Gemfile.lock file and running `bundle install` will rebuild your
snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

권고대로 잠금 파일을 제거하여 초기 오류를 넘긴 후, 이전에 설명했던 NameError: undefined method 'call’에 부딪혔습니다. redis 버전을 고정(pegging)하여 이를 해결했습니다. 그 후 모든 것이 정상 작동했습니다.

따라서, 제 스크립트는 a) 위에서 인용한 오류 때문에 Gemfile.lock을 제거하고, b) Gemfile를 자동으로 수정하여 redis를 4.8.0으로 고정하도록 수정되었습니다. 별문제 없이 잘 작동할 거라고 생각했습니다. 실제로 ‘동일한’ 20대 중 3대에서 작동했습니다! 나머지에서는 이 새로운 오류가 발생했습니다:

[discourse@in3020-discourse discourse]$ cd $INSTA; RAILS_ENV=production /usr/local/bin/bundle exec rake db:migrate # stuffing a lot of stuff into the database
rake aborted!
NoMethodError: undefined method `logger=' for Sidekiq:Module
Did you mean?  logger
/var/discourse/config/initializers/100-sidekiq.rb:58:in `<main>'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:667:in `load'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:667:in `block in load_config_initializer'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/activesupport-7.0.4.3/lib/active_support/notifications.rb:208:in `instrument'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:666:in `load_config_initializer'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:620:in `block (2 levels) in <class:Engine>'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:619:in `each'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/engine.rb:619:in `block in <class:Engine>'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/initializable.rb:32:in `instance_exec'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/initializable.rb:32:in `run'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/initializable.rb:61:in `block in run_initializers'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/initializable.rb:50:in `each'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/initializable.rb:50:in `tsort_each_child'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/initializable.rb:60:in `run_initializers'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/application.rb:372:in `initialize!'
/var/discourse/config/environment.rb:7:in `<main>'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.16.0/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/zeitwerk-2.6.7/lib/zeitwerk/kernel.rb:38:in `require'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/application.rb:348:in `require_environment!'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/railties-7.0.4.3/lib/rails/application.rb:511:in `block in run_tasks_blocks'
/var/discourse/vendor/bundle/ruby/2.7.0/gems/rake-13.0.6/exe/rake:27:in `<top (required)>'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/cli/exec.rb:58:in `load'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/cli/exec.rb:58:in `kernel_load'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/cli/exec.rb:23:in `run'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/cli.rb:486:in `exec'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/cli.rb:31:in `dispatch'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/cli.rb:25:in `start'
/usr/local/share/gems/gems/bundler-2.3.26/exe/bundle:48:in `block in <top (required)>'
/usr/local/share/gems/gems/bundler-2.3.26/lib/bundler/friendly_errors.rb:120:in `with_friendly_errors'
/usr/local/share/gems/gems/bundler-2.3.26/exe/bundle:36:in `<top (required)>'
/usr/local/bin/bundle:23:in `load'
/usr/local/bin/bundle:23:in `<main>'
Tasks: TOP => db:migrate => db:load_config => environment
(See full trace by running task with --trace)

이는 정말, 정말 이상했습니다. /var/discourse/config/initializers/100-sidekiq.rb 파일은 모든 머신에서 동일했고, 분명히 'logger='를 포함하지 않았으며, ‘logger = …’ 문장이 있었기 때문입니다.

결국, 정상 작동한 머신에서는 /usr/local/bin/bundle 버전이 2.3.20이었고, 실패한 머신에서는 2.3.26였다는 것을 알아냈습니다. 따라서 결론적으로, discourse 설치 명령 이전에 다음을 추가하는 것이 저에게는 효과가 있었습니다(discourse의 bundler 설치 후에 bundler를 2.3.20으로 고정하는 명령을 실행하는 것만으로는 충분하지 않았으며, 반드시 이전에 실행해야 했습니다):

rm Gemfile.lock
perl -pi.bak -e 's/gem "redis"/gem "redis","4.8.0"/;' Gemfile

/usr/bin/gem uninstall bundler -v 2.3.26
/usr/bin/gem install bundler -v 2.3.20
RAILS_ENV=production /usr/local/bin/bundle install
RAILS_ENV=production /usr/local/bin/bundle exec rake db:migrate
RAILS_ENV=production /usr/local/bin/bundle exec rake assets:precompile

도대체 왜 일부 머신에는 bundler 2.3.26이 있고 다른 곳에는 2.3.20이 있는지 저는 알 수 없습니다… 하지만 아마도 당신의 잘못은 아닐 것입니다 :laughing:.

그래서 저는 안정판 버전을 사용하는 거예요 :laughing:

Ruby 2.7은 EOL(지원 종료) 상태이며 더 이상 저희가 지원하지 않습니다. 표준 설치 가이드를 따라 Discourse를 설치할 때 이미 해당 버전을 받을 수 없었으므로, 이는 커스텀 설치인 것으로 추정됩니다.

Discourse 스택에 익숙한 사용자들도 지원되는 공식 설치 가이드를 따르는 것을 권장하며, 사용된 기술에 익숙하지 않은 사용자들에게는 더욱 중요합니다.

네, 공식적인 방법으로 했다면 훨씬 덜 고생했을 가능성이 높습니다 (아마도 아예 문제가 없었을 수도 있고요). 문제는 우리 IT 보안 부서의 사전 승인을 받지 않은 도커 이미지를 사용할 수 없다는 점인데, 당신의 이미지는 해당되지 않습니다.

따라서 실제로 RHEL8 머신에 커스텀 네이티브 설치를 수행해야 했으며, 이를 위해 SELinux 관련 설정도 일부 조정해야 했습니다.

혹시 저와 같은 문제를 겪고 있는 다른 분들이 있을까요? 제 설치 과정을 주제로 올리는 것이 그분들에게 도움이 될 수 있을 것 같습니다. 반대 의견도 환영합니다. 엄밀히 말해 그렇게 설치할 필요가 없는데도 이 방법으로 설치를 "장려"하는 것으로 오해받아, 여러분에게 더 많은 질문이 쏟아질 수 있다는 점을 이해하고 있기 때문입니다.

참고로, SELinux 관련 문제를 구글로 검색할 때 가장 자주 보이는 조언은 "이렇게 비활성화하세요"입니다:laughing:. 하지만 여기서는 그런 선택지가 아닙니다.

도와주셔서 감사합니다!

컨텍스트를 제공해 주셔서 감사합니다. 다만, 여기서 커스텀 설치에 대한 무료 지원을 제공하지 않는다는 점에 유의해 주세요.

이 설치 환경은 현재 Discourse 버전과 호환되지 않는 Ruby 버전을 사용하고 있으며, 정밀하게 관리되는 gem 버전도 무시하고 있고, 우리가 제공하는 수동 관리 OS 레벨 공유 라이브러리도 모두 사용하지 않고 있습니다. 따라서 문제가 발생할지는 '언제’의 문제이지, '여부’의 문제가 아닙니다.

개발자들이 검증하고 사용하는 이미지가, 여러분의 유일한 업무가 아닌 한 추적하기가 사실상 불가능한 것들에 보안 패치를 적용하려는 시도보다 훨씬, 훨씬 안전하다는 점을 보안 팀에 제안해 보실 수 있습니다.

. . . . 하지만, 그것이 도움이 되지는 않을 것입니다.

하하, 네 (아마도 여러분의 이미지들이 더 안전할 거예요) 그리고 아니요 (도움이 안 되지만, 그래도 코멘트해 주셔서 감사합니다 :blush:).

사실 이건 제 본업은 아니고, 그냥 "내가 할 수 있는 사람이니까"라는 생각으로 파일럿 프로그램으로 이 포럼들을 운영 중인 겁니다. 이렇게 해서 IT 부서에 이를 맡겨서 오슬로 대학교 전체에서 운영하도록 설득하려는 거예요.

다행히 그 계획이 잘 먹혀서, hopefully 이번 학기가 제가 직접 운영을 맡는 마지막 학기가 될 것 같습니다. IT 부서가 이미지들을 사용할 거라고 확신합니다… :wink:

그리고 Ruby 버전 불일치 문제를 지적해 주셔서 감사합니다, @Falco. 앞으로 업데이트를 할 때 문제가 생기면 그때 확인해 보겠습니다.

대단하군요! 미국에서 여러 대학의 교수로 근무해본 경험이 있어, 정말 인상 깊습니다.

아, 이 글에 답글을 달아야 한다는 걸 한참 전에 깜빡했네요…

@pfaffman 하하, 네. IT 부서가 괜찮다고 해도 아직 완전히 안심할 단계는 아니에요. 공식 소통 채널로 승인받으려면 대학 이사회라는 최상위 기관의 승인이 먼저 필요하거든요. 다행히 자연과학부가 아주 적극적으로 참여해 주셔서, 그분들이 절차를 밟아 주고 계세요.

이 파일럿 인스턴스를 직접 운영할 수 있었다는 게 꽤 자랑스러워요. IT 부서가 아닌 사람이면 (물론 필요한 보안 정책을 준수하는 범위 내에서) 이렇게 할 수 있는 경우가 거의 없거든요. 자연과학부 사람들은 이걸 계속 "Astro-Discourse"라고 불러요 (저는 이론천체물리학 연구소 소속이거든요) :laughing:.

하지만 이제… 나는 동일한 비표준 설정으로 이 쇼를 한 학기 더 진행해야 할 것 같다. 현재 Discourse 릴리스가 어떤 pgsql 버전과 호환되는지 궁금하다.