Deploy k8s - kubeadm으로 배포하기

2026. 1. 6. 22:44·CKA

쿠버네티스 클러스터는 단순히 “노드 몇 대에 쿠버네티스 설치”로 끝나지 않습니다. 컨트롤 플레인 컴포넌트(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 아키텍처 설계

전체 설치 흐름 (하이 레벨)

  1. 노드(여러 VM/서버) 준비
  2. 한 대를 Control Plane(구 master) 로, 나머지를 Worker 로 지정
  3. 모든 노드에 컨테이너 런타임 설치 (예: containerd)
  4. 모든 노드에 kubeadm + kubelet + kubectl 설치
  5. 컨트롤 플레인에서 kubeadm init 로 초기화
  6. Pod Network(CNI) 설치 (이 단계 전에는 노드가 NotReady인 것이 정상)
  7. 워커 노드에서 kubeadm join 으로 클러스터 조인
  8. 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

이 IP들은 Pod CIDR이 아니라 “노드(VM) 자체의 IP” 입니다. 즉, kube-apiserver에 접근하거나 노드 간 통신(조인 등)에 쓰입니다.

확인 예시:

ip addr
ip route

2) 문서 탭 2개는 켜두자 (설치/생성 가이드)

강의에서 강조하는 실전 팁:

  • 쿠버네티스 문서는 “대부분 잘 정리되어 있다”
  • 설치할 때 다음 탭을 같이 열고 따라가면 좋다

추천 탭:

  1. Installing kubeadm
    • 위치: “Installing Kubernetes with deployment tools” → “Bootstrapping clusters with kubeadm”
    • 여기서 패키지 저장소/키 설정, kubeadm 설치 절차를 확인
  2. 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)

문서에서는 보통 아래 세 가지를 한 번에 설치하도록 안내합니다.

  • kubelet
  • kubeadm
  • kubectl

강의 포인트:

  • “지금 당장 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: systemd
  • kubeadm init --config <file> 형태로 적용

하지만 강의에서는 “이미 기본이 systemd라서 굳이 config 파일 안 쓴다”는 선택을 합니다.

5.5 containerd는 기본이 systemd가 아닐 수 있어 설정 필요

반대로 containerd는 기본 설정에서 systemd cgroup이 false인 경우가 많아서, 이를 true로 바꿔야 합니다.

문서 흐름(강의에서 그대로 수행):

  1. /etc/containerd 디렉터리 생성
  2. 기본 config 생성
  3. SystemdCgroup = true로 변경
  4. 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 출력 마지막에는 다음 두 가지가 안내됩니다.

  1. Pod network add-on(CNI) 설치하라
  2. 워커 노드에서 실행할 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이 다르면 네트워크가 깨짐

정리

이 데모의 핵심 흐름은 다음과 같습니다.

  1. Vagrant로 VM 3대 준비(마스터 1, 워커 2) + 통신용 인터페이스/IP 확인
  2. 문서(Installing kubeadm / Creating a cluster with kubeadm) 열고 그대로 따라가기
  3. 모든 노드에 kubeadm/kubelet/kubectl 설치
  4. 모든 노드에 containerd 설치 + systemd cgroup driver로 맞추기
  5. master에서 kubeadm init (특히 --apiserver-advertise-address, --pod-network-cidr, --upload-certs)
  6. kubeconfig 설정 후 kubectl get nodes로 확인(NotReady는 정상)
  7. Flannel CNI 설치(기본 10.244.0.0/16, 커스텀이면 manifest 수정)
  8. 워커에서 kubeadm join 실행 → 노드 Ready 확인
  9. 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
'CKA' 카테고리의 다른 글
  • Helm - Helm 2 vs Helm 3
  • Helm
  • Deploy k8s
  • Design Kubernetes - HA of ETCD
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (242)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (118)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      hibernate5module
      reentarantlock
      @discriminatorcolumn
      Target
      JPQL
      페치 조인
      gesingleresult
      @discriminatorvalue
      requset scope
      양방향 맵핑
      jdk 동적 프록시
      Thread
      고급
      락
      단방향 맵핑
      자바
      WAS
      조회 성능 최적화
      스레드
      jpq
      프록시
      cglib
      log trace
      김영한
      @args
      프록시 팩토리
      typequery
      버퍼
      빈 후처리기
      @within
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    Deploy k8s - kubeadm으로 배포하기
    상단으로

    티스토리툴바