쿠버네티스를 처음 공부할 때 가장 큰 장벽은 설치 그 자체다.
특히 VMware와 같은 가상화 환경에서 여러 노드를 띄워 클러스터를 구성할 때는 가상 네트워크와 쿠버네티스 내부 통신 구조가 복잡하게 얽히면서 예상치 못한 에러가 계속 뜨곤 한다.
사실 도커 데스크톱 앱을 이용하면 설치 과정이 없이도 편하게 쿠버네티스를 실습할 수 있다.
클릭 몇번만 하면 쿠버네티스 환경이 뚝딱 만들어지는데 왜 굳이 직접 설치를 해야 할까?
도커 데스크톱은 쿠버네티스 환경을 추상화하여 제공하므로 내부 동작 원리를 파악하기 어렵다.
반면 우분투 머신을 활용해 클러스터를 직접 구축하면 컨트롤 플레인 구성 요소(etcd, API 서버 등)를 개별적으로 설정하며 시스템 아키텍처를 명확히 이해하게 된다.
특히 CNI(Container Network Interface) 기반의 노드 간 통신 설정과 멀티 노드 환경에서의 스케줄링 로직을 직접 다루는 경험을 해볼 수 있다는 것도 중요한 장점이다.
이 과정에서 발생하는 다양한 에러를 해결하면서 단순 사용자 차원을 넘어서 인프라 운영 수준의 기술적 깊이를 확보해갈 수 있지 않을까?

그래서 이번 포스팅에서는 우분투 18.04 환경에서 쿠버네티스 v1.30 클러스터를 구축하며 겪었던 시행착오와, 이를 해결한 과정을 간단히 정리해보려고 한다.
Part 1: 쿠버네티스 클러스터 구축 가이드
우분투 가상머신 한 대(k8s-master)에서 기초 설정을 마친 뒤, 이를 두 개 복제(clone)하여 워커 노드(k8s-worker1, k9s-worker2)들을 구성하는 방식으로 노드를 3대 준비한다.
1. 마스터 노드 선생 설정 (모든 노드 공통 패키지 포함)
가장 먼저 노드 간 통신과 원격 제어를 위한 기본 도구 및 쿠버네티스 패키지를 마스터 노드에 설치한다.
이 작업은 나중에 복제될 워커 노드들에도 동일하게 적용된다.
1.1. 원격 접속 및 보안 설정
# 1. SSH 서버 설치 및 실행 (원격 관리를 위한 필수 단계)
sudo apt update
sudo apt -y install ssh
sudo service ssh start
# 2. 보안 설정 (실습을 위해 방화벽 비활성화)
sudo apt -y install firewalld
sudo systemctl stop firewalld && sudo systemctl disable firewalld
sudo ufw disable
# 3. 스왑(Swap) 메모리 해제
# 쿠버네티스 kubelet의 정상 작동을 위해 스왑을 끄고 fstab에서 관련 설정을 주석 처리한다.
sudo swapoff -a
sudo nano /etc/fstab
1.2. 쿠버네티스 패키지 설치 및 자동 완성 설정
쿠버네티스 바이너리를 설치하고, 효율적인 터미널 사용을 위해 Bash 자동 완성 설정을 진행한다.
# 1. 패키지 저장소 및 GPG 키 등록
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/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.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
# 2. kubelet, kubeadm, kubectl 설치 및 버전 고정
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# 3. Kubelet 활성화
sudo systemctl daemon-reload
sudo systemctl restart kubelet && sudo systemctl enable kubelet.service
# 4. 명령어 자동 완성 설정 (root 및 일반 사용자 계정 동일 적용)
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc
source <(kubeadm completion bash)
echo "source <(kubeadm completion bash)" >> ~/.bashrc
source ~/.bashrc
2. 노드 복제와 개별 네트워크 설정
위 단계까지 완료된 마스터 노드를 VMware의 Full Clone하여 두 대의 워커 노드(k8s-worker1, k8s-worker2)로 복제한다.
복제된 노드들은 신원 정보가 같으므로 이를 각각 수정해줘야 한다.
2.1. 호스트네임 및 IP 변경
각 노드에 접속하여 /etc/hostname을 각각의 이름(예: k8s-worker1, k8s-worker2)로 수정하고, /etc/network/interfaces 파일에서 static IP(192.168.100.201~202)를 할당한다.
2.2. 호스트 파일(/etc/hosts) 정리
루프백 주소인 127.0.1.1은 주석 처리하고, 모든 노드의 IP를 수동으로 등록하여 서로 이름으로 통신할 수 있게 한다.
127.0.0.1 localhost
#127.0.1.1 master
192.168.100.200 k8s-master
192.168.100.201 k8s-worker1
192.168.100.202 k8s-worker2
2.3. 최종 연결 검증
모든 장비를 리부팅한 후, ip add로 IP 변경을 확인하고 외부 인터넷 통신(ping 8.8.8.8)과 노드 간 SSH 접속(ssh username@k8s-worker1)이 원활한지 최종적으로 테스트한다.
4. 클러스터 초기화 및 조인(Join)
3.1. 마스터 노드 초기화
마스터 노드에서 컨트롤 플레인을 생성한다. 파드 대역은 물리 IP와 겹치지 않게 주의한다.
# 1. 마스터 초기화 (Pod 네트워크 대역은 물리 IP와 겹치지 않게 10.244 대역 사용)
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=192.168.100.200
초기화 완료 후 화면에 출력되는 kubeadm join 192.168.100.200:6443 --token ... 명령어를 복사해둔다.

