La foret rouge

Kubernetes 클러스터 구축기

Published on
Published on

배경

Kubernetes 학습 및 테스팅을 위해 3대의 가상 머신을 이용하여 간단한 쿠버네티스 클러스터를 구축한 과정을 기록합니다. 설치 과정은 FastCampus의 실무까지 한 번에 끝내는 DevOps를 위한 Docker & Kubernetes 강의를 참고하여 진행하였습니다.

배포

클러스터는 VM 3대를 배포하여 Master Node 1대, Worker Node 2대로 구성하였습니다. VM은 Proxmox VE로 Homelab 환경을 구축해둔 것이 있어 해당 환경에 배포하고, Terraform을 이용해 코드로 관리하였습니다.

준비 작업

VM 3대 준비

테스트 목적의 클러스터이고 하이퍼바이저의 자원이 넉넉하지 않아 VM의 사양은 비교적 낮게 설정했습니다. 또한, 이전에 KISA의 보안 취약점 조치 가이드를 참고하여 생성해 둔 Ubuntu 24.04 템플릿을 복제하여 사용했습니다.

참고로, 이 tf 파일은 예시이므로 실제로는 각자 환경에 맞게 수정해서 사용해야 합니다. 특히 IP 주소, 게이트웨이, DNS, SSH 키 등은 자신의 환경에 맞게 변경해야 합니다.

vm-k8s.tf
resource "proxmox_vm_qemu" "k8s" {
  count            = length(var.k8s_vm_configs)
  name             = var.k8s_vm_configs[count.index].name
  target_node      = var.k8s_vm_configs[count.index].target_node
  tags             = var.k8s_vm_configs[count.index].tags

  clone      = var.k8s_vm_configs[count.index].template
  full_clone = true

  cpu {
    cores   = var.k8s_vm_configs[count.index].core
    numa    = true
  }
  memory = var.k8s_vm_configs[count.index].mem

  scsihw = "virtio-scsi-single"
  disks {
    scsi {
      scsi0 {
        disk {
          size       = "50G"
          storage    = "local-lvm"
        }
      }
    }
  }

  network {
    id       = 0
    model    = "virtio"
    bridge   = "vmbr0"
  }

  ipconfig0  = "ip=${var.k8s_vm_configs[count.index].ip1}/24,gw=${var.k8s_vm_configs[count.index].gw}"
  nameserver = var.k8s_vm_configs[count.index].dns
}

variable "k8s_vm_configs" {
  type = list(object({
    name           = string
    target_node    = string
    template       = string
    tags           = string
    ip1            = string
    gw             = string
    dns            = string
    core           = number
    mem            = number
  }))
}
vm-k8s.auto.tfvars
k8s_vm_configs = [
  {
    name           = "k8s-master"
    target_node    = "pve-node1"
    template       = "ubuntu24-tmpl"
    ip1            = "192.168.0.10"
    gw             = "192.168.0.1"
    dns            = "192.168.0.1"
    core           = 2
    mem            = 4096
    tags           = "k8s,prd,linux,ubuntu"
  },
  {
    # k8s-worker1 192.168.0.11 ...
  },
  {
    # k8s-worker2 192.168.0.12 ...
  }
]

OS 기본 설정

모든 노드(마스터, 워커)에 아래 설정을 동일하게 적용합니다.

  1. 호스트명 및 호스트 정보 등록

각 노드가 서로를 이름으로 찾을 수 있도록 호스트명을 설정하고, /etc/hosts 파일에 모든 노드의 IP와 호스트명을 등록합니다.

  • 호스트명 설정

    # 각 노드에 맞는 호스트명으로 설정 (예: k8s-master, k8s-worker1)
    > hostnamectl set-hostname k8s-master
    
  • 호스트 정보 등록

    /etc/hosts
    127.0.1.1       k8s-master # 또는 k8s-workerX
    
    # Kubernetes Cluster
    192.168.0.10    k8s-master
    192.168.0.11    k8s-worker1
    192.168.0.12    k8s-worker2
    
  • 통신 확인

    > ping k8s-master
    > ping k8s-worker1
    > ping k8s-worker2
    
  1. 스왑 비활성화

Kubernetes는 노드의 메모리와 CPU 자원을 직접 관리하며, 예측 가능성을 높이기 위해 스왑(Swap) 메모리 사용을 지원하지 않습니다. 따라서 스왑을 비활성화하고, 시스템 재부팅 시에도 적용되도록 fstab에서 해당 라인을 주석 처리합니다.

  • 스왑 비활성화 후 확인

    > swapoff -a && free -h
    # /etc/fstab 파일을 열어 swap 관련 라인을 찾아 주석 처리
    > vi /etc/fstab
    
  • swap 메모리 관련 이전 글이 있으니 참고하면 좋을 것 같습니다.

3. 커널 모듈 및 파라미터 설정

