사전 etcd
구현체

etcd

gabury1고친 사람 github-actions[bot]

etcd 는 여러 서버가 함께 믿고 읽는 작은 설정 값을 보관해 주는 저장소입니다. 서버 몇 대가 같은 값을 나눠 들고 있어서 한 대가 죽어도 값이 남습니다. 값이 바뀌면 지켜보던 프로그램에 바로 알려 줍니다. 쿠버네티스가 클러스터의 상태를 여기에 적어 둡니다.

쉽고 빠른 이해

etcd 는 여러 서버가 같이 보는 설정 수첩입니다. 「지금 데이터베이스 주소는 무엇인가」·「지금 작업 담당은 어느 서버인가」 같은 값을 적어 두면 모든 서버가 같은 답을 읽습니다.

이런 값을 서버 한 대에만 두면 그 서버가 죽는 순간 모두가 길을 잃습니다. 여러 대에 복사해 두면 이번에는 서로 다른 값을 들고 있을 수 있습니다. etcd 는 값을 여러 대에 둡니다. 그러면서도 모두가 같은 값을 보게 해 줍니다.

어떻게 도나:

  1. 서버 세 대나 다섯 대가 한 무리를 이룹니다
  2. 쓰기는 대표 한 대가 받아 나머지에게 돌립니다
  3. 절반 넘는 서버가 받아 적었다고 답해야 쓰기가 끝납니다
  4. 값이 바뀌면 그 값을 지켜보던 프로그램에 알림이 갑니다

대가가 있습니다. 큰 데이터는 못 담습니다. 서버를 늘려도 쓰기는 빨라지지 않습니다. 서버의 절반 이상이 끊기면 쓰기를 멈춥니다.

상세

etcd 는 분산 시스템에서 쓰는 키-값 저장소입니다. 내려받아 서버 여러 대에 띄우면 한 무리로 묶여 요청을 받습니다. 담는 것은 설정 값, 서버 목록, 누가 어떤 일을 맡았는지 같은 작고 중요한 데이터입니다.

이 절은 먼저 왜 이런 저장소가 따로 필요한지 봅니다. 그다음 데이터의 모양인 키와 값, 여러 대가 같은 값을 갖는 방법, 멤버 수를 차례로 봅니다. 이어서 변경 이력인 리비전, 변경 알림인 워치, 시간이 지나면 사라지는 키인 리스, 조건부 쓰기를 봅니다. 끝으로 명령 몇 줄로 쓰는 모습, 쓰는 곳, 이 물건이 포기한 것을 봅니다.

설정을 한곳에 두기 어려운 까닭

서버 열 대가 같은 데이터베이스 주소를 알아야 한다고 해 봅시다. 주소를 서버마다 설정 파일에 적으면 바꿀 때 열 군데를 고쳐야 합니다. 고치는 사이에 어떤 서버는 옛 주소, 어떤 서버는 새 주소를 씁니다.

주소를 한 서버에만 두고 모두가 물어보게 하면 고칠 곳은 하나가 됩니다. 대신 그 서버가 죽으면 열 대가 동시에 주소를 잃습니다. 이렇게 하나가 죽어 전체가 멈추는 곳을 단일 장애점이라고 부릅니다.

etcd 는 이 둘 사이를 메웁니다. 값을 여러 대에 복사해 두니 한 대가 죽어도 괜찮습니다. 그러면서 모든 복사본이 같은 순서로 같은 변경을 받게 해서 서로 다른 값을 들고 있지 않게 합니다.

키와 값

etcd 에 넣는 데이터는 키 하나에 값 하나입니다. 키는 /app/db 같은 문자열입니다. 값은 아무 바이트열이나 됩니다. 폴더처럼 보이는 키 이름은 관례일 뿐입니다. 안에서는 평평한 목록입니다.

