Profile picture

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

JaehyoJJAng2023년 02월 02일

▶︎ 구축 환경

구축 환경

  • Ubuntu 22.04 환경을 기준으로 구축
  • CRI(Container Runtime Interface): containerD
  • CNI(Container Network Interface): calico

최소 사양

  • 메모리: 2Gib 이상
  • 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. hosts 설정

sudo bash -c "echo -e '192.168.219.132 k8s-node1\n192.168.219.142 k8s-node2' >> /etc/hosts"

4. Swap 해제

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

🤷 swap off 하는 이유

쿠버네티스를 설치하는 글들을 보다보면 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 비활성화

setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

6. iptable 설정

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

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

$ 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. k8s 도구 설치


4-1. kubernetes repository 추가 (Deprecated)

sudo apt -y install curl apt-transport-https net-tools
curl  -fsSL  https://packages.cloud.google.com/apt/doc/apt-key.gpg|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/kubernetes.gpg

4-1. kubernetes repository 추가 (변경된 주소)

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

4-2. kubeadm, kubelet, kubectl 패키지 설치

### https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/
## 레포 버전 갱신
sudo apt update

## 설치 가능한 kubeadm, kubelet, kubectl 버전 확인
apt search kubeadm
apt search kubelet
apt search kubectl

## kubeadm, kubelet, kubectl 설치
sudo apt-get -y install kubeadm=1.27.13-2.1 kubelet=1.27.13-2.1 kubectl=1.27.13-2.1 --allow-downgrades
sudo apt-get update 

## 버전 고정 (각각의 버전이 달라지면 안됨)
sudo apt-mark hold kubelet kubeadm kubectl

💥 버전을 고정하는 이유?

kubeadm은 kubelet 또는 kubectl을 설치/관리를 하지 않으므로, kubeadm이 설치하려는 쿠버네티스의 Control Plane의 버전과 일치하는지 확인해야 한다.

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


4-3. 설치 확인

$ kubectl version --client && kubeadm version
WARNING: This version information is deprecated and will be replaced with the output from kubectl version --short.  Use --output=yaml|json to get the full version.
Client Version: version.Info{Major:"1", Minor:"27", GitVersion:"v1.27.3", GitCommit:"25b4e43193bcda6c7328a6d147b1fb73a33f1598", GitTreeState:"clean", BuildDate:"2023-06-14T09:53:42Z", GoVersion:"go1.20.5", Compiler:"gc", Platform:"linux/amd64"}
Kustomize Version: v5.0.1
kubeadm version: &version.Info{Major:"1", Minor:"27", GitVersion:"v1.27.6", GitCommit:"741c8db18a52787d734cbe4795f0b4ad860906d6", GitTreeState:"clean", BuildDate:"2023-09-13T09:19:54Z", GoVersion:"go1.20.8", Compiler:"gc", Platform:"linux/amd64"}

8. CRI 설치

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

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

이 중에서 원하는 컨테이너 런타임(CRI)을 설치하면 되는데 필자의 경우 containerd로 설치하도록 하겠다.


8-1. Docker CE

  • Docker CE로 설치하는 경우

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

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 

1-2 도커 구동 확인

docker ps

1-3 컨테이너의 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

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

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

8-2. ContainerD

  • Containerd로 설치하는 경우

2-1 containerd 설치

# 도커 레포지토리 설정
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/docker-archive-keyring.gpg
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

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

# containerd 재시작
sudo systemctl daemon-reload
sudo systemctl enable --now containerd

2-2 CRI 활성화

# defualt cgroupfs에서 systemd로 변경 (kubernetes default는 systemd)
sudo bash -c "containerd config default > /etc/containerd/config.toml"
sudo sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml

2-3 containerd 재시작

sudo systemctl restart containerd

‣ 마스터 노드

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

9. master 초기화

1. kubelet 서비스를 enable로 변경

sudo systemctl enable --now kubelet

2. 이미지 다운로드

# CRI 를 Containerd 로 설치했을 경우
sudo kubeadm config images pull --cri-socket unix:///run/containerd/containerd.sock

