쿠버네티스에서 동시성 제어와 리소스의 일관성을 보장하는 방법

728x90

resourceVersion 개념

쿠버네티스는 최적화 된 Concurrency 를 달성하기 위해 resourcVersion 개념을 사용하며, 모든 쿠버네티스 리소스는 메타데이터에 resourceVersion 이라는 필드를 갖는다. 

다시 말해, resourceVersion 은 클라이언트가 kubernetes 오브젝트가 변경된 시점을 확인하는 데 사용할 수 있는 내부 버전을 식별하는 문자열이다. 
 

동작

etcd 의 record 가 업데이트 될 때, 이전에 저장된 value 와 비교하여 크로스 체크를 하는데, 만약 매치되지 않은 value 일 경우 409 status code 를 반환하며, object 가 수정될 경우 API 서버에 의해 resourceVersion 이 변경된다.

만약 PUT 작업(리소스 변경 시)의 경우, 시스템은 resourceVersion 의 현재 값이 지정된 값과 일치하는지 확인하여 읽기/수정/쓰기 라는 일련의 Cycle 에서 해당 리소스에 다른 변경(실패한 이력 말고 성공한 이력)이 없었는지 확인한다. 

resourceVersion 은 etcd 의 mod_revision 에 의해 지원된다. 

resourceVersion 의 값을 예측하는 방법은 일반적으로 API 서버로 GET 요청을 보내는 방법밖에 없다. 
GET 요청으로 값을 예측하는 방법은 반드시 클라이언트에서 불투명하게 처리되어야 하고, 서버로 수정되지 않은채 전달되어야 한다. 

즉, resourceVersion 은 namespace 나 kubernetes 상의 다른 리소스와 관련된 값이 아니라는 것이다.

resouceVersion 의 값은 etcd 의 시퀀서와 값이 일치하도록만 되어 있지만, 나중에는 kind 나 namespace 별로 상태를 샤딩하거나 다른 스토리지 시스템으로 포팅하는 것처럼 resourceVersion 의 구현이 변경될 것으로 예상된다. 

만약 PUT 요청을 통해 리소스를 변경하는 도중에 confilct 이 발생할 경우, client 는 API 서버로 GET 요청을 통해 리소스를 다시 가져온 뒤, 변경 사항을 새로 적용한다. 

이 메카니즘을 사용하면 여러 요청이 동시에 들어와 경합이 발생하더라도 문제 없이 해결할 수 있다.
 

동시에 리소스를 변경하는 케이스

아래와 같은 시퀀스가 병렬로 발생하면, Foo.Bar 에 대한 변경 사항이나 Foo.Baz 에 대한 변경 사항이 손실될 수 있다. 이를 막기 위해 나온 개념이 위에서 설명한 resourceVersion 이다.

동시에 Foo 를 변경하는 케이스

resourceVersion 을 지정할 경우, 누가 리소스에 값을 Write 하든, 리소스를 성공적으로 변경하든 API 서버는 크게 신경쓰지 않는다. 

단지, 두 client 중에 하나가 실패하고 특정 client 의 Foo 가 저장된다는 것만을 알면 된다. 

이런 성질을 이용하여 resourceVersion 은 캐싱이 됐을 때 읽기 후 쓰기 일관성을 위해 향후 다른 작업(ex. GET, DELETE)의 전제 조건으로 사용될 수 있다. 

예를 들어, "Watch" 작업은 API 서버에 쿼리 매개변수를 사용해서 특정 resourceVersion 을 갖는 값을 가져오고, 해당 리소스가 변경되어 resourceVersion 이 변경될 경우를 감지한다.
 

활용

이 처럼 resourceVersion 을 잘 이해하면, go-client 라이브러리를 사용하여 resourceVersion 이 변경되는 것을 감지하여 사이드카 컨테이너를 강제로 injection 하거나, 특정 annotaion 이나 label 을 강제로 injection 하는 등의 작업을 할 수 있을 것으로 생각한다.

728x90