Kubernetes

CustomResource Version Converting 과정 분석

토마스.dev 2021. 9. 15. 22:58

k8s가 계속 새로운 버전이 릴리즈 되면서 리소스 버전도 계속 업데이트가 된다.

alpha -> beta -> stable 순으로 업데이트가 되며, alpha일때는 default false이고 beta부터 default true로 해당 리소스를 사용할수 있다.

이러한 버전이 변화되면서 필요한 버전 Convert에 관해 얘기해보고자 한다.

 

사실, 한우형님이 정말 친절하게 내부 구조에 대해서 친절히 설명한 자료가 있다.

https://www.youtube.com/watch?v=sfv7YpxgK20 

출처: [KCD KOREA 2021] 쿠버네티스 인터널스: 코드 레벨에서 보는 쿠버네티스 이야기 | 한우형

 

필자는 추가적으로 CRD의 버전 컨버팅 과정이 궁금하여 controller-runtime(kubebuilder) 기준으로 설명하고자 한다.

 

CRD는 CustomResourceDefinition의 약자로 사용자정의 리소스를 말한다. 이 CRD의 버전 컨버팅은 기본적으로 

https://book.kubebuilder.io/multiversion-tutorial/tutorial.html 여기서 자세히 설명하고 있다.

 

 

출처: kubebuilder - Converting example

여기서 v1이 Hub/Storage 버전을 동시에 취하고 있다. 하지만 위에 한우형님 설명처럼 Hub는 v1으로, Storage는 v3로 할수는 없는 것일까? 그래서 직접 kubebuilder로 3가지의 버전(v1,v2,v3)를 가진 guestbook 리소스를 만들어 과연 어떻게 처리가 되는지 정리해보았다.

 

먼저 CRD Conversion은 etcd 저장전에 발생한다. 이는 그림으로 나타내면 다음과 같다.

