K8S

K8S: 쿠버네티스 구성하기

Joon D&K 2022. 9. 14. 15:36
본 포스팅은 "컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커" 교재를 공부하며 작성했습니다.



1. 쿠버네티스 구성 방법
2. 쿠버네티스 구성하기
3. 쿠버네티스 구성요소 확인하기

 

1. 쿠버네티스 구성 방법


 쿠버네티스를 구성하는 방법은 크게 3가지로 구분 할 수 있습니다.

 

 첫 번째로는 Public Cloud 업체에서 제공하는 관리형 쿠버네티스를 사용하는 방법이 있습니다. 대표적으로 AWS, Azure, GCP의 EKS(Elastic Kubernetes Service), AKS(Azure Kubernetes Service), GKE(Google Kubernetes Engine)가 관리형 쿠버네티스입니다. 구성이 이미 다 갖춰져 있고 마스터 노드를 클라우드 업체에서 관리하기 때문에 학습용으로는 적절하지 않습니다. 

 

 두 번째는 Platform 업체에서 제공하는 설치형 쿠버네티스를 사용하는 방법입니다. 수세의 Rancher, 레드햇의 OpenShift가 있으며 유료이기 때문에 쉽게 사용하기는 어렵습니다.

 

 마지막으로 사용하는 시스템에 쿠버네티스 클러스터를 자동으로 구성해주는 솔루선인 구성형 쿠버네티스를 사용하는 방법이 있습니다. 대표적으로 kubeadm, kops(Kubernetes Operations), KRIB(Kubernetes Rebar Integrated Bootstrap), kubespray 등이 있습니다. 이 중 kubeadm이 가장 보편적인데, 사용자가 변경하기 수월하고 온프레미스와 클라우드 환경을 모두 지원하며 배우기도 쉽다는 장점이 있습니다. 

 

 

2. 쿠버네티스 구성하기


 본격적으로 쿠버네티스를 kubeadmin으로 구성해 보겠습니다. 쿠버네티스가 설치되는 서버 노드는 가상 머신을 이용하고 설치 과정은 베이그런트로 자동화 하겠습니다.

 

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  N = 3 # max number of worker nodes
  Ver = '1.18.4' # Kubernetes Version to install

  # Master Node
    config.vm.define "m-k8s" do |cfg|
      cfg.vm.box = "sysnet4admin/CentOS-k8s"
      cfg.vm.provider "virtualbox" do |vb|
        vb.name = "m-k8s(github_SysNet4Admin)"
        vb.cpus = 2
        vb.memory = 3072
        vb.customize ["modifyvm", :id, "--groups", "/k8s-SgMST-1.13.1(github_SysNet4Admin)"]
      end
      cfg.vm.host_name = "m-k8s"
      cfg.vm.network "private_network", ip: "192.168.1.10"
      cfg.vm.network "forwarded_port", guest: 22, host: 60010, auto_correct: true, id: "ssh"
      cfg.vm.synced_folder "../data", "/vagrant", disabled: true 
      cfg.vm.provision "shell", path: "config.sh", args: N
      cfg.vm.provision "shell", path: "install_pkg.sh", args: [ Ver, "Main" ]
      cfg.vm.provision "shell", path: "master_node.sh"
    end

  # Worker Nodes
  (1..N).each do |i|
    config.vm.define "w#{i}-k8s" do |cfg|
      cfg.vm.box = "sysnet4admin/CentOS-k8s"
      cfg.vm.provider "virtualbox" do |vb|
        vb.name = "w#{i}-k8s(github_SysNet4Admin)"
        vb.cpus = 1
        vb.memory = 2560
        vb.customize ["modifyvm", :id, "--groups", "/k8s-SgMST-1.13.1(github_SysNet4Admin)"]
      end
      cfg.vm.host_name = "w#{i}-k8s"
      cfg.vm.network "private_network", ip: "192.168.1.10#{i}"
      cfg.vm.network "forwarded_port", guest: 22, host: "6010#{i}", auto_correct: true, id: "ssh"
      cfg.vm.synced_folder "../data", "/vagrant", disabled: true
      cfg.vm.provision "shell", path: "config.sh", args: N
      cfg.vm.provision "shell", path: "install_pkg.sh", args: Ver
      cfg.vm.provision "shell", path: "work_nodes.sh"
    end
  end

