Profile picture

[k8s] Ubuntu 22.04 쿠버네티스 클러스터 구축하기

JaehyoJJAng2023년 09월 10일

개요

쿠버네티스를 주제로한 온라인 강의들의 대부분은 쿠버네티스를 설치할 때 Vagrant를 사용하여 설치한다.

수업 환경이 수강생들마다 다르기 때문에 이를 통일해야 원활한 강의 진행이 되기 때문에 이해가 안되는 것은 아니다.


하지만 초심자라면 자동화 스크립트를 작성하여 한 번에 설치하는 것보다,

하나하나 수동으로 직접 설치해 보는 것이 좋지 않을까라는 생각이 든다.


구축 환경

호스트 이름 IP 주소 OS 역할
kube-cp 192.168.219.151 Ubuntu 22.04 Control Plane
kube-wk01 192.168.219.152 Ubuntu 22.04 Worker Node
kube-wk02 192.168.219.153 Ubuntu 22.04 Worker Node
최소 사양
  • 1. 메모리: 2GB 이상
  • 2. CPU 2GHZ 이상

구성도

구성도의 경우 3대의 Ubuntu 22.04 서버를 기반으로 Master 1대, Node 2대로 구성
image


설치 진행

Master & Worker

  • 마스터와 워커 노드에서 1~8번 작업 진행

1. 패키지 최신화


패키지를 최신 버전으로 갱신 후 리부팅

sudo apt-get update -y && sudo apt-get upgrade -y

2. 타임존 설정

sudo timedatectl set-timezone Asia/Seoul

3. /etc/hosts 설정

sudo bash -c "echo -e '192.168.219.151 kube-cp\n192.168.219.152 kube-wk01\n192.168.219.153 kube-wk02' >> /etc/hosts"

4. Swap 해제

# 스왑 비활성화
sudo swapoff -a && sudo sed -i '/\/swap.img\|\/swap/s/^/#/' /etc/fstab

swap 해제 하는 이유

쿠버네티스를 설치하는 글들을 보다보면 swap memory 비활성화 이 대목은 꼭 들어가있다.

swap memory물리 메모리(RAM)의 용량이 부족할 때, 하드 디스크의 일부 공간을 메모리처럼 사용하는 것 의 기능을 한다.


스왑 메모리를 켜놔도 나쁠건 없어 보이는데

쿠버네티스 클러스터를 구축할 때에는 왜 끄도록 하는걸까?


쿠버네티스의 컴포넌트인 kubelet은 이러한 상황을 처리하도록 구현되지 않았기 때문이다.

쿠버네티스는 Pod를 생성할 때, 필요한 만큼의 리소스를 할당 받아서 사용하는 구조인데

이 때 메모리 swap을 전혀 고려하지 않고 설계되었기 때문에 클러스터 노드들은 모두 swap 메모리를 비활성화 해줘야 한다.


스왑 기능은 본래 가용된 메모리보다 더 큰 메모리 할당을 가능하도록 하기 위함이고,

쿠버네티스는 주어진 노드의 자원을 100%에 가깝게 사용하는 것이 목적이기에 스왑 메모리 사용은 노드 자원이 일관되지 않기 때문에

쿠버네티스에서 swap off를 권장하고 있는 것이다.

애초에 swap off를 하지 않으면 kubelet이 정상 실행되지 않기 때문에 쿠버네티스를 사용할 수 없다.

kubelet의 기본 동작에서 노드에서 스왑 메모리가 감지되면 시작에 실패하기 때문이다.

# 스왑 메모리를 활성화 했을 때 kubelet 로그
journalctl -u kubelet.service | grep 'swap'
May 07 04:45:30 k8s-master kubelet[1371]: E0507 04:45:30.439443    1371 run.go:74] \
"command failed" err="failed to run Kubelet: running with swap on is not supported, \
please disable swap! or set --fail-swap-on flag to false. /proc/swaps contained: [Filename\t\t\t\tType\t\tSize\t\tUsed\t\tPriority /swap.img\t\t4194300\t\t0\t\t-2]"