출처: Tutorial: Mastering Multi-version CRDs (https://www.youtube.com/watch?v=AAxuEPIzHUQ)

 

CRD를 생성할 때 spec에 "storage" 필드가 있다. 이 필드는 여러가지 버전중 한개에만 true로 해놔야 하는 것으로, 실제 etcd에 저장할 버전 스펙을 말한다. storage로 지정된 버전은 Conversion Webhook 에 ConversionReview를 보낼 때 사용된다.

(https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-request-and-response)

 

그렇다면 Hub는 어떻게 지정하는 것일까? 다음과 같이 빈 Hub 함수를 구현하여 인터페이스를 만들어주면 된다고 한다.

(https://book.kubebuilder.io/multiversion-tutorial/conversion.html)

Hub는 CRD Spec 과는 관련이 없다. 순전히 웹훅 컨트롤러 코드내에서 설정하고 구현해주어야 한다.

func (*Guestbook) Hub() {}

Spoke 버전인 경우 Hub 버전으로의 ConvertTo, ConvertFrom 함수를 구현해주어야 한다.

 

위의 그림에서는 Storage와 Hub버전을 동일하게 두었다. 이를 분리할 수 있는지 알아보자.

 

먼저 guestbook v1,v2,v3 버전을 각각 만들고, hub는 v2로 잡는다. 그러면 v1과 v3에서는 각각의 convert 함수를 구현해 주어야 한다.

 

pacakge v3

func (src *Guestbook) ConvertTo(dstRaw conversion.Hub) error {
	dst := dstRaw.(*v2.Guestbook)

	dst.Spec.Foo = src.Spec.Foo + "_from-v3-to-v2"

	dst.ObjectMeta = src.ObjectMeta
	return nil
}

func (dst *Guestbook) ConvertFrom(srcRaw conversion.Hub) error {
	src := srcRaw.(*v2.Guestbook)

	dst.Spec.Foo = src.Spec.Foo + "_from-v2-to-v3"

	dst.ObjectMeta = src.ObjectMeta
	return nil
}

guestbook 에는 전부 Foo 라는 string 필드가 존재한다. v1, v3의 convert 함수에서는 위와 같이 foo 필드에 어느버전에서 어느버전으로 변경되는지를 추가하여 기록한다.

 

storage버전은 v3로 하였다. 즉, hub 버전은 v2인데 storage버전은 v3로 두었다. 이 상황에서 과연 어떻게 변환이 되는지 살펴보자.

 

웹훅 컨트롤러를 설치하고, v3부터 생성해보자. API 서버는 storage버전인 v3로 desiredAPIVersion을 ConversionReview에 설정하여 웹훅 컨트롤러에게 요청한다.

출처: https://speakerdeck.com/sttts/sig-api-machinery-deep-dive-kubecon-na-2018?slide=3

 

 

같은 버전이므로 컨트롤러는 다음과 같은 에러를 내지만 실제 http status는 200으로, API서버는 이를 무시하고 v3 그대로 저장한다. (컨트롤러 등록은 해놨으나 실제 컨트롤러가 없는 경우(pod이 내려가있다던지) API 서버는 에러를 내지 않고 그대로 저장한다)

{"kind":"ConversionReview","apiVersion":"apiextensions.k8s.io/v1","response":{"uid":"705ab4f5-6393-11e8-b7cc-42010a800002","convertedObjects":null,"result":{"metadata":{},"status":"Failure","message":"conversion is not allowed between same type *v3.Guestbook"}}}

생성된 v3 버전의 리소스를 get 하면 Version Priority(https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#version-priority)에 따라 v3로 Discovery되고, 저장한 버전과 요청한 버전이 동일하기 때문에 별도의 convert 없이 표시된다. (불필요한 내용은 필터링하여 표시했다)

apiVersion: webapp.my.domain/v3
kind: Guestbook
metadata
  name: guestbook-sample3
  namespace: default
spec:
  foo: bar

이제는 v1을 만들어보고 v3버전으로 확인해보자. 생성시 이름과 버전만 다르고 내용은 foo: bar 동일하다.

apiVersion: webapp.my.domain/v3
kind: Guestbook
metadata:
  name: guestbook-sample
  namespace: default
spec:
  foo: bar_from-v1-to-v2_from-v2-to-v3

bar가 아닌 수정된 값으로 표시됨을 알 수 있다.

 

bar_from-v1-to-v2_from-v2-to-v3 를 좀 구분해서 나눠보면 bar / from-v1-to-v2 / from-v2-to-v3 로 생각할수 있다.

 

guestbook v1 생성시 API서버는 v1버전의 object를 v3로 review를 보낼 것이며, v1의 ConvertTo 함수에 따라 from-v1-to-v2가 붙게 된다.  그리고 나서 storage버전인 v3 ConvertFrom 함수에 따라 from-v2-to-v3가 붙게 되어 저장된다.

 

즉, v1 -> v2(Hub) -> v3(Storage) 버전으로 Convert되어 저장됨을 알 수 있다. 여기서 우리는 CRD도 hub와 storage버전을 분리해 적용할 수 있다는 사실을 알 수 있다.

 

여기서 이 리소스를 v1으로 요청하면 어떻게 나올까? 

$ kubectl get guestbook.v1.webapp.my.domain guestbook-sample -o yaml
apiVersion: webapp.my.domain/v1
kind: Guestbook
metadata:
  name: guestbook-sample
  namespace: 
spec:
  foo: bar_from-v1-to-v2_from-v2-to-v3_from-v3-to-v2_from-v2-to-v1

복잡하게 나왔는데, 분리해서 생각해보자. 

 

앞의 bar_from-v1-to-v2_from-v2-to-v3 는 저장할때 기록된 내용이다. 즉, 좀 전 예제 결과와 동일함을 알 수 있다.

 

그 뒤의 from-v3-to-v2_from-v2-to-v1 를 보면, from-v3-to-v2 / from-v2-to-v1 으로 분리해 볼 수 있고, 이는 v1으로 요청할 때는 반대로 v3(storage) -> v2(hub) -> v1 순으로 Convert되어 리턴된다는 것을 알 수 있다. 

 

 

좀 더 이해를 돕고자 이번에는 위의 저장된 리소스를 v2 버전으로 요청해보면, 다음과 같이 나오게 된다.

$ kubectl get guestbook.v2.webapp.my.domain guestbook-sample -o yaml
apiVersion: webapp.my.domain/v2
kind: Guestbook
metadata
  name: guestbook-sample
  namespace: 
spec:
  foo: bar_from-v1-to-v2_from-v2-to-v3_from-v3-to-v2

이 역시 bar_from-v1-to-v2_from-v2-to-v3 은 저장할때 기록된 것이며, from-v3-to-v2 으로 v3(storage) -> v2(hub) 로 Convert되어 v1를 요청했을때와는 다르게 리턴된다는 것을 알 수 있다.

 

이상으로 CRD에서 Hub - Spoke 버전 변환 과정을 알아보았다.

'Kubernetes' 카테고리의 다른 글

CKA 후기  (0) 2021.03.27
nginx reload와 keep-alive (부제: zero-downtime은 사기일까?)  (1) 2021.01.16
Git Flow 와 CI/CD  (0) 2020.03.19
Shared Informer  (0) 2019.12.11