Certified Kubernetes Administrator (CKA) #5 HA 클러스터: 여러 control plane, 외부 etcd cluster
#4 kubeadm 클러스터 설치에서 단일 control plane 클러스터를 부트스트랩했습니다. 그 클러스터에는 약점이 하나 있습니다. control plane 노드가 한 대뿐이라, 그 노드가 죽으면 apiserver도 etcd도 함께 사라집니다. 워커 노드의 Pod는 잠시 더 돌지만, 새 배포도 스케일링도 복구도 멈춥니다. 이번 글에서는 이 단일 장애점(single point of failure)을 없애는 고가용성(High Availability, HA) 클러스터를 다루겠습니다.
HA 클러스터의 핵심은 control plane을 여러 대로 늘리는 것입니다. 다만 control plane은 apiserver만 있는 것이 아니라 etcd라는 상태 저장소를 함께 가지므로, etcd를 control plane 노드 안에 둘지 밖에 둘지부터 결정해야 합니다. 이 선택이 stacked etcd와 external etcd 두 토폴로지를 가릅니다. 여기에 apiserver 앞단 로드밸런서, etcd 쿼럼이라는 두 개념이 더해지면 HA 클러스터의 그림이 완성됩니다.
단일 control plane의 단일 장애점 #
#2에서 봤듯이 control plane은 apiserver, etcd, scheduler, controller-manager로 이뤄집니다. 단일 노드 클러스터에서는 이 넷이 한 노드 위에 static Pod로 떠 있습니다. 그 노드가 멈추면 어떤 일이 벌어지는지 정리하겠습니다.
- apiserver 정지.
kubectl이 더는 클러스터에 닿지 못합니다. 새 배포, 스케일링, 삭제가 모두 막힙니다. - etcd 정지. 클러스터의 모든 상태가 한 노드에만 있었으므로, 그 노드의 디스크가 손상되면 클러스터 상태 자체를 잃습니다.
- scheduler 정지. 새 Pod가 노드에 배치되지 않습니다. Pending 상태로 쌓입니다.
- controller-manager 정지. reconciliation loop가 멈춰 Deployment의 원하는 상태를 유지하지 못합니다.
이미 떠 있던 워커 노드의 Pod는 kubelet이 로컬에서 계속 돌려주므로 당장은 살아 있습니다. 그러나 클러스터를 운영할 수단이 사라진 상태이므로, 사실상 운영 중단입니다. HA는 이 control plane을 여러 대로 복제해, 한 대가 죽어도 나머지가 클러스터를 계속 운영하게 만듭니다.
두 가지 토폴로지: stacked etcd와 external etcd #
control plane을 여러 대로 늘릴 때 가장 먼저 결정할 것이 etcd를 어디에 둘지입니다. kubeadm은 두 가지 토폴로지를 지원합니다.
stacked etcd 토폴로지 #
각 control plane 노드 안에 etcd 멤버를 함께 올리는 방식입니다. control plane 노드 3대를 띄우면, 그 안에서 etcd도 3-멤버 클러스터를 이룹니다. apiserver는 같은 노드의 로컬 etcd 멤버와 통신합니다.
[control plane 1] apiserver + etcd
[control plane 2] apiserver + etcd
[control plane 3] apiserver + etcd
|
(앞단 로드밸런서)
|
[워커 노드들]이 방식의 장점은 노드 수가 적고 구성이 단순하다는 것입니다. control plane 노드만 늘리면 control plane과 etcd가 동시에 HA가 됩니다. kubeadm의 기본 토폴로지이기도 합니다. 단점은 control plane과 etcd의 운명이 묶인다는 것입니다. 한 노드가 죽으면 그 노드의 apiserver와 etcd 멤버가 함께 사라집니다.
external etcd 토폴로지 #
etcd를 control plane 노드 밖에, 별도의 etcd 클러스터로 분리하는 방식입니다. control plane 노드들은 apiserver/scheduler/controller-manager만 올리고, etcd는 따로 둔 3대(혹은 5대) 노드가 담당합니다.
[control plane 1] apiserver [etcd 1]
[control plane 2] apiserver [etcd 2]
[control plane 3] apiserver [etcd 3]
| |
(앞단 로드밸런서) (apiserver가 외부 etcd로 접속)
|
[워커 노드들]이 방식의 장점은 control plane과 etcd의 장애가 분리된다는 것입니다. control plane 노드가 죽어도 etcd 클러스터는 멀쩡하고, 그 반대도 성립합니다. 단점은 노드가 더 많이 필요하고 구성이 복잡하다는 것입니다. etcd 클러스터를 별도로 세우고 인증서로 control plane과 연결하는 추가 작업이 따릅니다.
트레이드오프 정리 #
| 항목 | stacked etcd | external etcd |
|---|---|---|
| 노드 수 | 적음 (control plane = etcd) | 많음 (control plane + etcd 분리) |
| 구성 복잡도 | 낮음 (kubeadm 기본) | 높음 (etcd 별도 구축) |
| 장애 격리 | 약함 (한 노드에 apiserver+etcd) | 강함 (control plane과 etcd 분리) |
| 노드 손실 영향 | apiserver와 etcd 멤버 동시 손실 | apiserver만 손실, etcd는 무관 |
| 권장 상황 | 소규모, 빠른 구축 | 대규모, 높은 신뢰성 요구 |
CKA 관점에서는 stacked가 기본이고 단순하며, external은 장애 격리가 강한 대신 복잡하다는 트레이드오프를 이해하는 것이 핵심입니다. 실제 시험에서 둘을 처음부터 끝까지 구축하라는 문제가 나오기보다는, 이 차이와 etcd 멤버십을 이해하고 있는지를 봅니다.
앞단 로드밸런서 #
control plane을 여러 대로 늘리면 곧바로 따라오는 질문이 있습니다. 워커 노드와 kubectl은 어느 apiserver로 접속해야 하는가입니다. apiserver가 3개인데 그중 하나의 IP를 직접 적어 두면, 그 노드가 죽는 순간 다시 단일 장애점이 됩니다.
그래서 HA 클러스터는 여러 apiserver **앞단에 로드밸런서(LB)**를 둡니다. 워커 노드와 클라이언트는 LB의 단일 주소(가상 IP 또는 도메인)로 접속하고, LB가 살아 있는 apiserver로 요청을 분배합니다. 한 apiserver가 죽으면 LB가 그 노드를 빼고 나머지로 보냅니다.
- HAProxy. apiserver들 앞에 두는 L4로드밸런서로 흔히 쓰입니다. 6443 포트로 들어온 요청을 각 control plane의 6443으로 분배합니다.
- keepalived. VRRP로 가상 IP(VIP)를 노드 사이에서 떠다니게 해, LB 자체의 단일 장애점도 줄입니다. HAProxy와 함께 자주 묶입니다.
- 클라우드 로드밸런서. AWS NLB, GCP TCP LB처럼 클라우드가 제공하는 관리형 L4 LB도 같은 역할을 합니다.
--control-plane-endpoint로 init
#
이 LB 주소를 클러스터에 고정된 control plane 엔드포인트로 박아 두는 것이 핵심입니다. 첫 control plane을 부트스트랩할 때 --control-plane-endpoint로 LB 주소를 지정합니다.
kubeadm init \
--control-plane-endpoint "LOAD_BALANCER_DNS:6443" \
--upload-certs \
--pod-network-cidr=10.244.0.0/16--control-plane-endpoint를 LB의 DNS나 VIP로 지정하면, 이후 생성되는 kubeconfig와 클러스터 내부 설정이 모두 이 단일 엔드포인트를 바라봅니다. 그래서 control plane을 더 추가해도 클라이언트 설정을 바꿀 필요가 없습니다. 반대로 단일 노드로 kubeadm init을 한 뒤에 control plane을 추가하려 하면, 엔드포인트가 한 노드의 IP로 박혀 있어 HA 전환이 까다로워집니다. HA를 염두에 둔다면 처음부터 --control-plane-endpoint를 지정해야 합니다.
--upload-certs는 control plane용 인증서를 클러스터에 암호화해 올려, 뒤에 합류하는 control plane 노드가 그 인증서를 내려받아 쓰게 해 주는 옵션입니다. 다음 절의 조인에서 쓰입니다.
etcd 쿼럼과 내결함성 #
HA에서 control plane만큼 중요한 것이 etcd의 동작 방식입니다. etcd는 분산 키-값 저장소로, 여러 멤버가 같은 데이터를 복제해 가집니다. 다만 멤버끼리 데이터가 어긋나지 않도록, 과반(majority)의 멤버가 동의해야 쓰기를 확정합니다. 이 과반을 **쿼럼(quorum)**이라고 합니다.
쿼럼은 (N / 2) + 1로 계산합니다. 멤버가 N개일 때 그 과반이 살아 있어야 클러스터가 쓰기를 받습니다. 살아 있는 멤버가 과반에 못 미치면 etcd는 데이터 일관성을 지키기 위해 쓰기를 거부하고, 그 위의 apiserver도 사실상 멈춥니다.
| 멤버 수 (N) | 쿼럼 | 견딜 수 있는 장애 수 |
|---|---|---|
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
표에서 보듯 멤버를 홀수로 두는 것이 효율적입니다. 멤버가 2개면 쿼럼이 2라 한 대만 죽어도 클러스터가 멈춥니다. 1개일 때보다 내결함성이 늘지 않으면서 비용만 늘어납니다. 3개면 한 대가 죽어도 나머지 2개가 과반을 이뤄 정상 운영됩니다. 4개는 3개와 똑같이 한 대만 견디므로, 한 대를 더 쓰고도 이득이 없습니다. 그래서 etcd는 3개 또는 5개처럼 홀수로 구성하는 것이 정석입니다. 대부분의 클러스터에 3-멤버면 충분하고, 더 높은 내결함성이 필요하면 5-멤버를 씁니다.
control plane 노드 추가 조인 #
첫 control plane을 LB 엔드포인트로 부트스트랩하고 나면, 두 번째와 세 번째 control plane을 합류시킵니다. 워커 노드를 합칠 때 쓰던 kubeadm join에 --control-plane 플래그와 인증서 키를 더합니다.
kubeadm join LOAD_BALANCER_DNS:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key>각 인자의 역할을 정리하면 다음과 같습니다.
LOAD_BALANCER_DNS:6443. 합류 대상은 특정 control plane이 아니라 LB 엔드포인트입니다. 이 주소로 클러스터에 들어갑니다.--token/--discovery-token-ca-cert-hash. 워커 조인과 동일한 인증 정보로, 합류하는 노드가 올바른 클러스터인지 확인합니다.--control-plane. 이 노드를 워커가 아니라 control plane으로 합류시키라는 표시입니다. apiserver/scheduler/controller-manager(그리고 stacked라면 etcd 멤버)가 이 노드에 올라옵니다.--certificate-key. 앞서--upload-certs로 올려 둔 인증서를 내려받아 복호화하는 키입니다. 이 키가 있어야 control plane용 인증서를 공유받아 합류할 수 있습니다.
조인 명령에 필요한 토큰과 인증서 키는 첫 노드에서 다시 뽑을 수 있습니다.
# control plane 조인용 인증서 키 재업로드
kubeadm init phase upload-certs --upload-certs
# 조인 명령 전체를 출력 (control plane용)
kubeadm token create --print-join-command인증서 키는 보안상 일정 시간이 지나면 만료되므로, 새 control plane을 합칠 때 위 명령으로 다시 발급받는 경우가 많습니다.
검증: 노드와 etcd 멤버십 확인 #
control plane을 모두 합쳤다면 두 가지를 확인합니다. 노드 목록에 control plane 역할이 여러 개인지, 그리고 etcd 멤버가 그만큼 있는지입니다.
control plane 노드 확인 #
k get nodesNAME STATUS ROLES AGE VERSION
cp-1 Ready control-plane 30m v1.31.0
cp-2 Ready control-plane 12m v1.31.0
cp-3 Ready control-plane 8m v1.31.0
worker-1 Ready <none> 25m v1.31.0
worker-2 Ready <none> 25m v1.31.0ROLES 열에 control-plane이 세 개 보이면 control plane HA가 구성된 것입니다. 모두 Ready인지도 함께 확인합니다.
etcd 멤버 확인 #
stacked 토폴로지라면 etcd 멤버는 control plane 노드 안의 static Pod로 떠 있습니다. etcdctl member list로 멤버 수와 상태를 확인합니다. etcd는 mTLS로 보호되므로 인증서 경로를 함께 넘겨야 합니다.
ETCDCTL_API=3 etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key<id1>, started, cp-1, https://10.0.0.11:2380, https://10.0.0.11:2379, false
<id2>, started, cp-2, https://10.0.0.12:2380, https://10.0.0.12:2379, false
<id3>, started, cp-3, https://10.0.0.13:2380, https://10.0.0.13:2379, false멤버가 3개이고 모두 started이면 etcd 클러스터가 정상으로 쿼럼을 이루고 있습니다. 멤버 상태를 더 자세히 보려면 endpoint health와 endpoint status도 유용합니다.
ETCDCTL_API=3 etcdctl endpoint health \
--cluster \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key이 명령들은 #7 etcd 백업과 복구와 #24 Control plane 트러블슈팅에서 다시 쓰이므로, 인증서 경로와 함께 손에 익혀 두면 좋습니다.
시험 관점 #
CKA 실기에서 HA 클러스터를 처음부터 끝까지 구축하라는 문제는 흔치 않습니다. 2시간 안에 LB를 세우고 control plane 3대를 조인하는 것은 시간이 너무 많이 들기 때문입니다. 시험이 더 자주 묻는 것은 개념과 etcd 멤버십을 이해하고 있는지입니다.
- 단일 control plane의 단일 장애점이 무엇이고, HA가 그것을 어떻게 없애는지
- stacked etcd와 external etcd의 차이와 트레이드오프
- 왜 etcd 멤버를 홀수로 두는지, 3개일 때 견딜 수 있는 장애가 몇 개인지
--control-plane-endpoint를 처음부터 지정해야 하는 이유etcdctl member list로 멤버를 확인하고, 멤버를 추가/삭제하는 흐름
특히 etcd 멤버를 etcdctl member add / etcdctl member remove로 다루는 작업은 #7과 #24에서 다시 만나므로, 이번 글의 멤버십 개념을 그 토대로 삼겠습니다.
시험 포인트 #
- HA의 목적은 control plane의 단일 장애점 제거입니다. control plane을 여러 대로 복제해 한 대가 죽어도 클러스터를 계속 운영합니다.
- stacked etcd는 control plane 노드 안에 etcd를 함께 두는 kubeadm 기본 토폴로지로, 단순하지만 장애 격리가 약합니다.
- external etcd는 etcd를 별도 클러스터로 분리해 장애를 격리하지만, 노드가 더 필요하고 구성이 복잡합니다.
- 앞단 로드밸런서는 여러 apiserver를 단일 엔드포인트로 묶습니다. HAProxy/keepalived 또는 클라우드 LB를 씁니다.
- **
--control-plane-endpoint**는 LB 주소를 클러스터의 고정 엔드포인트로 박습니다. HA라면 첫kubeadm init부터 지정합니다. - etcd 쿼럼은
(N/2)+1입니다. 멤버를 홀수(3 또는 5)로 두는 것이 정석이며, 3-멤버는 1대, 5-멤버는 2대 장애까지 견딥니다. - control plane 조인은
kubeadm join에--control-plane과--certificate-key를 더합니다. 인증서는--upload-certs로 공유합니다. - 검증은
k get nodes로control-plane역할이 여러 개인지,etcdctl member list로 etcd 멤버가 모두started인지 확인합니다.
정리 #
이번 글에서 잡은 것을 정리하겠습니다.
- 단일 control plane은 그 노드가 죽으면 클러스터 운영이 멈추는 단일 장애점입니다. HA는 control plane을 여러 대로 복제해 이를 없앱니다.
- HA 토폴로지는 두 가지입니다. control plane 안에 etcd를 두는 stacked etcd와, etcd를 별도 클러스터로 분리하는 external etcd입니다. 단순함과 장애 격리 사이의 트레이드오프로 고릅니다.
- 여러 apiserver 앞에 로드밸런서를 두고,
--control-plane-endpoint로 그 주소를 클러스터의 고정 엔드포인트로 지정합니다. - etcd는 쿼럼(과반)으로 일관성을 지키므로 멤버를 홀수로 둡니다. 3-멤버는 1대 장애를 견딥니다.
- control plane 노드는
kubeadm join --control-plane에 인증서 키를 더해 합류시키고,k get nodes와etcdctl member list로 검증합니다.
다음: 클러스터 업그레이드 #
HA로 control plane을 여러 대로 늘렸으니, 이제 이 클러스터를 버전 업그레이드할 차례입니다. 여러 control plane이 있을 때 업그레이드는 한 대씩 순서대로 진행해야 무중단으로 끝납니다.
#6 클러스터 업그레이드에서는 kubeadm upgrade plan으로 업그레이드 경로를 확인하고, kubeadm upgrade apply로 control plane을 한 마이너 버전 올린 뒤, 노드별로 kubectl drain과 kubectl uncordon을 써서 워크로드를 비우고 업그레이드하는 흐름을 정리하겠습니다.