Etcd 관리

개요

본 문서는 ETCD3와 함께 설치되는 명령줄 도구인 etcdctl 을 이용한 ETCD3 클러스터의 멤버와 엔드포인트 Health 등 상태를 확인하거나 특정 조건을 만족하는 Key들의 값을 조회하고 클러스터의 읽기 / 쓰기 성능을 테스트하는 방법에 대하여 기술한다.

명령줄 도구 etcdctl 설치 확인

ETCD3 클러스터를 관리하기 위한 명령줄 도구인 etcdctletcd 서버 바이너리와 함께 제공된다. 노드에 etcdctl 이 설치되어 있는지 여부와 버전을 확인할 수 있다.

$ which etcdctl
/usr/local/bin/etcdctl

$ etcdctl version
etcdctl version: 3.5.6
API version: 3.5

etcdctl 의 API에는 v2v3 두 개의 버전이 있으며 etcdctl 실행 시 환경변수 ETCDCTL_API 를 각각 2 혹은 3 으로 설정하여 어떤 버전의 API를 호출할 지 명시할 수 있다.

v3.4.0 이상부터는 v3 버전의 API가 기본으로 사용되며 본 문서에서도 v3 버전의 API를 기준으로 사용법을 서술한다.

공통 옵션

etcdctl 명령어를 실행할 때 지정할 수 있는 공통 옵션에 대하여 설명한다.

  • --endpoints : etcdctl 이 접근할 ETCD3 gRPC 서버의 Endpoint URL들을 쉼표 , 로 구분된 목록으로 지정한다. 지정되지 않은 경우 실행되는 환경의 localhost 127.0.0.1:2379 를 기본 gRPC Endpoint URL로 인지하여 통신을 시도한다.

    • 유효하지 않은 Endpoint URL은 아래와 같이 에러를 반환한다.

      $ etcdctl member list
      Error:  dial tcp 127.0.0.1:2379: connect: connection refused
  • -w, --write-out : 출력값의 Format을 설정하는 옵션으로 fields, json, protobuf, simple, table 을 허용한다.

  • --key : https 엔드포인트로 접근하려는 경우 TLS 인증에 사용할 Client Key 파일 경로를 지정한다.

  • --cert : TLS 인증에 사용할 Client 인증서 파일 경로를 지정한다.

  • --cacert : TLS 인증에 사용할 인증기관 (CA) 인증서 파일 경로를 지정한다.

예시

$ ETCDCTL_API=3 etcdctl --endpoints="https://192.168.0.10:2379,https://192.168.0.11:2379" \
  --key="./etcd-client-key.pem" \
  --cert="./etcd-client-crt.pem" \
  --cacert="./etcd-ca-crt.pem" \
  member list


클러스터 상태 조회

클러스터의 멤버 및 Endpoint URL의 목록과 상태를 조회한다.

클러스터 멤버 조회

etcdctl member list 로 클러스터 노드의 이름, Advertise 된 Peer 통신을 위한 URL 목록과 Client 통신을 위한 URL 목록을 확인할 수 있다.

$ etcdctl member list
670b863301943618, started, node1, http://192.168.0.10:2380, http://192.168.0.10:2379, false
7825d7b04510b842, started, node3, http://192.168.0.11:2380, http://192.168.0.11:2379, false
c8245114d55ec576, started, node2, http://192.168.0.12:2380, http://192.168.0.12:2379, false

