Skip to main content

kubeadm으로 쿠버네티스 설치하기

글에서 다루는 내용

  • 쿠버네티스 기본 구조
  • kubeadm을 사용해 직접 쿠버네티스 구축
  • 쿠버네티스 클러스터를 설치할 노드(=컴퓨터)는 이미 있다고 가정하고 진행 (VM도 상관 없음)
  • Ubuntu 20.04 LTS(x86/64, amd64) 이미지를 사용
  • 패키지 버전은 kubernetes == 1.27.7, containerd == 1.6.24 을 사용

들어가며

회사에서 GKE를 통해 쿠버네티스를 사용하고 있다가, 워크스테이션에 직접 구축해야 할 일이 생겼다. 쿠버네티스의 기본 개념과, 여러가지 시행 착오와, 사용했던 스크립트를 정리한 포스팅이다. kubeadm으로 설치하다보니 control plane, worker node 설정을 모두 직접 해줬는데, 이 과정이 쿠버네티스에 대해 이해하는데 도움이 된 것 같다. 하지만, 직접 설정해야 할 것이 많기도 하고, 자동화 측면에서의 어려움이 있어서 다음에는 kubespray를 사용해 볼 예정이다. 아래 스텝을 진행할 때, 별도의 언급이 없다면 모든 노드에서 스크립트를 실행해주자.

쿠버네티스 기본 구조

쿠버네티스란?

쿠버네티스는 오픈소스로 컨테이너 관리 시스템이다. 컨테이너화된 워크로드, 서비스를 관리할 때 유용하다. 구글에서 개발한 Borg라는 통합 컨테이너 관리 시스템에서 파생되었다. 쿠버네티스는 쿠버네티스 클러스터 위에서 동작하고, 쿠버네티스 클러스터는 1개 이상의 노드(=물리적 머신 or 가상 머신)으로 구성되어 있다. 일반적으로 1개의 Control Plane과 N개의 Worker Node로 구성되어 있다.

Control Plane Components

  1. kube-apiserver: Control Plane이 외부(=사용자, 관리자, 클러스터의 다른 component)와 통신할 수 있게 도와준다. 쿠버네티스 명령어가 동작하는 시작점이다. 유일하게 etcd와 직접 소통한다.
  2. etcd: 클러스터와 관련된 중요한 정보(=configuration, state, metadata, etc)를 저장하고 있는 분산처리된 key-value 저장소이다.
  3. kube-scheduler: 다양한 요건(=resource requirements, affinity/anti-affinity rules, node conditions, etc)을 고려해서, Pod을 최적의 Node에 할당한다.
  4. kube-controller-manager: 다양한 컨트롤러(=Node Controller, Replication/ReplicaSet Controller, Namespace Controller, Job Controller, etc)들을 관리함으로써 클러스터 상태를 유지한다.

Node(=Worker Node) Components

  1. kubelet: kube-scheduler의 지시에 따라 파드를 노드에 가져오고 컨테이너를 시작하며, 파드 상태를 유지하고 파드가 원활하게 실행되도록 관리한다.
  2. kube-proxy: 노드의 네트워크를 관리한다.
  3. container runtime: 컨테이너의 실행과 생명주기를 관리한다.

kubeadm으로 쿠버네티스 클러스터 구축하기

01. Turn off swap memory

sudo swapoff -a && sudo sed -i '/swap/s/^/#/' /etc/fstab 
  • 스왑(swap)이란 물리 메모리(RAM)의 용량이 부족할 때 하드 디스크의 일부 공간을 메모리 처럼 사용하는 것이다.

  • 쿠버네티스는 Pod을 생성할 때, 필요한 만큼의 리소스를 할당 받아서 사용하는 구조이고, 쿠버네티스의 철학은 주어진 인스턴스의 자원을 100%에 가깝게 사용하는 것이다.

  • 스왑 메모리가 존재한다면 인스턴스 자원이 일관되게 관리하기가 어려워지기 때문에, 스왑 메모리를 끄는 것을 권장한다.

  • 스왑을 끄지 않은 상태에서 kubeadm init을 사용하면 아래와 같은 경고가 발생한다.

  • [preflight] Running pre-flight checks
    [WARNING Swap]: swap is enabled; production deployments should disable swap unless testing the NodeSwap feature gate of the kubelet

02. Set up kernel

# Load required kernel modules
sudo modprobe overlay
sudo modprobe br_netfilter

# Persist modules between restarts
cat << EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

# Set required networking parameters
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

# Apply sysctl params without reboot
sudo sysctl --system

# Verify that the br_netfilter, overlay modules
lsmod | grep br_netfilter
lsmod | grep overlay

# Verify that the net.bridge.bridge-nf-call-iptables, net.bridge.bridge-nf-call-ip6tables, and net.ipv4.ip_forward system variables are set to 1 in your sysctl config
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

03. Install CRI(container runtime interface)

# Set up Docker's apt repository
sudo apt-get update && \
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
jq && \
sudo install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \
sudo chmod a+r /etc/apt/keyrings/docker.gpg && \
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt-get update && \

