Certified Kubernetes Administrator (CKA) #4 kubeadm 클러스터 설치: 단일 control plane 부트스트랩
#3 노드와 Pod 네트워킹 모델에서 kubelet과 kube-proxy, CRI가 노드에서 어떻게 맞물리는지 정리했습니다. 이제 그 컴포넌트들을 실제로 깔아 빈 리눅스 머신을 쿠버네티스 클러스터로 만드는 차례입니다. 클라우드 매니지드 클러스터는 control plane을 가려 주지만, CKA는 그 control plane을 직접 세우는 능력을 봅니다. 이번 글에서는 kubeadm으로 단일 control plane 클러스터를 맨손으로 부트스트랩하겠습니다.
kubeadm은 클러스터를 세우는 공식 부트스트랩 도구입니다. apiserver,etcd,scheduler,controller-manager를 static Pod로 띄우고, 인증서를 발급하고, 워커가 붙을 토큰을 만들어 줍니다. 시험에서 자주 나오는 작업이 바로 이 kubeadm init과 kubeadm join이므로, 명령 하나하나를 정확히 손에 익히겠습니다.
큰 그림: kubeadm이 하는 일 #
kubeadm으로 클러스터를 세우는 순서는 머신마다 다음으로 나뉩니다.
- 모든 노드 공통 사전 준비. swap 비활성화, 커널 모듈, sysctl, 컨테이너 런타임, kubeadm,kubelet,kubectl 설치
- control plane 노드에서 부트스트랩.
kubeadm init으로 apiserver,etcd,scheduler,controller-manager를 static Pod로 기동 - CNI 설치. Pod 네트워크 플러그인을 깔아야 노드가 Ready가 되고 Pod끼리 통신
- 워커 노드 조인. 각 워커에서
kubeadm join으로 control plane에 합류
control plane과 워커는 1단계까지 동일하게 준비합니다. 2단계부터 역할이 갈립니다. 하나씩 명령으로 따라가겠습니다.
1. 사전 준비 (모든 노드 공통) #
control plane이든 워커든 다음 준비를 똑같이 합니다. 빠진 단계가 하나라도 있으면 kubeadm init이나 kubelet이 기동에 실패하므로, 순서대로 확인하겠습니다.
swap 비활성화 #
kubelet은 기본 설정에서 swap이 켜져 있으면 기동을 거부합니다. 메모리 압박 상황의 동작을 예측 가능하게 만들기 위함입니다. 먼저 현재 세션에서 끄고, 재부팅 후에도 꺼지도록 /etc/fstab의 swap 줄을 주석 처리합니다.
# 즉시 swap 끄기
sudo swapoff -a
# 재부팅 후에도 유지 (fstab의 swap 항목 주석 처리)
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 확인 (출력이 비어 있어야 함)
swapon --show
free -h커널 모듈: overlay와 br_netfilter #
컨테이너 런타임의 overlay 파일시스템과, bridge를 통과하는 트래픽이 iptables를 거치게 하는 br_netfilter가 필요합니다. 재부팅 후에도 자동 로드되도록 설정 파일에 적고, 지금 세션에도 즉시 로드합니다.
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 로드 확인
lsmod | grep -E 'overlay|br_netfilter'sysctl: 브리지 트래픽과 IP 포워딩 #
br_netfilter가 로드된 상태에서, bridge를 지나는 트래픽이 iptables 규칙을 타도록 하고 노드의 IP 포워딩을 켭니다. CNI와 kube-proxy가 정상 동작하기 위한 전제입니다.
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 재부팅 없이 적용
sudo sysctl --system
# 확인 (셋 다 1이어야 함)
sysctl net.ipv4.ip_forward net.bridge.bridge-nf-call-iptables컨테이너 런타임: containerd #
#3에서 본 대로 쿠버네티스는 CRI를 통해 런타임과 통신합니다. 가장 널리 쓰이는 containerd를 설치하고, kubelet과 같은 cgroup 드라이버(systemd)를 쓰도록 설정합니다. 이 cgroup 드라이버 불일치가 초보가 가장 자주 겪는 실패 원인입니다.
# containerd 설치 (배포판 패키지 또는 공식 바이너리)
sudo apt-get update && sudo apt-get install -y containerd
# 기본 설정 생성
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
# systemd cgroup 드라이버 사용 (kubelet과 일치시킴)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# 재시작과 부팅 시 자동 기동
sudo systemctl restart containerd
sudo systemctl enable containerdkubeadm, kubelet, kubectl 설치 #
세 패키지를 공식 저장소에서 설치합니다. 버전을 고정(hold)해 두면 의도치 않은 자동 업그레이드로 클러스터가 흔들리는 일을 막습니다. 업그레이드는 #6에서 의도적으로 다루겠습니다.
# 저장소 사용을 위한 패키지
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
# 쿠버네티스 apt 저장소 키와 소스 추가 (버전은 응시 시점에 맞춤)
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
# 설치 후 버전 고정
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# kubelet 부팅 시 자동 기동 (init 전에는 crashloop 상태가 정상)
sudo systemctl enable --now kubelet여기까지가 모든 노드 공통입니다. 워커 노드는 이 상태에서 멈추고, control plane 노드에서 다음 단계로 넘어갑니다.
2. control plane 부트스트랩: kubeadm init #
control plane 노드 한 대에서 kubeadm init을 실행합니다. 이 명령 하나가 인증서를 발급하고, control plane 컴포넌트를 static Pod로 띄우고, etcd를 기동하고, 워커가 붙을 토큰을 만듭니다.
sudo kubeadm init \
--pod-network-cidr=192.168.0.0/16 \
--apiserver-advertise-address=10.0.0.10두 옵션의 의미를 짚겠습니다.
--pod-network-cidr. Pod에 할당할 IP 대역입니다. 뒤에 설치할 CNI가 기대하는 대역과 일치시켜야 합니다. Calico 기본값이192.168.0.0/16, Flannel 기본값이10.244.0.0/16입니다.--apiserver-advertise-address. apiserver가 광고할 노드의 IP입니다. 노드에 인터페이스가 여러 개면 명시하는 편이 안전합니다.
kubeadm init이 성공하면 마지막에 두 가지를 출력합니다. 둘 다 그대로 복사해 두어야 합니다.
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1234...cdefkubeconfig 설정 #
위 출력의 첫 블록을 그대로 실행해야 kubectl이 클러스터에 접속할 수 있습니다. admin.conf를 일반 사용자의 ~/.kube/config로 복사하는 작업입니다.
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 접속 확인 (control plane 노드 한 줄이 보여야 함)
kubectl get nodes이 시점에서 kubectl get nodes를 하면 control plane 노드가 하나 보이지만 상태가 NotReady입니다. 아직 CNI를 깔지 않았기 때문이며, 정상입니다.
3. CNI 설치: 노드를 Ready로 #
쿠버네티스는 Pod 네트워크 플러그인(CNI)을 직접 제공하지 않습니다. 별도로 설치해야 노드가 Ready가 되고, Pod끼리 통신할 수 있습니다. CNI를 깔기 전까지는 CoreDNS Pod도 Pending에 머뭅니다.
Calico 설치 예시 #
# Calico 매니페스트 적용
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml
# CNI Pod가 Running이 되는 과정 관찰
kubectl get pods -n kube-system -wFlannel 설치 예시 #
Flannel을 쓴다면 kubeadm init을 --pod-network-cidr=10.244.0.0/16으로 실행했어야 합니다. 매니페스트와 CIDR이 어긋나면 Pod가 IP를 못 받습니다.
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.ymlCNI Pod가 모두 Running이 되면 노드 상태가 NotReady에서 Ready로 바뀝니다.
# 잠시 후 Ready로 전환됨
kubectl get nodes4. 워커 노드 조인: kubeadm join #
각 워커 노드에서 kubeadm init 출력에 있던 kubeadm join 명령을 root로 실행합니다. 워커도 1단계 사전 준비가 모두 끝나 있어야 합니다.
sudo kubeadm join 10.0.0.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:1234...cdef명령의 세 조각이 각각 신뢰 관계를 만듭니다.
10.0.0.10:6443. 워커가 접속할 apiserver의 주소와 포트입니다.--token. 부트스트랩 토큰입니다. 워커가 자신을 증명하는 데 씁니다. 기본 유효 기간이 24시간이라 만료에 주의합니다.--discovery-token-ca-cert-hash. control plane의 CA 인증서 해시입니다. 워커가 접속한 apiserver가 진짜 우리 클러스터인지 검증합니다.
토큰을 잃었거나 만료됐을 때 #
kubeadm init 출력을 못 챙겼거나 24시간이 지났다면, control plane 노드에서 join 명령을 새로 발급합니다. 이 한 줄이 토큰과 CA 해시를 모두 담은 완성된 join 명령을 출력합니다.
# control plane 노드에서: 완성된 join 명령 재발급
kubeadm token create --print-join-command토큰만 따로 만들거나, 현재 살아 있는 토큰을 확인할 수도 있습니다.
# 새 토큰만 생성
kubeadm token create
# 현재 유효한 토큰 목록
kubeadm token list5. 검증 #
조인이 끝나면 control plane 노드에서 클러스터 전체 상태를 확인합니다. CKA에서는 작업 후 이 검증을 반드시 거쳐야 점수를 잃지 않습니다.
# 모든 노드가 Ready인지
k get nodes
# control plane 컴포넌트와 CNI, CoreDNS가 Running인지
k get pods -n kube-system정상이라면 control plane과 워커가 모두 Ready로, kube-system 네임스페이스의 apiserver,etcd,scheduler,controller-manager,kube-proxy,CoreDNS,CNI Pod가 모두 Running으로 보입니다.
NAME STATUS ROLES AGE VERSION
controlplane Ready control-plane 8m v1.31.0
node01 Ready <none> 3m v1.31.0노드 안에서 kubelet 자체의 상태를 확인하려면 systemctl과 journalctl을 씁니다. #1에서 셋업한 명령들입니다.
systemctl status kubelet
journalctl -u kubelet -f흔한 함정 #
설치 작업에서 점수를 잃는 패턴은 대부분 정해져 있습니다.
- CNI를 안 깔아 노드가 NotReady.
kubeadm init직후 노드가NotReady인 것은 정상이지만, CNI를 깔지 않으면 영원히NotReady입니다. CoreDNS도Pending에 머뭅니다. 설치를 다 했는데 노드가 Ready가 안 되면 가장 먼저kubectl get pods -n kube-system으로 CNI Pod를 확인합니다. - swap이 켜져 있어 kubelet이 안 뜸.
kubeadm init이 사전 점검(preflight)에서 swap 에러로 멈춥니다.swapoff -a를 빠뜨렸거나 재부팅 후/etc/fstab처리를 안 한 경우입니다. - 토큰 만료. 부트스트랩 토큰 유효 기간이 24시간이라, 시간이 지난 뒤 워커를 붙이려 하면 인증에 실패합니다.
kubeadm token create --print-join-command로 새로 발급합니다. - cgroup 드라이버 불일치. containerd가 cgroupfs, kubelet이 systemd처럼 서로 다른 cgroup 드라이버를 쓰면 kubelet이 컨테이너를 못 띄웁니다. containerd의
SystemdCgroup = true설정을 확인합니다. - pod-network-cidr와 CNI 불일치.
kubeadm init에 준 CIDR과 CNI 매니페스트가 기대하는 대역이 어긋나면 Pod가 IP를 못 받습니다. Calico는192.168.0.0/16, Flannel은10.244.0.0/16을 기본으로 맞춥니다. - CRI 소켓 미설정. 런타임이 여러 개 깔려 있으면
kubeadm init --cri-socket unix:///run/containerd/containerd.sock처럼 명시해야 합니다.
망친 init를 되돌리려면 #
설정을 잘못해 처음부터 다시 하고 싶을 때는 kubeadm reset으로 노드를 초기 상태로 되돌립니다.
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d $HOME/.kube/config시험 포인트 #
kubeadm init은 control plane을 static Pod로 띄운다. apiserver,etcd,scheduler,controller-manager 매니페스트는/etc/kubernetes/manifests/에 있다.kubeadm init직후 출력의 두 블록(kubeconfig 설정과 join 명령)을 모두 챙긴다. join 명령을 놓쳤으면kubeadm token create --print-join-command로 재발급한다.- 노드가
NotReady이면 CNI 설치 여부를 가장 먼저 의심한다. CNI 없이는 노드가 Ready가 되지 않는다. - 사전 준비 4종(swap off, 커널 모듈, sysctl, 컨테이너 런타임)이 빠지면 init나 kubelet이 기동에 실패한다.
--pod-network-cidr는 설치할 CNI의 기본 대역과 일치시킨다.- kubelet 상태는
systemctl status kubelet과journalctl -u kubelet로 추적한다.
정리 #
이번 글에서 잡은 것:
- 사전 준비(모든 노드). swap 비활성화, 커널 모듈(overlay,br_netfilter), sysctl(ip_forward,bridge-nf), containerd 설치와 systemd cgroup, kubeadm,kubelet,kubectl 설치
- control plane 부트스트랩.
kubeadm init --pod-network-cidr=..., 출력의 kubeconfig 설정(mkdir -p ~/.kube; cp ...)과 join 명령 확보 - CNI 설치. Calico나 Flannel 매니페스트를 적용해야 노드가 Ready
- 워커 조인.
kubeadm join(토큰 + discovery hash), 만료 시kubeadm token create --print-join-command로 재발급 - 검증과 함정.
k get nodes,k get pods -n kube-system로 확인, NotReady,swap,토큰 만료,cgroup 불일치를 점검
다음: HA 클러스터 #
단일 control plane 클러스터를 세웠습니다. 그런데 control plane 노드가 한 대뿐이면 그 한 대가 죽는 순간 클러스터 관리가 멈춥니다.
#5 HA 클러스터: 여러 control plane, 외부 etcd cluster에서는 control plane을 여러 대로 늘려 가용성을 확보하는 구조를 다루겠습니다. apiserver 앞에 로드밸런서를 두는 방법, kubeadm init에 --control-plane-endpoint를 주어 추가 control plane이 붙게 하는 방법, 그리고 etcd를 control plane에 함께 두는 stacked 구성과 별도 클러스터로 분리하는 external etcd 구성의 차이까지 정리하겠습니다.