$ etcdctl member list -w table
+------------------+---------+-------+--------------------------+--------------------------+------------+
|        ID        | STATUS  | NAME  |        PEER ADDRS        |       CLIENT ADDRS       | IS LEARNER |
+------------------+---------+-------+----------------------------+------------------------+------------+
| 670b863301943618 | started | node1 | http://192.168.0.10:2380 | http://192.168.0.10:2379 |      false |
| 7825d7b04510b842 | started | node3 | http://192.168.0.11:2380 | http://192.168.0.11:2379 |      false |
| c8245114d55ec576 | started | node2 | http://192.168.0.12:2380 | http://192.168.0.12:2379 |      false |
+------------------+---------+-------+----------------------------+------------------------+------------+
  • ID : 해당 노드의 고유한 식별자로 ETCD 리더 노드를 선출하는 Raft 알고리즘에 이용된다.

  • STATUS : 해당 노드가 부팅되어 클러스터의 리더 선출에 성공적으로 참여 하였는지를 나타내는 값으로 started 혹은 unstarted 값을 가질 수 있다. 아직 시작된 적이 없는 ETCD3 노드의 경우 unstarted 값을 가진다.

  • NAME : 해당 노드의 고유한 이름으로 ETCD3 서버 프로세스 시작 시 --name 인자로 지정한 값이다.

  • PEER ADDRS : 해당 노드의 Advertise 된 Peer 통신을 위한 URL의 목록이다.

  • CLIENT ADDRS : 해당 노드의 Advertise 된 Client 통신을 위한 URL의 목록이다.

  • IS LEARNER : 해당 멤버가 ETCD 클러스터로부터 Snapshot 및 WAL 복제 상태를 유지하지만 리더 선출 Quorum 에는 참여하지 않는 Learner 노드인지를 나타낸다.

클러스터 엔드포인트 조회

etcdctl endpoint status 로 클러스터의 모든 멤버 노드들의 접속 URL, 데이터베이스 크기, Leader 여부 및 Raft 정보를 확인할 수 있다.

$ etcdctl endpoint status -w table
+--------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|         ENDPOINT         |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+--------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| http://192.168.0.10:2379 | 670b863301943618 |  3.5.21 |  168 kB |      true |      false |         6 |        520 |                520 |        |
| http://192.168.0.11:2379 | c8245114d55ec576 |  3.5.21 |  168 kB |     false |      false |         6 |        520 |                520 |        |
| http://192.168.0.12:2379 | 7825d7b04510b842 |  3.5.21 |  168 kB |     false |      false |         6 |        520 |                520 |        |
+--------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
  • ENDPOINT : 해당 노드의 Advertise 된 Client 통신을 위한 URL의 목록이다.

  • ID : ENDPOINT 와 동일하다.

  • VERSION : 해당 노드에서 구동중인 ETCD3 서버의 버전을 나타낸다.

  • DB SIZE : 디스크에 저장된 ETCD3 데이터베이스의 크기를 나타낸다.

  • IS LEADER : 해당 노드가 이 클러스터의 Raft Leader 인지 여부를 나타낸다.

  • IS LEARNER : IS LEADER 와 동일하다.

  • RAFT TERM : Raft 알고리즘에 따른 현재 임기 (Term). 새로운 Leader 선출이 이루어질 때마다 값이 1씩 늘어난다.

  • RAFT INDEX : 해당 노드에 저장된 쓰기 작업의 Log Entry 위치를 나타낸다. Leader 선출 시 참조하며 Raft Index가 가장 높은 (즉 가장 최신 상태를 유지하고 있는) 노드가 Leader 선출 시 우선권을 갖는다.

  • RAFT APPLIED INDEX : 해당 노드의 로컬 환경 Key-Value Store에 적용되어 (Applied) 읽기 가능한 상태의 Log Entry 위치를 나타낸다. 노드의 CPU 및 디스크 환경에 따라 Raft Applied Index가 Raft Index 보다 작은 값을 갖는 (즉 데이터 쓰기 작업이 지연되는) 상황이 발생할 수 있으며 이 경우 클라이언트 설정에 따라 Data Consistency가 유지되지 않을 수 있다.

    • etcdctl get 플래그 --consistency 값에 따라 동작이 달라진다. --consistency=s (Serializable, default) 인 경우 과거 시점의 데이터를 불러온다. --consistency=l (Linearizable) 인 경우 질의하는 서버가 최신 Raft Index를 따라잡을 때까지 대기한 후 최신 시점의 데이터를 불러온다.

  • ERRORS : 해당 노드의 Endpoint에서 감지된 문제를 메세지 형태로 표현한다.