그러나 쿠버네티스 v1.22 이상 버전부터 리눅스용 스왑 기능을 알파 단계로 도입하였다.

리눅스 사용자는 스왑 메모리 지원을 노드 단위로 활성화 할 수 있으나,

cgroup v2에 대한 부적절한 지원, 불충분한 메트릭 요약 및 API 통계, 부적절한 테스트 등을 포함한 많은 문제 등이 발생하였다.
https://kubernetes.io/blog/2021/08/09/run-nodes-with-swap-alpha/


v1.28 이상 버전부터 swap을 사용하기 위한 베타 버전을 지원하고 있고

알파 단계와 비교했을 때 스왑이 활성화된 실행에 대해 kubelet은 알려진 단점들을 많이 보완했다고 한다.

적용 방법은 아래 문서를 참고해보도록 하자.
https://kubernetes.io/blog/2023/08/24/swap-linux-beta/


5. SELinux 비활성화 (pass)

  • SELinux 미사용 환경에서는 넘어가세요.
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

6. 커널 파라미터 추가

  • bridge 네트워크를 통해 송수신되는 패킷(컨테이너 패킷)이 iptables 설정에 따라 제어되도록 설정 해야함.
  • 관련 링크
# 부팅 시에 overlay와 br_netfilter 라는 두 가지 모듈을 로드하도록 설정한다.
cat << EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF

# overlay 커널 모듈을 즉시 로드. 컨테이너 오버레이 파일 시스템에 사용됨.
sudo modprobe overlay

# br_netfilter 커널 모듈을 즉시 로드. Linux 브리지 네트워크와 관련된 네트워크 필터링에 사용됨.
sudo modprobe br_netfilter

# 여기서는 몇 가지 중요한 파라미터를 설정. 예를 들어, 
# net.bridge.bridge-nf-call-iptables는 iptables가 브리지 트래픽을 처리할 수 있도록 하는 설정임.
# sysctl params required by setup, params persist across reboots
cat <<EOF | sudo tee /etc/sysctl.d/kubernetes.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 설정했던 값을 적용.
sudo sysctl --system

정상적으로 설정되었는지 확인

$ lsmod | grep "^br_netfilter"
# Module               Size  Used by
br_netfilter           28672  0

$ lsmod | grep overlay
# Module              Size  Used by
overlay               151552  54

# 다음의 세가지가 모두 1 로 되어 있는 지 확인
$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1

7. CRI (Container Runtime Interface) 설치

쿠버네티스는 3가지의 도커 컨테이너 인터페이스(CRI) 를 지원한다.

  • Docker / Docker-Shim (kubernetes 1.27 버전 이상부터 지원)
  • CRI-O
  • Containerd

이 중에서 원하는 컨테이너 런타임(CRI)을 설치하면 되는데,

필자의 경우 containerd 로 설치하도록 하겠다.


7-1. ContainerD (권장)

  • Containerd로 설치하는 경우

의존성이 있는 패키지 먼저 설치

sudo apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates

이후에 도커 레포지토리를 활성화 해주자.

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/docker.gpg
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

그리고 containerd.io를 설치한다.

sudo apt update
sudo apt install -y containerd.io

설치가 완료되면 containerd가 systemd를 cgroup으로 사용하여 시작할 수 있도록 설정해주자.

# defualt cgroupfs에서 systemd로 변경 (kubernetes default는 systemd)
containerd config default | sudo tee /etc/containerd/config.toml 1>/dev/null 2>&1
sudo sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml

마지막으로 containerd 서비스를 재시작하고 자동 실행 등록을 해주자.

sudo systemctl restart containerd
sudo systemctl enable --now containerd

7-2. Docker CE

  • Docker CE로 설치하는 경우

