공부하고 기록하는, 경제학과 출신 개발자의 노트

학습일지/kubernetes

ContainerDays 2021 - Into the Core of Kubernetes: the internals of etcd 정리

inspirit941 2022. 12. 30. 15:10
반응형

영상: https://youtu.be/YIBQrP1grPE

발표자: Michael Gasch (VMware)

발표자분이 따로 정리했던 글: https://www.mgasch.com/2021/01/listwatch-part-1/

 

Onwards to the Core: etcd

A journey to the core behind the Kubernetes ListerWatcher interface

www.mgasch.com


 

스크린샷 2022-12-26 오후 2 31 25

  • kubernetes 아키텍처 설명
  • k8s에서 쓰는 etcd Data model
  • k8s Controller / operator에서 etcd를 어떻게 사용하는지

 

스크린샷 2022-12-26 오후 2 39 06



k8s는 크게 세 개의 레이어로 구분할 수 있다.

  • Control Plane: Controller Manager, Scheduler, Etcd, API server 등이 동작함.
  • Workers: kubelet으로 atomic workload (pod) 을 관리
  • Access Layer : REST API, SDKs, Web UI, kubectl 등

 

스크린샷 2022-12-26 오후 2 39 15



단순하게 표현하면 위 그림과 같다.

  • API server가 작업을 통제하고 요청을 전달하며, 각 컴포넌트는 Control Loop 상태.
  • Desired State와 Current State를 계속 비교하고, Desired State로 전환되도록.

 

스크린샷 2022-12-27 오전 11 46 10

 

예컨대 Replicas가 1인 Deployment를 k8s에 생성 요청했다고 하면

  1. kubectl create 명령어는 k8s의 API Server에 Post Request를 보낸다
  2. API server 내부에서 여러 작업을 수행한다 - validation, etcd에 저장...
  3. Watch -> stream of events changes inside k8s, register for notification.
    • http get request / with 'watch' query parameters.
  4. Reconcilation -> create a pod 같은 요청이 필요할 경우 post로 요청
    • 이벤트 발생 -> scheduler에서 이벤트 받아서 작업 수행 -> 이벤트 발생... 형태로 작업이 이어짐

Single Component가 모든 동작을 통제하는 건 아니고, 각각의 컴포넌트가 주어진 역할을 해내는 식.

 


스크린샷 2022-12-27 오후 1 35 53

 

이렇다보니 용어라던가 동작 관련해서 질문이 계속 나옴. 몇 가지 예시

  • 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

스크린샷 2022-12-27 오후 8 39 37

Distributed, Reliable Key-value store.

  • etcd v3 (latest version) 에서는 계층구조가 없어짐. flat key-value space.
  • key-value store에서 발생하는 변경사항을 inform하는 것이 가능해졌음. (Watch for changes)

 

스크린샷 2022-12-27 오후 8 43 05

Empty etcd container로 확인할 수 있는 예시.

  • etcdctl 명령어로 조회했을 때 보이는 revision 필드 -> 필드값에 변경이 있을 경우 auto-increasing되는 counter

 

스크린샷 2022-12-27 오후 8 56 36

etcd에 for문으로 값을 다섯 번 추가했을 때의 결과

  • headers : 일종의 metadata를 리턴하는 필드. revision은 1부터 시작해서 값을 5번 추가했으니 6을 리턴함
  • kvs (key-value pairs): version / mod_revision / create_revision 세 개의 필드가 기본으로 들어간다.
    • create과 mod 값이 같으면 생성 후 변경이력이 없다는 뜻
    • version은 해당 kvs가 얼마나 변경되었는지 횟수를 의미함

스크린샷 2022-12-27 오후 9 19 02

 

스크린샷 2022-12-27 오후 9 19 40

  • key값을 수정할 경우 revision값은 7로 증가했고, mod_revision 값도 7로 변경되고 kvs의 version값도 2로 업데이트되었다.
  • key값을 삭제할 경우 revision값은 8로 증가했고, get으로 key 리스트를 조회하면 key/1에 대응되는 값이 사라진 걸 볼 수 있다.

 

스크린샷 2022-12-27 오후 9 39 03

 

스크린샷 2022-12-27 오후 9 53 42

time travel Query

  • --rev 7 와 같이 rev 옵션으로 특정 revision 시점의 정보를 조회할 수 있음. 실제로는 삭제된 값인 key/1을 조회할 수 있다.
  • 이제 삭제했던 key값과 동일한 key값으로 다시 데이터를 추가하면, revision은 9로 업데이트되고 key/1에 대응되는 값이 value-1b로 입력된 상태.

key-value가 저장된 물리적 DB의 값을 덤프 떠서 내용을 확인해보면 아래와 같다.

스크린샷 2022-12-27 오후 9 56 58



