Kubernetes etcd 내부의 데이터 까보기

Intro

Kubernetes의 기본 아키텍처를 살펴보면 내부 데이터 저장을 위해 etcd라는 별도의 K-V store를 사용한다. k8s의 Pod Scheduling Workflow를 살펴보면, api-server로 요청된 구성을 etcd에 desired state로 등록한뒤, controller가 current state와 desired state를 맞추기 위한 조정을 수행하여 Pod가 생성된다.


일반적으로 명령어(cli)나 구성파일(YAML)의 형식으로 리소스를 정의해 api-server에 전송하여 실행한다. 이때 실제로 etcd에는 data가 어떤식으로 저장되는지 궁금해졌다. 하지만 일반적으로 k8s와 etcd를 검색하면 architecture 또는 etcd backup/restore만 나오지 실제 database 로써의 etcd를 제어하고 데이터를 조회하는 글은 없었다. 그래서 몇가지 자료를 reference 삼아 한번 수행해보았다.


M1 Macbook 환경에서 minikube를 사용해 로컬에 컨테이너 기반의 쿠버네티스 클러스터를 생성한다. 이후 해당 노드에 컨테이너로 떠있는 etcd-minikube에 대해서 내부에 어떤 데이터들이 있는지, 어떻게 저장되어있는지 확인해보고자 한다.


etcd 패키지 설치 및 명령어 사용

컨테이너 기반 k8s 클러스터 생성 도구인 minikube를 사용해 k8s환경을 구성할것이다. brew install minikube로 설치 한 뒤, minikube start로 기본 클러스터를 생성한다. 그 뒤 minikube ssh 명령어로 노드에 ssh 접속할 수 있다.

노드에서 https://github.com/etcd-io/etcd/releases/ 의 설치 지침을 따라 etcd package를 설치한다. 설치가 완료되면 다음과 같은 명령어 도구를 사용할 수 있다.

ETCD_VER=v3.5.12

# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}

rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test

curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz

/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version
/tmp/etcd-download-test/etcdutl version
  • etcd : etcd 서버 시작, 중지 삭제 등 관련
  • etcdctl: etcd서버랑 상호작용

이 둘 중 etcd 조회에 필요한것은 etcdctl 명령어이다. 기본적인 kubadm을 통한 구성 과정에서, cert,ca,key 같은 인증서가 같이 구성되어 HTTPS 암호화 되어있다.


minikube로 구성된 클러스터의 경우, 노드의 /var/lib/minikube/certs/etcd 경로에 인증서들이 저장되어있다. 해당 경로에는 ca, healthcheck-client, peer, server등 여러 인증서들이 존재하나 접근을 위해서는 ca.crt(CA), healthcheck-client.crt(Cert), healthcheck-client.key(Key)파일이 필요하다. bash 쉘에 다음과 같은 alias 를 추가해 놓는다.

alias etcdctl='sudo ETCDCTL_API=3 /tmp/etcd-download-test/etcdctl --cacert /var/lib/minikube/certs/etcd/ca.crt --cert /var/lib/minikube/certs/etcd/healthcheck-client.crt --key /var/lib/minikube/certs/etcd/healthcheck-client.key' >> ~/.bashrc

kubernetes에서 etcd에 저장되어있는 데이터 확인하기

etcd에서 Kubernetes API 오브젝트들은 /registry prefix와 함께 저장된다. 기본적으로 약 6000여개의 오브젝트들 존재
etcdctl get --prefix /registry | wc -l

etcdctl get --prefix /registry | wc -l
# 5882

각 오브젝트들은 /registry/[리소스유형]/[네임스페이스]/[리소스명] 등의 경로로 각 리소스 정보가 저장되어있다. (리소스별 차이 존재)

etcdctl get --prefix /registry --keys-only
etcdctl get --prefix /registry/pods/kube-system/ --keys-only

그러나 데이터(value)는 binary 형태로 저장되어있기때문에 바로 그 내용을 읽기 쉽지 않다.

etcdctl get --prefix /registry/pods/kube-system/etcd-minikube

etcdctl에 대해 api로 요청하면 JSON처럼 받아볼 수 있지만 지금처럼 직접 접근할경우 추가설정이 필요하다.

> API를 통한 확인은 하단 etcd를 데이터베이스로 활용하는 예시참조


json 형식으로 출력 및 정렬 > key/value가 base64 디코딩 되어있음

etcdctl get --prefix /registry/pods/kube-system/etcd-minikube --write-out=json | jq
# {
#   "header": {
#     "cluster_id": 3473169929273797577,
#     "member_id": 6163444737228147512,
#     "revision": 7237326,
#     "raft_term": 3
#   },
#   "kvs": [
#     {
#       "key": "L3JlZ2lzdHJ5L3BvZHMva3ViZS1zeXN0ZW0vZXRjZC1jMm1hc3RlcjE=",
#       "create_revision": 248,
#       "mod_revision": 2306673,
#       "version": 6,
#       "value": "az..중간생략..GgAiAA=="
#     }
#   ],
#   "count": 1
# }

