Chapter 24

클러스터 설계와 설치

  • 24.1 클러스터 설계
  • 24.2 인프라 선택
  • 24.3 고가용성 구성
  • 24.4 etcd HA
  • 24.5 kubeadm 소개
  • 24.6 VM 프로비저닝
  • 24.7 kubeadm 배포 데모

Kubernetes 클러스터를 처음부터 설계하고 직접 설치하는 전체 흐름을 다뤄보자. 클러스터 용도에 따른 설계 방식부터, 인프라 선택, 고가용성 구성, etcd HA, 그리고 kubeadm으로 실제 클러스터를 부트스트랩하는 데모까지 이어서 본다.

Kubernetes 클러스터를 설계할 때 가장 먼저 해야 할 건 "이 클러스터를 뭘 위해 쓸 건지" 정하는 거야. 학습용이냐, 개발/테스트용이냐, 프로덕션용이냐에 따라 구성이 완전히 달라지거든.

학습 목적이라면 미니큐브 같은 단일 노드 클러스터면 충분해. 로컬 VM이나 GCP, AWS 같은 클라우드에서 kubeadm으로 배포한 단일 노드도 괜찮고. 개발이나 테스트 목적이면 단일 마스터에 여러 워커 노드가 있는 멀티노드 클러스터가 필요해. kubeadm이 여기서 적절한 도구야. 아니면 관리형 클라우드 환경으로 GKE, EKS, AKS를 써서 빠르게 프로비저닝할 수도 있어.

프로덕션 환경이라면 얘기가 다르지. 여러 마스터 노드가 있는 고가용성 멀티노드 클러스터를 써야 해. kubeadm이나 GKE, AWS에서 Kops 같은 도구를 쓸 수 있어.

Kubernetes 클러스터가 지원하는 규모도 알아야 해. 최대 5,000개 노드, 총 150,000개 파드, 300,000개 컨테이너, 노드당 최대 100개 파드까지 가능해. 클러스터 규모에 따라 노드에 필요한 리소스가 달라지는데, GCP나 AWS 같은 클라우드 제공업체는 노드 수에 따라 적합한 크기의 인스턴스를 자동으로 선택해줘. 온프레미스에 배포한다면 문서에 나와 있는 리소스 사양 표를 참고해서 시작하면 돼.

배포 도구 선택지를 보면, 온프레미스에서는 kubeadm이 매우 유용해. GCP에서는 Google Container Engine이 원클릭 업그레이드 기능까지 제공하면서 클러스터를 아주 쉽게 프로비저닝할 수 있게 해줘. AWS에서는 Kops가 좋고, Azure에서는 AKS가 있어.

워크로드에 따라 스토리지 구성도 달라져. 고성능 워크로드라면 SSD 기반 스토리지를 써야 하고, 여러 포드에서 공유 액세스가 필요하면 네트워크 기반 스토리지를 고려해야 해. 영구 저장소 볼륨을 쓰고, 다양한 스토리지 클래스를 정의해서 적합한 애플리케이션에 맞는 클래스를 할당하는 게 좋아.

클러스터 노드는 물리적이든 가상이든 상관없어. 마스터 노드는 API 서버, etcd 서버 같은 컨트롤 플레인 구성 요소를 호스팅하고, 워커 노드가 실제 워크로드를 호스팅하는 구조야. 마스터 노드도 사실 워크로드를 올릴 수 있긴 한데, 프로덕션에서는 컨트롤 플레인 전용으로 쓰는 게 좋아. kubeadm은 마스터 노드에 테인트를 추가해서 워크로드가 올라가는 걸 방지하거든. 노드에는 64비트 Linux OS를 써야 해. 대규모 클러스터에서는 etcd 클러스터를 마스터 노드에서 분리해서 별도의 노드에 두는 것도 고려할 수 있어.

이제 클러스터를 어디에 어떻게 배포할지 인프라 선택으로 넘어가보자. Kubernetes를 배포할 수 있는 방법은 정말 다양해. 노트북부터 조직 내 물리/가상 서버, 클라우드 서버까지 다 가능하거든. 핵심은 요구 사항이랑 배포할 애플리케이션 종류에 따라 선택이 달라진다는 거야.