end

 먼저 베이그런트 프로비저닝을 위한 파일인 Vagrantfile을 작성합니다. 

 

#!/usr/bin/env bash

# vim configuration 
echo 'alias vi=vim' >> /etc/profile

# swapoff -a to disable swapping
# Kubernetes is for using memory 100%. but swap can extend a memory more than 100% with hard disk.
# So, disable swap to make sure that a memory can not be extended.
swapoff -a

# sed to comment the swap partition in /etc/fstab
# even system is restarted, Make server do not swap.
sed -i.bak -r 's/(.+ swap .+)/#\1/' /etc/fstab

# kubernetes repo
gg_pkg="packages.cloud.google.com/yum/doc" # Due to shorten addr for key
cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://${gg_pkg}/yum-key.gpg https://${gg_pkg}/rpm-package-key.gpg
EOF

# Set SELinux in permissive mode (effectively disabling it)
# Make container access to the host's file system.
setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

# RHEL/CentOS 7 have reported traffic issues being routed incorrectly due to iptables bypassed
cat >  /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
modprobe br_netfilter

# local small dns & vagrant cannot parse and delivery shell code.
# Make nodes communicate each other.
echo "192.168.1.10 m-k8s" >> /etc/hosts
for (( i=1; i<=$1; i++  )); do echo "192.168.1.10$i w$i-k8s" >> /etc/hosts; done

# config DNS
# Make nodes communicate external.
cat > /etc/resolv.conf << EOF
nameserver 1.1.1.1 #cloudflare DNS
nameserver 8.8.8.8 #Google DNS
EOF

 스크립트 파일은 총 4개입니다. 먼저 쿠버네티스 설치를 위한 사전 조건을 설정하는 config.sh 파일을 작성합니다. 필요한 설정이 몇 가지 있는데, 먼저 swap을 비활성화 해야 합니다. swap은 메모리가 부족한 경우 하드 디스크를 메모리처럼 사용하는 기술입니다. 쿠버네티스는 자원을 100% 가깝게 사용하는 것이 목적인데, swap이 활성화 되어 있으면 쿠버네티스의 목적에 부합되지 않게 됩니다. SELinux는 컨테이너가 호스트 파일 시스템에 접근할 수 있도록 허용하는 데 필요합니다. 이 부분은 SELinux의 지원이 개선될 때까지 계속 필요합니다. 다음으로 브릿지 네트워크를 통과하는 IPv4, IPv6 패킷을 iptables가 관리하도록 설정해 Pod의 통신을 iptables로 제어합니다. iptables가 정상적으로 작동하도록 br_netfilter를 활성화 해줍니다.(modprobe는 커널 모듈을 리눅스 커널에 적재하거나 제거하는 명령어입니다.)  마지막으로 노드 간 통신 및 외부 통신을 위한 호스트DNS 서버를 지정합니다.

 

#!/usr/bin/env bash

# install packages 
yum install -y epel-release vim-enhanced git

# install docker 
yum install docker -y && systemctl enable --now docker

# install kubernetes cluster 
yum install kubectl-$1 kubelet-$1 kubeadm-$1 -y
systemctl enable --now kubelet

# git clone _Book_k8sInfra.git 
if [ $2 = 'Main' ]; then
  git clone https://github.com/sysnet4admin/_Book_k8sInfra.git
  mv /home/vagrant/_Book_k8sInfra $HOME
  find $HOME/_Book_k8sInfra/ -regex ".*\.\(sh\)" -exec chmod 700 {} \;
