본문 바로가기
IT/HomeLabs

[홈랩 K8s 자동화 #1] Proxmox + Terraform으로 쿠버네티스 실습 VM 한방에 만들기

by 수누다 2026. 3. 21.

쿠버네티스를 공부하다 보면 클러스터를 수십 번 만들었다 부쉈다 반복하게 됩니다. 처음엔 Proxmox 웹 UI에서 VM을 하나하나 클릭해서 만들었는데, 세 번째쯤 되니까 "이걸 왜 손으로 하고 있지?"라는 생각이 들더군요. 결국 Terraform으로 VM 생성을 자동화하고, terraform destroyterraform apply 한 방이면 깨끗한 실습 환경이 다시 올라오는 구조를 만들었습니다.

이 글에서는 Proxmox 위에 Terraform으로 마스터 1대 + 워커 2대 VM을 자동 생성하고, kubeadm으로 쿠버네티스 클러스터를 구축하는 전 과정을 정리합니다. 도중에 만난 에러도 10가지 넘게 정리해뒀으니, 같은 삽질을 반복하지 않으셨으면 합니다.

시리즈 안내

  • #1 VM 자동화 — Terraform으로 Proxmox VM 생성 + kubeadm 클러스터 구축 (이번 글)
  • #2 워크로드 배포 — ArgoCD로 GitOps 파이프라인 구성 (예정)
  • #3 모니터링 스택 — Grafana + Prometheus로 클러스터 관측 (예정)


1. 환경 정보

항목
Proxmox VE 8.4.16 (커널 6.8.12-18-pve)
호스트 하드웨어 Intel Core i5-8500 @ 3.00GHz, RAM 16GB, HDD 512GB
Terraform 작업용 VM (tf-admin) Ubuntu 22.04, 2 vCPU, 2GB RAM, IP: 192.168.20.111
Terraform 1.14.7
Terraform Provider bpg/proxmox 0.98.1
Guest OS Ubuntu 24.04.4 LTS (cloud-init 이미지)
Kubernetes 1.31.14
컨테이너 런타임 containerd 1.7.28
CNI Calico (calico/node)

📌 스토리지 이름에 대해: 이 글에서는 local-zfs를 사용하고 있습니다. Proxmox 설치 시 ZFS를 선택했기 때문인데, 기본 설치(ext4/LVM)를 하셨다면 스토리지 이름이 local-lvm일 겁니다. 아래 Terraform 코드에서 datastore_id = "local-zfs" 부분을 본인 환경에 맞게 local-lvm 등으로 바꿔주세요. Proxmox 웹 UI → Datacenter → Storage에서 본인의 스토리지 이름을 확인할 수 있습니다.

전체 구성도

이 글에서 만들 VM은 총 4대입니다. Terraform을 실행할 작업용 VM 1대, 그리고 Terraform이 자동으로 만들어줄 K8s 노드 3대입니다.

┌──────────────────────────────────────────────────────────────┐
│                     Proxmox VE 호스트                         │
│                                                              │
│  ┌────────────┐                                              │
│  │ tf-admin   │ ◀── 이 VM에서 terraform apply 실행            │
│  │ (VM 100)   │     Proxmox API를 통해 아래 VM 3대를 자동 생성  │
│  │ 2 vCPU     │                                              │
│  │ 2GB RAM    │          ┌──── API 호출 ────┐                │
│  │ 20GB Disk  │          ▼                  ▼                │
│  │ .111       │  ┌───────────┐ ┌──────────┐ ┌──────────┐    │
│  └────────────┘  │ k8s-master│ │k8s-worker│ │k8s-worker│    │
│  ↑ 직접 생성     │  (VM 201) │ │ (VM 202) │ │ (VM 203) │    │
│  (웹 UI or CLI)  │ 4 vCPU    │ │ 2 vCPU   │ │ 2 vCPU   │    │
│                  │ 4GB RAM   │ │ 4GB RAM  │ │ 4GB RAM  │    │
│                  │ 30GB Disk │ │ 30GB Disk│ │ 30GB Disk│    │
│                  │ .112      │ │ .113     │ │ .114     │    │
│                  └───────────┘ └──────────┘ └──────────┘    │
│                  ↑ Terraform이 자동 생성                      │
│                                                              │
│                  네트워크: 192.168.20.0/24                     │
└──────────────────────────────────────────────────────────────┘

핵심 포인트는 이렇습니다. tf-admin VM은 한 번 만들어두면 계속 쓰고, K8s 노드 3대는 terraform destroy / terraform apply로 언제든 부수고 다시 만드는 일회용 VM입니다.


2. Terraform 작업용 VM 만들기 (tf-admin)

Terraform은 Proxmox 호스트에서 직접 실행하는 게 아니라, 별도의 리눅스 VM에서 Proxmox API를 호출하는 방식으로 동작합니다. 이 작업용 VM을 먼저 하나 만들어야 합니다.

2-1. Proxmox 웹 UI에서 VM 생성

Proxmox 웹 UI(https://서버IP:8006)에 접속해서 오른쪽 상단 Create VM 버튼을 클릭합니다.

General:
  VM ID: 100
  Name: tf-admin

OS:
  ISO image: ubuntu-24.04-live-server-amd64.iso (미리 다운로드해서 업로드)

System:
  기본값 그대로

Disks:
  Disk size: 20 GB

CPU:
  Cores: 2

Memory:
  Memory: 2048 MB (2GB)

Network:
  Bridge: vmbr0
  기본값 그대로

VM을 시작하고 Ubuntu Server 설치를 진행합니다. 설치 중 네트워크 설정에서 고정 IP를 잡아주세요.

IP:      192.168.20.111/24
Gateway: 192.168.20.1
DNS:     8.8.8.8

2-2. Terraform 설치

Ubuntu 설치가 끝나면 SSH로 접속해서 Terraform을 설치합니다.

ssh ubuntu@192.168.20.111
# 필수 패키지 설치
sudo apt-get update
sudo apt-get install -y gnupg software-properties-common curl

# HashiCorp GPG 키 추가
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

# HashiCorp 저장소 추가
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Terraform 설치
sudo apt-get update
sudo apt-get install -y terraform

# 설치 확인
terraform version

아래처럼 버전이 나오면 성공입니다.

Terraform v1.14.7
on linux_amd64

2-3. SSH 키 생성

이 키는 나중에 Terraform이 K8s VM을 만들 때 자동으로 주입됩니다. 그래야 tf-admin에서 K8s 노드들에 비밀번호 없이 SSH 접속할 수 있습니다.

ssh-keygen -t ed25519 -C "k8s-lab" -f ~/.ssh/id_ed25519 -N ""

# 공개키 확인 (나중에 terraform.tfvars에 넣을 값)
cat ~/.ssh/id_ed25519.pub

출력된 ssh-ed25519 AAAA... 값을 복사해두세요.

정리하면: tf-admin VM은 Terraform을 실행하는 "리모컨" 역할입니다. 이 VM에서 terraform apply를 치면, Proxmox API를 통해 K8s용 VM 3대가 자동으로 만들어집니다. tf-admin은 한 번만 만들면 되고, K8s VM은 실습할 때마다 부수고 다시 만들 수 있습니다.


3. Proxmox 사전 준비 (cloud-init 템플릿 + API 토큰)

3-1. cloud-init 템플릿 VM 만들기

Ubuntu cloud 이미지를 다운로드하고 Proxmox에 VM 템플릿으로 등록합니다. Proxmox 호스트 셸에서 아래 명령어를 실행하세요.

# Ubuntu 24.04 cloud 이미지 다운로드
cd /var/lib/vz/template/iso/
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

# VM 생성 (ID 9000번을 템플릿 용도로 사용)
qm create 9000 --memory 2048 --core 2 --name ubuntu-cloud-template --net0 virtio,bridge=vmbr0

# 다운로드한 이미지를 VM 디스크로 import (local-zfs는 본인 스토리지명으로 변경)
qm importdisk 9000 noble-server-cloudimg-amd64.img local-zfs

# 디스크를 SCSI로 연결
qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-zfs:vm-9000-disk-0

# cloud-init 드라이브 추가
qm set 9000 --ide2 local-zfs:cloudinit

# 부팅 순서 지정
qm set 9000 --boot c --bootdisk scsi0

# 시리얼 콘솔 활성화 (cloud-init 호환)
qm set 9000 --serial0 socket --vga serial0

# 템플릿으로 변환
qm template 9000

3-2. Terraform용 API 토큰 발급

Proxmox 웹 UI에서 Datacenter → Permissions → API Tokens로 이동합니다.

사용자: root@pam (또는 전용 사용자 생성 권장)
토큰 ID: terraform
Privilege Separation: 체크 해제 (전체 권한 상속)

발급된 토큰 값을 안전하게 보관하세요. 한 번만 보여주기 때문에 놓치면 재발급해야 합니다.

보안 팁: 프로덕션 환경이라면 전용 사용자 + 최소 권한으로 구성하는 게 맞지만, 홈랩 실습 용도에서는 root 토큰으로도 충분합니다. 다만 .tfvars 파일은 절대 Git에 올리지 마세요.


4. Terraform 코드 작성 (tf-admin VM에서 진행)

디렉토리 구조

k8s-lab/
├── main.tf           # Provider 설정
├── variables.tf      # 변수 정의
├── terraform.tfvars  # 변수 값 (Git 제외!)
├── master.tf         # 마스터 노드 VM
├── worker.tf         # 워커 노드 VM
└── outputs.tf        # IP 주소 출력

여기서부터는 tf-admin VM에 SSH 접속한 상태에서 진행합니다. ssh ubuntu@192.168.20.111로 접속하세요.

4-1. main.tf — Provider 설정

terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = ">= 0.98.0"
    }
  }
}