쿠버네티스 컨테이너 런타임을 위해 도커 설치

sudo apt-get update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
sudo usermod -aG docker $USER 

도커 구동 확인

docker ps

컨테이너의 cgroup 관리에 systemd를 사용하도록 도커 데몬 구성

sudo mkdir /etc/docker 2>/dev/null
cat << EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

도커 재시작, 부팅 시 자동시작 되도록 설정

$ sudo systemctl enable docker
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

8. kubelet, kubeadm, kubectl 설치


kubernetes repository 추가

# 최신 버전 사용시 v1.n으로 변경
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/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.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

정상적으로 레포지토리가 추가되었다면, kubelet, kubeadm, kubectl을 설치해주자

sudo apt update
sudo apt install -y kubelet kubeadm kubectl

그리고 kubelet, kubeadm, kubectl에 대한 버전을 고정해주자.

sudo apt-mark hold kubelet kubeadm kubectl
📌 버전을 고정하는 이유?

kubeadmkubelet 또는 kubectl을 설치 및 관리하지 않으므로

kubeadm이 설치하려는 쿠버네티스의 Control Plane의 버전과 일치하는지 확인해야 한다.

버전이 맞지 않는 경우 예상치 못한 오류로 이어질 수 있는 버전 차이(skew)가 발생할 위험이 크다.



설치 확인

kubectl version --client && kubeadm version

Client Version: v1.29.11
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
kubeadm version: &version.Info{Major:"1", Minor:"29", GitVersion:"v1.29.11", GitCommit:"960a2f019319ab5f7ac1c256efcc180a4113343a", GitTreeState:"clean", BuildDate:"2024-11-20T13:44:59Z", GoVersion:"go1.22.8", Compiler:"gc", Platform:"linux/amd64"}

마스터 노드

  • 마스터 노드에서만 9~13번 작업 진행

9. 컨트롤 플레인(Control Place) 구성

kubelet 서비스를 enable로 변경

sudo systemctl enable --now kubelet

클러스터 초기화 (Pod Network 설정)

sudo kubeadm init --pod-network-cidr=<원하는-pod-subnet> --apiserver-advertise-address <컨트롤-플레인-주소>
sudo kubeadm init --pod-network-cidr=172.24.0.0/24 --apiserver-advertise-address 192.168.219.150
sudo kubeadm token create --print-join-command > ~/join.sh

~/join.sh 파일에 노드 조인용 토큰 및 커맨드가 생성된다.

옵션 설명
--pod-network-cidr=172.24.0.0/24 이 플래그는 Kubernetes에서 Pod 네트워크의 CIDR (Classless Inter-Domain Routing)를 설정합니다. 이것은 클러스터 내에서 파드 간 통신을 관리하는 IP 주소 범위를 정의합니다. 여기서는 172.24.0.0/24로 설정되었습니다.
--apiserver-advertise-address 192.168.219.150 이 플래그는 API 서버가 사용할 IP 주소를 설정합니다. Kubernetes API 서버는 클러스터의 상태를 관리하고 클라이언트와 통신하는데 사용됩니다. 여기서는 192.168.219.150으로 쿠버네티스 마스터 서버 IP가 설정되었습니다.

10. 쿠버네티스 클러스터 권한 설정

아래 명령어를 실행하여 일반 사용자 권한으로 쿠버네티스 클러스터를 사용할 수 있도록 허용하자.

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

11. CNI(Container Network Interface) 설치

CNI란?

쿠버네티스 클러스터에는 수많은 Pod들이 생성과 삭제를 반복한다.

어떠한 이유로 인해 Pod가 작동하지 않는 상태가 되면 쿠버네티스 클러스터는 이를 감지하고

해당 Pod를 폐기한 후 새로운 Pod를 생성시킨다.

Pod는 고유한 IP를 가지고는 있지만, 이는 고정적이지 않기 때문에 새로운 Pod가 생성되면 주소 역시 새롭게 변경 된다.