물리적 DB에서 실제로 저장되는 key값은 revision counter. 사용자가 입력한 key값이 아니다.

  • value 자체가 또 다른 key-value pair임.
  • logical key를 physical revision number와 매핑하는 작업이 들어가있는 셈.
  • time travel query를 수행할 수 있는 유일한 방법은 revision을 조회하는 것.

 

스크린샷 2022-12-27 오후 10 08 26

watch 명령어

  • 오른쪽 터미널에 watch 명령어로 'keys/1' 의 상태가 변경될 때 이벤트를 터미널에 출력하도록 설정하고
  • 왼쪽 터미널에서 'keys/1'의 값을 value-1c 로 변경했을 때
  • events에 값이 출력되는 걸 확인할 수 있다.

즉 etcd에서 상태가 변경될 때마다 event를 받을 수 있음.


kubernetes Reconcilation - Lister and Watcher, with etcd

스크린샷 2022-12-30 오전 9 40 00

kubernetes object을 조회해보면 resourceVersion 필드가 있다. 이 값은 "해당 object가 어느 시점에 어떤 값으로 etcd에 저장되어 있는지"를 확인할 수 있는 값이다.

 

스크린샷 2022-12-30 오전 9 42 21

kubernetes Controller / Operator에서 쓰이는 Reconciler 개념을 보면

  • cluster의 현재 상태를 Desired State로 변경하는 로직을 담당.
  • Level-based. 이벤트가 올 때마다 실행되는 게 아니라, Current State값을 기반으로 동작한다.
    • resiliency에 강함. 개별 이벤트가 누락되더라도 consistency에 주는 영향력이 크지 않다.
  • 인터페이스의 Reconcile 메소드 파라미터로 들어가는 Request는 name / namespace 값이 포함된다.

Logical view

스크린샷 2022-12-30 오전 9 48 54

 

스크린샷 2022-12-30 오후 2 07 06

ListWatcher : "Operator가 이벤트를 어떻게 받도록 할 것인가" (subscribe) 를 담당.

  • etcdctl 명령어의 get = list, watch = watch에 대응됨.
  • k8s Resiliency에 중요한 역할을 담당함.

Lister 동작 방식

  1. kubectl get 동작으로 list api를 호출함. 예시의 경우 default namespace의 pod을 전부 조회함.
  2. 요청으로 들어온 path 변수값 (namespace / resource name ...) 을 토대로 API server에서 etcd Registry에 호출.
    • http resource path -> etcd registry path로 변환.
    • 예시의 경우 default namespace의 pod를 전부 조회하기 때문에 prefix로 pods/default가 있는 모든 etcd key값을 조회한다
  3. etcd에서 값을 리턴한다. (grpc)
    • header 값의 revision: 해당 값이 마지막으로 업데이트된 시점.
  4. API server는 etcd에서 응답한 grpc 응답을 http response로 변환한 후 응답한다.
    • 예시의 경우 pod 전체를 응답하므로 response의 kind는 podList. response의 metadata에 revision값이 etcd header에서 리턴해준 값임.

즉 podList에 있는 revision값의 의미는? -> etcd에서 해당 값들이 마지막으로 업데이트된 시점이기 때문. 일종의 snapshot.

 

스크린샷 2022-12-30 오후 2 24 07

 

Watcher 동작 방식

  1. watch 요청을 보낸다. api path 파라미터로 resourceVersion이 들어가는데, lister에서 응답값으로 받은 etcd revision 값을 넣는다. watch=true는 앞으로 있을 변화를 stream한다는 뜻.
  2. API server는 etcd Registry에 watch 요청을 보낸다. WithRev() 로 특정 revision 이후에 발생한 변경사항을 stream으로 수집할 수 있다.
  3. 해당 리소스의 상태가 변화면 watchResponse로 응답이 온다. API server는 grpc 응답을 http response로 변환한다.

 

Physical view

스크린샷 2022-12-30 오후 2 30 36

Database 관점에서 다시 보면

  • etcd에 7번의 데이터 변경과정이 있었다고 가정. 따라서 revision은 7.
  1. Controller에서 revision 7까지 진행된 상황에서 List 요청을 보낸다.
    • revision 7 시점에서 default namespace + resource 'pod' 에 매핑되는 etcd value값을 리턴한다.
  2. 이후에 kubectl이나 다른 controller 명령어로 pod 상태가 추가로 변경되어 revision이 3 증가했다.
    • pod1에 add / modified 로직이 수행되었음.
  3. 이전에 Get으로 조회한 revision 7을 토대로 watch를 수행하면, etcd에서 해당 revision 이후 변경된 내역을 리턴함.
    • 이 정보를 토대로 reconciliation이 동작한다.

즉 etcd의 list / watch 명령어와 revision, events를 활용하는 방식.

  • k8s controller는 stateless한 동작이 가능함.
  • crash가 발생하면 get으로 정보 조회 -> backup (restore). 따라서 operator 로직 자체는 Stateless하게 작성할 수 있음

Summary (Some Answers)

스크린샷 2022-12-30 오후 2 53 29

 

  • 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 보장.
반응형