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

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

Puma는 Ruby 애플리케이션을 위한 빠르고 멀티스레드이며 고도로 동시성이 높은 HTTP 1.1 서버입니다. 이는 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초입니다.

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

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

  1. /etc/gitlab/gitlab.rb 파일을 편집합니다.

    gitlab_rails['env'] = {
       'GITLAB_RAILS_RACK_TIMEOUT' => 600
     }
    
  2. GitLab을 다시 구성합니다.

    sudo gitlab-ctl reconfigure
    

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

caution
이 기능은 실험 기능으로 변경될 수 있으며 사전 고지 없이 변경될 수 있습니다. 이 기능은 운영 환경에서 사용할 준비가 되지 않았습니다. 이 기능을 사용하려면 먼저 운영 환경 외에서 테스트해야 합니다. 추가 정보는 알려진 문제를 참조하세요.

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을 참조하세요.

SSL을 통해 Puma 구성

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'] = '<인증서 경로>'
    puma['ssl_certificate_key'] = '<키 경로>'
       
    # UNIX 소켓 비활성화
    puma['socket'] = ""
    
  3. GitLab을 다시 구성합니다.

    sudo gitlab-ctl reconfigure
    
note
Unix 소켓 외에, Puma는 Prometheus에서 스크랩되는 메트릭을 제공하기 위해 포트 8080에서 HTTP로 수신합니다. 현재 이를 HTTPS로 스크랩하는 것은 불가능하고, 이에 대한 지원이 이 이슈에서 토의 중입니다. 따라서 프로메테우스가 HTTPS로 스크랩하려면 HTTP 수신기를 끄는 것은 기술적으로 불가능하며 이로 인해 프로메테우스 메트릭이 손실될 수 있습니다.

암호화된 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 차트 설명서를 참조하십시오.

Puma는 기본 웹 서버이며 Unicorn은 더 이상 지원되지 않습니다.

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

I/O 대기 시간 동안 MRI Ruby는 GVL(일반 GVL(Global VM Lock))을 다른 스레드에게 릴리스합니다. 다중 스레드 Puma는 따라서 단워크플로보다 더 많은 요청을 처리할 수 있습니다.

Puma로 전환하면 두 애플리케이션 서버 간의 차이로 인해 Unicorn 서버 구성이 자동으로 전달되지 않습니다.

Unicorn에서 Puma로 전환하려면:

  1. /etc/gitlab/gitlab.rb에서 적합한 Puma 워커 및 스레드 설정을 확인합니다.
  2. Linux 패키지를 사용할 때 사용자 지정 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. 선택 사항. 다중 노드 배포의 경우 로드 밸런서를 구성하여 준비 확인 항목(readiness check)을 사용합니다.

Puma 문제 해결

Puma가 100% CPU에서 돌고난 후 발생하는 502 게이트웨이 타임아웃

이 오류는 웹 서버가 Puma 워커로부터 응답을 듣지 못한 후(default: 60초) 시간초과(502 게이트웨이 타임아웃) 오류가 발생하는 경우입니다. 이러한 일이 발생하는 동안 CPU가 100%로 동작함에 따라 예상보다 긴 시간이 소요될 수 있습니다.

이 문제를 해결하려면 먼저 무엇이 일어나고 있는지 파악해야 합니다. 사용자에게 영향을 미치는 데 문제가 없다면 다음 섹션으로 건너뛰십시오.

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

    call (void) rb_backtrace()
    
  4. 이를 통해 프로세스가 Ruby 백트레이스를 생성하도록 forces합니다. 백트레이스를 확인하려면 /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 워커 타임아웃을 증가시킬 수 있습니다. Linux 패키지 설치 사용자는 /etc/gitlab/gitlab.rb를 편집하여 60초에서 600초로 증가시킬 수 있습니다.

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

자체 컴파일 설치의 경우 환경 변수를 설정하십시오. Puma 워커 타임아웃에 대해 참조하세요.

변경 사항이 적용되려면 GitLab을 다시 구성하십시오.

다른 사용자에게 영향을 미치지 않은 상태에서 문제 해결

이전 섹션에서는 실행 중인 Puma 프로세스에 연결하여, 시스템을 사용하려는 사용자에게 원하지 않는 영향을 끼칠 수 있습니다. 프로덕션 시스템에서 다른 사람에게 영향을 미치는 것에 대해 걱정되면 별도의 Rails 프로세스를 실행하여 문제를 디버깅할 수 있습니다:

  1. GitLab 계정에 로그인합니다.
  2. 문제가 발생하는 URL을 복사합니다(예: https://gitlab.com/ABC).
  3. 사용자 토큰(User Settings -> Access Tokens)에 대한 개인 액세스 토큰을 만듭니다.
  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을 실행합니다. 이 Ruby 프로세스가 100% CPU를 사용 중인 것이 표시됩니다. PID를 메모합니다.
  7. 이전 섹션에서 GDB 사용 방법에 대한 2단계를 따릅니다.

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에 있는 출력 내용이 근본 원인을 진단하는 데 도움이 될 수 있습니다.

관련 주제