Secrets of Running Etcd (번역)
2023 북미 KubeCon 에서 etcd 메인테이너가 발표한 자료를 번역한 글입니다. etcd 를 사용하면서 고려해야 할 점에 대해 자세히 다루고 있습니다. 특히 Kubernetes Event 리소스를 별도의 etcd 클러스터에 저장하는 내용과, Compaction / Defragmentation 자동화 과정에서 권장되는 내용등도 포함되어있습니다.

Failures in distributed systems (분산 시스템에서의 Failures)

etcd 는 Raft 합의 알고리즘을 사용하고 있기 때문에, 클러스터링 된 etcd 중 하나가 장애가 발생하더라도 최소한의 Quorum(정족수)이상의 클러스터 멤버가 정상적이라면 문제가 발생하지 않습니다.

Raft 알고리즘은 여러 concurrent request 를 하나의 조직화된 상태로 만들어서 그것을 모든 Cluster 멤버에게 동기화 시킬 수 있는 알고리즘이라고 이야기 할 수도 있습니다. 이 알고리즘을 통해 모든 멤버는 동일한 데이터를 갖고, 동일한 상태로 유지하도록 할 수 있습니다.

Raft 알고리즘의 특성으로, 하나의 애플리케이션이나 클라이언트에서 들어오는 잘못된 요청이나, 예기치않고 문제가 되는 데이터나, 행동들(많은 데이터를 미친듯이 박아넣는다던지) 역시나 모든 멤버에게 그런 잘못된 데이터나, 많은 데이터들이 뿌려지게 되어서 모든 멤버에게 영향을 끼칠수도 있기도 합니다.
Kubernetes events explosion (K8s 이벤트 오브젝트의 폭발적인 발생으로 인한 문제라고 생각하시면 되는..)

쿠버네티스는 2가지 타입의 리소스를 etcd에 저장하는데, 하나는 Objects
이고 하나는 Events
입니다.
Objects
는 우리가 잘 알고있는 Pod / Deplyoment / StatefulSet / Secret / ConfigMap 과 같은 리소스들로서, 쿠버네티스 클러스터 내의 워크로드들의 상태를 표현하는 것으로 생각 할 수 있습니다. 우리가 Pod
를 선언적으로든, 명령적으로든 쿠버네티스 클러스터에 apply
요청을하면 우리는 Desired State
를 쿠버네티스에 알려준셈입니다. 쿠버네티스는 Desired State
를 유지하기 위해서 kube-schedueler
, kube-controller-manager
, kubelet
등의 컴포넌트들이 열심히 일을 합니다.
따라서 Objects
들은 매우 매우 중요하며 (그것들 자체가 쿠버네티스 워크로드와 밀접한 관련이 있으니까), 삭제 하지 않는 한 etcd
에 영구히 남아있는 데이터들입니다. 또한 이러한 Objects
들은 쿠버네티스 관리자 / 백엔드 개발자 등 쿠버네티스를 접근하는 관리자들이 선언하는 리소스니까 당연하게도 예측 가능한 사이즈로 존재하게 됩니다. (봇/오퍼레이터 같은 자동화 된 쿠버네티스 요소가 갑자기 Pod 나 ConfigMap 을 1000개, 10000개 씩 만드는 이상한 일을 하지 않는 이상)
Events
는 쿠버네티스 오브젝트의 상태 변화를 기록한것으로, API 를 통한 로그또한 확인이 가능한 리소스입니다. Best effort 로 생성되며, 해당 이벤트가 쿠버네티스 관리자에게 전달되었든지, 안되었든지등을 판단하지 않고 상태가 변할 때 마다 계속해서 생성되는 리소스입니다. 또한 이 데이터들은 etcd Leases
기능을 사용해서 1시간 뒤에는 삭제되는 데이터들입니다. 보통 설정의 오류나 클러스터의 일부 소프트웨어 설정 오류로 인해서 failures 가 발생하는 경우 발생하게 됩니다.

etcd 의 leases 기능은 짧은 시간 저장할 데이터 (Time To Live 기반)를 위해 설계되었습니다.
- 오직 etcd 리더만이 TTL 만료 카운트를 처리하고
- TTL 카운트의 진행상태에 대해서는 멤버간에 공유되지 않습니다.
- 이 상황에서 리더가 변경되면 처음부터 다시 TTL 처리를 위한 카운트가 재시작됩니다.(초기화)
쿠버네티스는 Events
리소스에 대한 TTL 을 1시간으로 설정해두었습니다. (PPT 에는 2시간으로 되어있는데. kube-apiserver
의 --event-ttl
값을 명시적으로 주지 않을 경우 Default 가 1시간입니다. / kube-apiserver 참조)