로컬 환경부터 보면, Linux 머신에서 바이너리를 수동으로 설치해서 클러스터를 구성할 수 있긴 한데 초보자한테는 너무 지루한 작업이야. 그래서 자동화 솔루션을 쓰는 게 낫지. Windows에서는 네이티브로 Kubernetes를 설정할 수 없어. Windows 바이너리가 없거든. Hyper-V나 VMware, VirtualBox 같은 가상화 소프트웨어로 Linux VM을 만들어서 써야 해. Docker 컨테이너로 실행하는 솔루션도 있긴 한데, 내부적으로는 어차피 Hyper-V 위의 Linux OS에서 돌아가는 거야.

로컬에서 쉽게 시작할 수 있는 솔루션으로는 미니큐브가 있어. Oracle VirtualBox 같은 가상화 소프트웨어 위에 VM을 만들어서 단일 노드 클러스터를 배포해줘. kubeadm은 단일 노드나 멀티노드 클러스터를 빠르게 배포할 수 있는데, 호스트를 직접 프로비저닝해야 한다는 차이가 있어. 미니큐브는 자체적으로 VM까지 만들어주는 반면, kubeadm은 이미 프로비저닝된 VM이 있어야 해. 대신 멀티노드 클러스터를 배포할 수 있다는 장점이 있지.

프로덕션 환경에서의 배포 방식은 크게 턴키 솔루션호스팅(관리형) 솔루션으로 나뉘어. 턴키 솔루션은 네가 직접 VM을 프로비저닝하고 도구나 스크립트를 써서 Kubernetes를 구성하는 방식이야. VM 유지보수 책임은 너한테 있지만, 클러스터 관리는 도구가 도와줘. 예를 들어 Kops로 AWS에 배포하는 거지. 호스팅 솔루션은 VM과 클러스터 전부를 제공자가 배포하고 관리해주는 서비스형 쿠버네티스야. GKE 같은 걸 쓰면 몇 분 만에 클러스터가 뜨거든.

턴키 솔루션 몇 가지를 보면, OpenShift는 Red Hat의 온프레미스 Kubernetes 플랫폼이야. Kubernetes 위에 CI/CD 파이프라인 통합이나 GUI 같은 추가 도구를 제공해. Cloud Foundry Container Runtime은 Bosh라는 도구로 고가용성 Kubernetes 클러스터를 배포하고 관리하는 데 도움을 줘. VMware 환경을 활용하려면 VMware Cloud PKS를, 다양한 클라우드에 배포하려면 Vagrant 스크립트를 쓸 수 있어. 호스팅 솔루션으로는 GKE, OpenShift Online, AKS, EKS 같은 것들이 있어.

로컬 학습용으로는 VirtualBox에서 VM 여러 대를 만들어서 마스터 1대, 워커 2대 구성으로 클러스터를 배포하는 게 좋아. 퍼블릭 클라우드 계정이 없어도 되고, 로컬에서 다 해볼 수 있으니까.

인프라 선택이 끝났으면 이제 고가용성 구성을 알아보자. **고가용성(HA)**의 핵심은 단일 장애 지점을 없애는 거야. 마스터 노드가 하나뿐인데 그게 죽으면 어떻게 될까? 워커 노드가 살아 있으니 기존 애플리케이션은 일단 돌아가긴 해. 그런데 파드가 크래시되면? 레플리카 세트의 복제 컨트롤러가 새 파드를 띄워야 하는데, 마스터가 없으니 컨트롤러도 스케줄러도 없어서 아무도 파드를 재생성하거나 스케줄링할 수 없어. kube API 서버도 죽었으니 kubectl로 클러스터에 접근하는 것도 불가능하고. 그래서 프로덕션에서는 반드시 여러 개의 마스터 노드를 둬야 해.

그러면 마스터가 여러 개일 때 컨트롤 플레인 구성 요소들이 어떻게 돌아가는지 보자. API 서버는 요청을 받아서 처리하는 역할이잖아. 한 번에 하나의 요청을 독립적으로 처리하니까, 여러 마스터의 API 서버가 동시에 활성화되는 Active-Active 모드로 돌릴 수 있어. 다만 두 API 서버 앞에 로드 밸런서를 두고, kubectl이 그 로드 밸런서를 가리키게 해야 해. nginx나 HAProxy 같은 걸 쓰면 돼.

스케줄러컨트롤러 매니저는 다르게 동작해. 이들은 클러스터 상태를 감시하고 조치를 취하는 컨트롤러야. 예를 들어 복제 컨트롤러가 파드를 감시하다가 파드가 죽으면 새로 만드는데, 이게 여러 인스턴스에서 병렬로 돌아가면 필요한 것보다 더 많은 파드가 생길 수 있어. 그래서 Active-Standby 모드로 돌려야 해. 하나만 활성이고 나머지는 대기 상태야.