provider "proxmox" {
  endpoint = var.proxmox_api_url
  api_token = var.proxmox_api_token
  insecure = true  # 자체 서명 인증서 사용 시

  ssh {
    agent = true
  }
}

4-2. variables.tf — 변수 정의

variable "proxmox_api_url" {
  description = "Proxmox API 엔드포인트"
  type        = string
}

variable "proxmox_api_token" {
  description = "Proxmox API 토큰 (user@realm!tokenid=secret)"
  type        = string
  sensitive   = true
}

variable "proxmox_node" {
  description = "Proxmox 노드명"
  type        = string
  default     = "pve"          # 본인 Proxmox 노드명으로 변경
}

variable "template_vm_id" {
  description = "cloud-init 템플릿 VM ID"
  type        = number
  default     = 9000
}

variable "ssh_public_key" {
  description = "SSH 공개키"
  type        = string
}

variable "gateway_ip" {
  description = "네트워크 게이트웨이"
  type        = string
  default     = "192.168.20.1"
}

variable "worker_count" {
  description = "워커 노드 수"
  type        = number
  default     = 2
}

4-3. master.tf — 마스터 노드

resource "proxmox_virtual_environment_vm" "k8s_master" {
  name      = "k8s-master"
  node_name = var.proxmox_node
  vm_id     = 201

  clone {
    vm_id = var.template_vm_id
    full  = true
  }

  cpu {
    cores = 4
    type  = "x86-64-v2-AES"
  }

  memory {
    dedicated = 4096
  }

  disk {
    datastore_id = "local-zfs"    # 본인 환경에 맞게 변경 (기본 설치는 "local-lvm")
    size         = 30
    interface    = "scsi0"
  }

  network_device {
    bridge = "vmbr0"
  }

  initialization {
    ip_config {
      ipv4 {
        address = "192.168.20.112/24"
        gateway = var.gateway_ip
      }
    }

    user_account {
      username = "ubuntu"
      keys     = [var.ssh_public_key]
    }

    dns {
      servers = ["8.8.8.8", "8.8.4.4"]
    }
  }

  on_boot = true

  lifecycle {
    ignore_changes = [disk]
  }
}