key base64 디코딩

etcdctl get --prefix /registry/pods/kube-system/etcd-minikube --write-out=json | jq -r .kvs[].key | base64 --decode
# /registry/pods/kube-system/etcd-minikube

value binary를 xxd 명령어로 hexdump? 리버스?하기

etcdctl get /registry/pods/kube-system/etcd-minikube | xxd
etcdctl get /registry/pods/kube-system/etcd-minikube | xxd | grep -A2 Run

etcd를 데이터베이스처럼, 값을 입력하고 확인하기

값 입력 및 조회

etcdctl put foo bar
# OK

etcdctl get foo
# bar

입력 값 상세 조회 및 value decode (또는 –print-value-only 옵션)

etcdctl get --write-out=json foo | jq
# {
#     "header": {
#       "cluster_id": 3473169929273797577,
#       "member_id": 6163444737228147512,
#       "revision": 7237973,
#       "raft_term": 3
#     },
#     "kvs": [
#       {
#         "key": "Zm9v",
#         "create_revision": 7207313,
#         "mod_revision": 7207313,
#         "version": 1,
#         "value": "YmFy"
#       }
#     ],
#     "count": 1
#   }
etcdctl get --write-out=json foo | jq -r .kvs[].value | base64 --decode

etcdctl get foo --print-value-only

값지우기

etcdctl del foo
# 1

etcd를 데이터베이스로 활용하는 예시

https://gasidaseo.notion.site/MSA-13-382799b72d5d49a9a15dcafd123c1aa8#df5cb24d8e88436e92fe131c5b0fd97e 중 1. 은행 계좌 조회를 배포한다.

https://raw.githubusercontent.com/tigera/ccol1/main/yaobank.yaml
다만 기존에 떠있는 kube-system의 etcd를 활용하는것이아니라,
별도의 etcd pod를 생성하고 그곳에 데이터 저장 및 조회를 한다.

  • 다른 파드 접속해서 데이터베이스 확인
kubectl exec -it $(kubectl get pods -n yaobank -l app=customer -o name) -n yaobank -c customer -- curl -s http://database:2379/v2/keys?recursive=true | python -m json.tool
  • 직접 데이터베이스 파드에 요청보내서 확인
DATABASE=$(kubectl get pods -n yaobank -l app=database -o jsonpath={.items[0].status.podIP})
curl -s $DATABASE:2379/v2/keys | jq
# {
#     "action": "get",
#     "node": {
#       "dir": true,
#       "nodes": [
#         {
#           "key": "/accounts",
#           "dir": true,
#           "modifiedIndex": 4,
#           "createdIndex": 4
#         }
#       ]
#     }
#   }
curl -s $DATABASE:2379/v2/keys?recursive=true | jq
{
  "action": "get",
  "node": {
    "dir": true,
    "nodes": [
      {
        "key": "/accounts",
        "dir": true,
        "nodes": [
          {
            "key": "/accounts/436839",
            "dir": true,
            "nodes": [
              {
                "key": "/accounts/436839/name",
                "value": "George Ball",
                "modifiedIndex": 10,
                "createdIndex": 10
              },
              {
                "key": "/accounts/436839/balance",
                "value": "543.34",
                "modifiedIndex": 11,
                "createdIndex": 11
              }
            ],
            "modifiedIndex": 10,
            "createdIndex": 10
          },
          {
            "key": "/accounts/036273",
            "dir": true,
            "nodes": [
              {
                "key": "/accounts/036273/name",
                "value": "Sally Yates",
                "modifiedIndex": 12,
                "createdIndex": 12
              },
              {
                "key": "/accounts/036273/balance",
                "value": "3597.89",
                "modifiedIndex": 13,
                "createdIndex": 13
              }
            ],
            "modifiedIndex": 12,
            "createdIndex": 12
          },
          {
            "key": "/accounts/468009",
            "dir": true,
            "nodes": [
              {
                "key": "/accounts/468009/name",
                "value": "Laura Sargent",
                "modifiedIndex": 14,
                "createdIndex": 14
              },
              {
                "key": "/accounts/468009/balance",
                "value": "8854.39",
                "modifiedIndex": 15,
                "createdIndex": 15
              }
            ],
            "modifiedIndex": 14,
            "createdIndex": 14
          },
          {
            "key": "/accounts/519940",
            "dir": true,
            "nodes": [
              {
                "key": "/accounts/519940/name",
                "value": "Spike Curtis",
                "modifiedIndex": 4,
                "createdIndex": 4
              },
              {
                "key": "/accounts/519940/balance",
                "value": "2389.45",
                "modifiedIndex": 5,
                "createdIndex": 5
              }
            ],
            "modifiedIndex": 4,
            "createdIndex": 4
          },
          {
            "key": "/accounts/407221",
            "dir": true,
            "nodes": [
              {
                "key": "/accounts/407221/name",
                "value": "Simon Gideon",
                "modifiedIndex": 6,
                "createdIndex": 6
              },
              {
                "key": "/accounts/407221/balance",
                "value": "95780.11",
                "modifiedIndex": 7,
                "createdIndex": 7
              }
            ],
            "modifiedIndex": 6,
            "createdIndex": 6
          },
          {
            "key": "/accounts/387747",
            "dir": true,
            "nodes": [
              {
                "key": "/accounts/387747/name",
                "value": "Harry Norman Kynes",
                "modifiedIndex": 8,
                "createdIndex": 8
              },
              {
                "key": "/accounts/387747/balance",
                "value": "39082.11",
                "modifiedIndex": 9,
                "createdIndex": 9
              }
            ],
            "modifiedIndex": 8,
            "createdIndex": 8
          }
        ],
        "modifiedIndex": 4,
        "createdIndex": 4
      }
    ]
  }
}

