본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로인프라 › 컨테이너 › Kubernetes

Ubuntu k8s 설치

목차

Ubuntu k8s 설치

Ubuntu 24.04 서버 두 대로 컨트롤 플레인 1대와 워커 1대를 구성하는 실습 절차다. 2026년 9월 기준 Kubernetes v1.37 패키지 저장소와 Flannel v0.28.9 manifest를 사용한다. 설치 시점에는 kubeadm 설치 문서와 Flannel 릴리스에서 지원 버전을 다시 확인한다.

과거에는 Docker Engine 설치를 선행하는 예제가 많았지만, 여기서는 Kubernetes의 CRI(Container Runtime Interface)에 연결할 런타임으로 containerd를 사용한다. Docker Engine이나 /etc/docker/daemon.json은 필요하지 않다(Kubernetes 컨테이너 런타임).

flowchart LR
    A[두 Ubuntu 노드 준비] --> B[containerd와 kubeadm 설치]
    B --> C[컨트롤 플레인 초기화]
    C --> D[Flannel CNI 설치]
    D --> E[워커 조인]
    E --> F[노드와 CoreDNS 확인]

모든 노드에 같은 런타임과 Kubernetes minor 버전을 준비한 뒤 컨트롤 플레인을 초기화한다. Pod 네트워크를 설치하고 워커를 합류시킨 후에 노드와 DNS의 준비 상태를 확인한다.

1. 주소와 사전 조건 확인

예시 주소는 컨트롤 플레인 192.168.56.10, 워커 192.168.56.11이다. 실제 서버의 주소로 바꾸고, 두 노드에서 서로 접근되는지 확인한다. Pod 대역 10.244.0.0/16은 Flannel 기본값이며 호스트·VPN·Service 네트워크와 겹치면 안 된다. 각 노드에는 고유한 호스트 이름, 최소 2 GiB 메모리가 필요하고 컨트롤 플레인에는 CPU 2개 이상을 권장한다(kubeadm 클러스터 구성).

방화벽은 끄지 않는다. 노드 간 통신, API 서버 TCP 6443, kubelet TCP 10250 등 역할별 포트와 CNI가 쓰는 포트를 클러스터 노드 주소에서만 허용한다. 실제 포트 목록은 Kubernetes 포트 표와 선택한 Flannel 문서를 확인한다. API 서버를 초기화하기 전에는 127.0.0.1:6443에서 응답을 기대할 수 없다.

다음 준비 작업은 두 노드 모두에서 실행한다. 기본 kubelet 설정은 swap이 활성화된 노드에서 시작되지 않으므로 swap을 끄고 재부팅 후에도 유지한다. 기존 서버에서 swap이 필요한 다른 서비스가 있다면 먼저 영향 범위를 확인한다.

sudo swapoff -a
sudo sed -ri '/[[:space:]]swap[[:space:]]/s/^/#/' /etc/fstab

printf 'overlay\nbr_netfilter\n' | sudo tee /etc/modules-load.d/k8s.conf
sudo modprobe overlay
sudo modprobe br_netfilter
printf 'net.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1\n' |
  sudo tee /etc/sysctl.d/k8s.conf
sudo sysctl --system

커널 모듈과 포워딩 설정은 Pod 네트워크가 노드 사이에서 패킷을 전달하는 데 사용한다. 배포판의 기존 네트워크 정책이 있다면 적용 전에 충돌을 살핀다.

2. containerd 설정

Ubuntu 저장소에서 containerd를 설치하고 기본 설정을 생성한다. SystemdCgroup을 true로 맞춘다. containerd 1.x와 2.x는 설정 표의 경로가 다르므로 생성된 파일과 공식 cgroup 설정 예제를 함께 확인한다.

sudo apt-get update
sudo apt-get install -y ca-certificates curl gpg containerd
sudo install -d -m 0755 /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
grep 'SystemdCgroup = true' /etc/containerd/config.toml
sudo systemctl enable --now containerd
sudo systemctl restart containerd
systemctl is-active containerd

grep 결과가 없거나 서비스가 시작되지 않으면 다음 단계로 넘어가지 말고 containerd --version, sudo journalctl -u containerd -n 100 --no-pager와 해당 버전의 설정 형식을 확인한다. 기존 /etc/containerd/config.toml을 사용하던 서버에서는 위 명령이 설정을 덮어쓰므로, 이 절차는 새 실습 노드를 대상으로 한다.

3. kubelet·kubeadm·kubectl 설치