4-4. worker.tf — 워커 노드 (count 활용)

resource "proxmox_virtual_environment_vm" "k8s_worker" {
  count     = var.worker_count
  name      = "k8s-worker-${count.index + 1}"
  node_name = var.proxmox_node
  vm_id     = 202 + count.index

  clone {
    vm_id = var.template_vm_id
    full  = true
  }

  cpu {
    cores = 2
    type  = "x86-64-v2-AES"
  }

  memory {
    dedicated = 4096
  }

  disk {
    datastore_id = "local-zfs"    # 본인 환경에 맞게 변경 (기본 설치는 "local-lvm")
    size         = 30
    interface    = "scsi0"
  }

  network_device {
    bridge = "vmbr0"
  }

  initialization {
    ip_config {
      ipv4 {
        address = "192.168.20.${113 + count.index}/24"
        gateway = var.gateway_ip
      }
    }

    user_account {
      username = "ubuntu"
      keys     = [var.ssh_public_key]
    }

    dns {
      servers = ["8.8.8.8", "8.8.4.4"]
    }
  }

  on_boot = true

  lifecycle {
    ignore_changes = [disk]
  }
}

4-5. outputs.tf

output "master_ip" {
  value = "192.168.20.112"
}

output "worker_ips" {
  value = [for i in range(var.worker_count) : "192.168.20.${113 + i}"]
}

