IL-0191 Grafana Labs · 2022-09-01 (게시일)kubernetesobservability
Grafana Labs 는 Grafana Cloud Logs 클러스터 하나에서 Loki 의 링을 저장하던 별도 키-값 저장소를 내장 memberlist 로 바꾸는 중이었다. 링은 distributor 가 어느 ingester 에 데이터를 쓸지 정할 때 쓰는 분산 해시 테이블이다. 이 마이그레이션은 키-값 저장소라는 단일 장애점을 없애려는 작업이었다.
Grafana Labs 는 jsonnet 과 Grafana Tanka 로 Kubernetes 배포를 관리한다. 디플로이먼트나 스테이트풀셋에 붙은 레이블을 모두 복사해 서비스 셀렉터로 만드는 함수가 있었다. 다른 팀이 이 문제를 이미 지적하고 일부 레이블을 빼는 선택 인자를 추가해 두었지만, 마이그레이션 코드를 쓴 엔지니어는 이를 알아채지 못하고 인자를 넘기지 않았다. 그래서 memberlist 용 레이블이 distributor 서비스의 셀렉터에도 들어갔다.
1단계 변경을 롤아웃하자 distributor 서비스는 새 레이블이 붙은 파드만 가리켰다. gateway 는 인증을 확인한 뒤 쓰기 데이터를 distributor 로 넘기는 구성 요소다. gateway 는 Kubernetes API 로 distributor 를 찾기 때문에 롤아웃 초기에 뜬 새 파드만 보게 됐다. 쓰기 트래픽 100%가 distributor 의 10%에 몰렸고, 새 파드는 OOM으로 죽어 레디니스 체크를 통과하지 못했다. 롤아웃이 더 진행되지 않으니 같은 파드들이 계속 부하를 받았다.
같은 단계가 다른 환경에서는 장애를 내지 않았다. 장애가 난 환경은 distributor 를 수평으로 훨씬 많이 늘려 운영했고, 비슷한 규모의 다른 환경은 distributor 메모리 한도도 함께 키워 두었다. 그래서 다른 환경의 새 파드는 메모리 한도에 이르기 전에 완전히 뜰 시간이 조금 더 있었고, 롤아웃이 계속 이어지며 부하가 퍼졌다. 이번 환경에서는 원인을 찾기 전에 distributor 를 늘려 복구를 시작했고, 마이그레이션 단계를 되돌려 약 25분 만에 장애를 끝냈다.
Grafana Cloud Logs 의 한 환경에서 Grafana Loki 의 쓰기 경로가 내려갔다. 롤아웃 중이던 distributor 파드가 OOM으로 죽었다가 다시 뜨기를 되풀이했다.
kubectl get pods 와 지표 kube_deployment_status_replicas_updated 를 보니 distributor 140개 가운데 16개만 업데이트된 상태로 롤아웃이 멈춰 있었다. 새 파드는 CPU와 메모리 한도를 넘는 부하를 받아 OOM으로 죽었고, 헬스 체크와 레디니스 체크를 통과하지 못했다. Kubernetes 는 새 파드가 이 체크를 통과해야 다음 옛 파드를 교체하므로 롤아웃이 더 나아가지 않았고, 같은 파드들이 계속 부하를 받았다.
팀은 처음에 특정 로그 스트림의 처리량이 갑자기 늘었거나 새 스트림이 한꺼번에 많이 생긴 시점에 마침 배포가 겹쳤다고 의심했다. 그러나 들어오는 쓰기 요청 비율과 활성 스트림 수 지표는 장애 직전에 변하지 않았다. 각 distributor 가 보고하는 loki_request_duration_seconds_count 에서 쓰기 경로 요청 비율이 0보다 큰 시계열을 세어 보니 140개 중 16개만 쓰기 요청을 받고 있었다. 그래서 요청이 distributor 로 가는 경로가 바뀌었다고 보고 확인에 나섰다.
같은 마이그레이션 단계를 다른 환경에 배포했을 때도 오류가 몇 건 났다. 롤아웃 때는 구성 요소들이 서로 다른 시각에 뜨기 때문에 오류가 조금씩 나는 일이 흔해서, 팀은 이를 이상하게 여기지 않았다.
처음에는 distributor 가 OOM으로 죽는 장애로 보였다. 첫 단서는 kubectl get pods 와 kube_deployment_status_replicas_updated 지표에서 distributor 의 10%만 재시작됐다는 점이었다. 팀은 PR 마다 Kubernetes 매니페스트 diff 를 만들어 PR 에 올리고 별도 레포에도 저장하는 자동화를 갖고 있었다. 이 diff 에서 distributor 서비스에 셀렉터가 추가되고 서비스가 다시 만들어진 것을 찾았다. 평소 배포에서는 서비스가 다시 만들어지지 않는다.
서비스 셀렉터를 디플로이먼트 레이블에서 자동으로 만들면, 롤아웃 중에 새 레이블이 붙는 순간 서비스가 새 파드만 가리킬 수 있다. 다른 환경에서 본 작은 오류를 롤아웃 때 흔한 잡음으로 넘기면 이런 신호를 놓친다. 환경마다 수평 확장과 메모리 한도 설정이 다르면 다른 환경에서 문제없던 변경이 특정 환경에서만 장애를 낸다. 원인을 빨리 찾는 데에는 Loki 와 Kubernetes 지표, PR 마다 남겨 둔 매니페스트 diff 가 도움이 됐다.
같은 원인 · 공통 컨테이너 메모리 한도
같은 원인 · 공통 설정 템플릿
같은 원인 · 공통 설정 템플릿
같은 원인 · 공통 설정 템플릿
같은 원인 · 공통 컨테이너 메모리 한도