fi

 클러스터를 구성하기 위해 가상 머신에 설치되어야 하는 의존성 패키지를 설치하는 install_pkg.sh 파일을 작성합니다. 먼저 epel-release, vim-enhanced, git, docker를 설치합니다. Vagrantfile의 Ver 변수를 args로 가져와 kubectl, kubelet, kubeadm의 버전을 지정해 설치합니다. 교재 저자의 깃허브에서 코드를 가져온 후 정규 표현식을 통해 스크립트 파일을 찾아내 실행할 수 있도록 권한을 700으로 설정합니다.

 

#!/usr/bin/env bash

# init kubernetes
kubeadm init --token 123456.1234567890123456 --token-ttl 0 \
--pod-network-cidr=172.16.0.0/16 --apiserver-advertise-address=192.168.1.10

# config for master node only
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# config for kubernetes's network
kubectl apply -f \
https://raw.githubusercontent.com/sysnet4admin/IaC/master/manifests/172.16_net_calico.yaml

 m-k8s 가상 머신에 쿠버네티스 마스터 노드를 구성하는 master_node.sh 파일을 작성합니다. 먼저 kubeadm을 통해 마스터 노드가 워커 노드를 받아들일 준비를 합니다. 토큰을 지정한 후 ttl(time-to-live)을 0으로 설정해 기본값인 24시간 후에 토큰이 계속 유지되도록 설정해 워커 노드가 정해진 토큰으로 들어오도록 합니다. 쿠버네티스가 컨테이너에 부여하는 네트워크는 172.16.0.0/16(172.16.0.1~172.16.255.254)로 설정하고 워커 노드가 접속하는 API 서버의 IP는 192.168.1.10으로 지정해 워커 노드가 자동으로 API 서버에 연결되게 설정합니다. 마스터 노드의 사용자가 쿠버네티스를 정상적으로 사용할 수 있도록 설정 파일을 root의 홈 디렉토리에 복사하고 권한을 부여합니다. CNI(Container Network Interface)인 캘리코(Calico) 설정을 적용해 쿠버네티스 네트워크를 구성합니다.

 

#!/usr/bin/env bash

# config for work_nodes only 
kubeadm join --token 123456.1234567890123456 \
             --discovery-token-unsafe-skip-ca-verification 192.168.1.10:6443

 마지막으로 가상 머신에 쿠버네티스 워커 노드를 구성하는 worker_nodes.sh 파일을 작성합니다. kubeadm을 사용해 마스터 노드에 접속합니다. 이때 마스터 노드에서 생성한 토큰을 사용하며, 간단한 구성을 위해 인증을 무시하고 API 서버 주소인 192.168.1.10으로 기본 포트 번호인 6443에 접속합니다.

 

vagrant up
kubectl get nodes

 이제 모든 파일을 준비했으니 해당 디렉토리에서 베이그런트 프로비저닝을 실행합니다. 쿠버네티스 클러스터 구성이 완료되면 Xshell을 사용하여 마스터 노드에 접속해 쿠버네티스 클러스터에 마스터 노드와 워커 노드가 정상적으로 생성 및 연결이 되었는지 확인합니다.

 

 

3. 쿠버네티스 구성 요소 확인하기


 쿠버네티스 구성이 완료되었는데, 무작정 다루기 전에 먼저 구성 요소에 대해 짚고 넘어가려고 합니다.

 

3.1. 파드 배포를 중심으로 쿠버네티스 구성 요소 확인

 쿠버네티스 클러스터를 이루는 구성 요소로 kubectl, kubelet, API 서버, 캘리코, etcd, 컨트롤러 매니저, 스케줄러, kube-proxy, 컨테이너 런타임, 파드 등이 있습니다. 중간 중간 나오는 쿠버네티스 관련 용어는 일단 넘어가도 괜찮습니다.

 

