LabHub
开始
学习 学习路径 课程

用 Kubespray 与 Terraform 搭建集群

在一台 VM 上设计并检查五节点集群

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

컨트롤 플레인 3대(etcd 겸용)와 워커 2대의 인벤토리를 설계하고, 실제 노드 없이도 확인할 수 있는 것 — 그룹 배치, kubespray 의 인벤토리 검사, etcd 홀수 규칙과 쿼럼, API 서버 로드밸런싱 방식, 노드를 더하고 뺄 때 플레이북이 겨냥하는 곳 — 을 VM 한 대에서 확인합니다.

왜 중요한가

한 대짜리 클러스터에서 여러 대로 넘어갈 때 달라지는 것은 설치 명령이 아니라 설계입니다. 컨트롤 플레인을 몇 대 둘지, etcd 를 겸하게 할지, 워커가 어느 API 서버에 붙을지, 노드를 더하고 뺄 때 어떤 플레이북을 어디로 좁혀 돌릴지가 모두 인벤토리와 몇 개의 변수로 정해집니다. 그리고 이 결정들은 설치를 시작한 뒤에 바꾸기 어렵습니다. 실제 노드를 만들기 전에 인벤토리를 도구로 검사해 두면, 설치 도중에 멈추는 일의 상당수를 미리 막을 수 있습니다. 노드 조인·장애·롤링 업그레이드를 실제 VM 여러 대로 해 보는 실습은 세션 하나에 VM 여러 대를 주는 기능이 준비되면 이 모듈 뒤에 이어집니다.

단계

  1. /opt/ks/kubespray/inventory/sample/root/ks/inventory/ha 로 복사하고 /root/ks/inventory/ha/inventory.inicp1·cp2·cp3 을 kube_control_plane 에(etcd 는 kube_control_plane 을 자식으로), w1·w2 를 kube_node 에 넣고 k8s_cluster 가 두 그룹을 자식으로 받게 적으세요. 컨트롤 플레인은 워커를 겸하지 않습니다. 노드마다 /root/ks/inventory/ha/host_vars/<노드>.ymlansible_host·ip(cp1=192.0.2.11 부터 w2=192.0.2.15 까지 차례로)와 ansible_user: ubuntu 를 두세요.
  2. /opt/ks/kubespray 에서 ansible-playbook -i /root/ks/inventory/ha/inventory.ini playbooks/boilerplate.yml -e ansible_connection=local 을 돌려 출력 전체를 /root/ks/ha/validate.log 에 남기세요. 다섯 노드 모두 PLAY RECAP 이 failed=0 이어야 합니다.
  3. /root/ks/inventory/ha-even 에 1단계 인벤토리를 복사하되 etcd 를 cp1·cp2 두 대만 두도록([etcd] 에 두 이름) 바꾸고, 같은 방법(-e ansible_connection=local)으로 boilerplate 를 돌려 보세요. /root/ks/ha/even.jsonfailed_task(실패한 작업 이름, 역할 접두사 없이), etcd_members(그 인벤토리의 etcd 호스트 수), quorum(그 수의 과반), tolerated_failures(몇 대까지 잃어도 쓰기가 되는가)를 적으세요.
  4. /root/ks/ha/quorum.json 에 etcd 멤버 수 1·3·5·7 각각에 대해 quorum(과반)과 tolerated_failures(쓰기를 유지하며 잃을 수 있는 멤버 수)를 {"1": {"quorum": .., "tolerated_failures": ..}, "3": {...}, ...} 모양으로 적으세요.
  5. kubespray 의 roles/kubespray_defaults/defaults/main/main.ymldocs/operations/ha-mode.md 를 읽고 /root/ks/ha/lb.jsonlocalhost_lb(외부 로드밸런서를 정의하지 않았을 때 loadbalancer_apiserver_localhost 의 결과, 불리언), lb_type(loadbalancer_apiserver_type 기본값), lb_port(그 로컬 프록시가 쓰는 포트 — loadbalancer_apiserver_port 가 없으면 따르는 값, 숫자), who_uses_it(그 로컬 프록시를 쓰는 노드 그룹: "kube_node""kube_control_plane" 중 문서가 말하는 쪽)를 적으세요.
  6. 1단계 인벤토리의 kube_node 에 w3(host_vars 에 192.0.2.16)을 더하세요. 그다음 /opt/ks/kubespray 에서 scale.yml --list-hosts --limit w3remove-node.yml --list-hosts -e node=w2 를 인벤토리 /root/ks/inventory/ha/inventory.ini 로 돌려 보고, /root/ks/ha/plan.jsonscale_node_play_hosts(scale.yml 의 "...(node)" 로 끝나는 play 가 겨냥하는 호스트 정렬 배열), remove_confirm_hosts(remove-node.yml 의 "Confirm node removal" play 가 겨냥하는 호스트 정렬 배열)를 적으세요. /root/ks/ha/runbook.sh 에는 w3 를 더하는 명령, w2 를 빼는 명령, 노드를 한 대씩 올리는 업그레이드 명령을 한 줄씩 적습니다(실행하지 않습니다).
  7. /opt/ks/kubespray/ansible.cfg 를 읽고 /root/ks/ha/facts.jsoncache_plugin(fact_caching 값), cache_dir(fact_caching_connection 값), cache_timeout_sec(fact_caching_timeout, 숫자), refresh_playbook(--limit 을 쓰기 전에 제한 없이 돌리라고 문서가 말하는 플레이북 경로, kubespray 저장소 기준 상대 경로)을 적으세요.

