Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Sidekiq 클러스터 스크립트로 EC2 환경의 워커 프로세스 관리하기

Honeybadger는 처리 파이프라인 전반에서 Sidekiq에 크게 의존하고 있습니다. 우리가 하는 거의 모든 작업은 어느 시점에는 큐를 거치게 됩니다. 그만큼 Sidekiq를 안정적으로 운영하는 일이 중요했습니다. 최근 EC2로 인프라를 이전하면서, 안정적으로 오래 운영되던 고정 서버 세트에서 끊임없이 생성되고 교체되는 인스턴스 세트로 환경이 바뀌었습니다. 이에 따라 부팅 시점에 Sidekiq를 어떻게 시작할지 다시 검토하게 되었는데, 이제는 예전보다 훨씬 중요한 문제가 되었습니다.

과거에는 god를 사용해 모든 워커 프로세스를 띄우고, 메모리를 과도하게 사용하는 프로세스를 감시해 종료하는 방식으로 운영했습니다. 당시 god 설정은 워커를 실행하던 특정 서버에 맞춰 손수 튜닝된 것이었습니다. 그런데 새로운 EC2 인스턴스에서 systemd로 기존 god 설정을 기동해 보면, 설정에서 지정한 개수보다 많은 워커가 뜨는 경우가 종종 있었습니다. 이는 인스턴스에 메모리 압박을 가중시키는 심각한 문제였습니다. 한동안 설정을 만져 보았지만, 어차피 god의 기능을 많이 활용하지도 않는다는 사실을 깨닫고 프로세스 관리 도구로서 god를 걷어내기로 결정했습니다.

하나의 간단한 명령으로 여러 Sidekiq 프로세스 실행하기

정말로 원했던 것은 systemd 서비스로 실행할 수 있는 스크립트였습니다. 요구 사항은 다음과 같습니다.

  • CPU 코어당 하나의 Sidekiq 프로세스를 생성할 것
  • 메모리를 과도하게 소비하는 프로세스를 자동으로 재시작할 것
  • 코어 수와 RAM 용량이 제각각인 다양한 크기의 EC2 인스턴스에서 모두 동작할 것

정확히 이 역할을 해 주는 스크립트를 찾을 수 없어서 직접 작성하게 되었습니다.

물론 스크립트에는 빌려 온 코드도 일부 포함되어 있습니다. process_count 메서드의 코드는 아마 Stack Overflow에서 가져온 것으로, 예전부터 Unicorn 설정에서 사용해 온 코드입니다. fork_child 메서드는 사실상 sidekiq 젬에 포함된 sidekiq 실행 파일의 로직을 그대로 옮긴 것입니다.

이 스크립트는 현재 머신의 CPU 코어 수만큼 자식 프로세스를 생성하며, 각 자식 프로세스는 sidekiq 명령을 직접 실행한 것과 완전히 동일하게 동작합니다. 따라서 스크립트에 전달한 명령줄 옵션은 그대로 Sidekiq CLI로 넘어가 파싱됩니다. 즉, sidekiq를 직접 실행할 때 쓸 수 있는 옵션이라면 무엇이든 이 스크립트에도 사용할 수 있습니다(다만 PID 파일 옵션처럼 이 구조에서는 의미가 없는 옵션도 있습니다). 또한 별도의 스레드가 주기적으로 각 자식 프로세스의 메모리 사용량을 점검하고, 임계값을 초과한 프로세스는 종료한 뒤 새 프로세스를 대신 띄워 줍니다.

클러스터 활용하기

프로세스 수는 기본적으로 코어 수와 같습니다(인스턴스를 Sidekiq 전용으로 사용한다고 가정할 때). 실행 전에 SK_PROCESS_COUNT 환경 변수를 설정하면 이 값을 재정의할 수 있습니다. 메모리 임계값 역시 인스턴스 전체 RAM의 백분율로 지정됩니다(스크립트 내부에서 process_count 값에 1을 더해 OS가 쓸 RAM을 약간 남겨 둡니다). 원하는 비율은 SK_MEMORY_PCT_LIMIT 환경 변수로 자유롭게 설정할 수 있습니다. 우리는 이 두 변수를 활용해 UI를 구동하는 Rails 앱과 함께 실행되는 인스턴스에서 Sidekiq 프로세스 수와 메모리 사용량을 제한하고 있습니다.

덤으로, 이 스크립트는 Sidekiq 프로세스를 관리할 때 일반적으로 사용되는 시그널을 받아 자식 프로세스들에게 그대로 전달합니다. 덕분에 pkill -f -USR1 skcluster 명령으로 자식 프로세스들이 새 작업 수락을 중단하도록 할 수 있고(배포 스크립트의 초기 단계에서 사용), pkill -f skcluster로 전체를 종료할 수도 있습니다.

systemd 구성하기

systemd 서비스 정의는 매우 단순합니다.

  • 모든 인스턴스에서 pgbouncer를 통해 postgres 연결을 프록시하므로, Sidekiq 프로세스가 기동되기 전에 pgbouncer 서비스가 먼저 실행 중이도록 RequiresAfter 지시자로 보장합니다.
  • 같은 인스턴스에서 Redis를 함께 운영한다면 redis-server.serviceRequiresAfter 항목에 추가하면 됩니다.
  • EnvironmentFile의 파일명 앞에 - 접두사를 붙이면, 해당 파일이 존재하지 않아도 systemd가 오류로 처리하지 않습니다. 이를 통해 기본 프로세스 수와 메모리 제한을 그대로 쓰고 싶을 때는 환경 파일을 아예 두지 않아도 됩니다.
  • --require 옵션으로 Sidekiq에게 Rails 애플리케이션의 설정과 이니셜라이저를 로드할 위치를 알려 줍니다.

skcluster 스크립트가 SIGTERM을 처리해 주기 때문에, systemctl restart skcluster.service 명령 하나로 모든 워커를 깔끔하게 재시작할 수 있습니다(배포 스크립트의 후반 단계에서 사용).

이 스크립트를 한동안 운영해 왔는데 아주 안정적으로 동작하고 있습니다. 부팅 시 과도한 프로세스가 생성되는 문제도 사라졌고, 모니터링 시스템에서 메모리 사용량 경고도 더 이상 울리지 않으며, 워크로드에 맞는 어떤 유형의 EC2 인스턴스든 자유롭게 선택해 실행할 수 있게 되었습니다. 번거로움 없는 운영, 그것이야말로 제가 가장 선호하는 운영 방식입니다.