이러한 유동적인 IP를 관리자가 일일이 관리할 수 있을까?

게다가 쿠버네티스는 멀티 노드 환경에서 구현되기 때문에 각 pod들 간의 통신에 있어 노드를 넘나들어야 하는 경우도 생긴다.

이런 경우에는 각 Pod들이 서로 통신하기 위한 오버레이(Overlay) 네트워크가 필요하게 된다.

이러한 것들을 해결하기 위해 만들어진 컨테이너 네트워킹 제어를 위한 표준이 바로 CNI(Container Network Interface)라고 보면 된다.


image

CNCF landscape에서 확인해보면 CNI가 꽤 많이 존재하는 것을 볼 수 있다.


그 중에 이번 설치에서 사용할 CNI는 Cilium이다. CNCF에서 밀어주는 프로젝트이고 BGP 기반 LB를 사용해 볼 수 있다.
(Calico 또한 BGP 기반의 LB 사용이 가능하다.)


설치 진행

1. Helm 설치

Kubernetes에는 여러가지 Add-on들이 존재한다.

이를 쉽게 설치하고 사용하기 위한 툴인 Helm이라는 것이 존재하는데, 이를 이용해 Cilium 설치를 간단히 수행해보려고 한다.


먼저 Helm을 설치해주자.

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list

sudo apt-get update
sudo apt-get install helm

Helm chart 설치는 비교적 간단하다. 위 명령어를 실행함으로써 Helm 설치가 완료된다.



2. Cilium repo 추가 및 설치

위 공식 문서를 참고하여 Cilium 설치가 가능하다.

아래 코드는 공식 문서 설치 방법을 정리한 것이다.

# helm의 cilium repo 추가
helm repo add cilium https://helm.cilium.io/

# cilium 설치
helm install cilium cilium/cilium --version 1.14.2 \
  --namespace kube-system

NAME: cilium
LAST DEPLOYED: Sun Aug 8 05:56:57 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
...

cilium 컨테이너가 모두 배포되는데까지 시간이 조금 걸린다.

시간이 지난 후 kubectl get pods -A 명령어를 실행하면 cilium 컨테이너들이 설치된 것을 볼 수 있고

core-dns Pod들 또한 정상 작동하는 것을 확인 가능하다.
image


3. Cilium CLI 설치

Cilium의 더 많은 기능을 사용하기 위해서는 Cilium CLI를 설치해야 한다.

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}

위 명령을 입력하면 설치가 완료된다.


설치가 정상적으로 되었는지 테스트해보자.

cilium status --wait

12. 마스터 격리 해제 (선택)

  • Control Place에 pod 가 설치가 가능하도록 격리를 해제한다.
  • Worker Node 없이 Control Plane으로만 클러스터를 구축하는 경우 해당 명령어를 사용하도록 함

마스터 노드 격리 해제

kubectl taint nodes --all  node-role.kubernetes.io/control-plane-

격리 해제 확인

# STATUS가 Ready로 바뀌어야 함.
$ kubectl get nodes
NAME          STATUS   ROLES           AGE   VERSION
master-node   Ready    control-plane   19m   v1.27.6

워커 노드

  • 워커 노드에서 14~15번 작업 진행

14. 워커노드 등록

컨트롤 플레인(Control Place) 구성에서 얻은 토큰 및 명령어(~/join.sh)를 참고하여 워커 노드 서버에 토큰을 등록

# 워커 노드가 될 서버에서 실행
sudo kubeadm join 192.168.121.56:6443 --token o99wfs.****** \
--discovery-token-ca-cert-hash sha256:*****

💥 토큰 값을 까먹었거나 파일이 삭제됐어요!

마스터 노드에서 다음과 같이 토큰을 다시 얻을 수 있다.

kubeadm token create --print-join-command


15. 워커노드 확인