4-6. terraform.tfvars (예시)

proxmox_api_url   = "https://192.168.20.1:8006"
proxmox_api_token = "root@pam!terraform=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
proxmox_node      = "pve"          # 본인 Proxmox 노드명으로 변경
ssh_public_key    = "ssh-ed25519 AAAA... your-key-here"
gateway_ip        = "192.168.20.1"
worker_count      = 2

⚠️ 보안 주의: terraform.tfvars에는 Proxmox API 토큰(비밀번호 역할)이 들어있습니다. 이 Terraform 코드를 Git(코드 버전 관리 도구)으로 관리하면서 GitHub 같은 곳에 올릴 계획이라면, .gitignore 파일을 만들어서 terraform.tfvars를 제외해야 합니다. 그래야 비밀번호가 인터넷에 노출되지 않습니다. Git을 사용하지 않고 tf-admin VM 안에서만 작업하신다면 이 부분은 넘어가셔도 됩니다.


5. Terraform 실행 — VM 생성

cd k8s-lab/

# 초기화
terraform init

# 플랜 확인
terraform plan

plan 결과에서 VM 3대가 생성되는 것을 확인한 뒤 apply합니다.

terraform apply -auto-approve

정상적으로 완료되면 아래와 같은 출력이 나옵니다.

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

master_ip = "192.168.20.112"
worker_ips = [
  "192.168.20.113",
  "192.168.20.114",
]

SSH 접속 테스트:

ssh ubuntu@192.168.20.112
ssh ubuntu@192.168.20.113
ssh ubuntu@192.168.20.114

6. kubeadm으로 쿠버네티스 클러스터 구축

여기서부터는 전체 노드(마스터 + 워커)에 공통 작업이 먼저 있고, 그 다음 마스터/워커 별 작업이 있습니다.

6-1. 전체 노드 공통 작업

아래 명령어를 3대 모두에서 실행합니다. 저는 마스터는 수동으로 먼저 진행하고, 워커 2대는 Ansible로 한 번에 돌렸습니다. Ansible 설정 방법은 아래 참고 박스를 확인하세요.

💡 Ansible로 워커 2대 한 번에 설정하기

tf-admin VM에 Ansible을 설치하고 (pip3 install ansible), 인벤토리 파일에 워커 IP를 등록한 뒤 플레이북으로 일괄 실행할 수 있습니다. 3대 각각 SSH 접속해서 같은 명령어를 치는 것보다 훨씬 편합니다. 저도 처음에 수동으로 하다가 결국 Ansible로 바꿨습니다. Ansible 설정 과정은 별도 포스팅으로 정리할 예정입니다.

# swap 비활성화 (kubeadm 필수)
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# 커널 모듈 로드
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# sysctl 설정
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

containerd 설치

# containerd 설치
sudo apt-get update
sudo apt-get install -y containerd

# containerd 기본 설정 생성
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# SystemdCgroup = true 로 변경 (중요!)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

sudo systemctl restart containerd
sudo systemctl enable containerd

핵심 포인트: SystemdCgroup = true를 빠뜨리면 나중에 kubelet이 제대로 동작하지 않습니다. Ubuntu 24.04는 기본적으로 cgroup v2를 사용하기 때문에 이 설정이 필수입니다.

kubeadm, kubelet, kubectl 설치

# 필수 패키지
sudo apt-get install -y apt-transport-https ca-certificates curl gpg