키는 이름 순서로 정렬돼 있습니다. 그래서 /app/ 으로 시작하는 키를 한 번에 읽는 접두사 조회가 됩니다. 한 서비스의 설정을 /app/ 아래에 모아 두고 통째로 가져오는 식으로 씁니다.

여러 대가 같은 값을 갖는 방법

etcd 서버 한 대를 멤버라고 부릅니다. 멤버의 무리는 클러스터입니다. 한 클러스터의 멤버들은 같은 키와 값을 저마다 한 벌씩 들고 있습니다.

멤버들은 Raft 라는 합의 알고리즘으로 값을 맞춥니다. 합의 알고리즘은 여러 서버가 「다음 변경은 이것」이라는 결정 하나에 함께 동의하게 하는 절차입니다.

Raft 에서는 멤버 가운데 한 대가 리더가 됩니다. 나머지는 팔로워입니다. 쓰기 요청은 언제나 리더가 받습니다. 팔로워에 쓰기가 들어오면 리더에게 넘깁니다.

리더는 변경을 자기 기록에 적고 팔로워들에게 보냅니다. 멤버 과반이 받아 적었다고 답하면 그 변경은 확정됩니다. 확정된 뒤에야 클라이언트에 성공을 돌려줍니다. 이 과반을 정족수라고도 부릅니다.

sequenceDiagram
    participant 앱
    participant 리더
    participant 팔로워1 as 팔로워 1
    participant 팔로워2 as 팔로워 2
    앱->>리더: /app/db 를 새 주소로 써라
    리더->>리더: 자기 기록에 적는다
    리더->>팔로워1: 이 변경을 적어라
    리더->>팔로워2: 이 변경을 적어라
    팔로워1-->>리더: 적었다
    Note over 리더: 리더와 팔로워 1 로 셋 중 둘이 적었다
    리더-->>앱: 성공
    팔로워2-->>리더: 늦게 적었다

그림에서 팔로워 2 의 답을 기다리지 않고 성공이 돌아갔습니다. 셋 중 둘이면 과반이기 때문입니다. 답이 늦는 멤버 한 대가 쓰기 전체를 붙잡지 않습니다.

리더는 살아 있는 동안 팔로워들에게 짧은 신호를 거듭 보냅니다. 팔로워는 리더의 연락이 일정 시간 끊기면 리더가 죽은 것으로 보고 새 리더를 뽑습니다. 멤버끼리 리더를 뽑는 이 과정이 Raft 의 리더 선출입니다. 뽑히는 동안에는 잠깐 쓰기가 멈춥니다. 새 리더가 서면 다시 받습니다.

멤버 수와 견디는 장애 수

과반이 살아 있어야 쓰기가 됩니다. 그래서 멤버 수에 따라 몇 대까지 죽어도 되는지가 정해집니다.

멤버 수 과반 죽어도 되는 수
1 1 0
3 2 1
4 3 1
5 3 2

표에서 넷은 셋과 견디는 수가 같습니다. 멤버만 하나 늘었습니다. 과반은 커져서 쓰기마다 답을 더 받아야 합니다. 그래서 보통 셋이나 다섯으로 꾸립니다.

네트워크가 끊겨 멤버들이 두 무리로 갈리는 일을 분단이라고 합니다. 과반을 쥔 쪽만 계속 씁니다. 소수 쪽은 쓰기를 받지 않습니다. 양쪽이 각자 다른 값을 확정해 버리는 일을 이렇게 막습니다.

리비전

etcd 는 키를 바꿀 때 옛 값을 바로 지우지 않습니다. 클러스터 전체에 하나뿐인 번호를 1 키웁니다. 새 값에 그 번호를 붙여 따로 남깁니다. 이 번호가 리비전입니다. 리비전이 붙은 값 하나를 이 글에서는 판이라고 적습니다.

값을 덮어쓰지 않고 판을 쌓아 두는 방식이 MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어)입니다. 이름의 「동시성 제어」는 여러 요청이 한꺼번에 읽고 쓸 때 서로 엉키지 않게 한다는 뜻입니다. 판을 여러 개 두는 것이 그 방법입니다. 덕분에 「리비전 4 시점의 /app/db 값」처럼 과거를 읽을 수 있습니다.