Control Place으로 이동해 아래 명령어 실행하여 노드들이 등록되었는지 확인

kubectl get nodes -o wide

Clean UP

  • 쿠버네티스 삭제
  • 워커노드, 마스터노드 전부 진행

쿠버네티스 삭제

sudo kubeadm reset
sudo apt-get -y purge kubeadm kubectl kubelet kubernetes-cni kube*   
sudo apt-get -y autoremove  
sudo rm -rf ~/.kube

잔여 CNI 캐시 삭제

sudo rm /etc/cni/net.d
# clean up iptables rules or IPVS tables.
sudo apt install ipvsadm
sudo ipvsadm --clear

셸 스크립트로 한방에!

위 모든 과정을 쉘 스크립트로 작성하여 자동화해보자.

참고로 해당 스크립트들은 root 권한/계정에서 실행하도록 한다.


1. 모든 노드에서 실행하는 스크립트

install_default.sh

#!/usr/bin/bash
#=========================================#
#           All Node Script               #
#=========================================#

echo "============== [1] 마스터 노드 & 워커 노드 기본 설정 =============="
echo -e "====== [1-1] 패키지 최신화 ======\n"
apt-get update -y

echo -e "====== [1-2] 타임존 설정 ======\n"
timedatectl set-timezone Asia/Seoul

echo -e "====== [1-3] hosts 설정 ======\n"
bash -c "echo '192.168.219.150 kube-cp\n192.168.219.151 kube-wk01\n192.168.219.152 kube-wk02' >> /etc/hosts"

echo -e "====== [1-4] 스왑 비활성화 ======\n"
swapoff -a && sudo sed -i '/ swap / s/^/#/' /etc/fstab

echo -e "====== [1-5] SELinux 비활성화 ======\n"
echo "" # pass

echo -e "====== [1-6] 커널 파라미터 추가 ======\n"
cat << EOF | tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat << EOF | tee /etc/sysctl.d/kubernetes.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sysctl --system

echo -e "====== [1-7] CRI (Container Runtime Interface) 설치 ======\n"
apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/docker.gpg
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt update
apt install -y containerd.io
containerd config default | tee /etc/containerd/config.toml 1>/dev/null 2>&1
sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd
systemctl enable --now containerd

echo -e "====== [1-8] kubelet, kubeadm, kubectl 설치 ======\n"
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/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.29/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list
apt update
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
kubectl version --client && kubeadm version

2. 마스터 노드에서만 실행하는 스크립트

master-node.sh

#!/usr/bin/bash
#=========================================#
#         Master Node Script              #
#=========================================#

echo "============== [2] 마스터 노드 설정 =============="
echo -e "====== [2-9] 컨트롤 플레인 구성 ======\n"
systemctl enable --now kubelet
kubeadm init --pod-network-cidr=172.24.0.0/24 --apiserver-advertise-address 192.168.219.150
kubeadm token create --print-join-command > ~/join.sh

echo -e "====== [2-10] 쿠버네티스 클러스터 권한 설정 ======\n"
USER_NAME="$(getent passwd 1000 | cut -d ':' -f 1)"
USER_HOME="/home/$USER_NAME"
USER_UID=1000
USER_GID=1000
mkdir -p $USER_HOME/.kube
cp -i /etc/kubernetes/admin.conf $USER_HOME/.kube/config
chown $USER_UID:$USER_GID $USER_HOME/.kube/config

echo -e "====== [2-11] CNI (Container Network Interface) 설치 ======\n"
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | tee /usr/share/keyrings/helm.gpg > /dev/null
apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | tee /etc/apt/sources.list.d/helm-stable-debian.list
apt-get update
apt-get install helm
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --version 1.14.2 \
  --namespace kube-system
echo "============== [2] 마스터 노드 설정 완료!=============="

3. 모든 스크립트가 정상적으로 설치되었다면 워커 노드로 이동하여 worker-node 등록해주도록 하자.


Loading script...