GitLab 패키지의 번들된 Puma 인스턴스 구성

Tier: Free, Premium, Ultimate Offering: Self-Managed

Puma는 루비 어플리케이션을 위한 빠르고 멀티 스레드이며 고도로 동시성이 높은 HTTP 1.1 서버입니다. 이는 GitLab의 사용자 인터페이스 기능을 제공하는 핵심 Rails 어플리케이션을 운영합니다.

메모리 사용량 줄이기

메모리 사용량을 줄이기 위해, Puma는 워커 프로세스를 포크합니다. 워커가 생성될 때마다, 기본 프로세스와 메모리를 공유합니다. 워커는 메모리 페이지에 변경 사항이나 추가 사항이 있을 때에만 추가 메모리를 사용합니다. 이는 Puma 워커들이 시간이 지남에 따라 추가 웹 요청을 처리함에 따라 물리적 메모리를 더 사용할 수 있음을 의미합니다. 시간이 지남에 따른 메모리 사용량은 GitLab의 사용에 따라 달라집니다. GitLab 사용자가 사용하는 기능이 더 많을수록, 예상되는 메모리 사용량이 더 많아집니다.

제어되지 않는 메모리 증가를 멈추기 위해, GitLab Rails 어플리케이션은 주어진 RSS (Resident Set Size) 임계값이 일정 시간 동안 초과된 워커들을 자동으로 다시 시작하는 감시 스레드를 실행합니다.

GitLab은 메모리 제한을 위해 기본 1200Mb 값을 설정합니다. 기본 값을 재정의하려면 per_worker_max_memory_mb를 새 RSS 제한(메가바이트 단위)으로 설정하세요:

  1. /etc/gitlab/gitlab.rb 파일을 편집하세요:

    puma['per_worker_max_memory_mb'] = 1024 # 1GB
    
  2. GitLab을 다시 구성하세요:

    sudo gitlab-ctl reconfigure
    

워커가 다시 시작되면, GitLab을 실행할 수 있는 용량이 잠시 감소합니다. 워커가 너무 자주 교체되는 경우, per_worker_max_memory_mb를 더 높은 값으로 설정하세요.

워커 수는 CPU 코어를 기준으로 계산됩니다. 4-8개의 워커를 가진 작은 GitLab 배포는 워커가 자주 다시 시작되는 경우 성능 문제를 겪을 수 있습니다 (1분당 한 번 이상).

높은 값으로 1200 또는 그 이상은 서버에 여유 메모리가 있는 경우 유용할 수 있습니다.

워커 재시작 모니터링

워커가 높은 메모리 사용으로 인해 재시작되면, GitLab은 로그 이벤트를 발생시킵니다.

다음은 /var/log/gitlab/gitlab-rails/application_json.log에서 이러한 로그 이벤트 중 하나의 예시입니다:

{
  "severity": "WARN",
  "time": "2023-01-04T09:45:16.173Z",
  "correlation_id": null,
  "pid": 2725,
  "worker_id": "puma_0",
  "memwd_handler_class": "Gitlab::Memory::Watchdog::PumaHandler",
  "memwd_sleep_time_s": 5,
  "memwd_rss_bytes": 1077682176,
  "memwd_max_rss_bytes": 629145600,
  "memwd_max_strikes": 5,
  "memwd_cur_strikes": 6,
  "message": "rss memory limit exceeded"
}

memwd_rss_bytes는 사용된 실제 메모리 양이며, memwd_max_rss_bytesper_worker_max_memory_mb를 통해 설정된 RSS 제한입니다.

워커 타임아웃 변경

기본 Puma 타임아웃은 60초입니다.

NOTICE: puma['worker_timeout'] 설정은 최대 요청 기간을 설정하지 않습니다.

워커 타임아웃을 600초로 변경하려면:

  1. /etc/gitlab/gitlab.rb 파일을 편집하세요:

    gitlab_rails['env'] = {
       'GITLAB_RAILS_RACK_TIMEOUT' => 600
     }
    
  2. GitLab을 다시 구성하세요:

    sudo gitlab-ctl reconfigure
    

메모리 제한된 환경에서 Puma 클러스터 모드 비활성화

경고: 이 기능은 실험 중이며 사전 통보 없이 변경될 수 있습니다. 이 기능은 아직 제품 사용 준비가 되지 않았습니다. 이 기능을 사용하려면 먼저 제품 사용 이전에 테스트해야 합니다. 추가 세부 정보는 알려진 문제를 참조하세요.