Kubernetes는 minor 버전별 pkgs.k8s.io 저장소를 제공한다. 아래 URL은 v1.37 전용이다. 두 노드에서 동일하게 실행한다(공식 Ubuntu 패키지 절차).

sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key |
  sudo gpg --dearmor --yes -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.37/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
kubeadm version

apt-mark hold는 무심코 일부 패키지만 업데이트하지 않도록 한다. 버전 업그레이드는 모든 노드의 순서와 호환성을 확인하는 별도 작업이다. kubelet은 kubeadm init 또는 join 전에 재시작될 수 있다.

4. 컨트롤 플레인 초기화

컨트롤 플레인 노드에서만 실행한다. --apiserver-advertise-address에는 워커에서 접근 가능한 실제 노드 IP를 넣는다. --pod-network-cidr에는 호스트 대역 192.168.56.0/24이 아니라 앞서 정한 별도의 Pod 대역을 넣는다.

sudo kubeadm init \
  --apiserver-advertise-address=192.168.56.10 \
  --pod-network-cidr=10.244.0.0/16 \
  --cri-socket=unix:///run/containerd/containerd.sock

mkdir -p "$HOME/.kube"
sudo cp /etc/kubernetes/admin.conf "$HOME/.kube/config"
sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config"
kubectl get nodes -o wide

초기화가 끝나면 출력되는 kubeadm join ... 명령을 보관한다. 여기에는 토큰과 CA 인증서 해시가 들어 있으므로 공개 문서에 그대로 복사하지 않는다. 잃어버렸다면 컨트롤 플레인에서 sudo kubeadm token create --print-join-command로 새 명령을 생성한다. admin.conf는 클러스터 관리자 권한을 제공하므로 파일 권한과 보관 위치에 주의한다(kubeadm 클러스터 구성).

5. Pod 네트워크 설치와 워커 조인

kubeadm은 CNI 플러그인을 자동으로 설치하지 않는다. 컨트롤 플레인에서 Flannel 릴리스 manifest를 적용한다. 다른 CNI를 선택하면 해당 CNI의 요구 대역과 설치 명령으로 함께 변경해야 한다.

kubectl apply -f https://github.com/flannel-io/flannel/releases/download/v0.28.9/kube-flannel.yml
kubectl -n kube-flannel get daemonset,pods

Flannel이 여러 NIC 중 잘못된 주소를 고르면 노드 간 Pod 통신이 되지 않을 수 있다. kubectl get nodes -o wide의 INTERNAL-IP와 각 노드의 ip -br -4 addr를 비교하고, 필요하면 Flannel 설정 문서에 따라 인터페이스를 지정한다. 예전 Weave Net URL은 이 절차에서 사용하지 않는다.

이제 워커 노드에서 초기화 출력에 나온 join 명령을 sudo와 함께 실행한다. 실제 토큰·해시는 실행할 때 생성된 값을 사용한다. 명령의 API 주소가 워커에서 접근할 수 있는 컨트롤 플레인 주소인지 확인한다.

sudo kubeadm join <CONTROL_PLANE_IP>:6443 --token <TOKEN> \
  --discovery-token-ca-cert-hash sha256:<CA_HASH>

위 블록은 형식 설명이며 꺾쇠 안 값을 그대로 실행하는 명령이 아니다. 필요하면 출력된 명령에 --cri-socket=unix:///run/containerd/containerd.sock을 붙여 런타임 소켓을 명시한다.

6. 상태 확인

워커 조인 후 컨트롤 플레인에서 확인한다.

kubectl get nodes -o wide
kubectl get pods -A
kubectl -n kube-system rollout status deployment/coredns --timeout=180s

두 노드가 Ready이고 CoreDNS가 Running·Ready이면 기본 설치가 된 것이다. 기본 컨트롤 플레인에는 일반 Pod가 스케줄되지 않으므로 워커가 조인하기 전 CoreDNS가 Pending일 수 있다. CNI만 설치한 상태에서 CoreDNS 준비를 기다리며 멈추지 않는다(kubeadm 클러스터 구성).

노드가 NotReady이면 kubectl describe node <NODE_NAME>, kubectl -n kube-flannel get pods -o wide, 해당 노드의 sudo journalctl -u kubelet -n 100 --no-pager 순서로 원인을 찾는다. 재설치를 위해 곧바로 kubeadm reset을 실행하면 문제 기록과 기존 상태가 사라질 수 있다. 서비스가 실제로 Pod를 실행하는지는 워커에 작은 테스트 Deployment를 배치해 별도로 확인한다.

참고

같은 카테고리의 글