누가 활성이고 누가 수동인지는 리더 선출 과정으로 결정돼. 컨트롤러 매니저 프로세스를 시작할 때 --leader-elect 옵션이 기본적으로 true로 설정돼 있어. 프로세스가 시작되면 kube-controller-manager라는 엔드포인트 오브젝트에 대한 **임대(lock)**를 얻으려고 해. 먼저 엔드포인트를 업데이트한 프로세스가 임대를 획득하고 활성 프로세스가 되는 거야. 임대 기간은 기본 15초이고, 활성 프로세스는 10초마다 임대를 갱신해. 두 프로세스 모두 2초마다 리더가 되려고 시도하니까, 첫 번째 마스터가 죽으면 두 번째가 잠금을 획득해서 리더가 될 수 있어. 스케줄러도 동일한 방식으로 동작해.

etcd는 두 가지 토폴로지로 구성할 수 있어. 첫 번째는 스택형 토폴로지인데, etcd가 마스터 노드 안에 있는 거야. 설정하기 쉽고 관리도 편하고 노드 수도 적게 필요해. 단점은 한 노드가 다운되면 etcd 멤버와 컨트롤 플레인 인스턴스가 모두 날아간다는 거야. 두 번째는 외부 etcd 토폴로지로, etcd가 별도의 서버에서 돌아가는 거야. 컨트롤 플레인 노드에 장애가 나도 etcd 데이터는 안전하지만, 설정이 더 어렵고 서버가 두 배로 필요해.

API 서버가 etcd와 통신하는 유일한 컴포넌트라는 걸 기억해야 해. API 서버 구성에서 etcd 서버 위치를 지정하는 옵션이 있으니까, 어떤 토폴로지를 쓰든 API 서버가 올바른 etcd 주소를 가리키고 있는지 확인해야 해.

etcd HA를 좀 더 깊이 파보자. etcd는 간단하고 안전하며 빠른 분산형 키-값 저장소야. HA 모드에서 Kubernetes를 구성하려면 etcd를 HA로 구성하는 게 필수적이거든.

키-값 저장소가 뭔지부터 짚어보면, 기존 테이블형 데이터베이스와 달리 문서 형태로 정보를 저장해. 각 개인에 대한 정보가 하나의 파일에 들어가고, 한 파일을 변경해도 다른 파일에는 영향이 없어. JSON이나 YAML 같은 형식으로 데이터를 다룰 수 있지.

분산이라는 게 핵심인데, 단일 서버 하나에만 중요한 데이터를 두면 위험하잖아. 그래서 여러 서버에 동일한 데이터 복사본을 유지하는 거야. 세 대의 서버가 etcd를 실행하면서 동일한 데이터베이스를 유지하니까, 하나를 잃어도 두 개가 남아 있어.

읽기는 간단해. 모든 노드에 같은 데이터가 있으니까 아무 노드에서나 읽으면 돼. 쓰기가 복잡한 부분이야. etcd는 실제로 모든 노드에서 쓰기를 처리하지 않아. 인스턴스 중 하나가 리더로 선출되고, 나머지는 팔로워가 돼. 리더 노드를 통해 쓰기가 들어오면 리더가 처리하고 다른 노드에 복제해. 팔로워 노드를 통해 들어오면 내부적으로 리더에게 전달하고, 리더가 처리한 후 클러스터 전체에 복제해. 클러스터의 다른 멤버로부터 동의를 얻어야만 쓰기가 완료된 걸로 인정돼.

리더 선출은 Raft 프로토콜을 사용해. 클러스터가 시작되면 세 노드에 무작위 타이머가 돌아가는데, 타이머를 가장 먼저 완료한 노드가 다른 노드에게 리더 허가를 요청해. 다른 노드들이 투표로 응답하면 그 노드가 리더가 돼. 리더는 정기적으로 알림을 보내서 자기가 살아 있다는 걸 알려주고, 다른 노드가 알림을 못 받으면 재선거를 해서 새 리더를 뽑아.

쿼럼이라는 개념이 중요해. 클러스터가 제대로 작동하기 위해 필요한 최소 노드 수야. 공식은 **"총 노드 수 / 2 + 1"**이야. 3개 노드면 쿼럼은 2, 5개면 3이야. 그래서 3개 노드에서 하나가 죽어도 쿼럼(2)을 충족하니까 계속 작동해. 대다수 노드에 쓸 수 있으면 쓰기가 완료된 걸로 간주하고, 나중에 죽었던 노드가 살아나면 데이터를 복제해줘.

