쿠버네티스 클러스터는 단순히 “노드 몇 대에 쿠버네티스 설치”로 끝나지 않습니다. 컨트롤 플레인 컴포넌트(kube-apiserver, etcd, controller-manager, scheduler) 와 노드 에이전트(kubelet), 그리고 이들 간 통신을 위한 인증서/키(PKI), 올바른 네트워킹(CNI 기반 Pod Network) 등이 맞물려야 실제로 동작합니다.
이걸 수동으로 노드마다 설치하고, 설정 파일을 서로 맞추고, 인증서를 직접 만들고 배포하는 건 번거롭습니다. 그래서 실무/학습에서 많이 쓰는 표준 도구가 kubeadm 입니다. kubeadm은 이러한 구성 요소들을 정해진 순서와 베스트 프랙티스에 맞춰 부트스트랩하도록 도와줍니다.
스크립트에서 “cube adm”처럼 들리지만 정확한 명칭은 kubeadm 입니다.
kubeadm이 해주는 일 (왜 쓰나)
kubeadm은 “쿠버네티스 설치 자동화 도구”라기보다, 쿠버네티스 클러스터를 ‘표준 절차대로’ 부트스트랩 해주는 도구입니다.
kubeadm이 주로 책임지는 것들:
- 컨트롤 플레인 컴포넌트 설치/구성 (Static Pod 매니페스트 생성 등)
- 클러스터 인증서 생성 및 배치 (PKI)
- 부트스트랩 토큰 생성 및 워커 조인 흐름 제공 (
kubeadm join) - 클러스터 기본 설정값(버전 호환, 권장 구성) 기반 초기화
- (옵션) 컨트롤 플레인 확장(HA 구성 시 다른 control-plane 노드 조인) 관련 워크플로 제공
반대로 kubeadm이 하지 않는 것(사용자가 꼭 해야 하는 것):
- Pod Network(CNI) 설치 (Calico/Flannel/Cilium 등)
- 로드밸런서, 스토리지, Ingress Controller 같은 “애드온” 설치
- (특히 HA) 외부 LB, 다중 컨트롤 플레인/etcd 아키텍처 설계
전체 설치 흐름 (하이 레벨)
- 노드(여러 VM/서버) 준비
- 한 대를 Control Plane(구 master) 로, 나머지를 Worker 로 지정
- 모든 노드에 컨테이너 런타임 설치 (예: containerd)
- 모든 노드에 kubeadm + kubelet + kubectl 설치
- 컨트롤 플레인에서
kubeadm init로 초기화 - Pod Network(CNI) 설치 (이 단계 전에는 노드가 NotReady인 것이 정상)
- 워커 노드에서
kubeadm join으로 클러스터 조인 kubectl get nodes/pods로 정상 동작 확인 + 간단한 Pod 배포로 테스트
사전 준비 체크리스트 (여기서 많이 삽질함)
1) 모든 노드 공통: Swap 비활성화
swap은 RAM이 부족할 때 디스크를 임시 메모리처럼 쓰는 기능입니다. 쿠버네티스 기본 설치 흐름에서는 swap이 켜져 있으면 kubelet 동작이 꼬일 수 있어 보통 끕니다.
# 상태 확인
swapon --show
free -h
# 즉시 끄기(재부팅 전까지)
sudo swapoff -a
2) 커널 모듈/네트워크 sysctl
브릿지 트래픽이 iptables로 흘러가게 하는 설정, IP forwarding은 CNI에서 사실상 필수인 경우가 많습니다.
sudo modprobe br_netfilter
sudo modprobe overlay
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
3) 노드 간 통신/포트
노드끼리 기본 통신이 되어야 하고(같은 네트워크), 방화벽/포트 차단으로 인해 kubeadm init/join이 실패할 수 있습니다.
1) 노드 준비 (강의 데모: Vagrant로 VM 3대 프로비저닝)
강의에서는 이미 Vagrant로 VM 3대를 준비해 둔 상태에서 kubeadm 부트스트랩을 진행합니다.
- 노드 구성: master 1대 + worker 2대
- 터미널 프롬프트에서 노드가
kubemaster,kubenode01,kubenode02처럼 보이는 형태
(중요) 어떤 NIC/IP를 통신에 사용할지 정하기
VM에는 네트워크 인터페이스가 여러 개 있을 수 있습니다. 강의에서는 클러스터 통신용으로 특정 인터페이스를 지정합니다.
- 통신에 사용할 인터페이스:
enp0s8(강의에서는 “INP zero eight”로 들리는 부분) - 노드 IP(이 인터페이스 기준):
- master:
192.168.56.11 - node01:
192.168.56.21 - node02:
192.168.56.22
- master:
이 IP들은 Pod CIDR이 아니라 “노드(VM) 자체의 IP” 입니다. 즉, kube-apiserver에 접근하거나 노드 간 통신(조인 등)에 쓰입니다.
확인 예시:
ip addr
ip route
2) 문서 탭 2개는 켜두자 (설치/생성 가이드)
강의에서 강조하는 실전 팁:
- 쿠버네티스 문서는 “대부분 잘 정리되어 있다”
- 설치할 때 다음 탭을 같이 열고 따라가면 좋다
추천 탭:
- Installing kubeadm
- 위치: “Installing Kubernetes with deployment tools” → “Bootstrapping clusters with kubeadm”
- 여기서 패키지 저장소/키 설정, kubeadm 설치 절차를 확인
- Creating a cluster with kubeadm
- 실제로
kubeadm init, CNI 설치,kubeadm join흐름을 확인
- 실제로
추가로 HA를 할 계획이면:
- Creating Highly Available Clusters with kubeadm 문서를 참고
(강의는 단일 컨트롤 플레인이라 기본 “Creating a cluster…” 문서를 따름)
3) kubeadm 설치 (모든 노드에 설치)
강의에서는 최신(당시 기준) 버전으로 Kubernetes 1.31을 사용합니다. 문서에 버전별 저장소 경로가 다르게 나오므로, 설치 버전에 맞게 선택해야 합니다.
3.1 Kubernetes 패키지 저장소 서명 키 등록
문서의 “Download the public signing keys…” 섹션의 명령을 사용합니다.
버전이 1.31이면 1.31용을, 1.32면 1.32로 바꿔야 합니다.
강의에서는 이 명령을 master, node01, node02 모두에 실행합니다.
3.2 Kubernetes apt repository 추가
마찬가지로 문서에 나온 repository 추가 명령을 3개 노드에 동일하게 적용합니다.
3.3 패키지 설치 (kubelet/kubeadm/kubectl)
문서에서는 보통 아래 세 가지를 한 번에 설치하도록 안내합니다.
kubeletkubeadmkubectl
강의 포인트:
- “지금 당장 kubeadm만 필요”할 수도 있지만,
- 어차피 곧
kubectl도 필요하니 한 번에 다 설치하는 게 편하다
설치 흐름은 이런 형태입니다.
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
설치 후 버전 확인:
kubeadm version
# 예: v1.31.1 (major 1, minor 31)
4) 컨테이너 런타임 설치 (모든 노드)
다음 단계는 문서에서도 강조하는 Container Runtime 설치입니다.
워커 노드는 컨테이너(파드)를 실행해야 하니 당연히 필요하고, 컨트롤 플레인도 컨트롤 플레인 컴포넌트들이 Pod(컨테이너)로 실행되므로 런타임이 필요합니다.
강의에서는 런타임으로 containerd를 선택합니다.
설치 예시:
sudo apt-get update
sudo apt-get install -y containerd
강의에서는 이 작업도 3개 노드에 모두 수행합니다.
5) cgroup driver 맞추기 (여기서 많이 꼬임)
문서에서 “중요” 섹션으로 다루는 부분이 cgroup drivers입니다.
5.1 cgroups가 뭐고 왜 중요하나
- cgroups는 리눅스의 리소스 제어 기능(프리미티브)입니다.
- 컨테이너에 CPU/메모리 제한(예: 512Mi) 등을 설정할 때 cgroups가 사용됩니다.
5.2 kubelet과 런타임은 같은 드라이버를 써야 한다
가능한 드라이버:
cgroupfs(기본값인 경우가 많음)systemd
중요 규칙:
- kubelet이 systemd면 런타임도 systemd
- kubelet이 cgroupfs면 런타임도 cgroupfs
강의에서는 “리눅스 배포판의 init system이 systemd면 systemd 드라이버 사용”을 따릅니다.
5.3 init system 확인: PID 1 확인
ps -p 1
# systemd면 systemd 사용
5.4 kubelet 쪽은 (1.22+) 기본이 systemd
강의에서 강조한 문서 내용:
- Kubernetes 1.22 이후: kubeadm은 kubelet cgroup driver를 명시하지 않으면 기본으로 systemd를 선택
즉, kubelet은 따로 설정 파일을 넘기지 않아도 “대부분 기본값으로 해결”됩니다.
(참고) 문서에는 kubeadm config 파일로 명시하는 방법도 나옵니다:
- ClusterConfiguration / KubeletConfiguration에서
cgroupDriver: systemdkubeadm init --config <file>형태로 적용
하지만 강의에서는 “이미 기본이 systemd라서 굳이 config 파일 안 쓴다”는 선택을 합니다.
5.5 containerd는 기본이 systemd가 아닐 수 있어 설정 필요
반대로 containerd는 기본 설정에서 systemd cgroup이 false인 경우가 많아서, 이를 true로 바꿔야 합니다.
문서 흐름(강의에서 그대로 수행):
/etc/containerd디렉터리 생성- 기본 config 생성
SystemdCgroup = true로 변경- containerd 재시작
1) 디렉터리 생성
sudo mkdir -p /etc/containerd
2) 기본 config 생성 + sed로 값 변경해서 저장
강의에서 사용한 “한 방” 커맨드(동작 설명 포함):
containerd config default로 기본 설정 생성sed로SystemdCgroup = false를true로 바꿈/etc/containerd/config.toml에 저장
containerd config default | \
sed 's/SystemdCgroup = false/SystemdCgroup = true/' | \
sudo tee /etc/containerd/config.toml
이걸 3개 노드에 모두 적용합니다.
3) 제대로 적용됐는지 확인
강의에서는 grep로 확인합니다(앞뒤 라인까지 같이 보기 위해 옵션을 추가).
grep -n "SystemdCgroup" /etc/containerd/config.toml
# SystemdCgroup = true 확인
그리고 설정 위치도 확인합니다:
- 보통
plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options아래
4) containerd 재시작
sudo systemctl restart containerd
sudo systemctl status containerd --no-pager
6) 컨트롤 플레인 초기화: kubeadm init (master에서)
이제 다시 “Creating a cluster with kubeadm” 문서로 돌아와서 다음 단계인 Initializing your control-plane node를 진행합니다.
6.1 (선택) 나중에 HA로 갈 계획이면 control-plane-endpoint 고려
문서 step에서 말하는 요지:
- 지금은 단일 컨트롤 플레인이라도,
- 추후 다중 컨트롤 플레인(HA)로 확장할 생각이 있다면
--control-plane-endpoint를 “가상 IP/로드밸런서 주소”로 잡아두면 마이그레이션이 쉬워짐
강의에서는 “단일 컨트롤 플레인만 할 것”이라서 이 플래그는 사용하지 않습니다.
6.2 Pod Network CIDR 지정
--pod-network-cidr는 파드 IP가 할당될 대역입니다.
예를 들어 10.244.0.0/16이면 파드들이 이 대역에서 IP를 받습니다.
강의에서는 Flannel을 사용할 것이므로 흔히 쓰는 기본 대역인:
10.244.0.0/16
을 사용합니다.
6.3 CRI 소켓 자동 감지와 --cri-socket
kubeadm은 보통 well-known endpoint로 런타임 소켓을 자동 감지합니다.
- 기본 경로에 있으면 자동 감지 →
--cri-socket불필요 - 런타임 소켓이 특이한 경로면 →
--cri-socket <path>필요
강의에서는 containerd가 기본 경로에 있으므로 별도 플래그를 주지 않습니다.
6.4 --apiserver-advertise-address (멀티 NIC 환경에서 중요)
VM에 NIC가 여러 개 있으면 kubeadm이 “어느 IP를 API 서버 주소로 쓸지” 헷갈릴 수 있습니다. Vagrant가 만드는 내부 인터페이스(실습용 관리 NIC)가 별도로 존재하기 때문입니다.
그래서 강의는 다음을 명시합니다.
- API 서버가 광고(advertise)할 주소를
- 노드 간 통신에 사용할
enp0s8의 IP로 지정 - master의 IP:
192.168.56.11
6.5 실제 init 명령(강의 흐름 그대로)
sudo kubeadm init \
--apiserver-advertise-address=192.168.56.11 \
--pod-network-cidr=10.244.0.0/16 \
--upload-certs
--upload-certs: 컨트롤 플레인 확장 시 인증서를 Secret 형태로 공유할 수 있게 업로드(강의에서 포함)- 단일 컨트롤 플레인이라도 포함해두면, 나중에 확장할 때 도움이 되는 흐름으로 이해하면 됩니다.
7) kubeadm init 출력 해석: “실제로는 뭘 했나?”
강의에서는 init 완료 후 출력 로그를 “그대로 읽어보면 kubeadm이 한 일을 알 수 있다”고 설명합니다.
대표적으로 출력에 보이는 작업들:
- CA 인증서 생성
- kube-apiserver 인증서 생성/구성
- etcd 서버 인증서 생성
- 각 컴포넌트별 kubeconfig 생성/설정
- kubelet 설정 파일 생성
- controller-manager, scheduler 설정 생성
- 컨트롤 플레인 컴포넌트를 static pod manifest로 생성
/etc/kubernetes/manifests/아래에 배치
- 필요한 컨트롤 플레인 Pod를 띄우고 상태를 맞춤
8) kubectl 접속 설정: admin.conf 복사
kubeadm init이 끝나면 kubeconfig 파일이 준비됩니다. 강의에서는 다음 파일을 확인합니다.
/etc/kubernetes/admin.conf
확인:
sudo cat /etc/kubernetes/admin.conf
그리고 kubectl 기본 위치로 복사:
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(네트워크 플러그인)를 아직 설치하지 않았기 때문
9) 다음 단계 안내: CNI 설치 + 워커 조인 명령 저장
kubeadm init 출력 마지막에는 다음 두 가지가 안내됩니다.
- Pod network add-on(CNI) 설치하라
- 워커 노드에서 실행할
kubeadm join ...명령이 출력된다
강의에서는 join 명령을 복사해서 따로 저장(notepad 등)해두라고 합니다.
(토큰/해시가 들어가므로 나중에 다시 찾기 귀찮아짐)
추가로 강의에서는 “HA 문서에서는 control-plane 노드 조인 명령도 같이 출력된다”는 점을 보여줍니다.
- 단일 컨트롤 플레인: 워커 조인 명령만 출력
- 다중 컨트롤 플레인(HA): 워커 조인 + control-plane 조인 명령이 함께 출력
10) CNI 설치: Flannel 적용 (기본 Pod CIDR 10.244.0.0/16)
문서에서 “Installing a pod network add-on” 섹션을 따라갑니다.
여기에는 다양한 CNI 목록이 나오고, 강의는 Flannel을 선택합니다.
Flannel 설치는 보통 “manifest 한 줄 apply”로 끝납니다.
kubectl apply -f <FLANNEL_MANIFEST_URL>
10.1 Pod CIDR을 커스텀으로 바꿨다면?
강의는 중요한 분기점을 설명합니다.
- Flannel 기본 Pod CIDR:
10.244.0.0/16 - 내가
kubeadm init --pod-network-cidr를 다른 값으로 줬다면:- manifest를 다운로드해서
- network 설정을 내 CIDR로 바꿔야 함
강의에서는 기본 CIDR을 그대로 썼기 때문에 “수정은 필요 없다”고 합니다.
다만 “어디를 고치는지” 보여주기 위해 manifest를 다운로드해 확인합니다.
10.2 manifest 다운로드 후 수정 위치 확인(강의 흐름)
강의에서는 처음에 curl을 쓰려다가 wget을 사용해 파일로 받습니다(의도는 “파일로 저장해서 열어보기”).
wget <FLANNEL_MANIFEST_URL> -O kube-flannel.yml
파일을 열어보면 다양한 리소스가 들어있습니다.
- Namespace
- ServiceAccount
- ClusterRole / ClusterRoleBinding
- DaemonSet 등
여기서 중요한 건 Flannel이 사용하는 네트워크 대역 설정 부분입니다. 보통 이런 형태를 찾습니다.
10.244.0.0/16값이 들어간 곳(예: env/configmap 등)
강의 포인트:
- 여기 값을 kubeadm init에 준
--pod-network-cidr와 반드시 일치시켜야 한다
10.3 apply로 배포
kubectl apply -f kube-flannel.yml
배포 후 상태 확인:
- kube-system에 없을 수도 있고(강의에서 처음 kube-system을 봤을 때 “없네?”라고 언급)
- Flannel 전용 namespace(예:
kube-flannel)에 생성될 수 있음
네임스페이스 확인:
kubectl get ns
Flannel 파드 확인:
kubectl get pods -n kube-flannel
그리고 다시 노드 상태 확인:
kubectl get nodes
CNI가 제대로 깔리면, master가 Ready로 바뀌는 것을 확인할 수 있습니다.
11) 워커 노드 조인: kubeadm join (node01, node02에서)
이제 저장해둔 조인 명령을 워커 노드에서 실행합니다.
sudo kubeadm join <MASTER_IP>:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
강의에서 조인 과정 중 출력되는 포인트:
- 사전 체크 실행
- kubelet이 “새로운 secure connection details”를 받음
- API 서버에 연결되고 정상 조인 완료
두 노드 모두 실행 후, master에서 확인:
kubectl get nodes
결과:
kubemaster,kubenode01,kubenode02가 모두Ready
Flannel은 노드마다 에이전트를 띄운다 (DaemonSet 개념 힌트)
강의에서는 “Flannel 파드가 3개로 늘어나는 이유”를 설명합니다.
- CNI 플러그인은 보통 각 노드에서 네트워크 처리를 담당하는 에이전트가 필요
- 그래서 DaemonSet으로 “노드당 1개”가 뜨는 패턴이 흔함
- 노드를 추가하면 자동으로 해당 노드에 Flannel 파드가 추가로 뜸
확인:
kubectl get pods -n kube-flannel
# 노드 수만큼 flannel pod가 보이는 것이 정상
12) 최종 테스트: nginx Pod 하나 띄워보기
클러스터가 준비됐는지 가장 빠르게 확인하는 방법은 “간단한 파드 하나 실행해보기”입니다.
강의에서는 다음을 수행합니다.
kubectl run web --image=nginx
kubectl get pod
kubectl get pod -w
- 처음엔
ContainerCreating일 수 있음 - 곧
Running으로 바뀌면 정상
정리:
kubectl delete pod web
이걸로 “kubeadm으로 부트스트랩 + CNI 구성 + 워커 조인 + 파드 실행”까지 전체 흐름이 완료됩니다.
자주 터지는 포인트를 강의 흐름 기준으로 다시 요약
- 멀티 NIC 환경(Vagrant VM)에서
--apiserver-advertise-address를 안 주면- 의도하지 않은 인터페이스 IP로 잡힐 수 있음
- cgroup driver 불일치
- kubelet은 systemd인데 containerd는 기본 false → 문제 발생 가능
/etc/containerd/config.toml에서SystemdCgroup = true+ 재시작이 핵심
- CNI 미설치
kubectl get nodes에서NotReady는 CNI 설치 전엔 정상- Flannel/Calico 등 설치 후 Ready로 바뀌는 흐름 확인
- Pod CIDR 불일치
kubeadm init --pod-network-cidr와 CNI manifest의 CIDR이 다르면 네트워크가 깨짐
정리
이 데모의 핵심 흐름은 다음과 같습니다.
- Vagrant로 VM 3대 준비(마스터 1, 워커 2) + 통신용 인터페이스/IP 확인
- 문서(Installing kubeadm / Creating a cluster with kubeadm) 열고 그대로 따라가기
- 모든 노드에 kubeadm/kubelet/kubectl 설치
- 모든 노드에 containerd 설치 + systemd cgroup driver로 맞추기
- master에서
kubeadm init(특히--apiserver-advertise-address,--pod-network-cidr,--upload-certs) - kubeconfig 설정 후
kubectl get nodes로 확인(NotReady는 정상) - Flannel CNI 설치(기본 10.244.0.0/16, 커스텀이면 manifest 수정)
- 워커에서
kubeadm join실행 → 노드 Ready 확인 kubectl run web --image=nginx로 파드 실행 테스트
'CKA' 카테고리의 다른 글
| Helm - Helm 2 vs Helm 3 (0) | 2026.01.07 |
|---|---|
| Helm (0) | 2026.01.07 |
| Deploy k8s (0) | 2026.01.06 |
| Design Kubernetes - HA of ETCD (0) | 2026.01.06 |
| Design Kubernetes - HA(High Availability) (0) | 2026.01.06 |