k8s 구성을 kubeadm을 통해서 해보자. 지지난 주에는 k8s 구성을 좀 빡센 방법으로 했다면. 이번에는 조금 순한 맛이랄까.
언제나 그렇듯. 컨트롤 플레인 구성하는 것이 가장 중요하다. 컨트롤 플레인 구성하는 부분부터 살펴보자.
컨트롤 플레인 구성전 세팅 점검 사항
본격적인 컨트롤 플레인 구성에 앞서 몇 가지 세팅을 진행하려고 한다.
1. 타임 세팅
# timedatectl 정보 확인
timedatectl status # RTC in local TZ: yes -> Warning:...
timedatectl set-local-rtc 0
timedatectl status
timedatectl set-timezone Asia/Seoul
# systemd가 시간 동기화 서비스(chronyd) 를 관리하도록 설정되어 있음 : ntpd 대신 chrony 사용 (Rocky 9/10 기본)
timedatectl status
timedatectl set-ntp true # System clock synchronized: yes -> NTP service: active
chronyc sources -v
chronyc tracking
2. SELinux 설정, firewalld(방화벽) 끄기
k8s에서는 SELinux는 Permissive로 세팅하는 것을 권장한다.
setenforce 0
getenforce
sestatus # Current mode: permissive
# 재부팅 시에도 Permissive 적용
cat /etc/selinux/config | grep ^SELINUX
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
cat /etc/selinux/config | grep ^SELINUX
# firewalld(방화벽) 끄기
systemctl status firewalld
systemctl disable --now firewalld
systemctl status firewalld
3. (중요) Swap 비활성화
이게 좀 특이할 수도 있다. k8s 구성 시 swap 비활성화는 k8s의 사상에 따라 매우 중요하다.
k8s에서 노드를 통째로 관리하고자 한다. swap 영역이 활성화되어 있으면 여러 개의 노드가 실행되고 있을 때 swap을 쓰면서 계속 버티고 버텨서 결국에는 전체의 노드의 성능을 낮추게 된다는 것이다. k8s 입장에서 리소스 관리 차원에서 메모리가 부족하면 Pod를 종료하고 다른 노드에서 시작하면 그만이다. 그런데 swap 영역이 활성화되어 있으면 메모리 부족 시에 그 판단을 하지 못하고 느린 상태로 계속 실행되고 있게 된다.
# Swap 비활성화
swapoff -a
# 재부팅 시에도 'Swap 비활성화' 적용되도록 /etc/fstab에서 swap 라인 삭제
cat /etc/fstab | grep swap
sed -i '/swap/d' /etc/fstab
cat /etc/fstab | grep swap
4. OverlayFS 모듈 활성화
OverlayFS 모듈 활성화도 해야 한다. OverlayFS란 도커 이미지의 사이즈를 절약하기 위해 사용한다. 보통 Dockerfile 작성할 때 한 줄 한 줄이 모두 레이어 단위로 생성되는데, OverlayFS는 이것을 하나로 묶어준다. 가령 아래와 같이 우분투 이미지를 하나 사용하는 컨테이너1, 2, 3이 있다고 해보자.
# 같은 Base Image 사용하는 컨테이너 100개
ubuntu:20.04 (100MB) ← 이건 한번만 저장
↓
Container 1 (변경사항만 1MB)
Container 2 (변경사항만 2MB)
Container 3 (변경사항만 1MB)
...
OverlayFS가 없으면 100MB짜리 이미지가 3개 사용되지만, OverlayFS가 있으면 100MB에 각 컨테이너 별 변동 사항(1+2+1MB) 만 사용되면 된다.
# 커널 모듈 로드
modprobe overlay
modprobe br_netfilter
lsmod | grep -iE 'overlay|br_netfilter'
#
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
tree /etc/modules-load.d/
# 커널 파라미터 설정 : 네트워크 설정 - 브릿지 트래픽이 iptables를 거치도록 함
cat <<EOF | 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
tree /etc/sysctl.d/
# 설정 적용
sysctl --system
# 적용 확인
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.ipv4.ip_forward
5. CRI 설치하기
파드가 노드에서 실행될 수 있도록 클러스터의 각 노드에 컨테이너 런타임을 설치해야 한다. 이 때 설치해야 하는 것이 containerd이다.
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf repolist
dnf makecache
# 설치 가능한 모든 containerd.io 버전 확인
dnf list --showduplicates containerd.io
# containerd 설치
dnf install -y containerd.io-2.1.5-1.el10
# 설치된 파일 확인
which runc && runc --version
which containerd && containerd --version
which containerd-shim-runc-v2 && containerd-shim-runc-v2 -v
which ctr && ctr --version
cat /etc/containerd/config.toml
tree /usr/lib/systemd/system | grep containerd
cat /usr/lib/systemd/system/containerd.service
# 기본 설정 생성 및 SystemdCgroup 활성화 (매우 중요)
containerd config default | tee /etc/containerd/config.toml
cat /etc/containerd/config.toml | grep -i systemdcgroup
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
cat /etc/containerd/config.toml | grep -i systemdcgroup
# systemd unit 파일 최신 상태 읽기
systemctl daemon-reload
# containerd start 와 enabled
systemctl enable --now containerd
#
systemctl status containerd --no-pager
journalctl -u containerd.service --no-pager
pstree -alnp
systemd-cgls --no-pager
# containerd의 유닉스 도메인 소켓 확인 : kubelet에서 사용 , containerd client 3종(ctr, nerdctr, crictl)도 사용
containerd config dump | grep -n containerd.sock
ls -l /run/containerd/containerd.sock
ss -xl | grep containerd
ss -xnp | grep containerd
# 플러그인 확인
ctr --address /run/containerd/containerd.sock version
6. kubeadm, kubelet 및 kubectl 설치
이제 kubeadm, kubelet, kubectl 삼총사 설치다.
dnf repolist
tree /etc/yum.repos.d/
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
## 버전 정보 미지정 시, 제공 가능 최신 버전 설치됨.
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
# kubelet 활성화 (실제 기동은 kubeadm init 후에 시작됨)
systemctl enable --now kubelet
ps -ef |grep kubelet
# 설치 파일들 확인
which kubeadm && kubeadm version -o yaml
which kubectl && kubectl version --client=true
which kubelet && kubelet --version
# cri-tools
which crictl && crictl version
# /etc/crictl.yaml 파일 작성
cat << EOF > /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
EOF
#
systemctl is-active kubelet
systemctl status kubelet --no-pager
journalctl -u kubelet --no-pager
tree /usr/lib/systemd/system | grep kubelet -A1
컨트롤 플레인 구성하기
이제 컨트롤 플레인을 본격적으로 구성해보자. 시작은 kubeadm init 를 통해서 할 수 있다. init을 통해 마스터 노드가 설치되고(컨트롤 플레인), API 서버, 스케줄러, 컨트롤러 매니저 등 핵심 구성 요소가 컨테이너로 실행된다. 그리고 결과적으로 워커 노드들이 join할 수 있는 상태를 만들어 준다.
cat << EOF > kubeadm-init.yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
bootstrapTokens:
- token: "123456.1234567890123456"
ttl: "0s"
usages:
- signing
- authentication
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: "192.168.10.100" # 미설정 시 10.0.2.15 맵핑
criSocket: "unix:///run/containerd/containerd.sock"
localAPIEndpoint:
advertiseAddress: "192.168.10.100"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: "1.32.11"
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/16"
EOF
cat kubeadm-init.yaml
# (옵션) 컨테이너 이미지 미리 다운로드 : 특히 업그레이드 작업 시, 작업 시간 단축을 위해서 수행할 것
kubeadm config images pull
# k8s controlplane 초기화 설정 수행
kubeadm init --config="kubeadm-init.yaml"
kubeadm init을 통해서 아래와 같이 컨테이너 이미지 들이 받아지고, 실행된다.
$ crictl images
IMAGE TAG IMAGE ID SIZE
registry.k8s.io/coredns/coredns v1.11.3 c69fa2e9cbf5f 18.6MB
registry.k8s.io/etcd 3.5.24-0 8cb12dd0c3e42 23.7MB
registry.k8s.io/kube-apiserver v1.32.11 7757c58248a29 29.1MB
registry.k8s.io/kube-controller-manager v1.32.11 0175d0a8243db 26.7MB
registry.k8s.io/kube-proxy v1.32.11 4d8fb2dc57519 31.2MB
registry.k8s.io/kube-scheduler v1.32.11 23d6a1fb92fda 21.1MB
registry.k8s.io/pause 3.10 873ed75102791 320kB
$ crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
292892b19d9f9 4d8fb2dc57519 21 seconds ago Running kube-proxy 0 89e451c3bf121 kube-proxy-zb866 kube-system
f50ef12c64408 0175d0a8243db 43 seconds ago Running kube-controller-manager 0 caf30030971b9 kube-controller-manager-k8s-ctr kube-system
72740e8e7377e 8cb12dd0c3e42 43 seconds ago Running etcd 0 8d389a0ae1ea1 etcd-k8s-ctr kube-system
d3856ede49213 23d6a1fb92fda 43 seconds ago Running kube-scheduler 0 f276d7991ed95 kube-scheduler-k8s-ctr kube-system
02519515fe4e8 7757c58248a29 43 seconds ago Running kube-apiserver 0 2351d78edf708 kube-apiserver-k8s-ctr kube-system
이제 클러스터의 상태와 노드의 상태를 확인해보면 아래와 같다.
$ kubectl cluster-info
Kubernetes control plane is running at https://192.168.10.100:6443
CoreDNS is running at https://192.168.10.100:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
$ kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr NotReady control-plane 2m7s v1.32.11 192.168.10.100 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.x86_64 containerd://2.1.5
이제 작업의 편리성을 위한 alias 설정이나 kubecolor, kubectx, kubens, kube-ps, helm, k9s 등을 설치해보자.
echo "sudo su -" >> /home/vagrant/.bashrc
# Source the completion
source <(kubectl completion bash)
source <(kubeadm completion bash)
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'source <(kubeadm completion bash)' >> /etc/profile
# kubectl get <tab 2번>
# Alias kubectl to k
alias k=kubectl
complete -o default -F __start_kubectl k
echo 'alias k=kubectl' >> /etc/profile
echo 'complete -o default -F __start_kubectl k' >> /etc/profile
k get node
# kubecolor 설치 : https://kubecolor.github.io/setup/install/
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --add-repo https://kubecolor.github.io/packages/rpm/kubecolor.repo
dnf repolist
dnf install -y kubecolor
kubecolor get node
alias kc=kubecolor
echo 'alias kc=kubecolor' >> /etc/profile
kc get node
kc describe node
# Install Kubectx & Kubens"
dnf install -y git
git clone https://github.com/ahmetb/kubectx /opt/kubectx
ln -s /opt/kubectx/kubens /usr/local/bin/kubens
ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
# Install Kubeps & Setting PS1
git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
cat << "EOT" >> /root/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=true
function get_cluster_short() {
echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT
kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab"
kubens default
# helm 3 설치 : https://helm.sh/docs/intro/install
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | DESIRED_VERSION=v3.18.6 bash
helm version
# k9s 설치 : https://github.com/derailed/k9s
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_${CLI_ARCH}.tar.gz
tar -xzf k9s_linux_*.tar.gz
ls -al k9s
chown root:root k9s
mv k9s /usr/local/bin/
chmod +x /usr/local/bin/k9s
# k9s
# ---> [k8s-ctr] Flannel CNI 설치 v0.27.3
# 현재 k8s 클러스터에 파드 전체 CIDR 확인
kc describe pod -n kube-system kube-controller-manager-k8s-ctr
# 노드별 파드 CIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
# Deploying Flannel with Helm
# https://github.com/flannel-io/flannel/blob/master/Documentation/configuration.md
helm repo add flannel https://flannel-io.github.io/flannel
helm repo update
kubectl create namespace kube-flannel
cat << EOF > flannel.yaml
podCidr: "10.244.0.0/16"
flannel:
cniBinDir: "/opt/cni/bin"
cniConfDir: "/etc/cni/net.d"
args:
- "--ip-masq"
- "--kube-subnet-mgr"
- "--iface=enp0s9"
backend: "vxlan"
EOF
helm install flannel flannel/flannel --namespace kube-flannel --version 0.27.3 -f flannel.yaml
# 확인
helm list -A
helm get values -n kube-flannel flannel
kubectl get ds,pod,cm -n kube-flannel -owide
kc describe cm -n kube-flannel kube-flannel-cfg
kc describe ds -n kube-flannel
# flannel cni 바이너리 설치 확인
ls -l /opt/cni/bin/
# cni 설정 정보 확인
tree /etc/cni/net.d/
cat /etc/cni/net.d/10-flannel.conflist | jq
# cni 설치 후 아래 상태(conditions) 정상 확인
crictl info | jq
여기까지 실행하면 coredns 가 정상적으로 실행된다.
$ crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
2f0ab103a3d29 5e785d005ccc1 20 minutes ago Running calico-kube-controllers 0 fddf38adda54d calico-kube-controllers-7498b9bb4c-4wrgb kube-system
c8f835582e200 c69fa2e9cbf5f 20 minutes ago Running coredns 0 bc064c8bf7f8a coredns-668d6bf9bc-bv5pj kube-system
e50e390260ee4 c69fa2e9cbf5f 21 minutes ago Running coredns 0 4581742a31a86 coredns-668d6bf9bc-mhxr5 kube-system
6e9bfdc3bef00 08616d26b8e74 21 minutes ago Running calico-node 0 5253a657ddf86 calico-node-6kjzd kube-system
5b2f7851119ca 4d8fb2dc57519 33 minutes ago Running kube-proxy 0 9fa051aa68bb9 kube-proxy-krvgr kube-system
b9d4fb053a535 23d6a1fb92fda 33 minutes ago Running kube-scheduler 0 a1d3adc9deccb kube-scheduler-k8s-ctr kube-system
e7ec4646e8643 8cb12dd0c3e42 33 minutes ago Running etcd 0 5344400bd942d etcd-k8s-ctr kube-system
14c967a56d607 0175d0a8243db 33 minutes ago Running kube-controller-manager 0 7983ea6661deb kube-controller-manager-k8s-ctr kube-system
aafaa8dbd170b 7757c58248a29 33 minutes ago Running kube-apiserver 0 750ff351df496 kube-apiserver-k8s-ctr kube-system
사실, 바로 실행되진 않았고, coredns가containercreating 상태로 뜨면서 계속 Running 상태가 안됐었다.
위와 같이 실행하니 Running 상태로 떴다. coredns는 반드시 네트워크 플러그인이 적용되어야 실행되는데 Flannel이 잘 설정이 안됐는지 에러가 났다. Calico를 통해서 네트워크 플러그인 적용하니 해결돼었다. (Flannel은 다음에 좀 더 보는걸로...)
위 그림을 보면 SSH Connect 시도 후 Gather facts를 실행하는데, 컨트롤 노드가 관리 노드에 SSH를 붙은 후에 여러 정보들을 수집하는 과정임을 알 수 있다. 수집된 정보들은 ansible_facts를 통해 컨트롤 노드로 수집된다. facts가 어떤 값을 가지고 있는지 알아보면 아래와 같다.
Ansible 팩트의 값을 활용하고 싶으면 아래와 같이 하면 된다. 예를 들어 IPv4 주소를 출력하고 싶다고 하면...
$ cat facts-ipv4.yml
---
- hosts: db
tasks:
- name: Print all facts
ansible.builtin.debug:
msg: >
The default IPv4 address of {{ ansible_facts.hostname }}
is {{ ansible_facts.default_ipv4.address }}
$ ansible-playbook facts-ipv4.yml
PLAY [db] ***********************************************************************************************************************************************************************************************************************************
TASK [Print all facts] **********************************************************************************************************************************************************************************************************************
ok: [tnode3] => {
"msg": "The node's host name is tnode3 and the ip is 10.0.2.15\n"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************************
tnode3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
위와 같이 출력이 된다. ansible_facts.default_ipv4.address 를 통해서 접근할 수 있게 된다.
루프
루프는 플레이북 yml 파일을 작성할 때 귀찮은 작업을 줄여주기 위해서 변수 등을 통해 특정 작업을 반복하도록 하는 것이다. 예를 들어보자.
$ cat loop.yaml
---
- hosts: all
vars:
services:
- vboxadd-service # ssh
- rsyslog
tasks:
- name: Check sshd and rsyslog state
ansible.builtin.service:
name: "{{ item }}"
state: started
loop: "{{ services }}"
위 yml 파일의 경우 services라는 변수를 vboxadd-service, rsyslog로 vars에 저장해두었다. 그러면 name: "{{ item }}" 의 item 부분에 차례로 vboxadd-service, rsyslog가 대입되어 실행된다.
조건문
when절을 위에서도 잠깐 소개했었다. 조건문을 변수를 통해서도 아래와 같이 실행해볼 수 있다.
$ cat when.yml
---
- hosts: localhost
vars:
run_my_task: true
tasks:
- name: echo message
ansible.builtin.shell: "echo test"
when: run_my_task
register: result
- name: Show result
ansible.builtin.debug:
var: result
위 플레이북 yml을 해석해보면 run_my_task가 true일 경우에만 shell "echo test"를 실행하는 것이다. 그리고 결과를 result에 저장 하여 결과를 출력한다.