Kubernetes Operator – Reconciling resource

쿠버네티스 오퍼레이터를 개발하면서, 대상 리소스가 변경 되었는지 확인하는 경우가 있습니다. 리소스 내용이 변경되었으면, 어떠한 행위를 하기 위해서입니다.

두 리소스를 비교하기 위한 가장 쉬운 방법은 DeepEqual 을 사용하는 것입니다.

if !reflect.DeepEqual(expected, actual) {
	// need to update...
}

하지만 이 방법은 한계를 가지고 있습니다. 메타데이터나 기본값을 적용될 경우 제대로 된 비교가 안될 수 있기 때문입니다.

예를 들어 다음과 같은 hello-pod 를 생성했다고 가정해보겠습니다.

apiVersion: v1
Kind: Pod
metadata:
  name: hello-pod
spec:
  containers:
  - name: busybox
    image: busybox

쿠버네티스 API를 이용하여, hello-pod 의 정보를 조회해오면 다음과 같이 기본값들과 메타데이터 정보가 포함되어 있습니다.

apiVersion: v1
Kind: Pod
metadata:
  creationTimestamp: "2021-01-17T03:00:00Z"
  namespace: default
  name: hello-pod
  uid: 673795dc-595b-11eb-a633-fa163fad3156
spec:
  containers:
  - name: busybox
    image: busybox
    imagePullPolicy: Always
dnsPolicy: ClusterFirst

즉, 두 개의 Pod은 같은 것이지만, DeepEqual 을 이용하여 비교를 하면 다른것으로 판별됩니다.

두 개의 Pod가 진짜 다른것인지를 판별하기 위해서는 다음과 같이 할 수도 있습니다.

if sameName(expected, actual) &&
   sameContainers(expected, actual) && ...

필요한 부분만 직접 비교를 하는 것입니다. 비교할 필드가 1,2개 정도면 이 방법도 사용할 수 있습니다만, 현실세계에서는 대부분 많은 필드를 가지고 있기 때문에 효율적이지 않습니다.

두 리소스를 비교하는 가장 스마트한 방법은 리소스의 Hash값을 이용하는 것입니다. 다음은 HashObject 함수를 이용하여, 리소스의 해시값을 계산한 후 비교하는 코드입니다.

hash := HashObject(expected)
expected.Annotations["ResourceHash"] = hash

actualHash := actual.Annotations["ResourceHash"]
if actualHash != hash {
	// need to update...
}

다음은 HashObject 함수입니다.

// HashObject writes the specified object to a hash using the spew library
// which follows pointers and prints actual values of the nested objects
// ensuring the hash does not change when a pointer changes.
// The returned hash can be used for object comparisons.
//
// This is inspired by controller revisions in StatefulSets:
// <https://github.com/kubernetes/kubernetes/blob/8de1569ddae62e8fab559fe6bd210a5d6100a277/pkg/controller/history/controller_history.go#L89-L101>
func HashObject(object interface{}) string {
	hf := fnv.New32()
	printer := spew.ConfigState{
		Indent:         " ",
		SortKeys:       true,
		DisableMethods: true,
		SpewKeys:       true,
	}
	_, _ = printer.Fprintf(hf, "%#v", object)
	return fmt.Sprint(hf.Sum32())
}

출처 : https://github.com/elastic/cloud-on-k8s/blob/master/pkg/controller/common/hash/hash.go

참고 : https://github.com/kubernetes/kubernetes/blob/master/pkg/util/hash/hash.go