kubectl get pods --all-namespaces

 일단 마스터 노드에서 쿠버네티스 구성 요소를 확인해 보겠습니다. 위와 같이 명령어를 입력하면 모든 네임 스페이스에 대한 파드를 확인할 수 있으며, 이를 통해 쿠버네티스 클러스터는 파드 형태의 구성 요소로 이루어져 있음을 알 수 있습니다.

 

 실제 관리자나 개발자가 파드를 배포할 때 실행되는 순서에 따라 쿠버네티스 구성 요소의 유기적인 연결 관계를 확인해 보겠습니다. 먼저 마스터 노드에서 kubectl은 쿠버네티스 클러스터에 명령을 내리는 역할을 합니다. 다른 구성 요소와는 달리 바이너리(Binary, 바로 실행되는 명령 형태)로 배포되기 때문에 꼭 마스터 노드에 있을 필요는 없지만, 통상적으로 API 서버와 주로 통신하므로 API 서버가 위치한 마스터 노드에 구성했습니다.

 

 API 서버는 쿠버네티스 클러스터의 중심 역할을 하는 통로입니다. 주로 상태 값을 저장하는 etcd와 통신하며 이 외에도 모든 클라이언트, 컴포넌트로부터 오는 요청을 받아내는 서버입니다.  etcd는 구성 요소들의 상태 값이 모두 저장되는 곳입니다. etcd의 정보만 백업이 되어 있다면 장애가 발생하더라도 쿠버네티스 클러스터를 복구할 수 있으며, 분산 저장이 가능한 key-value 저장소이므로 가용성을 확보할 수 있습니다.

 

 컨트롤러 매니저는 쿠버네티스 클러스터의 오브젝트 상태를 관리합니다. 다양한 상태 값을 관리하는 주체(노드 컨트롤러, 레플리카셋 컨트롤러 등)가 컨트롤러 매니저에 속해 각각 역할을 수행합니다. 스케줄러는 노드의 상태, 자원, 레이블, 요구 조건 등을 고려해 파드를 어떤 워커 노드에 생성할 것인지 결정하고 할당합니다. 

 

  다음으로 워커 노드에서 kubelet은 파드의 구성 내용을 받아 컨테이너 런타임으로 전달하고, 파드 내 컨테이너들을 모니터링합니다. 컨테이너 런타임이란 파드를 이루는 컨테이너 실행을 담당하는 표준 인터페이스입니다. 파드는 1개 이상의 컨테이너가 모인 단위로 언제라도 죽을 수 있는 존재입니다. 언제라도 죽을 수 있다는 것에 대해서는 실습을 통해 구체적으로 다루겠습니다.

 

 여기까지가 기본 설정으로 배포된 쿠버네티스에서 이루어지는 통신 단계에 따라 구성 요소를 구분해 놓은 것입니다. 이 외에도 추가적으로 네트워크 플러그인, CoreDNS 등이 있는데, 아직 깊에 알 필요는 없습니다. 여기서는 클러스터의 통신을 위한 구성 요소인 네트워크 플러그인으로 캘리코를 선택했습니다. CoreDNS는 쿠버네티스 클러스터에서 도메인 이름을 이용해 통신하는 데 사용합니다. 실무에서는 쿠버네티스 클러스터를 구성할 때 IP 보다는 CoreDNS를 사용하는 것이 일반적이라고 합니다.

 

 사용자가 배포된 파드에 접속할 때는 간단합니다. 쿠버네티스 클러스터는 파드가 위치한 노드에 kube-proxy를 통해 파드가 통신할 수 있는 네트워크를 설정합니다. 이때 통신은 br_netfilter와 iptables로 관리합니다. 파드에 접속하면 필요한 내용을 전달 받는데, 사용자는 파드가 어떤 워커 노드에 위치하고 있는지 신경 쓸 필요가 없습니다.

 

 