데이터 조회

ETCD3 클러스터에 저장된 데이터를 조회한다.

Prefix로 조회

etcdctl get 명령어 인자로 --prefix 옵션을 지정해 선행하는 특정 문자열과 매칭되는 Key들을 조회할 수 있다.

$ etcdctl get --prefix "/opensql/opensql/members" -w simple
/opensql/opensql/members/pg-1
{"conn_url":"postgres://192.168.131.12:5432/postgres","api_url":"http://192.168.131.12:8008/patroni","state":"running","role":"replica","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"replication_state":"streaming","timeline":3}
/opensql/opensql/members/pg-2
{"conn_url":"postgres://192.168.131.13:5432/postgres","api_url":"http://192.168.131.13:8008/patroni","state":"running","role":"primary","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"timeline":3}
/opensql/opensql/members/pg-3
{"conn_url":"postgres://192.168.131.14:5432/postgres","api_url":"http://192.168.131.14:8008/patroni","state":"running","role":"replica","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"replication_state":"streaming","timeline":3}

Range로 조회

etcdctl get 명령어 인자로 특정 Key 값 key 와 다른 Key range_end 를 지정하여 Key 인덱스 공간의 두 지점 사이, 정확히는 [key, range_end) 에 해당하는 Key의 목록을 조회할 수 있다.

  • ETCD3에 저장되는 모든 Key들은 Byte의 배열로 치환되어 Index 공간에 저장되며 대소를 비교하여 색인된다.

    • aa < ab

    • a\xff < b

  • range_end 와 일치하는 Key는 포함되지 않는다. (Exclusive)

$ etcdctl get "/opensql/opensql/members/pg-1" "/opensql/opensql/members/pg-3"
/opensql/opensql/members/pg-1
{"conn_url":"postgres://192.168.131.12:5432/postgres","api_url":"http://192.168.131.12:8008/patroni","state":"running","role":"replica","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"replication_state":"streaming","timeline":3}
/opensql/opensql/members/pg-2
{"conn_url":"postgres://192.168.131.13:5432/postgres","api_url":"http://192.168.131.13:8008/patroni","state":"running","role":"primary","version":"4.0.5","proxy_url":"postgres://192.168.131.15:6432/postgres","xlog_location":223510016,"timeline":3}

$ etcdctl get "/opensql/opensql/failover" "/opensql/opensql/historz"
/opensql/opensql/failover
{}
/opensql/opensql/history
[[1,223438192,"no recovery target specified","2025-04-18T14:27:03.269756+09:00","pg-1"],[2,223438824,"no recovery target specified","2025-04-21T15:13:37.632010+09:00","pg-2"]]


데이터베이스 유지 보수

ETCD3는 동시성 있는 클라이언트 접근을 제어하며 데이터 일관성을 유지하기 위해 Revision 정보를 기반으로 한 MVCC (Multi-Version Concurrency Control) 를 이용한다. 클러스터가 오랜 기간 유지되며 Patroni Switchover / Failover 등의 동작이 많이 일어난 경우 실제 Patroni에서 ETCD3 클러스터에 저장하는 데이터 사이즈가 작음에도 DB 사이즈가 계속해서 증가하는 문제가 발생할 수 있다.

정리할 대상 Revision 지정

etcdctl compact 명령어로 특정 Revision을 지정해 그 이전 시점의 Revision을 더 이상 참조되지 않는 상태로 정의한다. 해당 동작은 클러스터 단위의 작업이므로 별도의 endpoint를 지정하지 않고 실행할 수 있다.

## Endpoint Status를 확인해 각 노드에서 유지하고 있는 가장 최신 Revision 정보를 가져온다.
$ etcdctl endpoint status -w json | jq | grep 'revision'
        "revision": 447871,
        "revision": 447871,
        "revision": 447871,

## 해당 Revision 이전 시점의 Revision들을 더 이상 참조되지 않는 상태로 지정한다.
$ rev=447871
$ etcdctl compact $rev
compacted revision 447871

데이터 조각모음

etcdctl defrag 명령어로 참조되지 않는 Revision을 DB File에서 삭제해 디스크 공간을 확보한다. 별도 --endpoints 옵션이 없으면 로컬 노드의 디스크 공간만을 확보한다. 클러스터의 모든 Endpoint URL을 --endpoints 옵션으로 지정하면 각 호스트들의 디스크 공간을 모두 확보한다.

$ etcdctl defrag --endpoints "http://192.168.0.10:2379,http://192.168.0.11:2379,http://192.168.0.12:2379"
Finished defragmenting etcd member[http://192.168.0.10:2379]
Finished defragmenting etcd member[http://192.168.0.11:2379]
Finished defragmenting etcd member[http://192.168.0.12:2379]

$ etcdctl endpoint status --endpoints "..."
http://192.168.0.10:2379, 670b863301943618, 3.5.21, 25 kB, true, false, 8, 448000, 448000, 
http://192.168.0.11:2379, c8245114d55ec576, 3.5.21, 25 kB, false, false, 8, 448000, 448000, 
http://192.168.0.12:2379, 7825d7b04510b842, 3.5.21, 25 kB, false, false, 8, 448000, 448000,

쓰기 성능 테스트

etcdctl check perf 명령어로 1분 동안 클러스터의 쓰기 전체 처리량 (Throughput)을 테스트해볼 수 있다.

$ etcdctl check perf
 60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooom! 100.00% 1m0s
PASS: Throughput is 150 writes/s
PASS: Slowest request took 0.291186s
PASS: Stddev is 0.029467s
PASS

--load 옵션으로 요청하는 클라이언트 수 및 초당 요청 횟수를 다르게 지정할 수 있다.

  • s : 50 Clients

  • m : 200 Clients

  • l : 500 Clients

  • xl : 1000 Clients

출력되는 결과에는 PASS / FAIL Criteria가 존재한다.

  • 생성된 요청의 90% 이상 Throughput이 나올 것이다.

  • 모든 요청은 500 ms 안에 처리될 것이다.

  • 요청의 처리에 걸린 시간의 표준 편차 (stddev) 가 100 ms 이하일 것이다.

$ etcdctl check perf
 60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooom! 100.00% 1m0s
PASS: Throughput is 150 writes/s
Slowest request took too long: 0.535645s
PASS: Stddev is 0.079037s
FAIL

$ etcdctl check perf --load="xl"
 60 / 60 Booooooooooooooooooooooooooooooooooooooooooooooooooooooom! 100.00% 1m0s
FAIL: Throughput too low: 3668 writes/s
Slowest request took too long: 0.645609s
Stddev too high: 0.105516s
FAIL

성능 테스트로 가해진 부하가 많은 경우 DB Revision History로 인해 ETCD 데이터베이스 사이즈가 크게 증가할 수 있으므로 compactdefrag 작업을 수행할 것을 권장한다.


복구

ETCD3 는 클러스터 노드의 접속 URL, 클러스터 토큰, Listen URL 등 자신을 포함한 클러스터의 모든 구성을 데이터베이스 스냅샷과 같이 관리한다. 이미 구성되어 있는 ETCD3 클러스터의 노드의 환경설정을 변경하여 Key-Value 데이터만 보존한 채로 재시작하는 경우에는 일반적으로 동작하지 않는다.

서비스 중단으로 인해 발생한 장애로 Scale-In이 요구되거나, 새로운 노드를 추가해 Scale-Out을 진행하거나, 호스트의 IPv4 주소가 변경되어 클러스터를 재설정해야 하는 등 이미 구성된 ETCD3 클러스터의 환경 구성을 변경하여 재시작하고자 하는 경우, 명령줄 도구 etcdctl 을 이용해 복구를 진행해야 한다.

스냅샷 확인

복구를 위해서는 가져오고자 하는 Key-Value 데이터가 포함된 ETCD3 노드의 스냅샷 (Snapshot) 이 필요하다. ETCD3의 스냅샷은 관계형 데이터베이스의 데이터 파일에 대응되며, 특정 시점에 ETCD3 클러스터에 저장된 Key-Value 데이터뿐만 아니라 클러스터의 구성 정보 및 상태 (Raft State) 를 같이 저장한다. 파일 시스템에 저장된 ETCD3의 db 파일을 가져오거나, 구동 중인 ETCD3 클러스터로부터 생성할 수 있다.

파일시스템에서 확인

ETCD3 데이터 경로 ETCD_DATA_DIR 하부 경로 member/snap/db 에 위치한다.

$ ls -l $ETCD_DATA_DIR/member/snap/
total 168
-rw-------. 1 opensql opensql 16805888 Apr 18 14:29 db

$ cp $ETCD_DATA_DIR/member/snap/db ./mysnapshot.db

구동중인 ETCD3로부터 생성

명령줄 도구 etcdctlsnapshot save 명령어를 이용해 ETCD3 서버로부터 현재 시점의 Snapshot을 생성할 수 있다.

  • 여러 노드로 구성된 클러스터가 동작중이어도 ENDPOINT 인자로는 하나의 ETCD3 서버 URL만 주어져야 한다.

$ ETCDCTL_API=3 etcdctl --endpoints=${ENDPOINT} snapshot save mysnapshot.db

{"level":"info","ts":"2025-04-21T12:18:30.406283+0900","caller":"snapshot/v3_snapshot.go:65","msg":"created temporary db file","path":"mysnapshot.db.part"}
{"level":"info","ts":"2025-04-21T12:18:30.407142+0900","logger":"client","caller":"[email protected]/maintenance.go:212","msg":"opened snapshot stream; downloading"}
{"level":"info","ts":"2025-04-21T12:18:30.407162+0900","caller":"snapshot/v3_snapshot.go:73","msg":"fetching snapshot","endpoint":"192.168.131.12:2379"}
{"level":"info","ts":"2025-04-21T12:18:30.432538+0900","logger":"client","caller":"[email protected]/maintenance.go:220","msg":"completed snapshot read; closing"}
{"level":"info","ts":"2025-04-21T12:18:30.480983+0900","caller":"snapshot/v3_snapshot.go:88","msg":"fetched snapshot","endpoint":"192.168.131.12:2379","size":"168 kB","took":"now"}
{"level":"info","ts":"2025-04-21T12:18:30.481039+0900","caller":"snapshot/v3_snapshot.go:97","msg":"saved","path":"mysnapshot.db"}
Snapshot saved at mysnapshot.db

스냅샷으로부터 데이터 복구

명령줄 도구 etcdctlsnapshot restore 명령어를 이용해 Snapshot으로부터 ETCD3 클러스터 데이터를 생성할 수 있다.

  • ETCD3 클러스터를 초기 구성할 때와 마찬가지로 새롭게 구성할 클러스터의 초기 설정 중 Database Snapshot에 포함되는 인자들이 명령줄 인자로 모두 주어져야 한다.

    • --name : 복구할 ETCD3 서버의 노드 이름

    • --initial-cluster : 클러스터 내 모든 ETCD3 서버의 노드 이름 및 Peer 통신을 위한 Endpoint URL

    • --initial-cluster-token : 클러스터에 참여하기 위한 Initialize Token

    • --initial-advertise-peer-urls : 클러스터의 다른 노드에 알릴 이 노드의 Peer 통신을 위한 Endpoint URL

  • --skip-hash-check 옵션은 복구될 원본 스냅샷의 데이터 무결성을 검증하는 과정을 생략하기 위한 옵션이다.

    • snapshot save 명령어로 구동중인 ETCD3 서버에서 생성한 스냅샷의 경우 데이터 무결성이 유지되므로 해당 옵션 없이도 정상적으로 복구할 수 있다.

    • 파일시스템에서 복사해 온 ETCD3 데이터 db 파일의 경우 실행 시점의 클러스터 메타 정보를 포함하므로 해당 옵션 없이는 정상적으로 복구할 수 없다. --skip-hash-check 옵션을 부여해 데이터 무결성 검증을 생략한다.

$ ETCDCTL_API=3 etcdctl snapshot restore ./mysnapshot.db \
  --name node3 \
  --initial-cluster node1=http://192.168.0.8:2380,node2=http://192.168.0.9:2380,node3=http://192.168.0.10:2380 \
  --initial-cluster-token new-etcd-cluster \
  --initial-advertise-peer-urls http://192.168.0.10:2380 \
  --skip-hash-check \
  # ...

Deprecated: Use `etcdutl snapshot restore` instead.

snapshot/v3_snapshot.go:248	restoring snapshot	{"path": "member/snap/db", "wal-dir": "node3.etcd/member/wal", "data-dir": "node3.etcd", "snap-dir": "node3.etcd/member/snap", "stack": "go.etcd.io/..." }
membership/store.go:141	Trimming membership information from the backend...
membership/cluster.go:421	added member	{"cluster-id": "154dfe96307df6f0", "local-member-id": "0", "added-peer-id": "3dfe6fc7fff49d22", "added-peer-peer-urls": ["http://192.168.0.8:2380"]}
membership/cluster.go:421	added member	{"cluster-id": "154dfe96307df6f0", "local-member-id": "0", "added-peer-id": "7f846315e3b9872d", "added-peer-peer-urls": ["http://192.168.0.9:2380"]}
membership/cluster.go:421	added member	{"cluster-id": "154dfe96307df6f0", "local-member-id": "0", "added-peer-id": "c0ea9022befd3eaa", "added-peer-peer-urls": ["http://192.168.0.10:2380"]}
snapshot/v3_snapshot.go:269	restored snapshot	{"path": "member/snap/db", "wal-dir": "node3.etcd/member/wal", "data-dir": "node3.etcd", "snap-dir": "node3.etcd/member/snap"}

  • 데이터가 성공적으로 복구된 경우 ${ETCD_NAME}.etcd 이름의 디렉토리 안에 새로운 클러스터가 구성된다.

$ ls -l
total 0
drwx------. 3 root root  20 Mar 17 14:48 node1.etcd

$ ls -l node1.etcd/
drwx------. 2 root root 246 Mar 17 14:41 snap
drwx------. 2 root root 257 Mar 17 14:41 wal

  • 복구된 데이터 디렉토리를 기반으로 ETCD3 서비스를 재시작한다. 데이터 복구 시 사용한 인자와 서비스 재시작 시 사용하는 인자가 일치해야 한다.

    • 생성된 ${ETCD_NAME}.etcd 디렉토리는 기존 ETCD3 데이터 경로 밑의 member/ 서브디렉토리와 대응된다.

$ rm -rf $ETCD_DATA_DIR/member

$ cp -r node1.etcd $ETCD_DATA_DIR/member

## Service 정의에 참조된 etcd.env 파일 내용 확인
$ vi /etc/etcd/etcd.env
ETCD_NAME=node1

ETCD_INITIAL_CLUSTER=node1=http://192.168.0.8:2380,node2=http://192.168.0.9:2380,node3=http://192.168.0.10:2380
ETCD_INITIAL_CLUSTER_TOKEN=new-etcd-cluster
ETCD_INITIAL_CLUSTER_STATE=new
  --initial-cluster-token new-etcd-cluster \
  --initial-advertise-peer-urls http://192.168.0.8:2380 \

$ systemctl restart etcd.service

Last updated