컨테이너 런타임이 원활하게 동작하고, Pod 간의 통신이 가능하도록 필요한 커널 모듈을 로드하고 관련 파라미터를 활성화합니다.

  • 커널 파라미터 변경

    > cat <<EOF | tee /etc/sysctl.d/k8s.conf
    net.ipv4.ip_forward = 1
    EOF
    
    > sysctl --system
    
    > sysctl net.ipv4.ip_forward
    
  • Container Runtime으로 containerd 사용하기 위한 설정

    > vi /etc/modules-load.d/containerd.conf
    overlay
    br_netfilter
    
    # 필요한 모듈을 커널에 등록
    > modprobe overlay br_netfilter
    
  • 노드간 통신을 위해 iptables에 브릿지 관련 설정 추가

    > cat <<EOF | tee /etc/sysctl.d/99-kubernetes-cri.conf
    net.ipv4.ip_forward                 = 1
    net.bridge.bridge-nf-call-iptables  = 1
    net.bridge.bridge-nf-call-ip6tables = 1
    EOF
    
    > cat <<EOF | tee /etc/modules-load.d/k8s.conf
    br_netfilter
    EOF
    
    > cat <<EOF | tee /etc/sysctl.d/k8s.conf
    net.ipv4.ip_forward                 = 1
    net.bridge.bridge-nf-call-iptables  = 1
    net.bridge.bridge-nf-call-ip6tables = 1
    EOF
    
    > sysctl --system
    
  1. 컨테이너 런타임(Containerd) 설치 및 설정

Kubernetes가 컨테이너를 실행하고 관리하기 위해 컨테이너 런타임이 필요합니다. 여기서는 containerd를 사용합니다.

  • 필수 패키지 설치 docker-ce 패키지를 설치하면 containerd가 의존성으로 함께 설치됩니다. Docker 공식 문서를 참고하여 설치를 진행합니다.

  • 컨테이너의 리소스 제어를 위해 사용하는 cgroup 드라이버를 systemd로 지정

    # 1. 기본 설정 파일 생성
    > sh -c "containerd config default > /etc/containerd/config.toml"
    
    # 2. cgroup 드라이버를 systemd로 변경
    > sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml
    
    # 3. containerd 서비스 재시작
    > systemctl restart containerd
    
  • Docker의 cgroup 드라이버도 systemd로 통일

    /etc/docker/daemon.json
    {
        "exec-opts": ["native.cgroupdriver=systemd"],
        "storage-driver": "overlay2"
    }
    
  • 서비스 재시작 및 확인

    > usermod -aG docker $USER
    > systemctl restart docker.service containerd.service
    > reboot
    
    > docker info | grep "Cgroup Driver"
        Cgroup Driver: systemd
    
  1. NTP 시간 동기화

클러스터 내 모든 노드의 시간이 동기화되지 않으면 예기치 않은 문제가 발생할 수 있습니다. NTP 시간 동기화 설정 문서를 참고하여 모든 노드의 시간을 동기화합니다.

kubernetes 설치

k8s 도구 설치

> curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key | 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.34/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
> apt update && apt install -y kubelet kubeadm kubectl

> kubeadm version; kubectl version; kubelet --version

# apt update때 자동 업데이트 되지 않도록 막기
> apt-mark hold kubelet kubeadm kubectl

> systemctl enable --now kubelet

클러스터 초기화

클러스터 초기화는 마스터 노드에서만 실행합니다. 이때 --apiserver-advertise-address 옵션에는 마스터 노드의 IP 주소를 명시해야 합니다. 초기화 과정에서 문제가 발생하면, 출력되는 에러 메시지를 참고하여 문제를 해결한 뒤 kubeadm reset 명령어로 클러스터를 초기화하고 다시 시도할 수 있습니다.

> kubeadm init --pod-network-cidr=10.96.0.0/12 --apiserver-advertise-address=192.168.0.10
# ...
Your Kubernetes control-plane has initialized successfully!
# ...
Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.0.10:6443 --token ... \
        --discovery-token-ca-cert-hash sha256:...

클러스터 시작

초기화가 완료되면, 다음 명령어를 통해 API 서버가 정상적으로 실행되고 있는지 포트를 확인합니다.

> mkdir -p $HOME/.kube
> cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
> chown $(id -u):$(id -g) $HOME/.kube/config
  • api-server 떠있고 포트 열려있는지 확인
    > netstat -ntlp | grep LISTEN
    # ...
    tcp6       0      0 :::6443                 :::*                    LISTEN      6591/kube-apiserver 
    
    # 지금은 마스터 노드밖에 없는 준비가 안된 상태. 이 준비 상태는 Calico 실행 후 등록됨.
    > kubectl get node
    NAME         STATUS     ROLES           AGE   VERSION
    k8s-master   NotReady   control-plane   12m   v1.32.1
    
    # DNS도 Calico 올린 후.
    > kubectl get pod -A
    NAMESPACE     NAME                                 READY   STATUS    RESTARTS   AGE
    kube-system   coredns-668d6bf9bc-bw4dw             0/1     Pending   0          14m
    kube-system   coredns-668d6bf9bc-n8tdf             0/1     Pending   0          14m
    kube-system   etcd-k8s-master                      1/1     Running   0          14m
    kube-system   kube-apiserver-k8s-master            1/1     Running   0          14m
    kube-system   kube-controller-manager-k8s-master   1/1     Running   0          14m
    kube-system   kube-proxy-4v227                     1/1     Running   0          14m
    kube-system   kube-scheduler-k8s-master            1/1     Running   0          14m
    

