Gitaly on Kubernetes
GitLab v19.4Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
요약
Kubernetes에서 Gitaly를 실행하면 가용성 측면에서 절충이 생기므로, 운영 환경을 계획할 때 이러한 절충을 고려하고 기대 수준을 그에 맞게 설정합니다. Gitaly on Kubernetes는 Gitaly 팀이 평가했으며 Gitaly를 배포하는 안전한 방식으로 판단했습니다.
히스토리
- GitLab 17.3에서 실험 기능으로 도입.
- GitLab 17.10에서 실험에서 베타로 변경.
- GitLab 18.2에서 베타에서 제한 공개로 변경.
- GitLab 18.11에서 제한 공개에서 일반 공개로 변경.
Kubernetes에서 Gitaly를 실행하면 가용성 측면에서 절충이 생기므로, 운영 환경을 계획할 때 이러한 절충을 고려하고 기대 수준을 그에 맞게 설정합니다. 이 문서는 현재 제약을 최소화하고 대비하는 방법을 설명하고 안내합니다.
Gitaly on Kubernetes는 Gitaly 팀이 평가했으며 Gitaly를 배포하는 안전한 방식으로 판단했습니다. 이 문서의 나머지 부분에서는 그 모범 사례를 설명합니다.
타임라인#
Gitaly on Kubernetes는 GitLab 18.11부터 일반 공개되었습니다. GitLab은 클라우드 공급자의 특정 관리형 Kubernetes 서비스(Amazon EKS, Google GKE, Azure AKS 등)와의 호환성은 보장하지 않습니다. 운영 환경에 배포하기 전에 사용 중인 환경에서 직접 검증해야 합니다.
배경#
설계상 Gitaly(비클러스터)는 단일 장애 지점(SPoF) 서비스입니다. 데이터는 단일 인스턴스에서 공급되고 제공됩니다. Kubernetes에서는 StatefulSet Pod가 교체될 때(예를 들어 업그레이드, 노드 유지 관리, 퇴출 과정에서) 해당 Pod나 인스턴스가 제공하던 데이터에 서비스 중단이 발생합니다.
Cloud Native Hybrid 구성(Gitaly VM)에서는 Linux 패키지(Omnibus)가 다음 방식으로 이 문제를 가립니다.
- Gitaly 바이너리를 제자리에서 업그레이드합니다.
- 그레이스풀 리로드를 수행합니다.
컨테이너나 Pod가 완전히 종료되고 새 컨테이너나 Pod로 시작해야 하는 컨테이너 기반 수명 주기에는 같은 방식이 맞지 않습니다.
클라우드 네이티브 배포에서는 Gitaly(비클러스터)가 유일한 일반 공개 옵션입니다. Kubernetes의 Gitaly Cluster(Praefect)는 베타입니다. 적절한 Kubernetes 및 Gitaly 기능과 구성을 활용하면 서비스 중단을 최소화하고 좋은 사용자 경험을 제공할 수 있습니다.
요구 사항#
이 페이지의 내용은 다음을 전제로 합니다.
- Kubernetes 버전
1.29이상. - Kubernetes 노드
runc버전1.1.9이상. - Kubernetes 노드 cgroup v2. 네이티브 및 하이브리드 v1 모드는 지원하지 않습니다.
systemd방식 cgroup 구조만 지원합니다(Kubernetes 기본값). - 노드 마운트 지점
/sys/fs/cgroup에 대한 Pod 접근 권한. - containerd 2.1.0 이상 버전.
/sys/fs/cgroup의root사용자 파일 시스템 권한에 대한 Pod init 컨테이너(init-cgroups) 접근 권한. Pod cgroup을 Gitaly 컨테이너 (사용자git, UID1000)에 위임하는 데 사용합니다.- cgroups 파일 시스템이
nsdelegate플래그로 마운트되어 있지 않아야 합니다. 자세한 내용은 Gitaly 이슈 6480을 참고합니다.
가이드#
Kubernetes에서 Gitaly를 실행할 때는 다음을 수행해야 합니다.
containerd에서 cgroup_writable 필드 활성화#
Gitaly의 cgroup 지원에는 권한이 없는 컨테이너에 대한 cgroups 쓰기 접근이 필요합니다. containerd v2.1.0에서 cgroup_writable 구성 옵션이 도입되었습니다. 이 옵션을 활성화하면 cgroups 파일 시스템이 읽기/쓰기 권한으로 마운트됩니다.
이 필드를 활성화하려면 Gitaly를 배포할 노드에서 다음 단계를 수행합니다. Gitaly가 이미 배포되어 있다면 구성을 변경한 뒤 Pod를 다시 생성해야 합니다.
-
/etc/containerd/config.toml에 있는 containerd 구성 파일을 수정하여cgroup_writable필드를 추가합니다.[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" cgroup_writable = true -
Kubelet과 containerd 서비스를 재시작합니다.
sudo systemctl restart kubelet sudo systemctl restart containerd서비스 재시작에 시간이 오래 걸리면 이 명령으로 노드가 NotReady 상태로 표시될 수 있습니다.
Pod 중단 대응#
Pod는 여러 이유로 교체될 수 있습니다. 서비스 수명 주기를 이해하고 계획하면 중단을 최소화할 수 있습니다.
예를 들어 Gitaly에서는 Kubernetes StatefulSet 이 spec.template 객체 변경 시 교체되며, 이는 Helm 차트 업그레이드(레이블 또는 이미지 태그)나 Pod 리소스 요청 또는 제한 변경 과정에서 발생할 수 있습니다.
이 섹션에서는 흔한 Pod 중단 사례와 대응 방법을 다룹니다.
유지 관리 기간 예약#
이 서비스는 고가용성이 아니므로 특정 작업에서 짧은 서비스 중단이 발생할 수 있습니다. 유지 관리 기간을 예약하면 서비스 중단 가능성을 알리고 기대 수준을 맞출 수 있습니다. 다음 경우에는 유지 관리 기간을 사용해야 합니다.
- GitLab Helm 차트 업그레이드 및 재구성.
- Gitaly 구성 변경.
- Kubernetes 노드 유지 관리 기간. 예를 들어 업그레이드와 패치입니다. Gitaly를 전용 노드 풀로 분리하면 도움이 될 수 있습니다.
PriorityClass 사용#
PriorityClass로 Gitaly Pod에 다른 Pod보다 높은 우선순위를 부여하면 노드 포화 압력, 퇴출 우선순위, 스케줄링 지연에 도움이 됩니다.
-
우선순위 클래스를 생성합니다.
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: gitlab-gitaly value: 1000000 globalDefault: false description: "GitLab Gitaly priority class" -
Gitaly Pod에 우선순위 클래스를 지정합니다.
gitlab: gitaly: priorityClassName: gitlab-gitaly
퇴출을 방지하기 위한 노드 자동 확장 신호 설정#
노드 자동 확장 도구는 Pod를 스케줄링하고 비용을 최적화하기 위해 필요에 따라 Kubernetes 노드를 추가하고 제거합니다.
축소 이벤트가 발생하면 리소스 사용을 최적화하기 위해 Gitaly Pod가 퇴출될 수 있습니다. 보통 이 동작을 제어하고 워크로드를 제외할 수 있는 어노테이션이 제공됩니다. 예를 들어 Cluster Autoscaler에서는 다음과 같습니다.
gitlab:
gitaly:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
리소스 경합 및 포화 대응#
Git 작업은 예측하기 어려운 특성이 있어 Gitaly 서비스의 리소스 사용량도 예측하기 어렵습니다. 모든 리포지터리가 같지 않으며, 특히 모노레포에서는 크기가 성능과 리소스 사용량에 큰 영향을 미칩니다.
Kubernetes에서 리소스 사용량이 통제되지 않으면 Out Of Memory(OOM) 이벤트가 발생하고, 플랫폼이 Pod를 종료하고 모든 프로세스를 강제로 종료합니다. Pod 종료는 다음 두 가지 중요한 문제를 일으킵니다.
- 데이터·리포지터리 손상
- 서비스 중단
이 섹션에서는 영향 범위를 줄이고 서비스 전체를 보호하는 방법을 다룹니다.
Git 프로세스 리소스 사용량 제한#
Git 프로세스를 격리하면 단일 Git 호출이 서비스와 Pod의 모든 리소스를 소진하지 못하도록 보장할 수 있습니다.
Gitaly는 Linux Control Groups(cgroups)를 사용하여 리포지터리 단위로 더 작은 리소스 사용량 할당량을 적용할 수 있습니다.
cgroup 할당량은 Pod 전체 리소스 할당량보다 낮게 유지해야 합니다. CPU는 서비스 속도만 느려지므로 결정적이지 않습니다. 다만 메모리 포화는 Pod 종료로 이어질 수 있습니다. Pod 요청량과 Git cgroup 할당량 사이에 1GiB 메모리 여유를 두는 것이 안전한 출발점입니다. 여유 크기는 트래픽 패턴과 리포지터리 데이터에 따라 달라집니다.
예를 들어 Pod 메모리 요청량이 15GiB일 때 Git 호출에 14GiB를 할당합니다.
gitlab:
gitaly:
cgroups:
enabled: true
# Total limit across all repository cgroups, excludes Gitaly process
memoryBytes: 15032385536 # 14GiB
cpuShares: 1024
cpuQuotaUs: 400000 # 4 cores
# Per repository limits, 50 repository cgroups
repositories:
count: 50
memoryBytes: 7516192768 # 7GiB
cpuShares: 512
cpuQuotaUs: 200000 # 2 cores
자세한 내용은 Gitaly 구성 문서를 참고합니다.
Pod 리소스 크기 산정#
Gitaly Pod의 크기 산정은 매우 중요하며, 참조 아키텍처가 출발점으로 삼을 만한 지침을 제공합니다. 다만 리포지터리와 사용 패턴에 따라 소비하는 리소스 양이 다릅니다. 리소스 사용량을 모니터링하면서 시간이 지남에 따라 조정해야 합니다.
메모리는 부족해지면 Pod 종료를 유발할 수 있어 Kubernetes에서 가장 민감한 리소스입니다. cgroups로 Git 호출 격리는 리포지터리 작업의 리소스 사용량을 제한하는 데 도움이 되지만, Gitaly 서비스 자체는 포함하지 않습니다. 앞서 설명한 cgroup 할당량 권장 사항과 마찬가지로, 안전성을 높이려면 전체 Git cgroup 메모리 할당량과 Pod 메모리 요청량 사이에 여유를 둡니다.
Pod의 Quality of Service 클래스는 Guaranteed가 바람직합니다
(리소스 요청량과 제한이 일치). 이 설정에서는 Pod가 리소스 경합에 덜 취약하고, 다른 Pod의 사용량 때문에 퇴출되지 않도록 보장됩니다.
리소스 구성 예시는 다음과 같습니다.
gitlab:
gitaly:
resources:
requests:
cpu: 4000m
memory: 15Gi
limits:
cpu: 4000m
memory: 15Gi
init:
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
cpu: 50m
memory: 32Mi
동시성 제한 구성#
동시성 제한을 사용하면 비정상적인 트래픽 패턴으로부터 서비스를 보호할 수 있습니다. 자세한 내용은 동시성 구성 문서와 제한 모니터링 방법을 참고합니다.
Gitaly Pod 격리#
Gitaly Pod를 여러 개 실행할 때는 장애 도메인을 분산하기 위해 서로 다른 노드에 스케줄링해야 합니다. Pod 안티 어피니티로 이를 강제할 수 있습니다. 예를 들면 다음과 같습니다.
gitlab:
gitaly:
antiAffinity: hard
Pod 교체 시간 최적화#
이 섹션에서는 Pod가 트래픽을 처리하기까지 걸리는 시간을 줄여 유지 관리 이벤트나 예기치 않은 인프라 이벤트에서 다운타임을 줄이는 최적화 영역을 다룹니다.
영구 볼륨 권한#
데이터 크기가 커지면(Git 히스토리와 리포지터리 증가) Pod가 시작되어 준비 상태가 되기까지 점점 더 오래 걸립니다.
Pod 초기화 중 영구 볼륨 마운트 과정에서 파일 시스템 권한과 소유권이 컨테이너의 uid와 gid로 명시적으로 설정됩니다.
이 작업은 기본적으로 실행되며, 저장된 Git 데이터에는 작은 파일이 많기 때문에 Pod 시작 시간을 크게 늦출 수 있습니다.
이 동작은
fsGroupChangePolicy
속성으로 구성할 수 있습니다. 이 속성을 사용하면 볼륨 루트의 uid 나 gid가 컨테이너 사양과 일치하지 않을 때만 작업을 수행합니다.
gitlab:
gitaly:
securityContext:
fsGroupChangePolicy: OnRootMismatch
헬스 프로브#
Gitaly Pod는 readiness 프로브가 성공한 후 트래픽 처리를 시작합니다. 기본 프로브 시간은 대부분의 사용 사례를 포괄하도록 보수적으로 설정되어 있습니다.
readinessProbe의 initialDelaySeconds 속성을 줄이면 프로브가 더 일찍 실행되어 Pod 준비가 빨라집니다. 예를 들면 다음과 같습니다.
gitlab:
gitaly:
statefulset:
readinessProbe:
initialDelaySeconds: 2
periodSeconds: 10
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
Gitaly 그레이스풀 종료 타임아웃#
기본적으로 Gitaly는 종료할 때 진행 중인 요청이 완료되도록 1분의 타임아웃을 부여합니다. 언뜻 유용해 보이지만 이 타임아웃은 다음과 같은 영향을 줍니다.
- Pod 교체를 느리게 합니다.
- 종료 과정에서 요청을 거부하여 가용성을 떨어뜨립니다.
컨테이너 기반 배포에서는 클라이언트 측 재시도 로직에 의존하는 편이 낫습니다. gracefulRestartTimeout 필드로 타임아웃을 다시 구성할 수 있습니다.
예를 들어 그레이스풀 타임아웃을 1초로 부여하려면 다음과 같이 설정합니다.
gitlab:
gitaly:
gracefulRestartTimeout: 1
디스크 사용량 모니터링#
로그 순환이 활성화되어 있지 않으면 로그 파일 증가가 스토리지 문제를 일으킬 수 있으므로, 장기간 실행되는 Gitaly 컨테이너의 디스크 사용량을 정기적으로 모니터링합니다.
Gitaly on Kubernetes로 마이그레이션#
기존 리포지터리를 Kubernetes가 아닌 Gitaly 노드에서 Gitaly on Kubernetes로 마이그레이션하려면 다음 단계를 수행합니다.
- Gitaly on Kubernetes 노드를 배포하고 GitLab Admin 영역에서 새 리포지터리 스토리지로 추가합니다. 모든 새 리포지터리가 새 리포지터리 스토리지에 생성되도록 스토리지 가중치를 구성합니다. 이렇게 하면 마이그레이션이 진행되는 동안 기존 리포지터리 스토리지에 새 프로젝트가 생성되지 않습니다.
- 리포지터리 이동 API로 기존 리포지터리를 새 스토리지로 옮깁니다. GitLab 리포지터리는 프로젝트, 그룹, 스니펫에 연결될 수 있으며 유형별로 API가 따로 있습니다. 전체 지침은 GitLab이 관리하는 리포지터리 이동을 참고합니다.
이동하는 동안 각 리포지터리는 읽기 전용이 되며, 이동이 완료될 때까지 쓸 수 없습니다.