어제까지, 오전 중반까지, 조금 전까지만 해도 Ruby On Rails 애플리케이션은 정상적으로 작동했지만, 아무리 해도 갑자기 "Routing Error: Uninitialized constant XXXXX Controller"라는 오류가 발생했고, 예스나 노를 구분할 수 없는 신비한 증상에 빠졌으며, 원인이 밝혀질 때까지 한 달 정도 울고 싶을 정도로 무감각한 경험을 했습니다. 그래서 해결책(원래는 어려움?)을 찾을 때까지 팁을 적어보려고 했습니다. 여기에 적어두려고요.
범인을 찾아
우선, 이 레일스 오류 화면은 이미 외상 상태에 가까워...

우선, 이 오류가 발생하면 Rails는 URL을 직접 지정하고 호출된 컨트롤러를 바꾸거나 다른 어떤 것도 하지 않고, 같은 "라우팅 오류"와 컨트롤러 이름만 다르다는 오류를 계속 뱉어냅니다. 이 오류는 이미 그 오류 메시지를 아는 것처럼 말할 것입니다. 하지만 Rails 경로 설정 파일 'config/routes.rb'는 정상입니다(방금 전까지 실행 중이었고 라우팅이 변경되지 않았기 때문에 자연스럽습니다). Rails 로그를 봐도 오류 화면과 같은 양의 정보만 볼 수 있고, 서버에서 오류 로그를 볼 때마다 404 오류가 발생했고 경로(URL)에 내용이 없다고만 표시됩니다(Rails 컨트롤러가 작동하지 않음). 오류 로그 처리 스택을 다시 살펴보더라도 특별히 이상한 점은 없습니다... 이게 도대체 뭐야!? 내 마음은 이미 부서졌어, 많이 부서졌어, 레일스가 싫지만, 버릴 수 없어, 내 일이니까 원인을 찾지 못하면 앞으로 나아갈 수 없어 (T_T)
StackOverflow나 다른 지원 포럼을 검색하면 같은 증상을 겪는 사람들의 질문을 찾을 수 있을 거예요... 하지만 답은 없어요. 진짜로~ 아무도 해결 안 하는 거야~!? 누군가 제발 도와주세요~... 그게 현실이에요.
그리고 이 증상의 더 신비로운 부분은, Rails 환경을 다시 구축(모든 것을 처음부터 재설치)함으로써 해결할 수 있다는 점입니다(정확히 말하면, 그 당시에는 복구할 수 있는 방법이 하나뿐이었습니다). 그래서 처음에는 서버 측의 구성과 캐시(예: Apache와 Rails를 중계하는 Passenger 등)에 문제가 있는지 많은 조사를 했지만, 모든 것이 빗나갔습니다(버그 수정이 수정되었을 때, '여기 있지 않나?'라는 직감이 틀리면 손상될 것입니다... 저도 나이가 좀 있는 것 같아요).
Rails 재구성에서는 여러 번 Ruby 버전과 Rails 버전을 변경하려고 시도했습니다. 처음에는 Ruby 1.9.3 + Rails 4.1.0이었지만, Ruby 2.0.0 + Rails 4.2.0으로 변경했습니다. 버전 간 호환성이 꽤 좋아서 그 부분에서 공격을 시도했습니다. 재구성 직후에는 문제가 없었지만, 다음 날 정오에 같은 증상이 다시 나타났습니다. Rails SQLite 데이터베이스가 고장 났을 수 있으니, 재구성 직후 백업된 DB로 다시 전환하는 것은 무의미합니다. DB를 다시 초기화하고 데이터를 다시 삽입해 보세요. … 응? seeds.rb는 작동하지 않습니다(Rails에서 데이터베이스에 초기 데이터를 등록할 때 'rake db:seed' 명령어를 'db/seeds.rb'로 실행하는데, 이게 오류입니다). 이게 고장 난 Rake 바이너리인가요?
──음, 이게 내가 가장 울게 만든 감정이었어... 나는 오직 무력감과 절망감만 있는 것 같아. 번쩍임조차 없어.
거의 한 달 동안 같은 조사를 계속했지만, 결국 빛이 들어왔어요. 음, 이끼의 생각은... 아마 저 사람이군요~. 놀랍게도 그런 사이트를 찾았어요!
Ruby가 갑자기 "/usr/bin/ruby: No such file or directory" prelink 기능을 멈췄나요? CentOS 환경에서는 필수 설치로 설치되며, cron.daily(하루에 한 번 실행하는 cron 작업)와 함께 자동으로 실행됩니다. 나쁜 점은, Ruby 바이너리 파일이 깨진 경우도 있다는 것입니다. 꽤 수상합니다. 이번에는 prelink+ruby로 검색했더니 드디어 나왔습니다... 금맥에 도달한 기분입니다.
너무 의심스러운 프리링크입니다.
cron.daily는 매일 새벽 3시부터 4시까지 무작위로 실행됩니다. 참고로, Rails 환경의 시간대는 UTC라서, +9:00을 누르면 프리링크는 일본에서 12:00 ~ 13:00 사이에 진행됩니다. 문제가 발생한 유일한 시간은 점심시간이었습니다... 즉...
**프리링크, 너야~? **
그래서 실제로 검증을 했습니다. 재구성된 Rails 환경에서, SQLite 데이터베이스에 쓰는 동안(이 타이밍이 매우 중요합니다). 주요 애플리케이션이 바이너리로 전환할 때, 프리링크가 겹치지 않고 증상이 나타나지 않는 경우도 있었습니다), 저는 프리링크를 수동으로 실행하려고 했습니다...
**결국 당신이 범인이었으니까요, 프리링크! **
"라우팅 오류"에 축하드립니다! 아니요, Rails 앱이 고장 난 것에 기뻐하고 싶진 않지만, 저를 포함한 모든 분들에게는 매우 기쁩♪니다
그래서 Prelink 실행 전에 Rails 체크섬을 가져왔는데, 실행 후 체크섬과 비교해보니 Gemfile.lock과 db/development.sqlite3****의 바이너리가 고장 난 것 같습니다. SQLite 데이터베이스의 파일만 반환해도 복구할 수 없습니다.
특히 Gemfile.lock이 깨졌다면, Rails가 어떻게 동작할지 알 수 없습니다. Rails는 gem 의존성, 버전, 목적지(파일 경로)를 중앙에서 관리하는 바이너리이기 때문입니다. 이번에는 모두 "라우팅 오류"였지만, 환경에 따라 Rails는 또 다른 오류만 표시할 수 있습니다.
이제 원인을 찾기 위한 고군분투입니다. 이제부터는 버그 수정 팁을 소개하겠습니다.
프리링크 비활성화
우선, prelink가 뭔가요? —— 물론 지금까지는 용의자지만, 그렇게 나쁜 짓을 하는 사람이 아닐 수도 있어요. 찾아보니 prelink는 바이너리에서 생성된 동적 링크 처리를 분석한 뒤 바이너리 파일을 실행하고, 이를 바이너리 본체에 삽입해 애플리케이션 성능을 최적화하는 미들웨어라고 하더군요. 음, 성능 향상을 위한 것이긴 하지만, 바이너리를 자체적으로 다시 쓰는 것도 괜찮아요. "가끔 리라이트가 실패해서 바이너리가 망가졌지만, 테헤페로 ♥" 같은 말이죠**!** ― 제가 찾아보니 해롭고 수익성 없는 앱인 것 같아요. 왜 CentOS는 기본적으로 그런 폭탄을 설치하는 걸까요?
아, 센토스가 드디어 깨달았다. 그의 위험에 대하여(웃음)
그래서 나는 네가 이렇게 위험한 사람에게서 최대한 빨리 벗어나길 바라는 거야. 자, 프리링크를 끄는 방법. 아래에 몇 가지 있어.
- cron.daily에서 prelink 삭제 해제
- /etc/prelink.conf에 바이너리 리라이트하지 말고 싶은 파일과 경로의 블랙리스트로 지정하세요
- /etc/sysconfig/prelink에서 PRELINKING=no 설정하세요
우선, cron.daily에는 다른 미들웨어 설정도 포함되어 있어서 너무 건드리고 싶지 않습니다(다른 것에 영향을 주면 원하지 않기 때문입니다). 다음으로 두 번째로, 프리링크 자체는 블랙리스트가 아니지만, 정상적으로 작동하는 앱을 블랙리스트 없이 부르는 것은 명예훼손적이고 불편합니다. … 그래서 이건 거절입니다 (웃음). 마지막 설정이 가장 적절합니다. CentOS 7도 이런 식으로 비활성화해서, 다시 바꾸고 싶으면 다시 예로 되돌려버립니다(그럴 때는 더 이상 없지만요...).
그럼 방법 3에서 prelink를 비활성화해봅시다. 꺼둔 후에는 혹시 몰라 서버를 재시작하고, 그러면 베개를 높이 씌운 채로 잘 수 있습니다.
결론
끝까지 읽어주시고, 긴 글들과 잡다한 글들을 읽어주셔서 감사합니다. 이 긴 글의 요약은 한 단어로 요약되어 있습니다.
만약 Ruby on Rails 환경이 갑자기 중단된다면, 그 프리링크가 Rails 바이너리 파일을 손상시키고 있을 가능성이 큽니다
하지만 Rails를 사용하지 않는 환경에서도 CentOS 환경에서는 prelink를 비활성화하는 것이 기본적으로 더 안전합니다.