4GB 미만의 RAM이 있는 메모리 제한된 환경에서 Puma 클러스터 모드를 비활성화하는 것을 고려하세요.

메모리 사용량을 수백 메가바이트로 줄이려면 workers 수를 0으로 설정하세요:

  1. /etc/gitlab/gitlab.rb 파일을 편집하세요:

    puma['worker_processes'] = 0
    
  2. GitLab을 다시 구성하세요:

    sudo gitlab-ctl reconfigure
    

기본적으로 설정된 클러스터 모드와 달리, 이 구성으로는 단일 Puma 프로세스만 어플리케이션을 제공하게 됩니다. Puma 워커 및 스레드 설정에 대한 자세한 내용은 Puma 요구 사항을 참조하세요.

이 구성으로 Puma를 실행하는 경우 쓰루풋이 감소하지만, 메모리 제한된 환경에서 공정한 트레이드오프로 간주될 수 있습니다.

메모리 부족 (OOM) 조건을 피하기 위해 충분한 스왑이 있는지 확인하세요. 세부 정보는 메모리 요구 사항을 확인하세요.

Puma 단일 모드 알려진 문제

Puma를 단일 모드로 실행할 때 일부 기능이 지원되지 않습니다:

자세한 내용은 epic 5303을 참조하세요.

Puma를 SSL로 수신하도록 구성

Linux 패키지 설치와 함께 배포된 Puma는 기본적으로 Unix 소켓상에서 수신합니다. 대신 HTTPS 포트에서 Puma를 수신하도록 구성하려면 다음 단계를 따르세요:

  1. Puma가 수신할 주소에 대한 SSL 인증서 키 페어를 생성하세요. 아래 예시에서는 127.0.0.1입니다.

    NOTIC: 사용자 정의 인증 기관(CA)에서의 자체 서명된 인증서를 사용하는 경우, 다른 GitLab 구성 요소가 신뢰할 수 있도록 하려면 문서를 참조하세요.

  2. /etc/gitlab/gitlab.rb 파일을 편집하세요:

    puma['ssl_listen'] = '127.0.0.1'
    puma['ssl_port'] = 9111
    puma['ssl_certificate'] = '<인증서_경로>'
    puma['ssl_certificate_key'] = '<키_경로>'
    
    # UNIX 소켓 비활성화
    puma['socket'] = ""
    
  3. GitLab을 다시 구성하세요:

    sudo gitlab-ctl reconfigure
    

NOTICE: Unix 소켓 외에 Prometheus가 스크래핑하기 위해 HTTP 상에서 8080 포트에서도 Puma가 수신합니다. 현재로서는 Prometheus가 HTTPS를 통해 스크래핑하는 것은 불가능하며, 이에 대한 지원이 이 이슈에서 논의되고 있습니다. 따라서 HTTP 수신기를 제거하지 않고는 Prometheus 메트릭을 잃을 수 있습니다.

암호화된 SSL 키 사용