3.2. 파드의 생명주기로 쿠버네티스 구성 요소 확인

 위에서는 파드가 배포되는 내용을 중심으로 구성 요소를 확인해 보았습니다. 이번에는 파드가 배포되는 과정을 자세히 살펴 보면서 구성 요소에 대해 알아보겠습니다.

 

 쿠버네티스의 가장 큰 장점은 쿠버네티스의 구성 요소마다 역할이 명확하게 구분되어 있어 맡은 일만 충실히 수행하면 시스템이 안정적으로 운영된다는 것입니다. 따라서 문제가 발생하더라도 어느 부분에서 문제가 발생했는지 디버깅하기도 쉽습니다.

 

 이제 파드의 생명주기(생성,수정,삭제)를 자세히 살펴보겠습니다. 먼저 kubectl을 통해 API 서버에 파드 생성을 요청합니다. API 서버에 전달된 내용이 있으면 etcd에 전달된 내용을 모두 기록해 클러스터의 상태 값을 최신으로 유지합니다. 컨트롤러 매니저가 API 서버에 전달된 파드 생성 요청을 인지하면, 파드를 생성하고 생성 상태를 API 서버로 전달합니다. 이번에는 스케줄러가 API 서버에 전달된 파드 생성 상태를 인지하고 어떤 워커 노드에 할당할 지 판단하여 워커 노드에 파드를 띄우도록 요청합니다. 스케줄러는 kubelet으로 지정한 워커 노드에 파드가 속해있는지 확인한 후, kubelet에서 컨테이너 런타임으로 파드 생성 요청을 보냅니다. 파드가 생성되고 사용 가능한 상태가 됩니다.

 

 쿠버네티스는 각 요소가 추구하는 상태(desired status)를 선언하면 현재 상태(current status)와 맞는지 점검하고 그것에 맞추려고 하는 선언적(declarative) 시스템 구조로 이루어져 있습니다. 따라서 API 서버에 추구하는 상태를 선언하면 다른 요소들이 API 서버에 와서 현재 상태와 비교하며 그에 맞게 상태를 변경합니다. 이때 API가 현재 상태를 갖고 있어야 하므로 etcd와 거의 함께 작동합니다. 다만, 워커 노드는 선언적 구조가 아닌 워크플로(절차적) 구조로 설계되었습니다. 쿠버네티스가 kubelet과 컨테이너 런타임을 통해 파드 생성/삭제가 이루어져야 하는 구조이고 절차적 방식이 시스템 성능을 높이는 데 효율적이기 때문입니다.

 

 

3.3. 쿠버네티스 구성 요소의 기능 검증

kubectl

 kubectl은 마스터 노드에 위치할 필요는 없다고 했습니다. 그렇다면 마스터 노드가 아닌 곳에서 kubectl을 실행하려면 무엇이 필요할까요? 워커 노드로 접속해 명령을 실행해봅시다.

 

kubectl get nodes

 워커 노드에서 kubectl을 실행하자 오류가 발생합니다. 이는 쿠버네티스 클러스터의 정보를 kubectl이 알지 못하기 때문입니다. 앞서 kubectl은 API 서버를 통해 쿠버네티스에 명령을 내린다고 했습니다. 즉, kubectl이 API 서버에 접속할 수만 있다면 마스터 노드가 아니더라도 kubectl을 실행할 수 있는 것입니다.

 

scp root@192.168.1.10:/etc/kubernetes/admin.conf .

 쿠버네티스 클러스터 정보는 /etc/kubernetes/admin.conf 파일에 있습니다. 마스터 노드의 해당 파일을 scp(secure copy) 명령으로 워커노드의 현재 디렉토리로 가져옵니다. 마스터 노드의 root 사용자 암호는 vagrant입니다. 

 

kubectl get nodes --kubeconfig admin.conf

쿠버네티스 클러스터 정보를 입력받는 옵션을 사용해 kubectl을 실행하면 정상적으로 노드 정보가 표시됩니다.

 

 추가로, kubectl get pod와 kubectl get pods처럼 명령어의 단수와 복수를 혼용해서 사용할 수 있습니다. 이는 쿠버네티스가 사람이 실수한 부분도 alias로 처리해 유연하게 명령어를 사용할 수 있게 해주기 때문입니다. 쿠버네티스는 클러스터 구조이기 때문에 단일 노드나 단일 파드로만 사용하는 경우는 거의 없으므로 일반적으로 명령어를 복수형으로 사용해도 상관 없습니다.

 

 

 

