ContainerDays 2021 - Into the Core of Kubernetes: the internals of etcd 정리
영상: https://youtu.be/YIBQrP1grPE
발표자: Michael Gasch (VMware)
발표자분이 따로 정리했던 글: https://www.mgasch.com/2021/01/listwatch-part-1/
- kubernetes 아키텍처 설명
- k8s에서 쓰는 etcd Data model
- k8s Controller / operator에서 etcd를 어떻게 사용하는지
k8s는 크게 세 개의 레이어로 구분할 수 있다.
- Control Plane: Controller Manager, Scheduler, Etcd, API server 등이 동작함.
- Workers: kubelet으로 atomic workload (pod) 을 관리
- Access Layer : REST API, SDKs, Web UI, kubectl 등
단순하게 표현하면 위 그림과 같다.
- API server가 작업을 통제하고 요청을 전달하며, 각 컴포넌트는 Control Loop 상태.
- Desired State와 Current State를 계속 비교하고, Desired State로 전환되도록.
예컨대 Replicas가 1인 Deployment를 k8s에 생성 요청했다고 하면
- kubectl create 명령어는 k8s의 API Server에 Post Request를 보낸다
- API server 내부에서 여러 작업을 수행한다 - validation, etcd에 저장...
- Watch -> stream of events changes inside k8s, register for notification.
- http get request / with 'watch' query parameters.
- Reconcilation -> create a pod 같은 요청이 필요할 경우 post로 요청
- 이벤트 발생 -> scheduler에서 이벤트 받아서 작업 수행 -> 이벤트 발생... 형태로 작업이 이어짐
Single Component가 모든 동작을 통제하는 건 아니고, 각각의 컴포넌트가 주어진 역할을 해내는 식.
이렇다보니 용어라던가 동작 관련해서 질문이 계속 나옴. 몇 가지 예시
- What makes an "Event" in k8s? -> 아키텍처에서 쓰는 event (Event-driven 같은)와
kubectl get events
로 조회했을 때 나오는 이벤트는 다름 - How do controllers retrieve these events? -> 어떻게 이벤트 받아서 반응하나?
- 이벤트를 받을 때마다 반응해야 하나? (edge-trigger) vs 특정 state가 되었을 때에만 반응해야 하나? (level-trigger)
- Can a controller miss events?
- How to deal with eventual consistency and multiple Actors (Controllers?)
- k8s resource에에 podlist 같은 리스트에도 왜 ResourceVersion 필드가 있나? Abstract object 아님?
- 만약 이 값이 중요하다면, etcd에는 왜 이 값이 저장되어 있지 않은지?
- k8s에서 etcd가 맡고 있는 역할은 정확히 뭔지?
Etcd
Distributed, Reliable Key-value store.
- etcd v3 (latest version) 에서는 계층구조가 없어짐. flat key-value space.
- key-value store에서 발생하는 변경사항을 inform하는 것이 가능해졌음. (Watch for changes)
Empty etcd container로 확인할 수 있는 예시.
- etcdctl 명령어로 조회했을 때 보이는 revision 필드 -> 필드값에 변경이 있을 경우 auto-increasing되는 counter
etcd에 for문으로 값을 다섯 번 추가했을 때의 결과
- headers : 일종의 metadata를 리턴하는 필드. revision은 1부터 시작해서 값을 5번 추가했으니 6을 리턴함
- kvs (key-value pairs): version / mod_revision / create_revision 세 개의 필드가 기본으로 들어간다.
- create과 mod 값이 같으면 생성 후 변경이력이 없다는 뜻
- version은 해당 kvs가 얼마나 변경되었는지 횟수를 의미함
- key값을 수정할 경우 revision값은 7로 증가했고, mod_revision 값도 7로 변경되고 kvs의 version값도 2로 업데이트되었다.
- key값을 삭제할 경우 revision값은 8로 증가했고, get으로 key 리스트를 조회하면 key/1에 대응되는 값이 사라진 걸 볼 수 있다.
time travel Query
- --rev 7 와 같이 rev 옵션으로 특정 revision 시점의 정보를 조회할 수 있음. 실제로는 삭제된 값인 key/1을 조회할 수 있다.
- 이제 삭제했던 key값과 동일한 key값으로 다시 데이터를 추가하면, revision은 9로 업데이트되고 key/1에 대응되는 값이 value-1b로 입력된 상태.
key-value가 저장된 물리적 DB의 값을 덤프 떠서 내용을 확인해보면 아래와 같다.
물리적 DB에서 실제로 저장되는 key값은 revision counter. 사용자가 입력한 key값이 아니다.
- value 자체가 또 다른 key-value pair임.
- logical key를 physical revision number와 매핑하는 작업이 들어가있는 셈.
- time travel query를 수행할 수 있는 유일한 방법은 revision을 조회하는 것.
watch 명령어
- 오른쪽 터미널에 watch 명령어로 'keys/1' 의 상태가 변경될 때 이벤트를 터미널에 출력하도록 설정하고
- 왼쪽 터미널에서 'keys/1'의 값을 value-1c 로 변경했을 때
- events에 값이 출력되는 걸 확인할 수 있다.
즉 etcd에서 상태가 변경될 때마다 event를 받을 수 있음.
kubernetes Reconcilation - Lister and Watcher, with etcd
kubernetes object을 조회해보면 resourceVersion 필드가 있다. 이 값은 "해당 object가 어느 시점에 어떤 값으로 etcd에 저장되어 있는지"를 확인할 수 있는 값이다.
kubernetes Controller / Operator에서 쓰이는 Reconciler 개념을 보면
- cluster의 현재 상태를 Desired State로 변경하는 로직을 담당.
- Level-based. 이벤트가 올 때마다 실행되는 게 아니라, Current State값을 기반으로 동작한다.
- resiliency에 강함. 개별 이벤트가 누락되더라도 consistency에 주는 영향력이 크지 않다.
- 인터페이스의 Reconcile 메소드 파라미터로 들어가는 Request는 name / namespace 값이 포함된다.
Logical view
ListWatcher : "Operator가 이벤트를 어떻게 받도록 할 것인가" (subscribe) 를 담당.
- etcdctl 명령어의 get = list, watch = watch에 대응됨.
- k8s Resiliency에 중요한 역할을 담당함.
Lister 동작 방식
- kubectl get 동작으로 list api를 호출함. 예시의 경우 default namespace의 pod을 전부 조회함.
- 요청으로 들어온 path 변수값 (namespace / resource name ...) 을 토대로 API server에서 etcd Registry에 호출.
- http resource path -> etcd registry path로 변환.
- 예시의 경우 default namespace의 pod를 전부 조회하기 때문에 prefix로 pods/default가 있는 모든 etcd key값을 조회한다
- etcd에서 값을 리턴한다. (grpc)
- header 값의 revision: 해당 값이 마지막으로 업데이트된 시점.
- API server는 etcd에서 응답한 grpc 응답을 http response로 변환한 후 응답한다.
- 예시의 경우 pod 전체를 응답하므로 response의 kind는 podList. response의 metadata에 revision값이 etcd header에서 리턴해준 값임.
즉 podList에 있는 revision값의 의미는? -> etcd에서 해당 값들이 마지막으로 업데이트된 시점이기 때문. 일종의 snapshot.
Watcher 동작 방식
- watch 요청을 보낸다. api path 파라미터로 resourceVersion이 들어가는데, lister에서 응답값으로 받은 etcd revision 값을 넣는다. watch=true는 앞으로 있을 변화를 stream한다는 뜻.
- API server는 etcd Registry에 watch 요청을 보낸다. WithRev() 로 특정 revision 이후에 발생한 변경사항을 stream으로 수집할 수 있다.
- 해당 리소스의 상태가 변화면 watchResponse로 응답이 온다. API server는 grpc 응답을 http response로 변환한다.
Physical view
Database 관점에서 다시 보면
- etcd에 7번의 데이터 변경과정이 있었다고 가정. 따라서 revision은 7.
- Controller에서 revision 7까지 진행된 상황에서 List 요청을 보낸다.
- revision 7 시점에서 default namespace + resource 'pod' 에 매핑되는 etcd value값을 리턴한다.
- 이후에 kubectl이나 다른 controller 명령어로 pod 상태가 추가로 변경되어 revision이 3 증가했다.
- pod1에 add / modified 로직이 수행되었음.
- 이전에 Get으로 조회한 revision 7을 토대로 watch를 수행하면, etcd에서 해당 revision 이후 변경된 내역을 리턴함.
- 이 정보를 토대로 reconciliation이 동작한다.
즉 etcd의 list / watch 명령어와 revision, events를 활용하는 방식.
- k8s controller는 stateless한 동작이 가능함.
- crash가 발생하면 get으로 정보 조회 -> backup (restore). 따라서 operator 로직 자체는 Stateless하게 작성할 수 있음
Summary (Some Answers)
- Event라는 용어 자체는 k8s에서 다양한 맥락에서, 여러 의미로 쓰임.
- 여기서는 Change event -> controller / operator가 상태변화를 감지하고 reconciliation 동작을 정상적으로 수행할 수 있도록 만드는 과정에서 사용되는 요소.
- 주기적으로 변경사항이 있는지 요청을 보내서 확인하는 방식은 체크해야 할 노드 개수가 많아질수록 대응이 어렵다. (scale 관점에서 단점)
- k8s는 Lister / Watcher 패턴을 사용함. (ListWatcher Pattern)
- 따라서 Controller 자체에서 특정 event를 놓치는 것이 가능함. 예컨대 Delete 이벤트로 이미 삭제된 object는 나중에 controller가 요청을 보냈을 때 응답으로 오지 않음.
- k8s는 대부분 level-triggered state 기반으로 동작하므로, controller crash로 특정 이벤트를 받지 못했다 해도 일반적으로는 큰 문제 없다.
- 예컨대 삭제요청을 제때 처리하지 못했다 해도, k8s에서는 markAsDeleted처럼 일단 상태변경이 있음을 체크해두기만 함. 나중에 Finalizer가 동작을 처리한다.
- Eventual Consistency 확보 방법? -> Compare-and-set with ResourceVersion (etcd snapshot값)
- k8s에서 Etcd로 요청을 보낼 때 쓰는 resourceVersion값과 etcd의 현재 resourceVersion 값을 비교함.
- etcd는 multi-version concurrency control (MVCC), HA distributed key-value store 역할을 수행함.
- 리소스의 상태 변화를 revision으로 추적할 수 있음. raft-consensus 기반의 strong consistency 보장.