Cloud/Kubernetes

[Kubernetes] 온프레미스 환경에 싱글노드 쿠버네티스 구축: 01 - 클러스터 준비

grayroom 2023. 7. 15. 20:56

들어가며

생에 첫 회사의 DX팀에 입사한 직후, 개발 중이던 프로젝트를 자체 Kubernetes환경에 배포해 달라는 요청을 받았습니다. 그 과정에서 겪었던 시행착오를 정리하고, 이후 클러스터 구축 시에 참고할 겸 정리해 봅니다. 이번 포스팅에서는 클러스터를 구축하고, CNI 및 Ingress Controller를 설치하는 과정까지 다룹니다.

배포환경

처음엔 어딘가에서 돌고있는 On-premise 머신이라고 생각했는데, 실제로는 Azure Virtual Machine에 생성된 인스턴스였습니다. 일반적인 PC 서버와 큰 차이가 없을 것이라 생각됩니다.

 

ssh 접속시 확인할 수 있는 서버 스펙

간단하게 정리하면 아래와 같겠습니다.

  • Ubuntu 20.04.6 LTS
  • Azure x86_64기반 가상 인스턴스

Kubernetes 설치: 준비

[01] iptable 설정

# 아래 명령어로 net.bridge.bridge-nf-call-iptables 에 대한 설정 확인
sudo sysctl -a | grep net.bridge.bridge-nf-call-iptables
sudo sysctl -a | grep net.ipv4.ip_forward
sudo sysctl -a | grep net.bridge.bridge-nf-call-ip6tables

# net.bridge.bridge-nf-call-iptables = 1 이와같은 출력을 확인할 수 있다면 OK 
# -> 확인할 수 없다면 아래 명령어를 통해 설정
sysctl -w net.bridge.bridge-nf-call-iptables=1
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.bridge.bridge-nf-call-ip6tables=1

쿠버네티스는 노드 내부 네트워크를 사용하여 원하는 서비스, 파드로 네트워크 요청을 전달하는 것으로 보입니다. 그 과정에서 Linux에 내장된 iptable을 이용하여 트래픽을 제어합니다. 클러스터를 구축한 다음 확인 해 보면, 쿠버네티스가 iptable을 이용하는 것이 확실해 보입니다.

 

각각이 어떤 설정인지는 몰라도, iptable을 사용하는건 맞는듯...

[02] Port 확인

쿠버네티스가 사용하는 포트들을 다른 서비스가 점유하지 않도록 해야합니다.

 

구성요소 포트명 프로토콜
etcd 2379-2380 TCP
kubectl 6443 TCP
kubelet-check 10248 TCP
kube-proxy 10249 TCP
kubelet API 10250 TCP
kube-scheduler 10251 TCP
kube-controller-manager 10252 TCP
NodePort 서비스 30000-32767 TCP/UDP

제가 파악한 바, 위와 같은 포트를 쿠버네티스가 점유하여 사용합니다. 클린설치된 인스턴스에 설치하는 경우 중복될 일이 없겠지만, 기존 사용중이던 서버에 설치한다면 위와같은 포트를 확인하는 게 좋겠습니다.

[03] 메모리 Swap 비활성화

쿠버네티스를 사용하기 위해, 각 노드의 메모리 스왑을 비활성화할 필요가 있습니다.

sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

쿠버네티스는 기본적으로 메모리스왑을 사용하지 않는다고 합니다. 스왑영역 접근 시 소요되는 CPU Cycle을 고려하면 이해가 되는 부분입니다. 하지만 그런 이유로, 자원이 부족하여 실행 중인 Pod가 OOMKilled 될 수 있습니다.

NAME        READY    STATUS       RESTARTS    AGE
my-pod-1    0/1      OOMKilled    0           1m11s

[04] Docker, Containerd 런타임 설치

# apt패키지 인덱스 업데이트 및 HTTPS레포지토리 접근 허용을 위한 패키지 설치
sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

# GPG키 추가
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

# docker apt 레포지토리 추가
echo \
    "deb [arch=amd64 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

# apt 인덱스 업데이트
sudo apt-get update

위의 과정을 통해, apt레포지토리에 docker 레포지토리를 추가합니다. GPG키는 그 과정에서 통신 암호화를 위한 키입니다.

# docker 및 containerd 설치
sudo apt-get install docker-ce docker-ce-cli containerd.io