판을 여러 개 두면 읽기도 편해집니다. 읽는 쪽은 한 리비전의 판을 골라 읽습니다. 그 사이에 누가 값을 바꾸면 새 판이 따로 생길 뿐입니다. 그래서 읽던 값이 중간에 바뀌지 않습니다.

옛 판을 끝없이 쌓으면 디스크가 찹니다. 그래서 어느 리비전보다 오래된 판을 걷어 내는 컴팩션을 주기적으로 돌립니다. 걷어 낸 리비전은 다시 읽을 수 없습니다.

워치

워치는 키를 지켜보다가 바뀌면 알려 달라는 요청입니다. 프로그램이 /app/db 를 워치해 두면 누가 그 값을 바꿀 때 새 값이 바로 흘러옵니다. 몇 초마다 다시 읽어 바뀌었나 확인하는 폴링이 필요 없습니다.

워치는 리비전과 맞물려 돕니다. 프로그램이 잠깐 끊겼다가 다시 붙을 때 「리비전 120 이후 변경부터 달라」고 요청할 수 있습니다. 끊긴 동안 놓친 변경을 빠짐없이 받습니다. 다만 그 사이 컴팩션으로 걷힌 리비전은 받을 수 없습니다.

리스

리스는 정해진 시간 뒤에 저절로 만료되는 계약입니다. 리스를 만들 때 TTL(Time To Live, 살아 있는 시간)을 초 단위로 줍니다. 클라이언트가 그 시간 안에 계속 연장하지 않으면 리스가 끝납니다.

키를 리스에 매달아 둘 수 있습니다. 리스가 끝나면 매달린 키도 함께 지워집니다. 서버가 살아 있는 동안만 목록에 이름이 남게 하고 싶을 때 이것을 씁니다.

예를 들어 서버가 켜질 때 /servers/web-1 키를 리스에 매달아 넣고 계속 연장한다고 합시다. 서버가 죽으면 연장이 끊기고 키가 사라집니다. 다른 프로그램은 이 목록을 워치해서 살아 있는 서버만 알게 됩니다. 이런 쓰임을 서비스 디스커버리라고 부릅니다.

조건부 쓰기

여러 프로그램이 같은 키를 동시에 고치면 한쪽 변경이 덮일 수 있습니다. etcd 는 이를 막는 트랜잭션을 줍니다. 「조건을 비교하고, 맞으면 이것, 틀리면 저것」을 한 번에 실행합니다. 중간에 다른 쓰기가 끼어들 틈이 없습니다.

가장 흔한 쓰임은 「이 키가 아직 없으면 내 이름을 쓴다」입니다. 여러 서버가 동시에 시도해도 한 대만 성공합니다. 이 키를 리스에 매달면 이긴 서버가 죽을 때 키가 사라집니다. 그러면 다른 서버가 이어받습니다. 분산 락을 이렇게 만듭니다.

애플리케이션 서버 여러 대 가운데 일을 맡을 한 대를 고르는 일도 같은 방법으로 만듭니다. 이것은 etcd 위에서 돌아가는 프로그램끼리 담당을 고르는 일입니다. 앞에서 본 etcd 멤버끼리 Raft 리더를 뽑는 일과는 다릅니다.

명령으로 보기

etcd 에는 명령줄 도구 etcdctl 이 딸려 옵니다. 아래는 키를 쓰고 읽는 모습입니다. 오른쪽 주석이 그 명령이 찍는 내용입니다. get 은 키 이름과 값을 두 줄로 찍습니다. ↵ 는 줄바꿈입니다. --prefix 는 /app/ 아래 키마다 그 두 줄을 이어 찍습니다.