# 2. 일반 사용자 계정 권한 설정
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
3.2. Calico 설정 파일 수정 (★중요)
Calico는 기본적으로 192.168.0.0/16 대역을 사용하려 한다.
이를 우리가 init 할 때 정한 10.244.0.0/16으로 강제 일치시켜야 한다.
# 매니페스트 다운로드
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
# 설정 변경
nano calico.yaml
파일 내에서 ctrl+w로 CALICO_IPV4POOL_CIDR를 찾아 주석(#)을 제거하고 값을 다음과 같이 수정한다.
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
수정 후 kubectl apply -f calico.yaml을 실행하여 CNI를 설치한다.
3.3. 워커 노드 조인 및 상태 확인
각 워커 노드(k8s-worker1, k8s-worker2)에서 복사해둔 명령어를 실행하여 클러스터에 합류시킨다.
sudo kubeadm join 192.168.100.200:6443 --token <토큰> --discovery-token-ca-cert-hash sha256:<해시값>
마스터 노드에서 kubectl get nodes 명령어로 모든 노드가 Ready 상태임을 확인하면 클러스터 구성이 마무리된다.

Part 2: Nginx 배포 및 테스트
클러스터 구축 후 Nginx를 배포해 보면, 파드가 없는 노드 IP로 접속해도 서비스가 열리는 것을 확인할 수 있다.
1. Nginx 배포 및 서비스 노출
마스터 노드에서 Nginx 파드를 배포하고, 외부에서 접속할 수 있도록 NodePort 타입으로 노출한다.
# 1. Nginx deployment 생성
kubectl create deployment nginx-test --image=nginx
# 2. NodePort 타입으로 서비스 생성 (80포트를 노드 포트로 개방)
kubectl expose deployment nginx-test --port=80 --type=NodePort
# 3. 서비스 포트 및 파드 위치 확인
kubectl get svc
kubectl get pods -o wide
kubectl get svc 결과에서 80:3XXXX/TCP 형태의 포트 번호를 확인한다. (예: 31706)
2. 외부 접속 테스트
이제 윈도우(호스트 PC) 브라우저를 켜고, 어떤 노드의 IP로 접속해도 동일한 화면이 뜨는 것을 확인한다.
- http://192.168.100.200:31706 (마스터 노드) -> 성공
- http://192.168.100.201:31706 (워커1 노드) -> 성공
- http://192.168.100.202:31706 (워커2 노드) -> 성공
3. NodePort와 kube-proxy
파드는 분명 특정 워커 노드 하나에만 떠 있는데, 왜 모든 노드 IP로 접속이 가능할까?
이것은 쿠버네티스의 kube-proxy 덕분이다.
동작 원리: 서비스를 NodePort 타입으로 생성하면 클러스터의 모든 노드가 해당 포트(ex. 31706)를 똑같이 개방한다.
트래픽 전달: 사용자가 파드가 없는 노드로 접속하더라도 해당 노드의 kube-proxy가 트래픽을 가로채 실제 파드가 떠 있는 노드로 전달(Forwarding)한다. 이 과정 덕분에 클러스터 전체가 하나의 서비스 장치처럼 동작한다.

3. Overlay Network와 캡슐화
이런 유연한 통신은 물리 인터페이스 위에 가상 터널을 만드는 Overlay Network 덕분에 가능하다.
패킷을 노드 간 통신이 가능한 형태(Node IP)로 감싸는 캡슐화(Encapsulation) 과정을 거치며, tunl0 인터페이스가 이 통로 역할을 수행한다.
구축 과정에서의 IP 대역 충돌은 바로 이 가상 통로와 실제 물리 도로의 번지수가 같아 발생했던 문제였다.
Part 3: 트러블슈팅 - 주요 장애 진단 및 해결
구축 과정에서 발생한 핵심적인 에러들과 그 해결 과정
1. 네트워크 라우팅 충돌 (CIDR Overlap)
- 현상: 마스터와 워커 노드 사이의 통신이 불가능하거나(ping이 가지 않음) 워커 노드 조인 시 타임아웃 발생.
- 원인: 초기 구축 시 --pod-network-cidr을 노드의 실제 IP 대역인 192.168.100.0/24로 설정하여 커널 라우팅이 꼬였다.
- 해결: 클러스터를 리셋하고 파드 CIDR 대역을 10.244.0.0/16으로 완전히 분리하여 물리 네트워크와의 간섭을 제거했다.

2. 컨테이너 런타임 오류 (CRI API Error)
- 현상: 조인 과정에서 rpc error: code = Unimplemented desc = unknown service 발생.
- 원인: containerd 설정 중 CRI 플러그인이 비활성화되어 있고, cgroup 드라이버 설정이 systemd와 일치하지 않았다.
- 해결: containerd config default 명령으로 설정을 재생성하고, SystemdCgroup = true를 적용하여 런타임을 정상화했다.
# 1. 관리자 권한으로 전환 (편의상)
sudo -i
# 2. 기존 설정 파일이 있다면 백업 (혹은 삭제)
mv /etc/containerd/config.toml /etc/containerd/config.toml.bak
# 3. 기본 설정 파일 새로 생성
containerd config default > /etc/containerd/config.toml
# 4. 설정 파일 편집기 열기
nano /etc/containerd/config.toml
# 변경 전
SystemdCgroup = false
# 변경 후
SystemdCgroup = true
#5. 서비스 재시작
systemctl restart containerd
3. 잔존 인터페이스 및 캐시 문제
- 현상: 일부 노드가 NotReady 상태를 유지함.
- 디버깅: ip link show를 했을 때 목록 중에 tunl0라는 이름이 보임.
- 원인: 처음에 192.168.100.x 대역으로 설정을 시도했을 때, 리눅스 커널 내부에는 tunl0 같은 가상 네트워크 다리가 이미 만들어졌음. 이후 설정 파일은 10.244.x.x로 수정했지만, 메모리에 남아 있는 가상 인터페이스 tunl0가 새 설정을 방해함.
- 해결: sudo ip link delete tunl0로 인터페이스를 삭제하고 관련 캐시를 삭제한 뒤 서비스를 재시작하여 해결함.
- sudo rm -rf /var/lib/cni/networks/* #혹시 모를 찌꺼기 파일 삭제
- sudo systemctl restart containerd
- sudo systemctl restart kubelet
'AI Journey > 클라우드' 카테고리의 다른 글
| [k8s] 내 파드는 잘 연결되었을까? 쿠버네티스 서비스(Service) 상태 확인 (0) | 2026.01.29 |
|---|---|
| [k8s] Vagrant와 VirtualBox를 사용해서 쿠버네티스 클러스터 구축하기 (0) | 2026.01.29 |
| [Docker Swarm] 도커 스택(Stack)을 배포해보자 (0) | 2026.01.16 |
| [Docker Swarm] 데이터 관리: 시크릿(secret)과 컨피그(config) (0) | 2026.01.16 |
| [Docker Swarm] Rolling Update - 서비스 업데이트와 복구 (0) | 2026.01.15 |