클러스터에 Worker 노드 추가

마스터 노드에서 kubeadm init이 성공적으로 완료되면, 워커 노드를 클러스터에 추가하기 위한 join 명령어가 출력됩니다. 이 명령어를 복사하여 각 워커 노드에서 root 권한으로 실행합니다.

> kubeadm join 192.168.0.10:6443 --token ... \
        --discovery-token-ca-cert-hash sha256:...
# ...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
# ...
  • master node
    > kubectl get nodes
    NAME          STATUS     ROLES           AGE     VERSION
    k8s-master    NotReady   control-plane   17m     v1.32.1
    k8s-worker1   NotReady   <none>          2m14s   v1.32.1
    k8s-worker2   NotReady   <none>          25s     v1.32.1
    
    > kubectl get pod -A
    NAMESPACE     NAME                                 READY   STATUS              RESTARTS   AGE
    kube-system   coredns-668d6bf9bc-bw4dw             0/1     Pending             0          17m
    kube-system   coredns-668d6bf9bc-n8tdf             0/1     Pending             0          17m
    kube-system   etcd-k8s-master                      1/1     Running             0          17m
    kube-system   kube-apiserver-k8s-master            1/1     Running             0          17m
    kube-system   kube-controller-manager-k8s-master   1/1     Running             0          17m
    kube-system   kube-proxy-4v227                     1/1     Running             0          17m
    kube-system   kube-proxy-7s45b                     1/1     Running             0          111s
    kube-system   kube-proxy-c2nqt                     0/1     ContainerCreating   0          2s
    kube-system   kube-scheduler-k8s-master            1/1     Running             0          17m
    

CNI (Container Network Interface)

CNI는 컨테이너 런타임과 오케스트레이션 도구(여기서는 Kubernetes) 사이의 네트워크 계층을 구현하기 위한 표준 인터페이스입니다. 이를 통해 다양한 네트워크 플러그인을 사용하여 컨테이너 간의 통신을 제어할 수 있습니다.

  • master node
    > curl -O https://raw.githubusercontent.com/projectcalico/calico/refs/heads/master/manifests/calico.yaml
    
    # 클러스터에 연결된 노드에 calico를 각각 심어서 연결해줌
    > kubectl apply -f calico.yaml
    
    # calico가 올라오면 pending 돼있던 dns도 진행됨
    > kubectl get pod -A
    NAMESPACE     NAME                                       READY   STATUS              RESTARTS   AGE
    kube-system   calico-kube-controllers-5b97c7b9dd-dqhlq   0/1     ContainerCreating   0          108s
    kube-system   calico-node-6gv2k                          1/1     Running             0          108s
    kube-system   calico-node-bn7gh                          1/1     Running             0          108s
    kube-system   calico-node-cr42c                          1/1     Running             0          108s
    kube-system   coredns-66bc5c9577-6vkp6                   1/1     Running             0          5m22s
    kube-system   coredns-66bc5c9577-fgmpr                   1/1     Running             0          5m22s
    kube-system   etcd-k8s-master                            1/1     Running             1          5m28s
    kube-system   kube-apiserver-k8s-master                  1/1     Running             1          5m28s
    kube-system   kube-controller-manager-k8s-master         1/1     Running             1          5m28s
    kube-system   kube-proxy-5b77n                           1/1     Running             0          3m38s
    kube-system   kube-proxy-p7l5w                           1/1     Running             0          3m10s
    kube-system   kube-proxy-q27lk                           1/1     Running             0          5m22s
    kube-system   kube-scheduler-k8s-master                  1/1     Running             1          5m30s
    
    # Pod가 Running으로 바뀌면 노드도 Ready인지 확인
    > kubectl get node
    NAME          STATUS   ROLES           AGE     VERSION
    k8s-master    Ready    control-plane   26m     v1.32.1
    k8s-worker1   Ready    <none>          10m     v1.32.1
    k8s-worker2   Ready    <none>          8m59s   v1.32.1
    

결론 및 다음 단계

이 가이드를 통해 3대의 가상 머신(마스터 노드 1대, 워커 노드 2대)으로 구성된 기본적인 Kubernetes 클러스터를 성공적으로 구축했습니다. 이 글이 Kubernetes 클러스터 구축에 관심이 있는 분들에게 도움이 되었기를 바랍니다.

이제 이 클러스터를 활용하여 다양한 Kubernetes 기능을 학습하고 테스트할 수 있습니다.