- Event 를 위한 etcd 인스턴스(클러스터)를 분리하는 것. (kubespray 에서 이것을 쉽게 활성화해서 event 용 etcd 를 분리해서 프로비저닝 할 수 있음. 직접하는것도 크게 문제 될 요소가 아님. kube-apiserver 에 event 용 etcd 서버를
--etcd-servers-overrides
인자를 통해서 별도 지정하면 됨.) etcd
이벤트를 persistent 하게 만들지 말고 별도의 로깅 백엔드로 전달하기. (e.g Loki / ElasticSearch, etcs..)- 만약 Event 리소스때문에 etcd 로드가 계속해서 늘어난다면 TTL 을 줄이기. (
kube-apiserver
) - 리더의 TTL 만료를 확인하기 위한 count 체크를 아래 두가지 인자를 활성화해서 더 안전하게 만들기. (카운트 리셋을 방지하기)
-experimental-enable-lease-checkpoint
→ 리더 변경시 TTL 리셋을 방지-experimental-enable-lease-checkpoint-persist
→ 재시작시 TTL 리셋 방지
(Leases may be auto-renewed indefinitely due to leader elections · Issue #9888 · etcd-io/etcd )
etcd quota deadlock

etcd는 아래와 같은것을 저장
- 특정 키에 대한 최신 상태
- compaction(압축) 이후의 키의 모든 변경사항 (revision)
etcd 는 아래와 같은 quota 존재
- Disk 에 저장되는 db 파일 사이즈의 limit (디폴트 2GB)
- db 파일사이즈 리밋에 도달하면 etcd member 는 알람을 발생시킴
- 이 알람은 etcd 관리자의 개입이 필요함 (
compaction
andDefragmentation
)



--etcd-compaction-interval
옵션에 대한 설명. Default 는 5m0s 로 되어있음.kube-apiserver
의 --etcd-compaction-interval
알고리즘에 대한 간단한 설명인데. 가장 큰 문제는 etcd
가 out of quota
상황일 경우 동작하지 않는 다는 점이 있습니다.

- Compaction(압축)
- etcd 의 빌트인 compaction 매커니즘을 활용하기 (
--auto-compaction-retention=5m
)- etcd 가 할당량(db 파일 사이즈 리밋)을 넘어섰을 때도 동작 함
- Reduces overhead of window
kube-apiserver
의--etcd-compaction-interval
옵션보다etcd
의--auto-compaction-retention
이 오버헤드가 상대적으로 적다는 메인테이너의 발언이 있음. 즉kube-apiserver
의--etcd-compaction-interval
을 0으로 설정하여 비활성화하고,etcd
측에서--auto-compaction-retention
을 설정하는 편이 오버헤드가 훨씬 적다고 함.kube-apiserver
의 로직은 필요한 etcd 데이터 용량의 2배정도를 상용하는 반면,etcd
의 로직은 10% 의 오버헤드만 가진다고 함
- etcd 할당 대비 사용량(db size) 모니터링
- Exclude
out of quota
alarm from health probes
- etcd 의 빌트인 compaction 매커니즘을 활용하기 (
- Deframentation(조각모음)
- X 분마다 Cronjob 형태로 수행하기 + 일부 jitter 시간을 둬서 동시에 여러 etcd 멤버가 Defrag 수행하는것을 방지하기
- Defrag 를 아래 컨디션에서만 수행하기
- At least xxx MB of data to recover AND
- Quota utilization over 75%
- Disalarm out of quota alarm
etcd watch starvation

다양한 쿠버네티스 컴포넌트 (kube-scheduler / kube-controller-manager 등)은 kube-apiserver 를 통해 통신하는데. 결국 각 리소스 유형 별로 별도의 스토리지와 Go 스트럭쳐 구조가 있고, 각 클라이언트는 etcd에 독립적인 gRPC 커넥션을 맺습니다. 그래서 특정 리소스(Deployment / Pods / Nodes)에 대한 트래픽이 몰리면 그 많은 트래픽이 단일 커넥션으로 몰리게 됩니다.

TLS 활성화를 위한 작업을 진행하던 도중 리소스 watch
에 대해서 starvation 이 발생했던 경험 공유
어느 한 클러스터에서만 문제가 생겼는데. 그 클러스터는 다수의 데몬셋 컨트롤러(데몬셋)가 실행중이였다는 것. 해당 클러스터에는 Fluentd / Fluentbit 과 같이 로깅과 관련된 데몬셋이 많이 떠있었습니다. 이런 데몬셋 컨트롤러들은 로깅과 관련된 메타데이터를 수정한다든지(Fluentbit 설정?) 할 때 kube-apiserver 와 커넥션을 맺어야 하는데. 이 때 다수의 노드에 짧은 다운 타임이 있다면 정말 많은 동시 다발적인 LIST 요청이 들어가고, 단일 리소스 타입에 대해서 하나의 gRPC 커넥션에 엄청난 트래픽이 몰렸습니다.
거기에 더해 알고리즘이 Watch 보다 List 에 우선순위가 더 높아서 List 응답만 계속 하다가 Watch 는 응답을 몇분동안 못받는 포화(starvation)상태가 되어버렸습니다.

이 문제가 발생한 이유는 HTTP2 표준에서 TLS커넥션에 대해서 Multiplexing를 허용하지 않았다는것.
non-TLS 트래픽인경우 HTTP 요청은 HTTP 서버로, gRPC 요청인경우 gRPC 서버로 커넥션 레벨에서 체크해서 트래픽을 틀어줄 수 있는데, TLS 트래픽인경우에는 커넥션 레벨에서 HTTP 요청인지, gRPC 요청인지 구분을 할 수 없기 때문에 일단 HTTP 서버에 넘겨주고 gRPC 헤더가 있는경우에 gRPC 핸들러(서버)로 넘겨주는 구조로 되어있었습니다.
즉, non-TLS 트래픽은 gRPC
요청(stream) 을 gRPC handler
로 곧바로 넘겨 줄 수 있었기 때문에 stream
요청에 대한 우선순위가 밀리는등의 처리가 없이 잘 동작했지만. TLS 트래픽의 경우는 일단 HTTP
서버로 넘어간 이후에 처리되었고, 이 때 stream
요청에 대한 우선순위 처리가 제대로 되지 않아 문제가 생겼었던것으로 정리 할 수 있습니다.

gRPC 는 HTTP 서버를 통해 실행하는것을 지원하지만
- 성능과 기능이 좀 다를 수 있고
- grpc-go의 HTTP/2 서버를 통해 제공되는 일부 gRPC 기능을 지원하지 않습니다.
원인은
- HTTP/2 는 writer 별 다중 스트림을 지원하고
- 어떤 스트림을 통해 응답할지 결정하는 알고리즘이 다르고
- HTTP 서버는
PriorityWriteScheduler
를 사용하는데 (golang)- 짧은 요청은 우선순위가 높고 처리가 잘 되었지만
- etcd Watch 같은 gRPC 스트림은 starvation 상태
- 정리하자면, 짧은 요청이 지속적으로 많이 발생하는동안, gRPC streams 는 완전히 차단되어버림
(net/http: Stream starvation in http2 priority write scheduler · Issue #58804 · golang/go )
결론적으로는 etcd
와 golang
팀과의 협력을 통해서 HTTP/2 server (golang)
부분이 개선되고, etcd watch
에 대한 처리 알고리즘 또한 개선되었습니다.

etcd v3.4.28, v3.5.10 에서는 이 부분이 해결되었다고 합니다. (golang.org/x/net 이 RR 알고리즘을 사용하도록 업데이트 되면서 같이 반영)
Failure Mitigation


Kubernetes가 100% 신뢰성을 제공하거나 ,개발자의 실수로부터 완전히 보호 해 줄 것이라고 가정해서는 안됩니다.
.클러스터에 변경이 발생 할 경우 발견되지 않은 문제로 이어 질 수 있고, 모든 것을 한 곳에 의존하기보다는 클러스터를 분리하고, 장애 발생시 다른 클러스터가 트래픽을 인계 받을 수 있도록 준비하는것이 좋습니다.
전체가 장애가 발생하는것보다는, 부분 적인 다운타임이 발생하거나 약간의 성능저하나 지연이 발생하는것이 낫습니다.

블루-그린 배포처럼 모든 변경 사항을 클러스터의 잠재적인 중단으로 간주하고 독립적으로 롤아웃하는것을 추천합니다. etcd
업그레이드, Kubernetes
업그레이드, 혹은 새로운 트래픽 패턴을 유발하는 애플리케이션 업그레이드 같은것 모든것을 잠재적인 중단, 장애포인트로 생각해야합니다. 이런 변경사항을 모두 블랙박스로 간주하고 독립적으로 롤아웃한다면 문제를 조기에 발견하고 장애 여파를 줄일 수 있습니다.

모든 변경을 중단, 잠재적인 장애 포인트로 간주하고 모든 변경을 검증해야 합니다. 클러스터를 다양한 중요도로 분리하고 각 각에 대해서 단계적 롤아웃을 할당함으로써 위험을 최소화 할 수 있습니다. GKE 가 그렇게 하고 있습니다.
이런 모든 해결책은 장애를 완화하기위한 표준적인 접근 방식이며, 새로 제안된 내용이 아닙니다.
What is the secret

이런 etcd 운영에 관련한 비밀, 노하우는 어딘가에 숨겨져있는것이 아니라 모두 공개적으로 논의되고있었습니다. 여러분들은 그것을 읽고 확인 할 수 있었습니다.
예를들어 Kubernetes 이벤트 이슈는 오랫동안 존재해왔고 etcd 의 이벤트 저장용 클러스터를 분리하는 것이 해결책으로 제시되었습니다. 그럼에도 불구하고 여전히 많은 사람들이 이벤트를 어떻게 다뤄야 하는지 질문하고있습니다.

사람들은 오픈소스를 완성된 해결책으로 여기고 경험을 쌓는 과정을 건너뛰고, 무료 맥주를 하나 꺼내 마시듯이 그것을 사용하려 하지만 오픈 소스는 아이디어를 교환하고 함께 배우는 자유에 관한 것입니다.
etcd 를 운영 할 때 오픈 소스 커뮤니티가 어떻게 운영되는지를 알고 오랜 시간 동안 테스트되어온 etcd 의 일반적인 사용 사례를 벗어나는 함정을 피하기 위해 시간을 투자해야 합니다. etcd 는 생산적인 key-value 스토어이지만 그것을 프로덕션 레디 상태로 만드는 것은 여러분의 몫입니다.
이 발표자가 논의한 대부분의 etcd 이슈에 대한 해결책은 오픈 소스로 구현 할 수 없었습니다. Kubernetes 클러스터를 재설계 해야하거나 재구성 해야 하는 것을 요구하기 때문입니다. (실제로 이 메인테이너가 TLS , HTTP2 관련해서 이슈를 올린 것을 살펴보면 여러 대안을 제시하고 일부는 현실적으로 어려움이 있어서 폐기하기도 하였음. / Etcd watch stream starvation under high read response load when sharing same connection and TLS is enabled · Issue #15402 · etcd-io/etcd )
여러분은 그것을 고려하고 어떻게 알고있는 흔한 함정을 피할 수 있을지 생각해야 합니다. 그러기 위해 가장 좋은 방법은 커뮤니티의 집단적인 경험을 활용하는 것입니다.
대부분의 사람들이 여전히 알려진 문제가 있는 etcd 버전을 사용하고 있으며, 그 문제에 대해 여러차례 메일이 발송되었음에도 불구하고 여전히 그것을 따르지 않다는 것을 알 고 있습니다. 저의 제안(메인테이너)은 커뮤니티가 무엇을 하고 있는지, 논의가 어떻게 진행되고 있는지를 알아보는데 시간을 투자하는 것입니다.
Wrapping up
이 세션을 발표한 etcd 메인테이너이자, 구글 소프트웨어 엔지니어인 Marek Siarkowicz는 etcd 의 잘 알려진 (그러나 많이들 따르지 않거나 모르고 있는) 문제에 대해서 발표 전체에 걸쳐서 다뤘습니다.
뒤쪽에서는 etcd
를 단순히 쉽게 free beer
처럼 꺼내 쓰는 것이 아니라 정말 생산적이고, Production Ready 상태로 만드는것은 사용자(개발자)의 몫이며, 잘 알려진, 오랜시간동안 테스트되어온 사용사례를 따르기 위해서 커뮤니티를 살펴보고 이 프로젝트가 어떻게 흘러가고있는지 시간을 투자하라는 이야기를 하고 있습니다.
또한 etcd 를 사용하다 문제가 생긴 경우 커뮤니티, etcd 프로젝트를 찾아보거나 메일링 리스트를 살펴보고, 커뮤니티에 질문하고 더 나아가서는 겪은 문제에 대해서 공유하고, 혼자서 해결하려고 시도하지 말고 커뮤니티의 집단적인 경험을 잘 활용하라고 이야기 합니다.
모든 것을 혼자서 해결 하려 하지 말고, 커뮤니티의 집단적 경험을 활용하고, 대화를 나누면서 배우세요.
발표자의 발표 말미의 한 마디를 인용하면서 글을 마칩니다.