capistrano3-puma 버전업에 따른 버그 해결하기

March 22, 2017

근 2주 사이에 capistrano3-puma 버전이 v1.2.1에서 v3.0.2로 올라가는 대격변이 있었습니다. 이 때문에 있었던 왠지 저만 빠진듯한 버그를 해결하고 겸사겸사 잡지식도 설명합니다.

버그 간단 소개

puma 관련 태스크를 실행하려고 하면 다음과 같이 bundle 명령어를 찾을 수 없다는 메시지가 출력됩니다.

00:31 puma:restart
      01 bundle exec pumactl -S /destination/app/shared/tmp/pids/puma.state -F /destination/app/shared/puma.rb restart
      01 bash: bundle: command not found
bundle exec pumactl -S /destination/app/shared/tmp/pids/puma.state -F /destination/app/shared/puma.rb restart

심플하네요. 디버깅 과정은 생략하고 일단 상황을 재현해보죠.

상황 재현하기

# deploy.rb
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
# rvm이라면 `:rvm_map_bins`, chruby라면 `:chruby_map_bins`겠죠.

단지 이것만으로 당신의 배포 프로세스가 죽는건 한순간!

원인

capistrano-bundlercapistrano-rbenv는 조건에 맞는 명령에 한정해서 이를 감싸주게 되는데요. 로그를 잘 살펴보면 rbenv의 래핑이 동작하지 않고 있음을 확인할 수 있습니다.

:{rvm, chruby, rbenv}_map_bins, :bundle_bins

여기서 명령을 감쌀지 아닐지를 결정하는 것이 이 변수들입니다. 이 변수 배열에 들어있는 명령어인 경우 RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec를 명령어의 앞에 추가해줍니다(기본값이며 변경 가능). 돌아와서 이 값에 기본으로 들어있는 명령어들은 %w{rake gem bundle ruby rails}입니다.

# Ref: https://github.com/seuros/capistrano-puma/commit/c6e34a45d917d76dea941707f4ed6e60d8442213#diff-56c283d8f205519259d1321eb2dcf468R26

+ set :bundle_bins, fetch(:bundle_bins).to_a.concat(%w{ puma pumactl })

...

- execute :bundle, 'exec', :pumactl, "-S #{fetch(:puma_state)} #{command}"
+ execute :pumactl, "-S #{fetch(:puma_state)} #{command}"

위는 문제를 발생시키는 커밋의 일부입니다. 잘 살펴보면 bundle_binspuma, pumactl을 추가하고 덮어쓰고 있는 것을 확인할 수 있습니다. 그런데 왜 안될까요?

변수 초기화 순서

답은 실행 순서에 있습니다.

여기서 알 수 있는 사실은 두 가지가 있겠네요.

지금까진 왜 동작했습니까?

위의 변경사항을 살펴보면 이전에는 bundle 명령을 경유해서 puma를 실행하고 있었음을 알 수 있습니다. 다시 말해 execute가 인식하는 명령은 bundle exec hogehoge이므로 deploy.rb가 덮어쓴 목록으로도 대응할 수 있었던 것입니다! 유레카!

커스텀하고 싶은 경우에는 어떻게 해야할까요?

해당 변수에 개발자가 원하는 명령을 추가하고 싶다면 append를 쓰면 됩니다.

append

공식 문서를 참고하도록 합시다.

New in Capistrano 3.5: for a variable that holds an Array, easily add values to it using append. This comes in especially handy for :linked_dirs and :linked_files (see Variables reference below).

여지껏 모르고 있었지만 좋은게 있었습니다.

우리는 동작하는 코드를 사랑하니까 capistrano3-puma의 코드를 보도록 하죠. XD

# Ref: https://github.com/seuros/capistrano-puma/commit/c1d165948602a150f438b0a7f11835ac90852a54#diff-56c283d8f205519259d1321eb2dcf468R22

append :rbenv_map_bins, 'puma', 'pumactl'
# 위와 아래는 동일한 결과를 가져옵니다.
set :rbenv_map_bins, fetch(:chruby_map_bins).to_a.concat(%w{ puma pumactl })

결론