# docker 그룹에 사용자 추가
sudo usermod -aG docker [user]
exit

# 재접속 후 아래 명령어를 통해 groups에 docker가 추가되었는지 확인!
id

docker와 containerd 런타임을 설치한 다음, sudo 없이 docker를 계속 사용할 수 있도록 현재 사용자를 docker group에 포함시킨 뒤, ssh 재접속합니다.

[05] kubelet, kubeadm, kubectl 설치

# GPG키 다운로드
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
	| sudo apt-key add -

# 쿠버네티스 레포지토리 추가
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] \
	https://apt.kubernetes.io/ kubernetes-xenial main" \
    | sudo tee /etc/apt/sources.list.d/kubernetes.list

# apt 인덱스 업데이트
sudo apt-get update
# 원하는 버전의 kubelet, kubeadm, kubectl 설치
sudo apt-get install -y \
	kubelet=1.25.11-00 \
	kubeadm=1.25.11-00 \
	kubectl=1.25.11-00

# apt-get upgrade로 업그레이드 되지 않도록 버전 고정
sudo apt-mark hold kubelet kubeadm kubectl

kubelet, kubeadm, kubectl을 설치할 때 고려할 사항이 있습니다. 제가 배포할 환경에서는 1.25.11 버전이 문제가 발생하지 않았지만, 본인이 사용하는 여러 의존성과의 호환여부를 따져보시길 바랍니다.

  • 자신이 배포 시에 사용할 yaml API버전과 호환되는지 [참고링크]
  • ingress사용 시, ingress-controller와 호환여부 [참고링크]
  • cert-mangaer 사용시 호환여부 [참고링크]
  • 그 외 여러 클라우드네이티브 애플리케이션의 버전 호환여부

Kubernetes 설치: 클러스터 구성

[06] kubeadm을 통한 클러스터 구성

sudo kubeadm init --pod-network-cidr 192.168.0.0/16

kubeadm으로 클러스터를 구성합니다. 이때 사용되는 옵션은 아래와 같습니다.

  • --control-plane-endpoint: 마스터노드의 엔드포인트(접근가능한 IP)를 지정합니다.
  • --apiserver-advertise-address: 마스터노드 API(워커노드가 호출)의 주소를 지정합니다.
  • --pod-network-cidr: 클러스터 내에서 Pod가 할당받을 IP주소의 CIDR를 설정합니다. 저는 Calico CNI를 사용하기로 했으므로, Calico에서 요구한 대로 192.168.0.0/16로 설정하였습니다.

Calico CNI에 대해 알고 싶다면 아래 링크를 참고해 주세요

 

Component architecture | Calico Documentation

Learn the basic Calico components.

docs.tigera.io

CNI에 대한 설명은 아래입니다.

 

네트워크 플러그인

쿠버네티스 1.27 버전은 클러스터 네트워킹을 위해 컨테이너 네트워크 인터페이스(CNI) 플러그인을 지원한다. 사용 중인 클러스터와 호환되며 사용자의 요구 사항을 충족하는 CNI 플러그인을 사용

kubernetes.io

[07] kubectl설정

설치를 마치고 난 후, 콘솔 출력을 살펴보면 아래와 같은 명령어를 입력하라는 부분을 찾을 수 있을 것입니다. 참고로, admin.conf는 클러스터에 접근가능한 사용자정보를 담고 있습니다.

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

자 이제, 클러스터 구성이 끝났습니다. 어떤 구성요소가 있는지 확인하기 위해 아래 명령어를 입력합니다.

kubectl get all -A

[07-1] kubectl get 트러블슈팅

Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of
"crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")

위와 같은 에러를 뱉는경우, 아래와 같이 처리합니다.

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

직전에 분명 admin.conf를 설정했는데, 위와같은 에러가 발생하는 이유는 미스터리입니다... 원인을 알아내면 내용 추가하겠습니다.

[07-2] core-dns ContainerCreating 트러블슈팅

worker node를 추가하지 않아 Pod가 master node에 배치되어야 하는데, master node는 기본적으로  Pod가 배치되지 않습니다. 이러한 Pod 배치에 대한 제약조건을 Taint라고 부릅니다. 저희는 싱글노드 클러스터를 사용하기로 했으므로, 마스터노드의 Pod 배치 Taint를 해제해 줄 필요가 있습니다.

# 현재 노드에 설정되어있는 Taint를 확인합니다
kubectl describe no | grep Taints