# CRI 를 Docker 로 설치했을 경우
sudo kubeadm config images pull --cri-socket unix:///run/cri-dockerd.sock 

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

sudo kubeadm init --pod-network-cidr=172.24.0.0/24 --apiserver-advertise-address 192.168.219.110
sudo kubeadm token create --print-join-command > ~/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.110 이 플래그는 API 서버가 사용할 IP 주소를 설정합니다. Kubernetes API 서버는 클러스터의 상태를 관리하고 클라이언트와 통신하는데 사용됩니다. 여기서는 192.168.219.110으로 쿠버네티스 마스터 서버 IP가 설정되었습니다.

10. kubectl 설정

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

11. Calico 설치

  • 컨테이너 네트워크 인터페이스(CNI) 설치 작업
  • 최신 버전 확인 - relases page
  • 설치 Docs

Calico

Calico는 컨테이너 오케스트레이션 시스템인 Kubernetes와 같은 컨테이너 오케스트레이션 플랫폼에서 네트워크 정책 및 네트워킹 솔루션을 제공하는 오픈 소스 프로젝트입니다. Calico는 컨테이너화된 애플리케이션의 네트워크 요구 사항을 관리하고, 가상 머신 (VM) 및 물리적 서버와 통합하여 대규모 컨테이너 인프라스트럭처에서 확장 가능하고 안전한 네트워킹을 제공합니다


1. Calico 프로젝트에서 Operator와 Custom resource 다운로드 받기

curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml

2. Operator를 클러스터에 설치

kubectl create -f tigera-operator.yaml

3. custom-resources.yaml 파일에 기재된 CIDR를 #9-master-초기화의 3번 항목에 설정했었던 CIDR 주소로 변경

sed -ie 's/192.168.0.0\/16/172.24.0.0\/24/g' custom-resources.yaml

4. custom-resources.yaml을 통해 custom-resource를 설치

kubectl create -f custom-resources.yaml

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

$ kubectl get pods --all-namespaces -w

calico-apiserver   calico-apiserver-75757f57f7-8587h          0/1     Running   0          14s
calico-apiserver   calico-apiserver-75757f57f7-g2vqj          0/1     Running   0          14s
calico-system      calico-kube-controllers-64886d94fb-dxnf7   1/1     Running   0          85s
calico-system      calico-node-ddgmw                          1/1     Running   0          85s
calico-system      calico-typha-7bf6c8fb7c-gnckv              1/1     Running   0          85s
calico-system      csi-node-driver-zzdss                      2/2     Running   0          85s
kube-system        coredns-5d78c9869d-lkb9l                   1/1     Running   0          11m
kube-system        coredns-5d78c9869d-qcbnv                   1/1     Running   0          11m
kube-system        etcd-k8s-master                            1/1     Running   0          11m
kube-system        kube-apiserver-k8s-master                  1/1     Running   0          11m
kube-system        kube-controller-manager-k8s-master         1/1     Running   0          11m
kube-system        kube-proxy-rbzkh                           1/1     Running   0          11m
kube-system        kube-scheduler-k8s-master                  1/1     Running   0          11m
tigera-operator    tigera-operator-5f4668786-2qtbj            1/1     Running   0          114s

12. 마스터 격리 해제

  • 선택 사항
  • master node 에 pod 가 설치가 가능하도록 격리를 해제한다
  • Worker Node 없이 Master Node로만 클러스터를 구축하는 경우 해당 명령어 사용하도록 함

마스터 노드 격리 해제

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


13. 대쉬보드 설치

1. Dashboard 설치

kubectl create -f https://raw.githubusercontent.com/k8s-1pro/install/main/ground/k8s-1.27/dashboard-2.7.0/dashboard.yaml

2. Metrics Server 설치

kubectl create -f https://raw.githubusercontent.com/k8s-1pro/install/main/ground/k8s-1.27/metrics-server-0.6.3/metrics-server.yaml

‣ 워커 노드

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

14. 워커노드 등록