etcd 시각화 도구 – 쓸만한 도구 없음

위처럼 cli 명령어를 통해서 조회할 수 있었지만 조금 더 사용하기 쉽고 편하게 볼수있는 GUI 환경이 있나 궁금해졌다. 관련도구를 서치해보았을때 공식적으로 지원되는 도구는 없으며 몇가지 조회되는 도구는 최소 2년 이상 관리되지 않고있었다.

데이터베이스로써의 etcd 클러스터를 생성하고 관리하는 도구에 가깝지 k8s에 포함된 etcd를 위한 tool은 별로 없었다.

etcd manager

https://etcd.io/docs/v3.5/integrations/

etcd 공식홈페이지에서의 도구에서 소개되어있다.

https://etcdmanager.io/

연결을 위한 인증 등록시 Error: Error: 14 UNAVAILABLE: Empty update 에러와 함께 연동 실패한다. 이에관한 많은 이슈가 등록되어있으나 패치되지 않을듯 하다

https://github.com/gtamas/etcdmanager/issues/73
https://github.com/gtamas/etcdmanager/issues/119
https://github.com/gtamas/etcdmanager/issues/72

etcder

etcd manager의 issue들을 살펴보던중 https://github.com/gtamas/etcdmanager/issues/109 한 issue의 댓글로 알게되었다.

구성한 뒤 etcd 서버 정보를 연결하면 위와같이 시각화되어 조회할 수 있다.

그러나 한페이지에 최대 100개의 값밖에 조회할 수 없으며, 중간의 tree 모양 view로 전환하는듯한 아이콘을 클릭하면 앱이 뻗어버린다.

뭐가 문제일까 궁금해서 소스코드를 보려고 하였으나 소스코드는 공개되지않은채 release만 올라오고 있었고 그 와중에 GPL-3.0 license 라이센스는 등록하여 수상하여 바로 삭제

kstone

https://github.com/kstone-io/kstone

2년전 개발중단 되었으나 설치를 위한 chart의 일부값을 수정하면 설치하면 webUI 등 일부 기능은 사용이 가능하다.

  • sa 생성시 token이 같이 생성되지 않기에 별도 token 생성
  • kstone helm 차트에 내장되어있는 kube-promethus-stack enabled:false (리소스 미반영)
  • 기타 애매한 항목 차트에서 제거
  • nginx-ingress 생성 후 ingress를 통한 접속
  • 초기 ID/비밀번호: admin/adm1n@kstone.io

아래 사진과 같이 tree 형식으로 /registry에 등록된 내용을 확인할 수 있고, 보통 kubectl edit [리소스종류] [리소스명]으로 보는 결과와 비슷하게 저장되어있음을 확인할 수 있었다.

그러나 일부 리소스(crd?)는 여전히 binary 상태로 조회됨을 확인할 수 있다.

이 외에도 제대로 구성한 kstone은 etcd에 대한 monitoring/ 백업 자동화 등을 수행할 수 있다. 다만 해당기능을 굳이 kstone이라는 별도 어플리케이션 레이어를 사용해야하느냐는 의문이다. 기존 모니터링을 위해 kube-prometheus-stack이 구성되어있으면 거기서 etcd를 타겟잡으면 되고, 없을 경우에 구성할 순 있겠다. 백업 또한 k8s 플랫폼단이나 마스터 노드단에서 진행할 수 있을텐데 굳이..?


Outro

일련의 실습과정을 통해 Kubernetes API object들이 ETCD 서버안에 어떻게 저장되는지 CLI 명령어를 통해 / WEB UI로 확인해 보았다.

뭔가 특별하게 재밌는것이 있을 줄 알았는데 대체로 /registy/[리소스종류/[리소스명] 라는 key 에 kubectl get [리소스종류] [리소스명] -o json/yaml 에 해당하는 value로 저장되있다는 사실을 확인할 수 있었다.

Leave a Reply