인스턴스가 1개면 당연히 날아가면 끝이고, 2개여도 쿼럼이 2니까 하나만 죽어도 쿼럼을 못 맞춰. 결국 2개나 1개나 마찬가지야. 그래서 최소 3개가 필요해.

짝수보다 홀수가 좋은 이유가 있어. 6개 노드 클러스터에서 네트워크 분할이 일어나면, 3:3으로 나뉘면 양쪽 다 쿼럼(4)을 못 맞춰서 클러스터가 실패해. 반면 7개면 4:3으로 나뉘어도 4개 쪽이 쿼럼을 충족해서 계속 운영될 수 있어. 그래서 3, 5, 7 같은 홀수가 권장되는 거야. 실무에서는 3개면 좋은 시작이고, 더 높은 내결함성이 필요하면 5개가 적당해. 그 이상은 보통 불필요해.

etcd를 설치할 때는 바이너리를 다운로드하고, 인증서 파일을 복사하고, 서비스를 구성해야 해. 여기서 중요한 건 --initial-cluster 옵션인데, 피어 정보를 전달해서 각 etcd 서비스가 자신이 클러스터의 일부이고 동료가 어디 있는지 알 수 있게 해줘. 설치가 끝나면 etcdctl 유틸리티로 데이터를 저장하고 검색할 수 있어. etcdctl put name John으로 저장하고, etcdctl get name으로 가져오고, etcdctl get / --prefix --keys-only로 모든 키를 확인할 수 있어.

설계와 HA 개념을 알았으니, 이제 실제로 클러스터를 설치해보자. kubeadm은 Kubernetes 모범 사례에 따라 멀티노드 클러스터를 부트스트랩하는 도구야. Kubernetes 클러스터는 kube API 서버, etcd, 컨트롤러 같은 다양한 구성 요소로 이루어져 있고, 이들 사이의 보안 통신을 위한 인증서 설정도 필요하잖아. 이걸 하나하나 다른 노드에 설치하고, 서로 가리키도록 구성 파일 수정하고, 인증서 세팅하는 건 정말 지루한 작업이야. kubeadm이 이 모든 걸 대신 처리해줘.

kubeadm으로 클러스터를 설정하는 큰 흐름을 보면 이래. 먼저 클러스터용으로 여러 시스템이나 VM이 프로비저닝되어 있어야 해. 시스템이 준비되면 하나를 마스터, 나머지를 워커로 지정해. 다음은 모든 노드에 컨테이너 런타임(containerd)을 설치하는 거야. 그 다음은 모든 노드에 kubeadm 도구를 설치하는 거야. kubeadm이 필요한 모든 컴포넌트를 올바른 노드에 올바른 순서로 설치하고 구성해줘. 마스터 서버를 초기화하면 필요한 모든 컨트롤 플레인 컴포넌트가 설치되고 구성돼. 마스터가 초기화된 후에 워커를 연결하기 전에, 포드 네트워크라는 특별한 네트워킹 솔루션을 설치해야 해. 일반 네트워크 연결만으로는 부족하거든. 마지막으로 워커 노드를 마스터에 조인시키면 끝이야.

VM 프로비저닝부터 해보자. Kubernetes 클러스터를 위한 VM을 프로비저닝하려면 VirtualBoxVagrant 두 가지 소프트웨어가 필요해. VirtualBox는 하이퍼바이저로 실제 가상 머신을 실행하는 역할이고, Vagrant는 자동화 도구로 특정 구성의 VM을 쉽게 띄울 수 있게 해줘. Vagrant를 쓰면 모두가 정확히 동일한 설정으로 VM을 실행할 수 있어.

먼저 virtualbox.org에서 VirtualBox를, Vagrant 문서 페이지에서 Vagrant를 설치해야 해. 설치가 끝나면 Vagrantfile이 필요한데, VM의 모든 구성 요소가 정의된 파일이야. 이 파일은 코스 저장소에 이미 준비되어 있으니까 리포지토리를 clone하면 돼.

git clone <repository-url>
cd <repository-directory>

Vagrantfile을 열어보면 마스터 노드 1개와 워커 노드 2개가 설정돼 있어. IP 주소는 192.168.56 대역을 사용하게 돼 있고. VM을 띄우는 건 명령 하나면 돼.

vagrant up

이 명령이 Ubuntu Bionic64 기본 이미지를 끌어오고, 세 VM을 순차적으로 프로비저닝해. kubemaster를 먼저 프로비저닝하고, 그다음 kubenode01, kubenode02 순서야. VM 상태를 확인하려면 vagrant status를 실행하면 되고, VM에 접속하려면 vagrant ssh kubemaster 같은 식으로 연결하려는 노드 이름을 지정하면 돼.