# 설정된 Taint를 삭제합시다
# master node의 pod할당에 대한 taint는 node-role.kubernetes.io/master:NoSchedule 입니다.
kubectl taint nodes <node-name> <taint-key>-

[08] Calico CNI설치

컨테이너 간 네트워크를 위한 컨테이너 네트워크 플러그인을 설치합니다. 이번에는 Calico라는 플러그인을 사용하도록 하겠습니다.

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/tigera-operator.yaml

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

kubectl create -f custom-resources.yaml

저는 크게 네트워크 설정을 건들일이 없어서, 기본설정으로 문제가 발생하지 않았습니다. 또한, kubelet 1.25.11 버전에서 아무런 문제 없이 동작함을 확인했습니다. 트러블슈팅은 아래 공식페이지를 확인해 주세요.

 

Install Calico networking and network policy for on-premises deployments | Calico Documentation

Install Calico networking and network policy for on-premises deployments.

docs.tigera.io

[09] Ingress controller 설치

ingress controller에는 여러 종류가 있는 것 같지만, 가장 대중적으로 사용되는 건 nginx-ingress-controller인 것 같습니다. 아래와 같이 yaml 파일을 받아옵니다.

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.7.1/deploy/static/provider/cloud/deploy.yaml

ingress controller 또한 결국 kubernetes내부의 pod와 서비스로 동작합니다. 따라서, Loadbalancer 타입의 서비스가 아닌 이상 HTTP(80), HTTPS(443) 요청을 곧바로 받아올 방법이 없는 것 같습니다. (NodePort 서비스는 30000- 포트를 사용하므로) 따라서, 받아온 yaml파일에 수정을 가하여 "우선 동작하는" ingress controller를 설정했습니다. 우선 아래와 같이 수정하여 적용하도록 합니다.

# deploy.yaml

apiVersion: apps/v1
kind: Deployment # 확인!!
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.7.1
  name: ingress-nginx-controller # 확인!!
  namespace: ingress-nginx
spec:
  # 중략...
  template:  
    # 중략...
    spec:
      hostNetwork: true # HostNetwork를 사용하도록 설정!
      containers:

ChatGPT형님도 hostNetwork: true를 사용하는것이 바람직하진 않다고 합니다...

물론 이 방법이 바람직한 방법은 아닙니다. 여러 문제를 야기할 수 있을 것으로 보이므로, 성능 / 보안상 민감한 환경이라면 이 방법을 시도하시 마시길 바랍니다. 이 부분에 대해서는 이후에 더 나은 방법을 찾는다면 수정하겠습니다. 

# nginx-ingress-controller 설치
kubectl apply -f deploy.yaml

# 실제 동작하는지 확인
kubectl get all -A

# 아래처럼 보이면 OK
NAMESPACE          NAME                                           READY   STATUS      RESTARTS      AGE
calico-apiserver   pod/calico-apiserver-58f79578c8-m57z9          1/1     Running     0             29h
calico-apiserver   pod/calico-apiserver-58f79578c8-mphwx          1/1     Running     0             29h
calico-system      pod/calico-kube-controllers-566bbd9cc7-xnqfh   1/1     Running     0             29h
calico-system      pod/calico-node-d9zwg                          1/1     Running     0             29h
calico-system      pod/calico-typha-6768476cdf-8b7rw              1/1     Running     0             29h
calico-system      pod/csi-node-driver-bv8w5                      2/2     Running     0             29h
...
ingress-nginx      pod/ingress-nginx-admission-create-5wj7s       0/1     Completed   0             29h
ingress-nginx      pod/ingress-nginx-admission-patch-kn5f9        0/1     Completed   2             29h
ingress-nginx      pod/ingress-nginx-controller-bf5c79b4d-zgctr   1/1     Running     0             29h

CNI도 잘 동작하고, ingress-controller도 잘 동작하는 것 같습니다. 이제 쿠버네티스를 통해 서비스를 배포하고 공개할 수 있는 토대가 마련되었습니다.


마치며

관리형 쿠버네티스를 쓴다면 참 좋겠지만, 직접 클러스터를 구성해야 하는 경우 위와 같은 방법을 통해 간단히 구축해 볼 수 있겠습니다. 다음 포스팅에서는 cert-manager를 통해 Ingress에서 SSL인증서를 관리하는 방법에 대해서 알아보겠습니다.