터미널
etcdctl put /app/db 10.0.0.5  # OK
etcdctl get /app/db  # /app/db ↵ 10.0.0.5
etcdctl get --prefix /app/  # 키 ↵ 값 ↵ 키 ↵ 값 …

리스를 먼저 만듭니다. 받은 번호는 키를 쓸 때 붙입니다. 아래 <번호> 에는 첫 줄이 찍은 16진수 번호가 들어갑니다.

터미널
etcdctl lease grant 60  # lease <번호> ...
etcdctl put --lease=<번호> /svc/a on  # OK

연장은 etcdctl lease keep-alive <번호> 로 합니다. 60초 안에 리스를 연장하지 않으면 /svc/a 가 사라집니다. 다른 터미널에서 etcdctl watch /svc/a 를 걸어 두면 값이 바뀌거나 지워질 때마다 그 내용이 찍힙니다.

프로그램에서는 이 명령 대신 원격 함수 호출 방식인 gRPC 로 같은 요청을 보냅니다. 언어마다 클라이언트 라이브러리가 있습니다.

쓰는 곳

가장 널리 알려진 쓰임은 Kubernetes 입니다. 쿠버네티스는 어느 컨테이너 묶음이 어느 기계에서 돌아야 하는지 같은 클러스터 상태를 전부 etcd 에 적습니다. 쿠버네티스의 부품들은 etcd 를 직접 읽지 않고 kube-apiserver 를 거칩니다. etcd 에 읽고 쓰는 일은 이 서버 하나가 맡습니다.

쿠버네티스가 상태 변화에 바로 반응하는 바탕에도 워치가 있습니다. 사용자는 「이 프로그램을 세 벌 띄워 두라」처럼 클러스터가 이렇게 돌아가야 한다는 목표를 적어 둡니다. 누가 이 목표를 바꾸면 그 변경을 지켜보던 부품이 알림을 받고 움직입니다.

포기한 것

etcd 는 여러 대가 같은 값을 확실히 갖게 하려고 몇 가지를 내려놓았습니다.

포기한 것 무엇이 곤란해지나
큰 데이터 요청 하나와 전체 저장 용량에 상한이 있습니다. 파일이나 대량 데이터는 못 담습니다
쓰기 확장 멤버를 늘려도 쓰기는 리더 한 대가 받습니다. 답할 멤버만 늘어 오히려 더 오래 걸립니다
분단 중의 쓰기 과반을 못 쥔 쪽은 쓰기를 멈춥니다
디스크가 더딘 환경 변경마다 디스크에 확실히 적은 뒤에 답합니다. 디스크 쓰기가 오래 걸리면 리더의 연락이 늦어져 리더 선출이 잦아집니다

그래서 etcd 는 애플리케이션 데이터를 담는 데이터베이스로 쓰지 않습니다. 주문이나 게시글은 일반 데이터베이스에 둡니다. 여러 서버가 함께 믿어야 하는 작은 값만 etcd 에 둡니다.

관련 항목

etcd 가 값을 맞추는 합의 방식

Raft · 합의 알고리즘 · 리더 · 팔로워 · 리더 선출 · 정족수 · 복제 · 복제 로그 · 분단 · 스플릿 브레인

etcd 가 데이터를 다루는 구조

키-값 저장소 · 리비전 · MVCC · 컴팩션 · 스냅샷 · 선행 기록 로그 · B+ 트리 · bbolt

etcd 가 클라이언트에 주는 기능

워치 · 리스 · TTL · 트랜잭션 · 분산 락 · 서비스 디스커버리 · 선형화 가능성 · gRPC · etcdctl

etcd 에 상태를 맡기는 시스템

Kubernetes · kube-apiserver · kubelet · 컨트롤 플레인

etcd 와 같은 역할을 두고 겨루는 조정 서비스

ZooKeeper · Consul · Chubby · Zab · Paxos

etcd 가 속하는 상위 분류

분산 시스템 · 클러스터 · 단일 장애점 · CAP 정리 · 일관성

다른 이름: 엣시디