master-node 설치에서 진행했던 마스터 노드 초기화의 5번 순서에서 얻은 토큰 값을 참고하여 워커 노드 서버에 토큰을 등록

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

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

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

kubeadm token create --print-join-command


15. 워커노드 확인

Master node 서버로 이동해 아래 명령어 실행

$ kubectl get nodes -o wide
NAME           STATUS     ROLES           AGE   VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master-node    Ready      control-plane   29m   v1.27.6   192.168.121.56    <none>        Ubuntu 20.04.6 LTS   5.15.0-84-generic   containerd://1.6.24
worker-node1   NotReady   <none>          19s   v1.27.6   192.168.121.101   <none>        Ubuntu 22.04.3 LTS   6.2.0-33-generic    containerd://1.6.24

sub 라는 NAME의 worker node가 설치된 것을 확인할 수 있다.


▶︎ 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"
sudo apt-get update -y && sudo apt-get upgrade -y

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

echo -e "====== [1-3] hosts 설정 ======\n"
sudo bash -c "echo '192.168.219.132 k8s-node1\n192.168.219.142 k8s-node2' >> /etc/hosts"

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

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

echo -e "====== [1-6] iptable 설정 ======\n"
cat << EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
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

echo -e "====== [1-7] k8s 도구 설치 ======\n"
sudo apt -y install curl apt-transport-https net-tools
curl -fsSL  https://packages.cloud.google.com/apt/doc/apt-key.gpg |sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/kubernetes.gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.27/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.27/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update
sudo apt-get -y install kubeadm=1.27.13-2.1 kubelet=1.27.13-2.1 kubectl=1.27.13-2.1 --allow-downgrades
sudo apt-get update 
sudo apt-mark hold kubelet kubeadm kubectl

echo -e "====== [1-8] CRI (ContainerD) 설치 ======\n"
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/docker-archive-keyring.gpg
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update -y
sudo apt-get install -y containerd.io-1.6.21-3.1.el8
sudo systemctl daemon-reload
sudo systemctl enable --now containerd

echo -e "====== [1-8-1] CRI 활성화 ======\n"
sudo bash -c "containerd config default > /etc/containerd/config.toml"
sudo sed -i 's/ SystemdCgroup = false/ SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
echo "============== [1] 마스터 노드 & 워커 노드 기본 설정 완료! =============="

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

master-node.sh

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

echo "============== [2] 마스터 노드 설정 =============="
echo -e "====== [2-9] 마스터 클러스터 초기화 ======\n"
sudo kubeadm config images pull --cri-socket unix:///run/containerd/containerd.sock
sudo kubeadm init --pod-network-cidr=172.24.0.0/24 --apiserver-advertise-address 192.168.219.110
sudo kubeadm token create --print-join-command > ~/join.sh

echo -e "====== [2-10] kubectl 설정 ======\n"
USER_HOME="/home/$(cat /etc/passwd | grep '1000' | cut -d ':' -f 1)"
mkdir -p "${USER_HOME}/.kube"
sudo cp -i /etc/kubernetes/admin.conf $USER_HOME/.kube/config
sudo chown $(id -u):$(id -g) $USER_HOME/.kube/config

echo -e "====== [2-11] Calico 설치 ======\n"
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/custom-resources.yaml
kubectl create -f tigera-operator.yaml
sed -ie 's/192.168.0.0\/16/172.24.0.0\/24/g' custom-resources.yaml
kubectl create -f custom-resources.yaml

echo -e "====== [2-13] 대쉬보드 설치 ======\n"
kubectl create -f https://raw.githubusercontent.com/k8s-1pro/install/main/ground/k8s-1.27/dashboard-2.7.0/dashboard.yaml

echo -e "====== [2-13-1] 매트릭스 설치 ======\n"
kubectl create -f https://raw.githubusercontent.com/k8s-1pro/install/main/ground/k8s-1.27/metrics-server-0.6.3/metrics-server.yaml
echo "============== [2] 마스터 노드 설정 완료!=============="

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


Loading script...