VM이 준비됐으면 실제 kubeadm 배포 데모로 넘어가자. kubeadm으로 Kubernetes 클러스터를 부트스트랩하는 전체 과정을 보자. Vagrant로 VM 3대를 프로비저닝한 상태에서 시작해. 마스터 노드 1개, 워커 노드 2개 구성이야. 클러스터 간 통신에 사용할 인터페이스는 enp0s8이고, 마스터는 192.168.56.11, 노드1은 192.168.56.21, 노드2는 192.168.56.22 IP를 가져.

가장 먼저 모든 노드에 kubeadm을 설치해야 해. 공개 서명 키를 다운로드하고, Kubernetes 저장소를 추가하고, sudo apt update를 한 다음 kubelet, kubeadm, kubectl 패키지를 설치해. 세 노드 모두에서 동일하게 진행하면 돼.

그 다음은 컨테이너 런타임 설치야. containerd를 쓸 건데, sudo apt update && sudo apt install -y containerd 명령으로 모든 노드에 설치해.

Cgroup 드라이버 설정이 중요해. Linux의 init 시스템이 systemd이면(대부분 그래) Cgroup 드라이버를 systemd로 설정해야 해. ps -p 1로 확인할 수 있어. kubelet은 1.22 이후 기본적으로 systemd를 사용하니까 따로 설정할 필요 없고, containerd 쪽에서 설정해줘야 해. /etc/containerd/ 디렉터리를 만들고, containerd config default 명령으로 기본 구성을 생성한 다음, SystemdCgroup = falseSystemdCgroup = true로 바꿔줘. 그리고 systemctl restart containerd로 재시작하면 돼.

이제 클러스터를 생성하는 단계야. 마스터 노드에서 kubeadm init 명령을 실행해. 두 가지 플래그가 중요한데, --apiserver-advertise-address에 마스터 노드의 IP 주소를 넣고, --pod-network-cidr에 파드 서브넷(예: 10.244.0.0/16)을 지정해.

sudo kubeadm init --apiserver-advertise-address=192.168.56.11 --pod-network-cidr=10.244.0.0/16

초기화가 완료되면 kubeconfig를 설정해야 해. 출력에 나오는 명령을 그대로 실행하면 돼.

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

이 시점에서 kubectl get nodes를 실행하면 마스터 노드가 NotReady 상태로 보여. CNI 플러그인을 아직 설치 안 했으니까 정상이야.

포드 네트워크 애드온을 설치해야 해. Flannel을 쓸 건데, Flannel 문서에 있는 매니페스트를 적용하면 돼.

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

만약 커스텀 Pod CIDR을 사용한다면(기본값 10.244.0.0/16이 아닌 경우) 매니페스트를 먼저 다운로드해서 네트워크 설정을 수정한 후 적용해야 해.

Flannel이 배포되면 마스터 노드가 Ready 상태로 전환돼. 마지막으로 워커 노드를 조인시키면 돼. kubeadm init 출력에서 나온 join 명령을 각 워커 노드에서 실행해.

sudo kubeadm join 192.168.56.11:6443 --token <token> --discovery-token-ca-cert-hash <hash>

조인이 완료되면 마스터에서 kubectl get nodes를 실행하면 세 노드가 모두 Ready 상태로 보여. 테스트로 nginx 파드를 하나 배포해보면 잘 돌아가는 걸 확인할 수 있어.


정리

24장 읽고 기억할 거 세 가지:

  1. 클러스터 설계는 용도(학습/개발/프로덕션)에 따라 완전히 달라지고, 프로덕션에서는 반드시 고가용성 멀티 마스터 구성을 써야 한다. API 서버는 Active-Active, 스케줄러/컨트롤러 매니저는 Active-Standby(리더 선출) 방식으로 동작한다.
  2. etcd HA에서 **쿼럼(N/2 + 1)**은 핵심 개념이다. 최소 3개의 홀수 노드가 필요하고, Raft 프로토콜로 리더를 선출하며, 쿼럼을 충족해야 쓰기가 완료된다.
  3. kubeadm으로 클러스터를 부트스트랩하는 흐름은 VM 프로비저닝 -> 컨테이너 런타임(containerd) 설치 -> kubeadm 설치 -> kubeadm init -> CNI(Flannel) 설치 -> 워커 노드 kubeadm join 순서다.