참고

컨트롤 플레인 3대, 워커 2대

/opt/ks/kubespray/inventory/sample/root/ks/inventory/ha 로 복사하고 /root/ks/inventory/ha/inventory.inicp1·cp2·cp3 을 kube_control_plane 에(etcd 는 kube_control_plane 을 자식으로), w1·w2 를 kube_node 에 넣고 k8s_cluster 가 두 그룹을 자식으로 받게 적으세요. 컨트롤 플레인은 워커를 겸하지 않습니다. 노드마다 /root/ks/inventory/ha/host_vars/<노드>.ymlansible_host·ip(cp1=192.0.2.11 부터 w2=192.0.2.15 까지 차례로)와 ansible_user: ubuntu 를 두세요.

1모듈의 한 대짜리 인벤토리와 모양이 같고, 그룹에 이름이 늘고 host_vars 파일이 늘 뿐입니다. 연결 방식을 인벤토리 줄에 두지 않은 이유가 여기서 드러납니다. ip 는 kubespray 가 API 서버와 etcd 를 붙일 주소이고, ansible_host 는 Ansible 이 ssh 로 닿을 주소입니다 — 관리망과 서비스망이 다르면 둘이 달라집니다. 192.0.2.0/24 는 문서용으로 예약된 대역이라 실제로 닿지 않습니다.

ssh 없이 인벤토리 검사만

/opt/ks/kubespray 에서 ansible-playbook -i /root/ks/inventory/ha/inventory.ini playbooks/boilerplate.yml -e ansible_connection=local 을 돌려 출력 전체를 /root/ks/ha/validate.log 에 남기세요. 다섯 노드 모두 PLAY RECAP 이 failed=0 이어야 합니다.

boilerplate 는 facts 를 모으지 않고 인벤토리와 변수만 검사하므로, 연결을 local 로 덮어쓰면 실제 노드 없이도 돌릴 수 있습니다. -e 는 host_vars 보다 우선하니 이 실행에서만 연결 방식이 바뀝니다. 설치를 시작하기 전 인벤토리 검토에 쓸 수 있는 가장 싼 확인입니다.

etcd 가 두 대면

/root/ks/inventory/ha-even 에 1단계 인벤토리를 복사하되 etcd 를 cp1·cp2 두 대만 두도록([etcd] 에 두 이름) 바꾸고, 같은 방법(-e ansible_connection=local)으로 boilerplate 를 돌려 보세요. /root/ks/ha/even.jsonfailed_task(실패한 작업 이름, 역할 접두사 없이), etcd_members(그 인벤토리의 etcd 호스트 수), quorum(그 수의 과반), tolerated_failures(몇 대까지 잃어도 쓰기가 되는가)를 적으세요.

etcd 는 쓰기마다 과반(쿼럼)의 동의가 필요합니다. 과반은 멤버 수를 2로 나눈 몫에 1을 더한 것입니다. 두 대면 과반이 둘이라 한 대만 잃어도 멈추는데, 한 대일 때도 한 대를 잃으면 멈추므로 두 대는 한 대보다 나을 것이 없고 멤버만 늘어납니다. kubespray 가 이것을 인벤토리 검사로 막습니다.

몇 대까지 잃어도 되나

