kubespray 와 Terraform 으로 클러스터 세우기
한 대에서 여러 대로 — 쿼럼, 로드밸런서, 노드를 더하고 빼는 순서
한 줄 요약
노드를 여러 대로 넓힐 때 kubespray 에서 바뀌는 것은 인벤토리의 이름 몇 줄과 host_vars 파일 몇 개지만, 그 뒤에는 etcd 쿼럼·API 서버 로드밸런싱·노드 추가와 제거의 순서라는 설계 결정이 있습니다.
왜 이게 필요했나
이 코스의 실습은 VM 한 대에서 제어 노드와 대상 노드를 겸했습니다. 실제 클러스터는 다릅니다. 컨트롤 플레인 한 대가 죽으면 API 가 멈추고, etcd 한 대가 죽으면 데이터가 멈춥니다. 쿠버네티스 문서의 고가용성 토폴로지 절은 두 가지 모양을 설명합니다 — etcd 를 컨트롤 플레인 노드에 함께 두는 stacked 와, etcd 를 따로 두는 external. stacked 는 노드가 적게 들지만 노드 하나를 잃으면 컨트롤 플레인과 etcd 멤버를 함께 잃고, external 은 그 위험을 나누는 대신 노드가 두 배로 듭니다. kubespray 에서 이 선택은 [etcd:children] kube_control_plane 이냐 [etcd] 에 다른 이름을 적느냐의 차이입니다.
어떻게 동작하나
etcd 는 홀수입니다. etcd 는 쓰기마다 과반(쿼럼, n//2+1)의 동의를 받습니다. 그래서 견딜 수 있는 장애 수는 n − 과반입니다.
멤버 과반 견디는 장애
1 1 0
2 2 0 ← 한 대보다 나을 것이 없다
3 2 1
5 3 2
7 4 3 ← 쓰기마다 넷을 기다린다
두 대는 한 대와 견디는 수가 같으면서 과반만 커집니다. kubespray 는 이것을 validate_inventory 의 "Stop if even number of etcd hosts" 로 막고, 문서도 장애 대비에는 3대 이상을 두라고 합니다. etcd FAQ 는 운영 클러스터에 3대나 5대를 권하고, 멤버를 늘릴수록 쓰기 지연이 늘어난다고 설명합니다.
워커는 localhost 로 API 서버에 붙습니다. 컨트롤 플레인이 여럿이면 kubelet 과 kube-proxy 가 어느 API 서버에 붙을지 정해야 합니다. kubespray 의 기본은 외부 로드밸런서(loadbalancer_apiserver)를 정의하지 않으면 loadbalancer_apiserver_localhost 가 참이 되어, 컨트롤 플레인이 아닌 노드마다 nginx 프록시(loadbalancer_apiserver_type: nginx)를 띄우고 localhost 의 kube_apiserver_port(6443)로 받아 모든 API 서버에 나눠 주는 방식입니다. HA 문서는 이 방식이 전용 LB 보다 API 서버에 헬스 체크를 더 많이 보내 덜 효율적이지만, VIP 를 관리하기 번거로운 곳에서 실용적이라고 적습니다. 외부 클라이언트(운영자의 kubectl 등)를 위해서는 따로 LB 나 kube-vip 를 둡니다.
노드 추가와 제거는 전용 플레이북입니다. 시작하기 문서의 순서는 이렇습니다. 워커를 더할 때는 인벤토리 그룹에 이름을 더하고 scale.yml 을 돌립니다 — cluster.yml 을 다시 돌려도 되지만 scale.yml 은 새 워커에 kubelet 을 올리는 데 필요한 것만 합니다. 뺄 때는 remove-node.yml -e node=<이름> 이 드레인 → 서비스 중지 → 인증서 정리 → 노드 삭제를 합니다. 업그레이드 문서는 --limit 으로 노드를 골라 돌리기 전에 playbooks/facts.yml 을 제한 없이 한 번 돌려 facts 캐시를 새로 하라고 합니다. 저장소의 ansible.cfg 가 facts 를 /tmp 에 jsonfile 로 하루(86400초) 캐시하는데, --limit 실행은 다른 노드의 facts 를 모으지 않고 캐시를 쓰기 때문입니다.
인벤토리 검사는 노드 없이 할 수 있습니다. boilerplate.yml 은 facts 를 모으지 않고 인벤토리와 변수만 봅니다. -e ansible_connection=local 로 연결을 덮으면 실제 노드가 없어도 돕니다. 실측에서 다섯 노드 인벤토리를 2.4초에 검사했습니다. --list-hosts 는 아무것도 바꾸지 않고 각 play 가 누구를 겨냥하는지만 보여 주므로, scale.yml 이 새 노드만 겨냥하는지 remove-node.yml 이 뺄 노드만 확인하는지를 돌리기 전에 볼 수 있습니다.
현장에서 만나는 모습
자주 보는 실패는 셋입니다. etcd 를 짝수로 늘리는 것(4대가 되면 과반이 3이라 3대일 때보다 견디는 수는 그대로다), 컨트롤 플레인을 늘리면서 워커의 API 서버 접근 방식을 정하지 않는 것, 그리고 새 노드만 --limit 으로 돌리면서 facts 캐시가 비어 새 노드의 /etc/hosts 나 etcd 설정이 엉뚱한 주소를 받는 것입니다.
이 모듈은 설계와 검사까지입니다. 세션 하나에 VM 여러 대를 주는 기능이 준비되면 이 인벤토리를 그대로 써서 노드 조인(scale.yml), 컨트롤 플레인 3대와 etcd 쿼럼, 노드 장애와 복구(remove-node.yml·recover-control-plane.yml), 한 대씩 올리는 롤링 업그레이드(serial=1)를 실제로 해 보는 실습이 이어집니다. 한 대짜리 실습에서 연결 방식을 host_vars 로 빼 둔 것이 그때를 위한 준비였습니다.
다음 실습에서 할 것
샘플을 복사해 컨트롤 플레인 3대·워커 2대의 인벤토리와 노드별 host_vars 를 만들고, ssh 없이 kubespray 의 인벤토리 검사를 통과시킵니다. etcd 를 두 대로 바꾼 인벤토리가 어디서 막히는지 보고 쿼럼 표를 계산합니다. 워커가 API 서버에 붙는 방식을 기본값에서 읽고, 워커 하나를 더하고 빼는 명령이 누구를 겨냥하는지 --list-hosts 로 확인한 뒤 런북에 적습니다.