# Kubernetes 패키지 저장소 키 추가 (v1.31 기준)
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

6-2. 마스터 노드 초기화

마스터 노드(192.168.20.112)에서만 실행합니다.

sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=192.168.20.112 \
  --kubernetes-version=v1.31.14

초기화가 완료되면 아래와 같은 메시지가 나옵니다.

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 192.168.20.112:6443 --token abcdef.1234567890abcdef \
    --discovery-token-ca-cert-hash sha256:xxxxxxxxx...

kubeconfig 설정:

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

Calico CNI 설치

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml

6-3. 워커 노드 Join

kubeadm init 결과에서 나온 join 명령어를 워커 노드 2대에서 각각 실행합니다.

# 워커 노드에서 실행 (토큰 값은 본인 환경에 맞게)
sudo kubeadm join 192.168.20.112:6443 \
  --token abcdef.1234567890abcdef \
  --discovery-token-ca-cert-hash sha256:xxxxxxxxx...

토큰을 잊어버렸다면 마스터에서 kubeadm token create --print-join-command로 재생성할 수 있습니다.

6-4. 클러스터 상태 확인

마스터 노드에서 확인합니다.

kubectl get nodes
NAME           STATUS   ROLES           AGE     VERSION
k8s-master     Ready    control-plane   7m3s    v1.31.14
k8s-worker-1   Ready    <none>          4m10s   v1.31.14
k8s-worker-2   Ready    <none>          54s     v1.31.14

3대 모두 Ready 상태면 성공입니다.

# 시스템 Pod 상태도 확인
kubectl get pods -n kube-system

모든 Pod이 Running 상태인지 확인하세요. Calico 관련 Pod이 올라오는 데 1~2분 정도 걸릴 수 있습니다.


7. 트러블슈팅 모음

이 과정에서 실제로 제가 만난 오류들을 전부 정리합니다. 아마 따라하시다 보면 비슷한 걸 만나실 겁니다.

🔧 Terraform 단계

❌ terraform.tfvars에서 SSH 키 줄바꿈 에러

Error: Invalid multi-line string
  on terraform.tfvars line 4:
Quoted strings may not be split over multiple lines.