# Install containerd.io
sudo apt-get install -y containerd.io

# 제대로 설치되었는지 확인
containerd --version
systemctl status containerd

# containerd config 설정.
# (1) disabled_plugins 초기화
# (2) systemd를 cgroup driver로 사용하도록 설정. kubelet과 containerd의 cgroup driver가 일치해야 하기 때문.
containerd config default > config.toml
vi config.toml
# =============== 아래 내용처럼 수정하기 ===============
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
# =============================================
sudo mv config.toml /etc/containerd/config.toml

# containerd 재부팅
sudo systemctl restart containerd
  • containerd를 설치하면 기본적으로 disabled_plugins = ["cri"]가 설정되어 있다(/etc/containerd/config.toml에서 확인 가능). 이럴 경우, kubeadm init을 사용했을 때 아래와 같은 에러가 발생한다. 해결하기 위해서는 disabled_plugins에서 cri를 제거해야 한다.
    • [ERROR CRI]: container runtime is not running: output: time="2023-11-11T16:32:56+09:00" level=fatal msg="validate service connection: validate CRI v1 runtime API for endpoint \"unix:///var/run/containerd/containerd.sock\": rpc error: code = Unimplemented desc = unknown service runtime.v1.RuntimeService"
  • kubeadm은 어떤 CRI (Container Runtime Interface)를 사용할지 자동으로 찾는다. Linux의 경우 아래 경로를 확인해서 있는 것을 사용한다. 다만, 아무 경로도 없거나 여러개가 설치되어 있는 경우 에러를 반환한다.
    • containerd -->	unix:///var/run/containerd/containerd.sock
      CRI-O --> unix:///var/run/crio/crio.sock
      Docker Engine (using cri-dockerd) --> unix:///var/run/cri-dockerd.sock
  • cgroup driver 는 별도로 설정 안해도 kubeadm init으로 설치가 가능했다 (containerd==1.6.4, kubeadm==1.27.7)

04. Install kubeadm, kubelet, kubectl

  1. OS에 맞는 설치 명령어를 사용하자. 명령어는 공식 문서에서 확인할 수 있다.
    • 참고로, OS는 cat /etc/*-release 명령어를 통해 확인할 수 있다.
  2. Debian-based distributions의 경우 다음 아래의 명령어를 통해 설치하자.
    • sudo install -m 0755 -d /etc/apt/keyrings && \
      curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.27/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg && \
      # This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list
      echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.27/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
      kubelet --version
      systemctl status kubelet # kubeadm init 하기 전에는 inactive(dead) 상태임.
      kubectl version # kubeadm init 하기 전에는 "The connection to the server localhost:8080 was refused" 에러 발생함.
    • 설치하다가 실패했다면, kubeadm reset으로 초기화 하고 다시 kubeadm init을 사용하자. 안그러면 /etc/kubernetes 디렉토리를 직접 삭제해주거나, 쿠버네티스 클러스터에서 사용하는 포트를 닫아주거나 하는 등의 작업을 직접 수행해야 한다.

05. Control plane 노드 설정

아래 내용은 control plane 노드에서 진행

control plane 배포

# flannel 사용하기 위해서는 pod-network-cidr 파라미터가 필수적임.
sudo kubeadm init --pod-network-cidr=10.244.0.0/16

# 모든 사용자가 kubectl 명령어를 통해 클러스터와 통신할 수 있도록 default config 설정
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# kubelet, kubectl이 정상적으로 동작하는지 확인
systemctl status kubelet # active(running) 이어야 함
kubectl version # refused 에러 없어야 함

Pod network add-on 설치 (flannel)

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
  • (참고) flannel이 아닌 다른 Pod network Addon을 사용해도 된다.

bash에서 kubectl 자동 완성 사용하기

cat << EOF >> ~/.bashrc
source /etc/profile.d/bash_completion.sh
source <(kubectl completion bash)
alias k=kubectl
complete -F __start_kubectl k
EOF

싱글 노드 클러스터 구성 (=Control plane 노드를 Worker 노드처럼 사용하기)

# Control plane 노드에서 node-role.kubernetes.io/control-plane:NoSchedule taint 삭제
k taint no <노드 이름> node-role.kubernetes.io/control-plane:NoSchedule-
  • (참고) 버전이 달라지면 taint 이름이 다를 수 있음.

06. Worker 노드 설정

# Control plane 노드에서 수행
kubeadm token create --print-join-command

# Worker 노드에서 수행 (위에서 명령어 결과임)
kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>

07. 클러스터 동작 테스트

k run nginx --image=nginx

정리

이렇게 kubeadm을 통해 쿠버네티스 클러스터를 직접 구축했다. 위에서 언급한 것처럼, 위 방법은 직접 설정해야 할 것들이 많았고, 자동화가 어려워서 다음에는 kubespray를 사용할 예정이다. 그리고 kubespary를 사용하는김에, 맥북에서 VM을 생성하는 것부터 정리해볼 계획이다.

참고 자료