쿠버네티스를 구축하는 방법으로 여러가지 방법이 있다. 가장 간단한 방법은 클라우드 제공업체의 Managed Kubernetes 서비스(예: AWS의 EKS, Google Cloud의 GKE, Azure의 AKS 등)를 활용하는 것이다. 이것의 장점은 빠르게 클러스터 환경을 구성할 수 있을 뿐만 아니라 관리도 간편하게 할 수 있다.
그러나 자체적으로 커스터마이징이 필요하거나 레거시 환경의 경우는 Self-managed Kubernetes 클러스터를 구축하는 방법을 고려해야 한다. Self-managed 방식 중 일부를 소개하자면 다음과 같다.
이 글에서는 다양한 구축 방법 중 Kubespray를 사용하여 Kubernetes 클러스터를 구축하는 방법을 살펴볼 것이다. kubespray의 가장 장점은 Ansible을 기반으로 하기 때문에 구축 절차가 자동화되어 작업 절차가 매우 간편하다는 점이다.
테스트 환경은 다음과 같다.
OS Ubuntu: 20.04.01 LTS
환경: KVM VM
node 구성
| Hostname | Role | CPU(Core) | Memory(GB) | Disk(GB) | IP |
|---|---|---|---|---|---|
| kjh-m1 | Master Node | 4 | 6 | 30 | 192.168.122.21 |
| kjh-m2 | Master Node | 4 | 6 | 30 | 192.168.122.22 |
| kjh-m3 | Master Node | 4 | 6 | 30 | 192.168.122.23 |
| kjh-w1 | worker Node | 4 | 6 | 30 | 192.168.122.24 |
| kjh-w2 | worker Node | 4 | 6 | 30 | 192.168.122.25 |
kubespray 는 현재 시점(2024.12.07) 기준으로 가장 최신 버전인 2.26.0 버전을 이용할 것이다. kubespray 2.26.0 버전을 실행하기 위해서는 사전 요구사항이 충족되어야 한다.
<kubespray의 사전 요구사항>
A. kubespray 2.26.0 버전에서 구축 가능한 Kubernetes의 최소 버전은 v1.29 이다.
B. Ansible v2.14+, Jinja 2.11+ 및 python-netaddr이 Ansible 명령을 실행할 머신에 설치되어야 한다.
C. 대상 서버는 도커 이미지를 가져오기 위해 인터넷에 액세스 할 수 있어야 한다. 그렇지 않으면 추가 구성이 필요하다.
D. 대상 서버는 IPv4 전달을 허용하도록 구성되어 있어야 한다.
E. Pod 및 서비스에 IPv6를 사용하는 경우 대상 서버는 IPv6 전달을 허용하도록 구성된다.
F. 배포 중에 문제가 발생하지 않도록 하려면 방화벽을 비활성화해야 한다.
G. kubespray가 root가 아닌 사용자 계정에서 실행되는 경우 대상 서버에서 올바른 권한 상승 방법을 구성해야 한다. 그런 다음 ansible_become 플래그 또는 명령 매개변수를 --become or -b지정해야 한다.
H. 하드웨어: 이러한 제한은 Kubespray에서 보호한다. 워크로드에 대한 실제 요구 사항은 다를 수 있다. 크기 조정 가이드는 Building Large Clusters 가이드를 참조.
이는 kubernetes를 위한 사전 설정이다. kubelet의 기본 동작은 노드에서 swap 메모리가 감지되면 시작하는데 실패한다. (만약 kubelet에서 swap을 사용하고 싶다면 failSwapOn: false 을 추가하면 된다. 기본적으로 failSwapOn: true이며, 이는 swap이 활성화되어 있으면 kubelet을 동작시키지 않는다.) 본 테스트에서는 swap을 사용하지 않으므로 모든 노드에 swap을 disable 한다.
# 모든 노드에 적용
swapoff -a
sed -i '/ swap / s/^/#/' /etc/fstab
# 확인
free -m
cat /etc/fstab |grep swap
kubespray는 ansible로 구성되어 있으며, ansible이 모든 노드에 명령어를 수행하기 위해서는 패스워드 없이 ssh 로그인할 수 있어야 한다. 이 작업은 kubespray를 실행한 마스터 한 노드 에서만 수행해주면 된다.
ssh-keygen
ssh-copy-id kjh-m1
ssh-copy-id kjh-m2
ssh-copy-id kjh-m3
ssh-copy-id kjh-w1
ssh-copy-id kjh-w2
add-apt-repository ppa:deadsnakes/ppa
apt-update -y
apt install python3.12 python3.12-venv git
python3.12 --version
## kubespray를 git 에서 다운로드
git clone https://github.com/kubernetes-sigs/kubespray.git kubespray-226
#python 가상환경 생성
#kubespray-2.26-venv-312 디렉토리가 생성됨
python3.12 -m venv kubespray-2.26-venv-312
# 파이썬 가상환경 활성화
source kubespray-2.26-venv-312/bin/activate
## 위 명령어 수행 후 python 가상환경이 활성화 되며, ansible 명령을 수행할 수 있게 된다.
## Python 가상환경을 비활성화 하려면 deactivate 명령을 수행하면된다.
# 의존성 패키지 설치
cd kubespray-226
pip3 install -U pip
pip install -r requirements.txt
# ansible 인벤토리 준비
cp -rfp inventory/sample inventory/mycluster
vim inventory/mycluster/inventory.ini
---
# ## Configure 'ip' variable to bind kubernetes services on a
# ## different ip than the default iface
# ## We should set etcd_member_name for etcd cluster. The node that is not a etcd member do not need to set the value, or can set the empty string value.
[all]
kjh-m1 ansible_host=192.168.122.21
kjh-m2 ansible_host=192.168.122.22
kjh-m3 ansible_host=192.168.122.23
kjh-w1 ansible_host=192.168.122.24
kjh-w2 ansible_host=192.168.122.25
# ## configure a bastion host if your nodes are not directly reachable
# [bastion]
# bastion ansible_host=x.x.x.x ansible_user=some_user
[kube_control_plane]
kjh-m1
kjh-m2
kjh-m3
[etcd]
kjh-w1
kjh-w2
[kube_node]
kjh-m1
kjh-m2
kjh-m3
kjh-w1
kjh-w2
[calico_rr]
[k8s_cluster:children]
kube_control_plane
kube_node
calico_rr
[all:vars]
ansible_user=root
ansible_become=True
ansible_become_user=root
ansible_become_pass=imsi00
ansible_connection=ssh
ansible_port=22
# 모든 노드에 통신 가능한지 확인
ansible all -i inventory/mycluster/inventory.ini -m ping
#kubespray에 설정된 k8s 버전 확인
cat /root/kubespray-226/inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml |grep kube-version
# kubespray를 이용하여 k8s 배포
# k8s는 1.31.1 버전으로 설치되도록 명시함.
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml -vv -e kube_version=v1.31.1
(...생략...)
TASK [kubernetes/kubeadm : Join to cluster if needed] ***************************************************************************************************************************************************************************************
task path: /root/kubespray-226/roles/kubernetes/kubeadm/tasks/main.yml:81
fatal: [kjh-w2]: FAILED! => {"changed": true, "cmd": ["timeout", "-k", "120s", "120s", "/usr/local/bin/kubeadm", "join", "--config", "/etc/kubernetes/kubeadm-client.conf", "--ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests", "--skip-phases="], "delta": "0:00:00.225907", "end": "2024-12-05 14:48:18.786064", "msg": "non-zero return code", "rc": 1, "start": "2024-12-05 14:48:18.560157", "stderr": "\t[WARNING FileExisting-ethtool]: ethtool not found in system path\nerror execution phase preflight: [preflight] Some fatal errors occurred:\n\t[ERROR FileAvailable--etc-kubernetes-ssl-ca.crt]: /etc/kubernetes/ssl/ca.crt already exists\n[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`\nTo see the stack trace of this error execute with --v=5 or higher", "stderr_lines": ["\t[WARNING FileExisting-ethtool]: ethtool not found in system path", "error execution phase preflight: [preflight] Some fatal errors occurred:", "\t[ERROR FileAvailable--etc-kubernetes-ssl-ca.crt]: /etc/kubernetes/ssl/ca.crt already exists", "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`", "To see the stack trace of this error execute with --v=5 or higher"], "stdout": "[preflight] Running pre-flight checks", "stdout_lines": ["[preflight] Running pre-flight checks"]}
fatal: [kjh-w1]: FAILED! => {"changed": true, "cmd": ["timeout", "-k", "120s", "120s", "/usr/local/bin/kubeadm", "join", "--config", "/etc/kubernetes/kubeadm-client.conf", "--ignore-preflight-errors=DirAvailable--etc-kubernetes-manifests", "--skip-phases="], "delta": "0:00:00.265876", "end": "2024-12-05 14:48:18.795062", "msg": "non-zero return code", "rc": 1, "start": "2024-12-05 14:48:18.529186", "stderr": "error execution phase preflight: [preflight] Some fatal errors occurred:\n\t[ERROR FileAvailable--etc-kubernetes-ssl-ca.crt]: /etc/kubernetes/ssl/ca.crt already exists\n[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`\nTo see the stack trace of this error execute with --v=5 or higher", "stderr_lines": ["error execution phase preflight: [preflight] Some fatal errors occurred:", "\t[ERROR FileAvailable--etc-kubernetes-ssl-ca.crt]: /etc/kubernetes/ssl/ca.crt already exists", "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`", "To see the stack trace of this error execute with --v=5 or higher"], "stdout": "[preflight] Running pre-flight checks", "stdout_lines": ["[preflight] Running pre-flight checks"]}
NO MORE HOSTS LEFT **************************************************************************************************************************************************************************************************************************
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
kjh-m1 : ok=532 changed=24 unreachable=0 failed=0 skipped=618 rescued=0 ignored=1
kjh-m2 : ok=486 changed=22 unreachable=0 failed=0 skipped=552 rescued=0 ignored=1
kjh-m3 : ok=488 changed=22 unreachable=0 failed=0 skipped=550 rescued=0 ignored=1
kjh-w1 : ok=375 changed=14 unreachable=0 failed=1 skipped=432 rescued=0 ignored=1
kjh-w2 : ok=375 changed=14 unreachable=0 failed=1 skipped=430 rescued=0 ignored=1
#master node는 k8s가 배포되었으나 worker node가 join 되지 못함.
(kubespray-2.26-venv-312) root@kjh-m1:~/kubespray-226# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kjh-m1 NotReady control-plane 43m v1.31.1
kjh-m2 NotReady control-plane 41m v1.31.1
kjh-m3 NotReady control-plane 40m v1.31.1
# kjh-w1, kjh-w2에서 수동으로 join
#kubeadm join <control-plane-ip>:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
kubeadm join 192.168.122.21:6443 --token bjcodq.dimxip78mt5c2972 --discovery-token-ca-cert-hash sha256:0b193e45b0a18d4757f435c37b1a8530fa063a48c2ca97264a2f2a3195d34b13 --cri-socket unix:///var/run/containerd/containerd.sock --ignore-preflight-errors=FileAvailable--etc-kubernetes-pki-ca.crt
systemctl status kubelet
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kjh-m1 NotReady control-plane 65m v1.31.1
kjh-m2 NotReady control-plane 63m v1.31.1
kjh-m3 NotReady control-plane 62m v1.31.1
kjh-w1 NotReady <none> 2m36s v1.31.1
kjh-w2 NotReady <none> 83s v1.31.1
# kubespray를 이용하여 k8s 재배포하여 남은 작업을 수행함.
# ansible은 기존에 수행 완료된 작업은 넘어가고 완료되지 않은 작업을 수행시키기 때문에 여러번 실행해도 안전하다.
ansible-playbook -i inventory/mycluster/inventory.ini cluster.yml -vv -e kube_version=v1.31.1
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kjh-m1 Ready control-plane 147m v1.31.1
kjh-m2 Ready control-plane 145m v1.31.1
kjh-m3 Ready control-plane 144m v1.31.1
kjh-w1 Ready <none> 84m v1.31.1
kjh-w2 Ready <none> 83m v1.31.1
# kubectl label node kjh-w1 node-role.kubernetes.io/worker=
node/kjh-w1 labeled
# kubectl label node kjh-w2 node-role.kubernetes.io/worker=
node/kjh-w2 labeled
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
kjh-m1 Ready control-plane 148m v1.31.1
kjh-m2 Ready control-plane 145m v1.31.1
kjh-m3 Ready control-plane 145m v1.31.1
kjh-w1 Ready worker 85m v1.31.1
kjh-w2 Ready worker 83m v1.31.1
# kubectl get node kjh-w1 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
kjh-w1 Ready worker 85m v1.31.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kjh-w1,kubernetes.io/os=linux,node-role.kubernetes.io/worker=
# kubectl get node kjh-w2 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
kjh-w2 Ready worker 84m v1.31.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=kjh-w2,kubernetes.io/os=linux,node-role.kubernetes.io/worker=
이 패키지들은 pod를 테스트할 때 많이 사용되며, 필수적인 것은 아니므로 선택적으로 작업해도 된다.
apt install wget curl tree jq -y