원인: SSH 공개키를 붙여넣을 때 줄이 바뀌면서 닫는 따옴표(")가 다음 줄로 넘어감

해결: ssh-ed25519 AAAA... k8s-lab 전체를 반드시 한 줄 안에 큰따옴표로 감싸야 합니다. 터미널에서 복사할 때 줄바꿈이 들어가지 않도록 주의하세요.

❌ 마스터/워커 IP 서브넷 불일치

terraform plan을 찍어보니 마스터는 192.168.20.x, 워커는 192.168.1.x로 서로 다른 서브넷에 배치되어 있었습니다. 이러면 노드 간 통신이 안 돼서 kubeadm join이 실패합니다.

원인: master.tf와 worker.tf에서 IP 대역을 다르게 적음

해결: master.tf, worker.tf, terraform.tfvars의 IP와 게이트웨이를 전부 같은 서브넷으로 통일합니다. terraform plan으로 미리 확인하는 습관이 중요합니다.

❌ API 토큰 권한 부족 (HTTP 403, VM.Clone)

Error: error cloning VM: received an HTTP 403 response
Reason: Permission check failed (/vms/9000, VM.Clone)

원인: API 토큰을 만들 때 Privilege Separation이 체크되어 있으면, 토큰이 사용자 권한을 상속받지 못합니다.

해결: Proxmox 웹 UI → Datacenter → Permissions → API Tokens에서 기존 토큰을 삭제하고, Privilege Separation 체크를 해제한 뒤 새로 발급합니다. 새 토큰 값을 terraform.tfvars에 업데이트하세요.

❌ 스토리지 이름 불일치 (local-lvm does not exist)

Error: received an HTTP 500 response
Reason: storage 'local-lvm' does not exist

원인: .tf 파일에 local-lvm이라고 적었는데, 실제 Proxmox 스토리지 이름이 local-zfs였음. disk 블록뿐 아니라 initialization 블록(cloud-init 드라이브)에도 스토리지 이름이 필요합니다.

해결: 모든 .tf 파일에서 스토리지 이름을 실제 환경과 맞춥니다. initialization 블록에 datastore_id를 명시적으로 추가하세요.

# 어디에 잘못된 값이 남아있는지 한 번에 확인
grep -rn "local-lvm" *.tf

🔧 SSH 접속 단계

❌ sudo ssh 하면 Permission denied

ubuntu@192.168.20.112: Permission denied (publickey).

원인: sudo ssh를 하면 root 계정의 SSH 키(/root/.ssh/)를 찾습니다. 하지만 SSH 키는 일반 사용자 홈 디렉토리(~/.ssh/)에 만들었기 때문에 키가 안 맞습니다.

해결: sudo 빼고 접속합니다.

# ✕ 이렇게 하면 안 됨
sudo ssh ubuntu@192.168.20.112

# ○ 이렇게 해야 함
ssh ubuntu@192.168.20.112

🔧 Ansible 단계

❌ No module named 'ansible.module_utils.six.moves'

ModuleNotFoundError: No module named 'ansible.module_utils.six.moves'

원인: Ubuntu apt로 설치한 Ansible 버전이 너무 오래돼서 대상 서버의 Python 3.12와 호환되지 않음

해결: apt 버전을 제거하고 pip3로 최신 버전을 설치합니다.

sudo apt-get remove -y ansible
pip3 install ansible

❌ ansible 명령어가 안 먹힘 (command not found)

-bash: /usr/bin/ansible: No such file or directory

원인: pip3로 설치하면 ~/.local/bin/에 설치되는데, 이 경로가 PATH에 없음

해결:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

🔧 kubeadm 단계

❌ conntrack not found

[ERROR FileExisting-conntrack]: conntrack not found in system path

원인: cloud-init 이미지에는 conntrack 패키지가 기본 설치되어 있지 않음. kubeadm이 네트워크 연결 추적에 필요합니다.

해결: 마스터/워커 모두에 설치합니다.

sudo apt-get install -y conntrack

Ansible로 한 번에: ansible k8s -i inventory.ini -m apt -a "name=conntrack state=present" --become

❌ kubeadm join에서 "accepts at most 1 arg(s), received 2"

accepts at most 1 arg(s), received 2

원인: --token 뒤에 token이라는 단어가 중복으로 들어감 (복사 실수)

해결: --token 바로 뒤에 토큰 값만 와야 합니다.

# ✕ 잘못된 예
sudo kubeadm join 192.168.20.112:6443 --token token abcdef.1234567890abcdef

# ○ 올바른 예
sudo kubeadm join 192.168.20.112:6443 --token abcdef.1234567890abcdef

❌ 워커 노드에서 kubectl get nodes 실행하면 connection refused

The connection to the server localhost:8080 was refused

원인: kubectl은 API 서버에 접속하는 클라이언트 도구인데, API 서버는 마스터 노드에만 있습니다. 워커 노드에서 실행하면 로컬(localhost:8080)에 API 서버가 없으니 당연히 거부됩니다.

해결: 반드시 마스터 노드에서 kubectl 명령어를 실행하세요.

ssh ubuntu@192.168.20.112
kubectl get nodes

8. 정리 및 다음 편 예고

전체 소요 시간

단계 소요 시간
tf-admin VM 생성 + Terraform 설치 (최초 1회) 약 15분
cloud-init 템플릿 생성 (최초 1회) 약 10분
Terraform 코드 작성 약 30분
terraform apply (VM 3대 생성) 약 3~5분
kubeadm 클러스터 구축 약 15~20분
합계 약 1시간 15분 (최초), 이후 재구축은 20분

가장 큰 장점은 재현성입니다. 클러스터를 망쳐도 terraform destroy -auto-approve 한 번이면 깔끔하게 정리되고, 다시 terraform apply로 새 VM을 올리면 됩니다. 쿠버네티스 공부할 때 "부수고 다시 만들기"를 부담 없이 할 수 있는 환경이 되니, 이것저것 과감하게 실험할 수 있게 됐습니다.

다음 편에서는 이 클러스터 위에 ArgoCD를 올려서 GitOps 기반 배포 파이프라인을 구성해보겠습니다.


참고 링크