/root/ks/ha/quorum.json 에 etcd 멤버 수 1·3·5·7 각각에 대해 quorum(과반)과 tolerated_failures(쓰기를 유지하며 잃을 수 있는 멤버 수)를 {"1": {"quorum": .., "tolerated_failures": ..}, "3": {...}, ...} 모양으로 적으세요.

과반은 n//2 + 1, 견딜 수 있는 장애는 n - 과반입니다. 멤버를 늘리면 견디는 수는 늘지만 쓰기마다 더 많은 멤버의 동의를 기다려야 합니다. etcd 문서가 운영 클러스터에 3대나 5대를 권하고 7대를 넘기지 말라고 하는 이유가 이 표에 있습니다.

워커는 어느 API 서버에 붙나

kubespray 의 roles/kubespray_defaults/defaults/main/main.ymldocs/operations/ha-mode.md 를 읽고 /root/ks/ha/lb.jsonlocalhost_lb(외부 로드밸런서를 정의하지 않았을 때 loadbalancer_apiserver_localhost 의 결과, 불리언), lb_type(loadbalancer_apiserver_type 기본값), lb_port(그 로컬 프록시가 쓰는 포트 — loadbalancer_apiserver_port 가 없으면 따르는 값, 숫자), who_uses_it(그 로컬 프록시를 쓰는 노드 그룹: "kube_node""kube_control_plane" 중 문서가 말하는 쪽)를 적으세요.

컨트롤 플레인이 여럿이면 워커의 kubelet 과 kube-proxy 가 어느 API 서버에 붙을지 정해야 합니다. kubespray 기본값은 외부 로드밸런서(loadbalancer_apiserver)를 따로 정의하지 않으면 각 워커에 nginx 프록시를 띄워 localhost 로 받아 모든 API 서버에 나눠 주는 방식입니다. 문서는 이 방식이 전용 LB 보다 덜 효율적이지만 VIP 를 관리하기 번거로운 곳에서 실용적이라고 설명합니다.

노드를 더하고 빼는 명령이 겨냥하는 곳

1단계 인벤토리의 kube_node 에 w3(host_vars 에 192.0.2.16)을 더하세요. 그다음 /opt/ks/kubespray 에서 scale.yml --list-hosts --limit w3remove-node.yml --list-hosts -e node=w2 를 인벤토리 /root/ks/inventory/ha/inventory.ini 로 돌려 보고, /root/ks/ha/plan.jsonscale_node_play_hosts(scale.yml 의 "...(node)" 로 끝나는 play 가 겨냥하는 호스트 정렬 배열), remove_confirm_hosts(remove-node.yml 의 "Confirm node removal" play 가 겨냥하는 호스트 정렬 배열)를 적으세요. /root/ks/ha/runbook.sh 에는 w3 를 더하는 명령, w2 를 빼는 명령, 노드를 한 대씩 올리는 업그레이드 명령을 한 줄씩 적습니다(실행하지 않습니다).

--list-hosts 는 아무것도 바꾸지 않고 각 play 가 누구를 겨냥하는지만 보여 줍니다. scale.yml 은 새 노드에만 설치하므로 --limit 로 좁히고, remove-node.yml 은 -e node=<이름> 으로 뺄 노드를 받습니다. kubespray 문서는 --limit 을 쓰기 전에 facts.yml 을 제한 없이 한 번 돌려 facts 캐시를 새로 하라고 권합니다. 한 대씩 올리는 업그레이드는 serial 변수로 조절합니다.

--limit 앞에 facts.yml 을 돌리는 이유

/opt/ks/kubespray/ansible.cfg 를 읽고 /root/ks/ha/facts.jsoncache_plugin(fact_caching 값), cache_dir(fact_caching_connection 값), cache_timeout_sec(fact_caching_timeout, 숫자), refresh_playbook(--limit 을 쓰기 전에 제한 없이 돌리라고 문서가 말하는 플레이북 경로, kubespray 저장소 기준 상대 경로)을 적으세요.

--limit 으로 새 노드만 겨냥하면 그 실행은 다른 노드의 facts 를 모으지 않고 캐시를 씁니다. 그런데 설정 파일(예: /etc/hosts, etcd 멤버 목록, 로드밸런서 백엔드)은 모든 노드의 facts 로 만들어집니다. 캐시가 비었거나 낡았으면 새 노드가 엉뚱한 주소를 받습니다. 업그레이드 문서의 Node-based upgrade 절을 보세요.