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

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

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

메모리 사용량 감소

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

메모리 성장을 통제하기 위해, GitLab Rails 애플리케이션은 주어진 거주 세트 크기 (RSS) 임계값을 일정 시간 동안 초과하는 워커를 자동으로 다시 시작하는 감시 스레드를 실행합니다.

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 배포는 성능 문제를 겪을 수 있습니다.

서버에 여유 메모리가 있는 경우, 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초입니다.

참고: 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 미만의 메모리로 제한된 환경에서, 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입니다.

    note
    사용자 지정 인증 기관(CA)의 자체 서명 인증서를 사용하는 경우 문서를 따라 기타 GitLab 구성 요소에서 신뢰할 수 있도록 설정하세요.
  2. /etc/gitlab/gitlab.rb를 편집합니다:

    puma['ssl_listen'] = '127.0.0.1'
    puma['ssl_port'] = 9111
    puma['ssl_certificate'] = '<path_to_certificate>'
    puma['ssl_certificate_key'] = '<path_to_key>'
    
    # UNIX 소켓 비활성화
    puma['socket'] = ""
    
  3. GitLab을 다시 구성합니다:

    sudo gitlab-ctl reconfigure
    
note
Unix 소켓 외에도 Puma는 Prometheus에서 스크랩되는 지표를 제공하기 위해 포트 8080에서 HTTP를 통해 수신합니다. 현재로서는 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로 전환

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

GitLab 13.0부터 Puma가 기본 웹 서버이며 Unicorn은 비활성화되었습니다. GitLab 14.0에서는 Unicorn이 제거되었으며 Linux 패키지에서 더 이상 지원되지 않습니다.

Puma는 멀티 스레드 아키텍처를 사용하여 Unicorn과 같은 멀티 프로세스 응용 프로그램 서버보다 적은 메모리를 사용합니다. GitLab.com에서는 메모리 소비량이 40% 감소했습니다. 대부분의 Rails 애플리케이션 요청에는 일반적으로 I/O 대기 시간의 비율이 포함됩니다.

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

Puma로 전환할 때 두 응용 프로그램 서버 간의 차이로 인해 기존의 Unicorn 서버 구성이 자동으로 전이되지 않습니다.

Unicorn에서 Puma로 전환하려면:

  1. 적합한 Puma 워커 및 스레드 설정을 확인합니다.
  2. /etc/gitlab/gitlab.rb에서 사용자 정의 Unicorn 설정을 Puma로 변환합니다.

    Linux 패키지를 사용할 때 Unicorn 구성 키와 Puma 구성 키가 대응되는지 여부와 대응되는 경우에는 무엇인지를 아래 표에 요약했습니다.

    Unicorn 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 게이트웨이 타임아웃

이 오류는 웹 서버가 Puma 워커로부터 응답을 받지 못한 후 (기본값: 60초) 타임아웃(Timeout)될 때 발생합니다. 만약 CPU가 100%로 회전한다면, 예상보다 더 오랜 시간이 걸리는 어떤 작업이 있는 것입니다.

이 문제를 해결하려면, 먼저 무슨 일이 일어나고 있는지 확인해야 합니다. 아래의 팁들은 다음 섹션에서 사용자들이 다운타임에 영향을 받는 것을 감내할 수 있다면 추천됩니다. 그 외의 경우라면, 다음 섹션으로 건너뛰세요.

  1. 문제가 되는 URL을 불러옵니다.
  2. sudo gdb -p <PID>를 실행하여 Puma 프로세스에 연결합니다.
  3. GDB 창에서 다음을 입력하세요:

    call (void) rb_backtrace()
    
  4. 이 명령은 프로세스가 루비 백트레이스를 생성하도록 합니다. 백트레이스를 확인하기 위해 /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에서 오류가 보고됩니다. 시간을 더 확보하려면, 언제든지 Puma 워커 타임아웃을 늘릴 수 있습니다. 리눅스 패키지 설치 사용자는 /etc/gitlab/gitlab.rb를 편집하여 기본값 60초에서 600초로 늘릴 수 있습니다:

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

    스스로 컴파일한 설치의 경우, 환경 변수를 설정하세요. Puma 워커 타임아웃을 참조하세요.

    변경 사항이 적용되려면 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를 사용하는 것이 표시될 것입니다. 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에 출력된 내용이 원인을 진단하는 데 도움이 될 것입니다.

관련 주제