Puma는 실행 시 복호화할 수 있는 암호화된 개인 SSL 키를 지원합니다. 다음 지침은 이를 구성하는 방법을 설명합니다:

  1. 이미 그렇지 않은 경우 키를 암호화합니다.

    openssl rsa -aes256 -in /path/to/ssl-key.pem -out /path/to/encrypted-ssl-key.pem
    

    암호화된 파일을 작성하려면 두 번의 암호를 입력하세요. 이 예제에서는 some-password-here를 사용했습니다.

  2. 암호를 인쇄하는 스크립트 또는 실행 파일을 만듭니다.
    /var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password에 기본 스크립트를 만들어 다음과 같이 암호를 출력합니다:

    #!/bin/sh
    echo some-password-here
    

    디스크에 암호를 저장하지 않고 Vault와 같은 안전한 메커니즘을 사용하여 암호를 가져오는 것을 권장합니다. 예를 들어, 다음과 같이 스크립트를 작성할 수 있습니다:

    #!/bin/sh
    export VAULT_ADDR=http://vault-password-distribution-point:8200
    export VAULT_TOKEN=<some token>
    
    echo "$(vault kv get -mount=secret puma-ssl-password)"
    
  3. Puma 프로세스가 스크립트를 실행하고 암호화된 키를 읽을 수 있는 충분한 권한이 있는지 확인합니다.

    chown git:git /var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password
    chmod 770 /var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password
    chmod 660 /path/to/encrypted-ssl-key.pem
    
  4. /etc/gitlab/gitlab.rb를 편집하고 puma['ssl_certificate_key']를 암호화된 키로 바꾸고 puma['ssl_key_password_command]를 지정합니다.

    puma['ssl_certificate_key'] = '/path/to/encrypted-ssl-key.pem'
    puma['ssl_key_password_command'] = '/var/opt/gitlab/gitlab-rails/etc/puma-ssl-key-password'
    
  5. GitLab을 다시 구성합니다.

    sudo gitlab-ctl reconfigure
    
  6. GitLab이 성공적으로 시작되면 GitLab 인스턴스에 저장된 암호화되지 않은 SSL 키를 제거할 수 있습니다.

유니콘(Unicorn)에서 퓨마(Puma)로 전환

참고: Helm 기반 배포의 경우, webservice 차트 문서를 참조하세요.

퓨마는 기본 웹 서버이며 유니콘은 더 이상 지원되지 않습니다.

퓨마는 유니콘과 같은 다중 프로세스 응용 프로그램 서버보다 적은 메모리를 사용하는 멀티 스레드 아키텍처를 갖고 있습니다. GitLab.com에서는 메모리 소비량이 40% 감소한 것을 확인했습니다. 대부분의 Rails 애플리케이션 요청에는 보통 I/O 대기 시간의 일정 부분이 포함됩니다.

I/O 대기 시간 동안 MRI Ruby는 GVL을 다른 스레드로 릴리스합니다. 따라서 멀티 스레드 퓨마는 단일 프로세스보다 더 많은 요청을 처리할 수 있습니다.

퓨마로 전환할 때 두 응용 프로그램 서버 간의 차이로 인해 유니콘 서버 설정이 자동으로 전달되지 않습니다.

유니콘에서 퓨마로 전환하려면:

  1. 적합한 퓨마 워커 및 스레드 설정을 결정합니다.
  2. /etc/gitlab/gitlab.rb에서 사용 중인 모든 사용자 지정 유니콘 설정을 퓨마로 변환합니다.

    아래 표는 Linux 패키지를 사용할 때 유니콘 구성 키가 Puma에 해당하는지, 혹은 해당되지 않는지를 요약한 것입니다.

    유니콘 퓨마
    unicorn['enable'] puma['enable']
    unicorn['worker_timeout'] puma['worker_timeout']
    unicorn['worker_processes'] puma['worker_processes']
    해당되지 않음 puma['ha']
    해당되지 않음 puma['min_threads']
    해당되지 않음 puma['max_threads']
    unicorn['listen'] puma['listen']
    unicorn['port'] puma['port']
    unicorn['socket'] puma['socket']
    unicorn['pidfile'] puma['pidfile']
    unicorn['tcp_nopush'] 해당되지 않음
    unicorn['backlog_socket'] 해당되지 않음
    unicorn['somaxconn'] puma['somaxconn']
    해당되지 않음 puma['state_path']
    unicorn['log_directory'] puma['log_directory']
    unicorn['worker_memory_limit_min'] 해당되지 않음
    unicorn['worker_memory_limit_max'] puma['per_worker_max_memory_mb']
    unicorn['exporter_enabled'] puma['exporter_enabled']
    unicorn['exporter_address'] puma['exporter_address']
    unicorn['exporter_port'] puma['exporter_port']
  3. GitLab을 다시 구성합니다.

    sudo gitlab-ctl reconfigure
    
  4. 선택 사항. 다중 노드 배포의 경우 로드 밸런서를 구성하여 준비 확인을 사용합니다.

퓨마(Puma) 문제 해결

Puma가 100% CPU에서 돌다가 502 Gateway Timeout

이 오류는 웹 서버가 Puma 작업자로부터 듣지 못한 후 (기본: 60초) 웹 서버 시간이 초과될 때 발생합니다. CPU가 100%로 돌아갈 때 무언가가 예상보다 오래 걸리는 경우가 있습니다.

이 문제를 해결하려면 먼저 무엇이 일어나고 있는지 파악해야 합니다. 이를 알고자 하는 경우에만 다음 팁을 사용하세요. 그렇지 않은 경우 다음 섹션으로 이동하세요.

  1. 문제가 있는 URL을 로드합니다.
  2. sudo gdb -p <PID>를 실행하여 Puma 프로세스에 부착합니다.
  3. GDB 창에서 다음을 입력합니다:

    call (void) rb_backtrace()
    
  4. 이를 통해 프로세스가 Ruby 백트레이스를 생성하도록 합니다. 백트레이스를 확인하기 위해 /var/log/gitlab/puma/puma_stderr.log를 확인하세요. 예를 들어 다음과 같은 내용을 확인할 수 있습니다:

    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
    from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
    
  5. 현재 스레드를 확인하려면 다음을 실행합니다:

    thread apply all bt
    
  6. gdb 디버깅을 마친 후 프로세스에서 분리하고 종료하기 바랍니다:

    detach
    exit
    

프로세스가 이러한 명령을 실행하기 전에 Puma 프로세스가 종료되면 GDB는 오류를 보고합니다. 추가 시간을 확보하려면 Linux 패키지 설치 사용자의 경우 /etc/gitlab/gitlab.rb를 편집하여 60초에서 600초로 증가시키세요:

gitlab_rails['env'] = {
        'GITLAB_RAILS_RACK_TIMEOUT' => 600
}

자체 컴파일된 설치의 경우 환경 변수를 설정하세요. 자세한 내용은 Puma Worker timeout를 참조하세요.

변경 사항을 적용하려면 GitLab을 다시 구성하세요.

다른 사용자에게 영향을주지 않고 문제 해결

이전 섹션은 실행 중인 Puma 프로세스에 연결되어 있을 수 있으며 이는 GitLab에 액세스하려는 사용자들에게 원치 않는 영향을 미칠 수 있습니다. 프로덕션 시스템에서 다른 사용자에게 영향을 주지 않도록 걱정된다면 문제를 해결하기 위해 별도의 Rails 프로세스를 실행할 수 있습니다:

  1. GitLab 계정에 로그인합니다.
  2. 문제를 일으키는 URL을 복사합니다 (예: https://gitlab.com/ABC).
  3. 사용자용 개인 엑세스 토큰을 생성합니다 (사용자 설정 -> 엑세스 토큰).
  4. GitLab Rails 콘솔을 시작합니다.
  5. Rails 콘솔에서 다음을 실행합니다:

    app.get '<단계 2의 URL>/?private_token=<단계 3의 토큰>'
    

    예:

    app.get 'https://gitlab.com/gitlab-org/gitlab-foss/-/issues/1?private_token=123456'
    
  6. 새 창에서 top을 실행합니다. 100% CPU를 사용하는 이 Ruby 프로세스가 표시됩니다. PID를 적어둡니다.
  7. 이전 섹션의 단계 2를 따라 GDB를 사용합니다.

GitLab: API에 접근할 수 없음

이는 종종 GitLab Shell이 내부 API (예: http://localhost:8080/api/v4/internal/allowed)를 통해 인가를 요청하려고 할 때 발생하며, 확인 중에 실패하는 경우가 있습니다. 이런 경우 발생할 수 있는 다양한 이유가 있습니다:

  1. 데이터베이스에 연결하는 시간 초과 (예: PostgreSQL 또는 Redis)
  2. Git 후크 또는 푸시 규칙 오류
  3. 저장된 NFS 핸들 등의 이유로 리포지토리에 액세스하는 중 오류 발생

이 문제를 진단하기 위해 문제를 재현해보고, 그 후 top을 사용하여 Puma 워커가 스핀 중인지 확인합니다. 상기한 gdb 기법을 사용해 봅니다. 또한, strace 사용은 문제를 분리하는 데 도움이 될 수 있습니다:

strace -ttTfyyy -s 1024 -p <Puma 워커의 PID> -o /tmp/puma.txt

문제가 되는 Puma 워커를 확인할 수 없다면 모든 Puma 워커에서 /internal/allowed 엔드포인트가 어느 부분에 막혀 있는지 확인하기 위해 다음을 실행하세요:

ps auwx | grep puma | awk '{ print " -p " $2}' | xargs  strace -ttTfyyy -s 1024 -o /tmp/puma.txt

/tmp/puma.txt 파일의 출력 내용이 원인을 진단하는 데 도움이 될 수 있습니다.

관련 주제