Kubernetes 운영을 위한 etcd 기본 동작 원리의 이해

안녕하세요, 클라우드플랫폼팀 ted입니다.

Container Cloud 관련 업무를 하고 계신 분이라면 모두 etcd[etsy d]를 한 번쯤은 들어보셨을 거라 생각합니다. 왜냐하면, 컨테이너 생태계에서 사실상 표준이 된 Kubernetes가 컨트롤 플레인 컴포넌트로 채택을 했기 때문이죠.

Kubernetes는 기반 스토리지(backing storage)로 etcd를 사용하고 있고, 모든 데이터가 etcd에 보관됩니다. 예를 들어, 클러스터에 어떤 노드가 몇 개나 있고 어떤 파드가 어떤 노드에서 동작하고 있는지가 etcd에 기록됩니다. 만약 동작 중인 클러스터의 etcd 데이터베이스가 유실된다면 컨테이너뿐만 아니라 클러스터가 사용하는 모든 리소스가 미아가 되어 버립니다.

카카오의 Kubernetes as a Service인 DKOS는 Kubernetes의 라이브 업그레이드(Kubernetes Live Upgrade)를 API 형태로 제공하고 있습니다(https://if.kakao.com/session/33). 클러스터에서 동작 중인 애플리케이션에 영향을 주지 않고 무중단으로 Kubernetes 버전을 업그레이드하기 위해, DKOS는 마스터 노드를 하나씩 삭제하고 교체하는 방식을 사용하고 있습니다. 이 과정에서 etcd 서버 일부를 삭제하고 추가하는 작업이 반복됩니다. 따라서, 안정적인 API의 개발과 운영을 위해 etcd의 동작 원리를 파악할 필요가 있었습니다.

본 문서에는 Kubernetes Live Upgrade를 준비하고 개발하면서 조사했던 etcd의 기본 동작 방식과 유지 보수 방법을 소개합니다. 애플리케이션을 서비스하기 위해 Kubernetes를 사용하고 있으며 etcd에 대해 좀 더 자세히 알고 싶은 분께 본 문서가 도움이 될 수 있기를 바랍니다.

 


 

Etcd

Etcd는 key:value 형태의 데이터를 저장하는 스토리지입니다. etcd가 다운된다면 Kubernetes 클러스터는 제대로 동작하지 못하게 되므로 높은 신뢰성을 제공해야 합니다.

 

RSM(Replicated state machine)

Etcd는 Replicated state machine(이하 RSM)입니다. 분산 컴퓨팅 환경에서 서버가 몇 개 다운되어도 잘 동작하는 시스템을 만들고자 할 때 선택하는 방법의 하나로, State Machine Replication이 있습니다. 이는 똑같은 데이터를 여러 서버에 계속하여 복제하는 것이며, 이 방법으로 사용하는 머신을 RSM이라고 합니다.

RSM은 위 그림과 같이 command가 들어있는 log 단위로 데이터를 처리합니다. 데이터의 write를 log append라 부르며, 머신은 받은 log를 순서대로 처리하는 특징을 갖습니다. 하지만, 똑같은 데이터를 여러 서버에 복제해놨다고 해서 모든 게 해결되지는 않습니다. 오히려 더 어려운 문제가 생기기도 합니다. Robust 한 RSM을 만들기 위해서는 데이터 복제 과정에 발생할 수 있는 여러 가지 문제를 해결하기 위해 컨센서스(consensus)를 확보하는 것이 핵심입니다. Consensus를 확보한다는 것은 RSM이 아래 4가지 속성을 만족한다는 것과 의미가 같으며, etcd는 이를 위해 Raft 알고리즘을 사용하였습니다.

 

속성 설명
Safety 항상 올바른 결과를 리턴해야 합니다.
Available 서버가 몇 대 다운되더라도 항상 응답해야 합니다.
Independent from timing 네트워크 지연이 발생해도 로그의 일관성이 깨져서는 안됩니다.
Reactivity 모든 서버에 복제되지 않았더라도 조건을 만족하면 빠르게 요청에 응답해야 합니다.

 

용어 소개

Quorum Majority Index Commit  
State Leader Follower Candidate Term
Timer Heartbeat interval Election timeout Leader election  
Log replication Index Snapshot Compaction Runtime reconfiguration

Raft를 구현한 etcd의 동작을 설명하고 이해하기 위해서는 많은 용어가 필요합니다. 단순히 용어를 나열해놓고 설명한다면 잘 와닿지 않기 때문에, 가장 기본적인 용어 3가지만 정리하고 etcd의 동작을 설명하면서 하나씩 풀어보도록 하겠습니다.

 

쿼럼(Quorum)

Quorum(쿼럼)이란 우리말로는 정족수라는 뜻을 가지는데요, 의사결정에 필요한 최소한의 서버 수를 의미합니다. 예를 들어, RSM을 구성하는 서버의 숫자가 3대인 경우 쿼럼 값은 2(3/2+1)가 됩니다. etcd는 하나의 write 요청을 받았을 때, 쿼럼 숫자만큼의 서버에 데이터 복제가 일어나면 작업이 완료된 것으로 간주하고 다음 작업을 받아들일 수 있는 상태가 됩니다.

 

상태(State)

Etcd를 구성하는 서버는 State를 가지며 이는 Leader, Follower, Candidate 중 하나가 됩니다. 이 상태에 대해서는 etcd의 기본 동작을 설명하며 자세히 살펴보겠습니다.

 

타이머(Timer)

Etcd의 Leader 서버는 다른 모든 서버에게 heartbeat를 주기적으로 전송하여, Leader가 존재함을 알립니다. 만약 Leader가 아닌 서버들이 일정 시간(election timeout) 동안 heartbeat를 받지 못하게 되면 Leader가 없어졌다고 간주하고 다음 행동을 시작합니다.

 

 

Raft 알고리즘

 

리더 선출(Leader election)

3개의 서버를 이용해서 etcd 클러스터를 최초 구성했을 때, 각 서버는 모두 follower 상태(state)이며 term이라는 값을 0으로 설정하고 동작을 시작합니다. Term은 굳이 우리말로 번역하자면 임기가 가장 적당할 것 같습니다. 현재 etcd 클러스터는 초기 세팅되어서 리더가 없는 상태이기 때문에 그 누구도 hearbeat를 보내지 않습니다.

따라서 etcd 서버 중 한대에서 election timeout이 발생하게 되고, timeout이 발생한 서버는 자신의 상태를 candidate로 변경하고 term 값을 1 증가시킨 다음에 클러스터에 존재하는 다른 서버에게 RequestVote RPC call을 보냅니다. RequestVote를 받은 각 서버는 자신이 가진 term 정보와 log를 비교해서 candidate보다 자신의 것이 크다면 거절하는데, 위 예제에서는 거절할 이유가 없으므로 RequestVote에 대해서 OK 응답을 합니다.

Candidate는 자기 자신을 포함하여 다른 서버로부터 받은 OK 응답의 숫자가 quorum과 같으면 leader가 됩니다. Leader가 된 서버는 클러스터의 다른 서버에게 heartbeat를 보내는데요, 이 Append RPC call에는 leader의 term과 보유한 log index 정보가 들어있습니다.

Leader가 아닌 서버는 Append RPC call을 받았을 때 자신의 term보다 높은 term을 가진 서버의 call 인지 확인하고, 자신의 term을 받은 term 값으로 업데이트합니다. 이로써 etcd 클러스터는 leader가 선출되었고, 외부로부터 유입되는 write와 read 요청을 받아들일 준비가 되었습니다.

 

로그 복제(Log replication)

위 그림은 클러스터가 구성되고 leader가 선출된 이후, 이제 사용자로부터 write 요청을 받았을 때의 예를 보여줍니다.

각 서버는 자신이 가지고 있는 log의 lastIndex 값을 가지고 있으며 leader는 여기에 추가로 follower가 새로운 log를 써야 할 nextIndex를 알고 있습니다. 사용자로부터 log append 요청을 받은 leader는 자신의 lastIndex 다음 위치에 로그를 기록하고 lastIndex 값을 증가시킵니다. 그리고 다음 heartbeat interval이 도래하면 모든 서버에게 AppendEntry RPC call을 보내면서, follower의 nextIndex에 해당하는 log를 함께 보냅니다.

첫 번째 follower(이하 F1) 자신의 entry(메모리)에 leader로부터 받은 log를 기록했고 두 번째 follower(이하 F2)는 아직 기록하지 못했다고 가정하겠습니다. F1은 log를 잘 받았다는 응답을 leader에게 보내주게 되고, leader는 F1의 nextIndex를 업데이트합니다. F2가 아직 응답을 주지 않았지만, 자기 자신을 포함해서 쿼럼 숫자만큼의 log replication이 일어났기 때문에 commit을 수행합니다. Commit이란, log entry에 먼저 기록된 데이터를 서버마다 가지고 있는 db(파일시스템)에 write 하는 것을 의미합니다. 이 시점 이후부터, 사용자가 etcd 클러스터에서 x를 read하면 1이 return 됩니다. Follower들은 leader가 특정 로그를 commit한 사실을 알게 되면, 각 서버의 entry에 보관 중이던 log를 commit 합니다.

 

리더 다운(Leader down)

사용자의 write 요청에 따라 쿼럼 숫자만큼 log의 복제와 commit이 완료된 이후, leader가 다운(down)된다면 etcd는 어떻게 동작할까요? 로그 복제(Log replication)에서 다룬 상황 이후에 leader 서버의 etcd 프로세스가 알 수 없는 이유로 다운되었다고 가정해 보겠습니다.

Follower들은 leader로부터 election timeout 동안 heartbeat를 받지 못하고, F2가 timeout 됐다고 가정해 보겠습니다. F2는 자신을 candidate로 바꾸고 term을 증가시킨 다음 RequestVote RPC call을 모든 서버에게 보냅니다. RequestVote RPC call에는 요청을 받은 candidate가 leader가 되어도 문제가 없는지 follower가 판단할 수 있도록 term과 index 정보가 들어있습니다. RequestVote를 받은 F1은 F2가 보내온 RequestVote RPC 데이터를 살펴봅니다. 일단 term의 숫자는 하나 높긴 한데, F1이 가진 log가 F2보다 더 최신이기 때문에 F1는 F2의 RequestVote에 거절 응답을 보내고, F2는 leader로 선출되지 못합니다.

이후 F1에서 다시 election timeout이 발생하면 F1은 F2와 term이 같으며 더 최신의 log를 보유하고 있으므로 F2는 F1의 RequestVote RPC call에 OK 응답을 하고 F1은 새로운 leader가 됩니다. F1이 leader가 된 이후 보낸 첫 번째 heartbeat(Append RPC)를 수신한 F2는 자신의 상태를 follower로 변경합니다.

F1이 새로운 leader가 된 상태에서 write 요청을 받게 되더라도 쿼럼 숫자만큼의 etcd 서버가 running 중이므로 etcd 클러스터는 정상 동작합니다.

F1이 새로운 leader가 된 상태에서 구 leader가 복구되면, F1에게 heartbeat를 보내거나 F1으로부터 heartbeat를 받을 텐데 lastIndex 값이 더 작고 term도 낮으므로 자기 자신을 follower로 변경하고 term 숫자를 높은 쪽에 맞춥니다.

위 예제에서는 leader가 다운됨으로 인해서 여러 번의 election이 일어날 수 있는 상황을 다루었습니다. 짧은 시간이긴 하겠지만 새로운 leader를 뽑지 못하고 계속해서 election을 하게 된다면 사용자의 요청을 처리하지 못해 etcd 클러스터의 가용성이 떨어지게 될 수도 있는데요, Raft 알고리즘은 이미 이러한 상황이 일어날 수 있음을 예상했으며 randomize election timeout과 preVote라는 개념이 도입되어 있습니다. Etcd에서는 preVote가 구현되어 있습니다(https://github.com/etcd-io/etcd/pull/6624/files).

 

런타임 재구성(Runtime reconfiguration)

Etcd의 기본인 리더 선출(leader election)과 로그 복제(log replication)에 대해 살펴보았으니, 이번에는 런타임 재구성(Runtime reconfiguration)을 다루어 보겠습니다. Runtime reconfiguration이란 etcd 클러스터가 동작 중인 가운데 etcd 서버를 추가/삭제하는 것을 말합니다.

 

etcd 멤버 추가

이번에는, 3개의 서버로 구성된 etcd 클러스터가 지금까지 많은 데이터를 받아들여 lastIndex가 10101이라고 가정하겠습니다. Etcd에는 snapshot이라는 개념이 있는데, etcd 서버가 현재까지 받아들인 모든 log를 entry에서만 관리하는 것이 아니라 파일시스템에 백업해 놓는 것을 말합니다. 얼마나 자주 snapshot을 생성할 것이냐는 etcd 클러스터를 생성하면서 옵션으로 지정할 수 있고, 디폴트 값은 100,000입니다. 이러한 상황에서 새로운 4번째 서버를 추가하는 요청이 수신되었다고 가정하겠습니다. Etcd는 이런 config의 변경을 일반적인 log append와 다른 메커니즘으로 처리하지 않고 기존의 log replication을 그대로 이용합니다. Leader는 사용자에게 받은 reconfiguration 요청에 따라 새로운 config에 해당하는 Cnew를 log entry에 추가하고 Append RPC call을 이용해 다른 서버에 전파합니다.

일반적인 Log replication과 다른 점이 있다면, 이 config는 commit 된 이후에 효력을 발휘하는 게 아니라 entry에 쓰이자마자 효력을 발휘합니다. 여러 개의 config log가 entry에 존재할 때에는 가장 최신의 config를 사용합니다. 즉, Cnew에는 etcd 클러스터를 구성하는 전체 서버의 숫자가 4대로 설정되어 있으므로 Cnew를 leader가 db에 commit 하는데 필요한 log replication 완료 숫자는 3(4/2+1)이 됩니다.

서버 추가를 요청한 사용자에게 OK 응답을 한 이후, leader는 추가된 멤버가 가능한 한 빨리 같은 log를 보유할 수 있도록 snapshot을 보내줍니다. 이 snapshot은 현재 파일시스템에 백업으로 보유하고 있는 snapshot 중 가장 최신의 것과 leader의 현재 log entry를 합쳐 생성한 새로운 snapshot이며, 새로운 서버는 snapshot을 이용해 db를 만듦으로써 leader가 보낼 Append RPC call을 문제없이 받아들일 수 있는 상태가 됩니다.

Raft 알고리즘은 한꺼번에 여러 멤버를 추가하는 걸 지양합니다. 이것을 restriction이라고 하는데, etcd에서는 default로 활성화되어있고 조금 뒤에 다루어 보겠습니다.

하지만 뭔가 찝찝함이 남아있습니다. 멤버 추가 요청에 대해서 OK 응답은 했지만, 새로운 서버가 snapshot을 catch up 하는 동안에 write 요청이 들어오면 가용성에 정말 문제가 없을까요? 이 부분에 대해서 분명한 위험이 존재합니다.

만약 새롭게 추가한 4번째 멤버가 아직 catch up을 하지 못한 상태에서 새로운 멤버의 추가 요청을 받게 되면 새로운 config Cnew2가 leader의 entry에 생성됩니다. 쿼럼의 숫자는 3에서 더 증가하지는 않겠지만, 위 그림에서와 같이 10103번째 index의 로그를 복제 받은 follower가 부족한 경우 Cnew2의 commit은 지연됩니다. 이러한 상황에서 10104번째 index에 들어가야 할 값의 write가 발생하면 이 작업은 block이 되고, client timeout 안에 밀린 작업이 처리되지 못하면 availability 이슈가 발생합니다.

Raft에는 이러한 문제를 해결하기 위해 leader, follower, candidate 외에 제4의 상태 learner가 존재합니다. Learner는 etcd 클러스터의 멤버이지만 쿼럼 카운트에서는 제외하는 특별한 상태로 etcd 3.4.0부터 구현되었습니다(https://etcd.io/docs/v3.3.12/learning/learner/). Learner는 etcd의 promote API를 사용하여 일반적인 follower로 변경할 수 있습니다. Learner가 아직 log를 모두 따라잡지 못한 경우에는 거절됩니다(아래 그림 참조).

FYI.

Kubeadm, Kubespray와 같이 널리 쓰이는 Kubernetes 설치 자동화 도구는 etcd 클러스터 생성 시 learner의 개념을 활용하고 있지는 않습니다. Kubernetes에서 사용하는 etcd 클러스터의 snapshot은 평균적으로 크기가 수십 KB에 불과하기 때문에 이러한 이슈가 발생할 가능성은 낮습니다.

마지막으로 생각해 볼 것은, etcd 클러스터에 여러 멤버를 한꺼번에 추가하는 것입니다. Config는 commit 여부와 상관없이 log entry에 있는 최신의 것을 사용하기 때문에 충분한 replication이 일어나지 않은 configuration log가 entry에 여러 개 존재해서는 안 됩니다. Raft에서는 runtime reconfiguration은 한 번에 하나씩 처리할 것을 원칙으로 하고 있으며, etcd에서는 restriction을 설정하여 leader의 log에 커밋 되지 않은 config log가 있으면 새로운 config log를 받지 않습니다.

 

etcd 멤버 삭제

Etcd 클러스터에서 특정 서버를 삭제하는 요청도 etcd 멤버 추가에서 다룬 것과 같이 log를 복제하는 방식으로 진행됩니다. 단, leader가 leader를 제거하라는 요청을 받았을 때는 특별하게 동작합니다. 이 경우에는 leader가 새로운 config log를 commit 시키는 데 필요한 log replication 숫자를 셀 때 자기 자신을 제외합니다. 자신을 제외한 쿼럼 숫자만큼의 log replication이 확인되면 leader 자신이 삭제된 설정 Cnew를 commit 하고 클라이언트에게 OK 응답을 보냅니다. 위 예제에서는 etcd 클러스터의 서버 숫자가 4, 쿼럼이 3이기 때문에 leader를 제외한 모든 서버에 Cnew가 replicate되어야 commit이 가능합니다.

Commit 이후에는 leader의 자리를 내려놓게 되고 더는 heartbeat를 보내지 않습니다. 이것을 Raft와 etcd에서는 step down이라고 합니다. 클러스터를 구성하는 나머지 서버 중 누군가 election timeout이 발생하면 candidate가 되어 RequestVote RPC call 전송을 통해 새로운 leader를 선출하고 다음 term을 계속해나갑니다.

Leader가 클라이언트의 멤버 삭제 요청을 처리할 때 자기 자신을 제외한 쿼럼 숫자만큼의 log replication을 수행했기 때문에, 새로운 leader의 선출 성공이 보장됩니다.

 

만약 아직 step down을 시작하지 않은 상태에서 write 요청을 받으면 어떻게 처리될까요? Config log는 entry에 존재하는 것만으로도 즉시 효력을 발휘하지만, leader를 삭제할 때에는 entry에 존재하는 가장 최신의 config에 자신이 없다 하더라도 여전히 leader 역할을 수행합니다.

예를 들어, 아직 Cnew가 commit 되지 못한 상태에서 write 요청을 받으면 자기 자신을 제외한 쿼럼 숫자만큼 log replication을 수행합니다. Cnew가 commit이 되어 step down 되더라도, 나머지 서버 중 leader가 선출되고 safety를 유지하며 etcd 클러스터가 정상 동작하도록 하기 위함입니다.

이번에는 멤버 삭제 시의 restriction 예시를 살펴보겠습니다. 정상 동작하고 있는 etcd 서버의 개수를 started라고 정의할 때, 위 그림은 5개 노드가 etcd 클러스터를 구성하고 있는 상황이므로 started는 4, quorum은 3입니다.

이때 서버 한 대를 삭제하면, quorum은 3을 유지하고 started가 3으로 감소합니다. 연이어서 멤버 하나를 더 삭제해도 started가 quorum보다 작지는 않으므로 leader는 요청을 받아들이고 새로운 config를 entry에 생성합니다. 하지만, 만약 한 대를 더 삭제해서 started가 quorum보다 낮아질 것으로 예상되면 leader는 클라이언트의 멤버 삭제 요청을 거절합니다.

 

etcd의 유지보수 방법

 

지금까지 etcd의 기본 동작과 런타임 재구성(runtime reconfiguration)에 대해 살펴보았습니다. Etcd는 이처럼 리더 선출(leader election)과 로그 복제(log replication)을 기본으로 하여 몇 가지 trick과 restriction을 통해 consensus 확보라는 어려운 문제를 단순화 시키고 복잡하지 않게 처리하는 것이 가능합니다. 하지만 Raft를 완벽하게 구현하여 consensus를 확보하였다고 하더라도 etcd 프로세스가 서버의 메모리와 파일시스템을 무분별하게 사용한다면 etcd는 스토리지의 역할을 제대로 해내지 못할 것입니다.

본 절에서는 etcd가 메모리와 파일시스템을 어떻게 사용하는지, 그리고 예기치 못한 사고를 대비하는 백업 및 복구 방법에 대해 다루어보고자 합니다.

 

로그 리텐션(Log retention)

 

Etcd 프로세스는 기본적으로 log entry를 메모리에 보관합니다. 하지만 모든 log entry를 별다른 조치 없이 계속하여 메모리에 보관하면 서버 스펙이 아무리 훌륭하더라도 언젠가는 반드시 OOM이 발생할 것이므로 주기적으로 log entry를 파일시스템에 저장(snapshot) 하고, memory를 비우는 작업(truncate)을 합니다. 만약 truncate한 log를 사용해야 할 일이 발생하면(예를 들어, 특정 follower의 log catch 속도가 너무 느려서 leader의 메모리에 없는 log를 요구할 경우) leader는 최근의 snapshot 파일을 follower에게 전송합니다(etcd 멤버 추가 참조). Etcd 프로세스가 얼마나 자주 snapshot을 생성하고 메모리에서 log를 truncate할 것인지는 snapshot-count라는 옵션으로 정의할 수 있고, 디폴트 값은 100,000입니다.

위 그림은 etcd의 log retention을 쉽게 관찰할 수 있도록 snapshot-count를 1000으로 변경하고 write 테스트를 수행했을 때의 로그를 보여줍니다. 1000번의 write가 일어날 때마다 일단 메모리를 보고 snapshot을 만들고 메모리에 lastIndex–5,000까지의 log만 메모리에 보관하고 나머지는 비웁니다(5,000은 옵션을 통해 조정할 수 없는 값입니다).

 

리비전 및 컴팩션(Revision and Compaction)

Etcd 프로세스에 의해 commit된 데이터는 db(파일시스템)에 보관이 됩니다(로그 복제(Log replication) 참조). Etcd는 하나의 key에 대응되는 value를 하나만 저장하고 있는 것이 아니라, etcd 클러스터가 생성된 이후 key의 모든 변경사항을 파일시스템에 기록합니다. 이것을 revision이라고 합니다.

Etcd는 db에 데이터를 저장할 때 revision 값과 함께 저장합니다. 위 그림을 살펴보면, x라는 key에 대해서 value를 달리해서 여러 번 write를 했을 때 하나의 공간에 계속하여 덮어쓰는 것이 아니라, 이력(history)를 계속하여 저장하는 구조입니다. 파일시스템이 메모리와 비교했을 때 볼륨에 있어 비교적 풍부한 컴퓨팅 리소스임은 분명하지만, etcd가 revision에 대해 별다른 관리를 하지 않고 계속 write 하기만 한다면, 호스트 OS의 디스크 공간 부족으로 인해 etcd 프로세스는 언젠가 반드시 중단될 것입니다.

불필요한 revision의 과도한 리소스 사용을 피하고자, etcd는 compaction 기능을 제공합니다. Compaction으로 삭제한 revision 데이터는 더는 etcd 클러스터에서 조회할 수 없습니다. Compaction은 etcd가 제공하는 CLI 도구인 etcdctl을 사용해 간단하게 할 수 있습니다.

 

자동 컴팩션(Auto compaction)

Etcd는 운영자가 별도로 조치하지 않아도 일정 revision 또는 주기를 가지고 자동으로 revision을 정리하는 auto compaction을 지원합니다. Auto compaction은 2가지 모드(revision, periodic)가 있으며 어떤 모드를 선택했느냐에 따라 auto-compaction-retention 옵션이 가지는 의미가 달라집니다.

 

Revision 모드

Auto-compaction-mode revision으로 지정하면 5분마다 최신 revision – auto-compaction-retention까지만 db에 남기고 compaction합니다. Auto-compaction-retention의 값이 1,000이고 5분마다 500 revision의 데이터가 생성된다고 가정하면 5분마다 500 revision이 계속하여 compaction됩니다.

 

Periodic 모드

Auto-compaction-mode를 periodic으로 지정하는 경우 auto-compaction-retention에 주어진 값을 시간으로 인식합니다. etcd의 세부 버전이 무엇이냐에 따라 동작 방식이 약간씩 달라지는데(https://etcd.io/docs/v3.4/op-guide/maintenance/#auto-compaction), 위 예제는 etcd 버전 3.3을 기준으로 작성되었습니다. Auto-compaction-retention이 8h일 때 이를 10으로 나눈 값이 1h가 적기 때문에, 1h 단위로 compaction이 일어납니다. 1시간마다 100 revision이 생성된다고 가정하면 1시간마다 가장 오래된 100 revision을 compaction합니다.

 

단편화 제거(Defragmentation)

 

Compaction을 통해 etcd 데이터베이스에서 revision을 삭제했다고 해서, 파일시스템의 공간까지 확보되는 것은 아닙니다. Revision 삭제로 인해 발생한 fragmentation(단편화)을 정리해 주어야 디스크 공간이 확보됩니다. RDB에서 TRUNCATE를 하지 않고 DELETE를 하는 것만으로는 disk 공간이 확보되지 않는 것과 비슷합니다. Defragmentation(단편화 제거) 작업을 해야지만 revision 삭제로 인해 etcd 데이터베이스에 발생한 fragmentation을 정리해 디스크 공간을 확보할 수 있습니다. Defragmentation는 compaction과 달리 자동(auto) 기능이 제공되지 않고 있습니다. Defragmentation 시 주의해야 할 점은, 진행되는 동안 모든 read/write가 block 된다는 것입니다. 하지만 몇 ms 단위의 시간에 일어나는 일이라, kubernetes를 위한 etcd 클러스터가 defragmentation으로 인해 read/write 작업이 timeout될 가능성은 적습니다.

Etcd가 허용하는 db의 max size는 etcd_quota_backend_bytes라는 옵션으로 조절할 수 있으며, 디폴트 값은 2G입니다. 만약 leader의 db 사이즈가 2G를 넘으면 “database space exceeded”라는 메시지와 함께 더 이상 write 요청을 받아들이지 않는 read-only 모드로 동작하게 됩니다. 따라서, etcd가 많은 key:value를 사용할 것으로 예상이 된다면 별도의 크론잡(CronJob)을 개발하여 defragmentation을 주기적으로 수행해 주거나, etcd_quota_backend_bytes 옵션을 넉넉하게 부여하여 (max 8G) etcd 클러스터의 가용성에 문제가 생기는 일이 없도록 해야 할 것입니다.

 

백업 및 복구(Backup and Restore)

 

Etcd는 예기치 못한 사고(예: 서버 디스크 장애)로 인해 etcd 데이터베이스가 유실되는 경우를 대비하기 위한 backup API를 제공합니다. 백업에서도 snapshot을 생성한다는 용어가 사용되는데, 여기서의 snapshot은 로그 리텐션(Log retention)에서 다루었던 snapshot과는 다른 것입니다. Database backup snapshot은 etcd 프로세스가 commit한 log가 저장된 데이터베이스 파일의 백업입니다. 바꿔 말하면, etcd의 backup은 etcd 데이터 파일경로(data dir)에 존재하는 db 파일을 백업하는 것입니다. 많은 compaction이 일어나고 defragmentation된 적 없는 db에 대해서 백업하면, 불필요하게 많은 용량을 백업에 사용하게 될 수도 있습니다.

단순하게 etcd 데이터 파일 경로(data dir)에 존재하는 db 파일을 복제(copy)한 것과 etcd의 API를 이용한 백업 파일 간에는 차이가 있습니다. 위 그림과 같이 etcdctl snapshot save 명령으로 만들어진 db 파일은 무결성 해시(hash)를 포함하고 있어서 추후 etcdctl snapshot restore 명령으로 복구할 때 파일이 변조됐는지 체크가 가능합니다. 만약 피치 못할 사정으로 단순 복사로 만들어진 백업 파일로부터 복구를 진행해야 한다면, –skip-hash-check 옵션을 추가하여 복구를 진행해야 합니다.

Backup 파일로부터 etcd를 복구하는 것은 두 단계로 나누어 진행할 수 있습니다. 먼저 db 파일을 호스트 OS의 특정 경로(dir)에 옮겨두고, 새로운 etcd를 시작시키면 됩니다. 단, etcd 서버와 클러스터가 새로운 메타데이터를 가지고 시작할 수 있게 정보를 주어야 하고, etcd 클러스터는 새로운 메타데이터로 db를 덮어쓰기(overwrite)하고 동작을 시작합니다.

etcd의 메타데이터에는 etcd 클러스터를 식별하기 위한 uuid와 etcd 클러스터에 속한 멤버의 uuid가 저장되어 있습니다. 만약 이전 메타데이터를 유지한 채 백업 파일로부터 새로운 etcd 클러스터를 생성한다면, 망가진 줄 알았던 etcd 서버가 우연히 살아나게 될 때 구성이 꼬여 오동작할 가능성이 큽니다.

 

카카오의 etcd 백업 방법

카카오의 Container as a Service인 DKOS는 예기치 못한 사고 인해 etcd 데이터베이스가 유실된 상황에서도 빨리 클러스터를 복구할 수 있도록, Kubernetes 클러스터 생성 시 etcd 백업을 위한 크론잡(CronJob)을 함께 배포합니다. 크론잡(CronJob)은 마스터 노드에서 동작하며 카카오의 분산 오브젝트 스토리지 서비스인 MetaKage에 etcd 데이터베이스를 하루에 한 번 백업합니다. 클러스터 당 최근 5일간의 백업을 보관하며, 모든 클러스터가 같은 시간에 백업해서 네트워크에 부하를 주지 않도록 클러스터마다 고유한 백업 시간을 갖고 있습니다.

본 문서가 도움이 될 수 있기를 바라며, 긴 글 읽어주셔서 감사합니다.

 



참고 

 

Latest Posts