kubelet

 kubelet은 쿠버네티스에서 파드의 생성, 상태 관리 및 복구 등을 담당합니다. 따라서 kubelet에 문제가 생기면 파드가 정상적으로 관리되지 않습니다.

 

kubectl create -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml
kubectl get pod -o wide

 kubelet 검증을 위해 먼저 파드를 배포해 보겠습니다. 먼저 nginx 웹 파드를 배포하겠습니다. 파드 생성이 완료되면 정상적으로 배포가 완료 되었는지 상태를 확인합니다. -o wide 옵션을 추가해 해당 파드가 어느 워커 노드에서 작동되는지 확인이 가능합니다.

 

systemctl stop kubelet
kubectl delete pod nginx-pod

 배포된 노드인 w1-k8에 접속해 kubelet을 멈춘 후, 마스터 노드에서 파드를 삭제해봅니다. kubelet를 중지하여 문제가 발생한 상태를 가정했으므로 정상적으로 삭제되지 않습니다.

 

Ctrl + C
kubectl get pod
systemctl start kubelet
kubectl get pod

  파드 삭제를 중지하고 상태를 확인해보면 여전히 삭제 중이라고 나타납니다. kubelet이 작동하지 않고 있기 때문에 삭제는 진행되지 않습니다. kubelet을 다시 실행시킨 후 파드의 상태를 확인하면 정상적으로 삭제됨을 확인할 수 있습니다.

 

 

kube-proxy

  kube-proxy는 파드의 통신을 담당합니다. 앞서 config.sh 파일에서 br_netfilter 모듈을 적재하고 iptables를 통해 통신하도록 설정했었습니다. 그런데 kube-proxy에 문제가 생기면 어떻게 될까요?

 

kubectl create -f ~/_Book_k8sInfra/ch3/3.1.6/nginx-pod.yaml
kubectl get pod -o wide

 테스트를 위해 마스터 노드에 파드를 배포하고 IP와 워커 노드를 확인합니다.

 

curl 172.16.221.133

 curl을 이용해 파드의 IP로 nginx 웹 서버의 메인 페이지 내용을 확인합니다.

 

modprobe -r br_netfilter
systemctl restart network

 이제 워커 노드에서 br_netfilter 모듈을 제거한 후 네트워크를 다시 시작해서 kube-proxy에 문제를 발생시켜 보겠습니다.

 

curl 172.16.221.133
kubectl get pod -o wide

 마스터 노드에서 다시 한 번 curl을 이용해 파드의 IP로 nginx 웹 서버의 메인 페이지 내용을 확인합니다. 아무리 기다려도 파드의 정보를 가져오지 못합니다. 파드 상태를 확인해보면 파드의 정보와 상태는 정상적인 것으로 보이지만 kube-proxy가 이용하는 br_netfilter에 문제가 있으므로 파드와 정상적인 통신이 이루어지지 않는 상태입니다.

 

modprobe br_netfilter
reboot

 다시 워커 노드에서 br_netfilter 모듈을 커널에 적재하고 시스템을 다시 시작해 적용합니다. 

 

kubectl get pod -o wide
curl 172.16.221.134

 마스터 노드에서 파드의 상태를 확인해보면, 파드가 1회 재시작 되었고 IP가 변경됨을 확인할 수 있습니다. 변경된 IP를 curl로 확인해보면 정상적으로 파드의 정보를 받아옴을 확인할 수 있습니다.

 

 

 지금까지 쿠버네티스를 구성해 보면서 쿠버네티스의 구성 요소에 대해서 알아보았습니다. 다음 포스팅에서는 본격적으로 쿠버네티스의 사용법을 알아보겠습니다.