[Kubernetes] Cluster: Kubespray를 이용해 클러스터 구성하기 - 5.2.1. Full Client-Side LB(Case 1) HA 구성 - 클러스터 배포

12 분 소요

서종호(가시다)님의 On-Premise K8s Hands-on Study 5주차 학습 내용을 기반으로 합니다.


TL;DR

이번 글은 Case 1(Full Client-Side LB) HA 구성클러스터 배포까지 다룬다.

  • 사전 확인: Kubespray 버전
  • 인벤토리: inventory.ini, host vars, 그룹 구조
  • 변수 설정: k8s-cluster(CNI·kube-proxy 등), Flannel, addons(Metrics Server)
  • 배포: cluster.yml 실행, PLAY RECAP·/tmp·local_release_dir·sysctl 확인

이어지는 글(5.2.2)에서 배포 후 kubeconfig·노드·etcd·Worker 측 Client-Side LB 확인을 다룬다.


Kubespray 사전 확인

Kubespray 버전 확인

cd /root/kubespray/
git describe --tags

참고: 최근 Kubespray v2.30.0이 릴리스되었다. 최신 버전(v2.30.0) 사용 시 문서와 차이가 있을 수 있으니 릴리스 노트를 참고해야 한다.


인벤토리 확인

디렉터리 구조

클릭하여 펼치기: tree inventory/mycluster/ 출력
tree inventory/mycluster/
inventory/mycluster/
├── group_vars
│   ├── all
│   │   ├── all.yml
│   │   ├── aws.yml
│   │   ├── azure.yml
│   │   ├── containerd.yml
│   │   ├── coreos.yml
│   │   ├── cri-o.yml
│   │   ├── docker.yml
│   │   ├── etcd.yml
│   │   ├── gcp.yml
│   │   ├── hcloud.yml
│   │   ├── huaweicloud.yml
│   │   ├── oci.yml
│   │   ├── offline.yml
│   │   ├── openstack.yml
│   │   ├── upcloud.yml
│   │   └── vsphere.yml
│   └── k8s_cluster
│       ├── addons.yml
│       ├── k8s-cluster.yml
│       ├── k8s-net-calico.yml
│       ├── k8s-net-cilium.yml
│       ├── k8s-net-custom-cni.yml
│       ├── k8s-net-flannel.yml
│       ├── k8s-net-kube-ovn.yml
│       ├── k8s-net-kube-router.yml
│       ├── k8s-net-macvlan.yml
│       └── kube_control_plane.yml
└── inventory.ini

4 directories, 27 files

inventory.ini

cat /root/kubespray/inventory/mycluster/inventory.ini
[kube_control_plane]
k8s-node1 ansible_host=192.168.10.11 ip=192.168.10.11 etcd_member_name=etcd1
k8s-node2 ansible_host=192.168.10.12 ip=192.168.10.12 etcd_member_name=etcd2
k8s-node3 ansible_host=192.168.10.13 ip=192.168.10.13 etcd_member_name=etcd3

[etcd:children]
kube_control_plane

[kube_node]
k8s-node4 ansible_host=192.168.10.14 ip=192.168.10.14
#k8s-node5 ansible_host=192.168.10.15 ip=192.168.10.15
그룹 노드 역할
kube_control_plane k8s-node1~3 Control Plane (API Server, Controller Manager, Scheduler)
etcd k8s-node1~3 etcd 클러스터 (children으로 상속)
kube_node k8s-node4 Worker Node

host vars 확인 (ansible-inventory –list)

inventory.ini에 호스트별로 적은 변수(ansible_host, ip, etcd_member_name 등)와 group_vars/에서 로드된 변수가 합쳐져 각 호스트의 host vars로 적용된다. ansible-inventory --list를 이용하면 이렇게 해석된 인벤토리와 host vars를 확인할 수 있다. 출력 JSON의 _meta.hostvars에 호스트별 변수가 들어 있다.

ansible-inventory -i /root/kubespray/inventory/mycluster/inventory.ini --list

_meta.hostvars에 각 호스트별로 적용된 변수 예시(k8s-node1)는 아래와 같다.

"k8s-node1": {
    "ansible_host": "192.168.10.11",
    "ip": "192.168.10.11",
    "etcd_member_name": "etcd1",
    "etcd_data_dir": "/var/lib/etcd",
    "etcd_deployment_type": "host",
    "loadbalancer_apiserver_port": 6443,
    "loadbalancer_apiserver_healthcheck_port": 8081,
    "bin_dir": "/usr/local/bin",
    "docker_bin_dir": "/usr/bin",
    "docker_daemon_graph": "/var/lib/docker",
    "docker_iptables_enabled": "false",
    "docker_log_opts": "--log-opt max-size=50m --log-opt max-file=5",
    "ntp_enabled": false,
    "ntp_servers": ["0.pool.ntp.org iburst", "1.pool.ntp.org iburst", "2.pool.ntp.org iburst", "3.pool.ntp.org iburst"],
    "kube_webhook_token_auth": false,
    "no_proxy_exclude_workers": false,
    "unsafe_show_logs": false
}

k8s-node2, k8s-node3은 ip, ansible_host, etcd_member_name만 호스트별로 다르고 나머지 변수는 동일하다. k8s-node4(worker)는 etcd_member_name이 없고 ip/ansible_host만 192.168.10.14로 적용된다.

호스트 ip / ansible_host etcd_member_name loadbalancer_apiserver_port
k8s-node1 192.168.10.11 etcd1 6443
k8s-node2 192.168.10.12 etcd2 6443
k8s-node3 192.168.10.13 etcd3 6443
k8s-node4 192.168.10.14 6443


ansible-inventory --list 출력에서 그룹·호스트 관계만 따로 보면 다음과 같다. (_meta.hostvars 제외)

"all": { "children": ["ungrouped", "etcd", "kube_node"] },
"etcd": { "children": ["kube_control_plane"] },
"kube_control_plane": { "hosts": ["k8s-node1", "k8s-node2", "k8s-node3"] },
"kube_node": { "hosts": ["k8s-node4"] }

인벤토리 그래프 확인

ansible-inventory -i /root/kubespray/inventory/mycluster/inventory.ini --graph

@all:
  |--@ungrouped:
  |--@etcd:
  |  |--@kube_control_plane:
  |  |  |--k8s-node1
  |  |  |--k8s-node2
  |  |  |--k8s-node3
  |--@kube_node:
  |  |--k8s-node4


변수 설정

k8s-cluster.yml 수정

# CNI를 Flannel로 변경 (실습 환경 단순화)
sed -i 's|kube_network_plugin: calico|kube_network_plugin: flannel|g' \
  inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

# kube-proxy 모드를 iptables로 변경
sed -i 's|kube_proxy_mode: ipvs|kube_proxy_mode: iptables|g' \
  inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

# NodeLocal DNSCache 비활성화
sed -i 's|enable_nodelocaldns: true|enable_nodelocaldns: false|g' \
  inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

# 파일 소유자 변경
sed -i 's|kube_owner: kube|kube_owner: root|g' \
  inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

# CoreDNS autoscaler 비활성화
echo "enable_dns_autoscaler: false" >> inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

# 설정 확인
grep -iE 'kube_owner|kube_network_plugin:|kube_proxy_mode|enable_nodelocaldns:' \
  inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
kube_owner: root
kube_network_plugin: flannel
kube_proxy_mode: iptables
enable_nodelocaldns: false

enable_dns_autoscaler: falseecho "..." >>로 추가했으므로 위 grep에는 안 나온다. 파일 끝에 추가되어 있다.

변수 기본값 변경값 이유
kube_network_plugin calico flannel 실습 환경 단순화
kube_proxy_mode ipvs iptables 기본 모드로 변경
enable_nodelocaldns true false 복잡도 감소
kube_owner kube root 파일 소유자
enable_dns_autoscaler true false 메모리 절약

Flannel 설정

Flannel이 사용할 네트워크 인터페이스를 지정한다.

echo "flannel_interface: enp0s9" >> inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml

# 설정 확인
grep "^[^#]" inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml
flannel_interface: enp0s9

addons.yml 수정

# Metrics Server 활성화
sed -i 's|metrics_server_enabled: false|metrics_server_enabled: true|g' \
  inventory/mycluster/group_vars/k8s_cluster/addons.yml

# 리소스 제한 (실습 환경용)
echo "metrics_server_requests_cpu: 25m" >> inventory/mycluster/group_vars/k8s_cluster/addons.yml
echo "metrics_server_requests_memory: 16Mi" >> inventory/mycluster/group_vars/k8s_cluster/addons.yml

# 설정 확인
grep -iE 'metrics_server_enabled:' inventory/mycluster/group_vars/k8s_cluster/addons.yml
metrics_server_enabled: true

metrics_server_requests_cpu: 25m, metrics_server_requests_memory: 16Miecho "..." >>로 addons.yml 끝에 추가했다. 실습 환경 리소스 절약용이다.

Metrics Server 변수 확인

addons.yml에서 덮어쓴 변수는 Kubespray role의 기본값을 오버라이드한다. 변수 적용 위치를 확인하려면 roles/kubernetes-apps/metrics_server/를 보면 된다.

# 메트릭서버 role 구조
ls roles/kubernetes-apps/metrics_server/
# defaults/  tasks/  templates/

# 디폴트 변수 (오버라이드 전 기본값)
cat roles/kubernetes-apps/metrics_server/defaults/main.yml

# Deployment 템플릿 (resources에 metrics_server_requests_* 등 사용)
cat roles/kubernetes-apps/metrics_server/templates/metrics-server-deployment.yaml.j2

defaults/main.yml:

---
metrics_server_container_port: 10250
metrics_server_kubelet_insecure_tls: true
metrics_server_kubelet_preferred_address_types: "InternalIP,ExternalIP,Hostname"
metrics_server_metric_resolution: 15s
metrics_server_limits_cpu: 100m
metrics_server_limits_memory: 200Mi
metrics_server_requests_cpu: 100m          # addons.yml에서 25m으로 오버라이드
metrics_server_requests_memory: 200Mi     # addons.yml에서 16Mi로 오버라이드
metrics_server_host_network: false
metrics_server_replicas: 1
metrics_server_extra_tolerations: []
metrics_server_extra_affinity: {}
metrics_server_nodeselector: {}


metrics-server-deployment.yaml.j2 (resources 부분): 템플릿에서 아래처럼 metrics_server_requests_cpu, metrics_server_requests_memory를 사용한다. addons.yml에 넣은 값이 여기 적용된다.

        resources:
          limits:
            cpu: 
            memory: 
          requests:
            cpu: 
            memory: 
변수 defaults/main.yml addons.yml 오버라이드
metrics_server_requests_cpu 100m 25m
metrics_server_requests_memory 200Mi 16Mi


클러스터 배포

Task 목록 확인 (Dry Run)

ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml --list-tasks

cluster.yml 실행

클러스터를 배포한다.

# 배포 실행 (약 8분 소요)
ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini \
  -v cluster.yml -e kube_version="1.32.9" | tee kubespray_install.log

끝나면 PLAY RECAP과 소요 시간 요약이 나온다. 모든 노드가 failed=0이면 성공이다.

PLAY RECAP *********************************************************************
k8s-node1                  : ok=532  changed=120  unreachable=0    failed=0    skipped=836  rescued=0    ignored=2
k8s-node2                  : ok=499  changed=111  unreachable=0    failed=0    skipped=822  rescued=0    ignored=2
k8s-node3                  : ok=501  changed=112  unreachable=0    failed=0    skipped=820  rescued=0    ignored=2
k8s-node4                  : ok=437  changed=87   unreachable=0    failed=0    skipped=615  rescued=0    ignored=0

Thursday 05 February 2026  21:49:05 +0900 (0:00:00.061)       0:08:50.108 *****
===============================================================================
download : Download_file | Download item ------------------------------- 25.02s
download : Download_container | Download image if required ------------- 20.32s
download : Download_file | Download item ------------------------------- 20.12s
...
kubernetes/kubeadm : Join to cluster if needed ------------------------- 16.05s
...
kubernetes/control-plane : Joining control plane node to the cluster. --- 8.74s
kubernetes/control-plane : Kubeadm | Initialize first control plane node (1st try) --- 8.44s
...

/tmp 디렉토리 확인

tree /tmp
/tmp
├── k8s-node1
├── k8s-node2
├── k8s-node3
├── k8s-node4
├── k9s_linux_arm64.tar.gz
├── ...
└── vagrant-shell

13 directories, 8 files

인벤토리 호스트명과 같은 이름의 항목(k8s-node1~k8s-node4)은 Ansible이 playbook 실행 시 호스트별로 두는 임시 디렉터리이다.

  • 모듈 전달·파일 스테이징 등에 쓰임
  • fact 캐시를 사용하는 설정이면 facts 수집 정보가 저장되는 경로와도 연관

배포 후에도 남아 있을 수 있으므로, 어떤 노드에 대해 작업이 수행되었는지·Ansible(playbook을 실행한 쪽)이 각 노드를 제대로 인식했는지 확인할 때 참고하면 된다.

systemd-private-*, vagrant-shell 등은 OS·Vagrant 쪽 임시 디렉터리다.

노드별 local_release_dir 확인

Kubespray는 변수 local_release_dir(기본값 "/tmp/releases")에 맞춰 각 노드에 바이너리·아카이브를 받아 둔다. 배포 후 확인해 보면 Control Plane 노드와 Worker 노드에 다운로드된 파일이 다르다.

# Control Plane 노드 (k8s-node1)
ssh k8s-node1 tree /tmp/releases

/tmp/releases
├── cni-plugins-linux-arm64-1.8.0.tgz
├── containerd-2.1.5-linux-arm64.tar.gz
├── containerd-rootless-setuptool.sh
├── containerd-rootless.sh
├── crictl
├── crictl-1.32.0-linux-arm64.tar.gz
├── etcd-3.5.25-linux-arm64.tar.gz
├── etcd-v3.5.25-linux-arm64
│   ├── etcd
│   ├── etcdctl
│   ├── etcdutl
│   └── ...
├── images
├── kubeadm-1.32.9-arm64
├── kubectl-1.32.9-arm64
├── kubelet-1.32.9-arm64
├── nerdctl
├── nerdctl-2.1.6-linux-arm64.tar.gz
└── runc-1.3.4.arm64

7 directories, 24 files

# Worker 노드 (k8s-node4)
ssh k8s-node4 tree /tmp/releases

/tmp/releases
├── cni-plugins-linux-arm64-1.8.0.tgz
├── containerd-2.1.5-linux-arm64.tar.gz
├── containerd-rootless-setuptool.sh
├── containerd-rootless.sh
├── crictl
├── crictl-1.32.0-linux-arm64.tar.gz
├── images
├── kubeadm-1.32.9-arm64
├── kubelet-1.32.9-arm64
├── nerdctl
├── nerdctl-2.1.6-linux-arm64.tar.gz
└── runc-1.3.4.arm64

2 directories, 11 files

Control Plane 노드에는 etcd(아카이브·풀린 디렉터리)와 kubectl 바이너리가 있고, Worker 노드에는 없다. 공통으로는 containerd, crictl, runc, nerdctl, CNI 플러그인, kubeadm, kubelet이 있다. 즉, local_release_dir만 봐도 어떤 노드가 Control Plane용·Worker용으로 구성되었는지 구분할 수 있다.

노드별 sysctl 적용값 확인

Kubespray는 모든 노드에 동일한 커널 파라미터(sysctl)를 적용한다. /etc/sysctl.conf에서 주석을 제외한 적용값만 보려면 아래처럼 확인하면 된다.

# Control Plane 노드 (k8s-node1)
ssh k8s-node1 grep "^[^#]" /etc/sysctl.conf

# Worker 노드 (k8s-node4)
ssh k8s-node4 grep "^[^#]" /etc/sysctl.conf

k8s-node1 (Control Plane):

net.ipv4.ip_forward=1
kernel.keys.root_maxbytes=25000000
kernel.keys.root_maxkeys=1000000
kernel.panic=10
kernel.panic_on_oops=1
vm.overcommit_memory=1
vm.panic_on_oom=0
net.ipv4.ip_local_reserved_ports=30000-32767
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-arptables=1
net.bridge.bridge-nf-call-ip6tables=1

k8s-node4 (Worker): 위와 동일한 목록이 나온다.

Control Plane·Worker 구분 없이 동일한 sysctl이 적용된다. net.ipv4.ip_forward=1은 Pod 네트워킹을 위해, net.bridge.bridge-nf-call-*는 브리지 트래픽이 iptables/ip6tables를 타도록 하기 위한 설정이다.

Kubespray가 자동으로 수행하는 작업

Kubespray는 아래와 같은 작업을 자동으로 수행한다. 클러스터 배포 - 실습 환경 구성에서는 Vagrant/VM 구성만 다루었고, 이번 글에서는 실제 cluster.yml 실행으로 HA Control Plane 3대 + Worker 1대를 배포한다. 그중 단계 4(Worker 노드에 nginx static pod 배포)가 Case 1(Full Client-Side LB)의 핵심이다.

단계 작업 내용
1 모든 노드에 containerd 설치
2 Control Plane에 API Server, Controller Manager, Scheduler 설치
3 Control Plane에 etcd 클러스터 구성 (3대)
4 Worker 노드에 nginx static pod 배포 ← Case 1 핵심
5 Flannel CNI 설치
6 CoreDNS 설치


배포 후 기본 확인

Control Plane API Server 확인

Control Plane 노드 각각에는 API Server 파드가 떠 있으므로, 해당 노드의 kubeconfig는 127.0.0.1:6443으로 설정되어 있다. 각 노드에서 kubectl cluster-info -v=6으로 확인하면 된다.

for i in {1..3}; do echo ">> k8s-node$i <<"; ssh k8s-node$i kubectl cluster-info -v=6; echo; done
>> k8s-node1 <<
I0205 22:18:19.355223   33204 loader.go:402] Config loaded from file:  /root/.kube/config
...
I0205 22:18:19.369517   33204 round_trippers.go:560] GET https://127.0.0.1:6443/api/v1/namespaces/kube-system/services?labelSelector=... 200 OK in 9 milliseconds
Kubernetes control plane is running at https://127.0.0.1:6443

>> k8s-node2 <<
I0205 22:18:42.158063   32648 loader.go:402] Config loaded from file:  /root/.kube/config
...
I0205 22:18:42.186430   32648 round_trippers.go:560] GET https://127.0.0.1:6443/api/v1/namespaces/kube-system/services?labelSelector=... 200 OK in 9 milliseconds
Kubernetes control plane is running at https://127.0.0.1:6443

>> k8s-node3 <<
I0205 22:18:14.430566   32774 loader.go:402] Config loaded from file:  /root/.kube/config
...
I0205 22:18:14.445100   32774 round_trippers.go:560] GET https://127.0.0.1:6443/api/v1/namespaces/kube-system/services?labelSelector=... 200 OK in 10 milliseconds
Kubernetes control plane is running at https://127.0.0.1:6443

세 노드 모두 kubeconfig는 /root/.kube/config에서 로드되고, API 요청이 https://127.0.0.1:6443으로 나가며 “Kubernetes control plane is running at https://127.0.0.1:6443”이 출력되면 Case 1 엔드포인트가 올바르게 설정된 것이다.

kubeconfig 설정

이번 실습에서는 admin-lb(별도 호스트)에서 하나의 API Server만 보게 kubeconfig를 설정한다. 클러스터 배포 - 실습 환경 구성이나 단일 노드 실습(4.1)에서는 admin-lb와 Control Plane이 분리되지 않았거나 kubectl을 Control Plane 노드에서만 썼기 때문에 server를 바꿀 필요가 없었다. Control Plane에서 가져온 config는 server: https://127.0.0.1:6443이라서, admin-lb에서는 그대로 쓰면 접근이 안 되므로 단일 IP(예: 192.168.10.11)로 바꾼다. 그 노드 장애 시 admin-lb에서 kubectl 접근이 불가할 수 있다.

kubeconfig 복사

클러스터 배포 시 admin용 kubeconfig는 Control Plane 노드에만 생성된다. Worker에는 /root/.kube/config가 없거나 kubelet용 등 역할이 다르므로, admin-lb에서 쓸 config는 Control Plane 중 한 대에서 가져와야 한다. 보통 첫 번째 Control Plane(k8s-node1)에서 복사한다.

mkdir -p /root/.kube
scp k8s-node1:/root/.kube/config /root/.kube/

# API Server 주소 확인
cat /root/.kube/config | grep server
config                                      100% 5665     1.7MB/s   00:00
    server: https://127.0.0.1:6443

단일 IP로 변경

sed -i 's/127.0.0.1/192.168.10.11/g' /root/.kube/config

kubectl get node -owide -v=6

변경 후 아래 사항들을 확인한다:

  • admin-lb에서 kubectl이 정상 동작하는지
  • -v=6 로그에 GET https://192.168.10.11:6443/... 200 OK가 나오는지(실제로 해당 IP로 요청이 나가는지)
  • 노드 목록이 4대 모두 나오는지
I0205 22:22:20.985079   14361 round_trippers.go:560] GET https://192.168.10.11:6443/api?timeout=32s 200 OK in 6 milliseconds
...
NAME        STATUS   ROLES           AGE   VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
k8s-node1   Ready    control-plane   34m   v1.32.9   192.168.10.11   <none>        Rocky Linux 10.0 ...  6.12.0-55...     containerd://2.1.5
k8s-node2   Ready    control-plane   34m   v1.32.9   192.168.10.12   <none>        Rocky Linux 10.0 ...  6.12.0-55...     containerd://2.1.5
k8s-node3   Ready    control-plane   34m   v1.32.9   192.168.10.13   <none>        Rocky Linux 10.0 ...  6.12.0-55...     containerd://2.1.5
k8s-node4   Ready    <none>          33m   v1.32.9   192.168.10.14   <none>        Rocky Linux 10.0 ...  6.12.0-55...     containerd://2.1.5

노드 확인

kubectl get node -owide
# 예상 출력
NAME        STATUS   ROLES           AGE     VERSION   INTERNAL-IP     ...
k8s-node1   Ready    control-plane   3m37s   v1.32.9   192.168.10.11   ...
k8s-node2   Ready    control-plane   3m31s   v1.32.9   192.168.10.12   ...
k8s-node3   Ready    control-plane   3m29s   v1.32.9   192.168.10.13   ...
k8s-node4   Ready    <none>          3m3s    v1.32.9   192.168.10.14   ...

Taint 확인

kubectl describe node | grep -E 'Name:|Taints'
Name:               k8s-node1
Taints:             node-role.kubernetes.io/control-plane:NoSchedule
Name:               k8s-node2
Taints:             node-role.kubernetes.io/control-plane:NoSchedule
Name:               k8s-node3
Taints:             node-role.kubernetes.io/control-plane:NoSchedule
Name:               k8s-node4
Taints:             <none>

Pod CIDR 확인

노드별 Pod 네트워크 대역(CIDR)은 Flannel 등 CNI가 할당한다.

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
k8s-node1       10.233.64.0/24
k8s-node2       10.233.65.0/24
k8s-node3       10.233.66.0/24
k8s-node4       10.233.67.0/24

Pod 목록 확인

kubectl get pod -A
NAMESPACE     NAME                                READY   STATUS    RESTARTS   AGE
kube-system   coredns-664b99d7c7-74zjn            1/1     Running   0          42m
kube-system   coredns-664b99d7c7-pcxrv            1/1     Running   0          42m
kube-system   kube-apiserver-k8s-node1            1/1     Running   1          43m
kube-system   kube-apiserver-k8s-node2            1/1     Running   1          43m
kube-system   kube-apiserver-k8s-node3            1/1     Running   1          43m
kube-system   kube-controller-manager-k8s-node1   1/1     Running   2          43m
kube-system   kube-controller-manager-k8s-node2   1/1     Running   2          43m
kube-system   kube-controller-manager-k8s-node3   1/1     Running   2          43m
kube-system   kube-flannel-ds-arm64-9fg8h         1/1     Running   0          43m
kube-system   kube-flannel-ds-arm64-kppg4         1/1     Running   0          43m
kube-system   kube-flannel-ds-arm64-sfnfh         1/1     Running   0          43m
kube-system   kube-flannel-ds-arm64-xnm8r         1/1     Running   0          43m
kube-system   kube-proxy-97rb2                    1/1     Running   0          43m
kube-system   kube-proxy-dsrms                    1/1     Running   0          43m
kube-system   kube-proxy-qdkq8                    1/1     Running   0          43m
kube-system   kube-proxy-txpcm                    1/1     Running   0          43m
kube-system   kube-scheduler-k8s-node1            1/1     Running   1          43m
kube-system   kube-scheduler-k8s-node2            1/1     Running   1          43m
kube-system   kube-scheduler-k8s-node3            1/1     Running   1          43m
kube-system   metrics-server-65fdf69dcb-wp2zj     1/1     Running   0          42m
kube-system   nginx-proxy-k8s-node4               1/1     Running   0          43m

Control Plane 3대에는 API Server, Controller Manager,Scheduler가 노드별 static pod로, Worker(k8s-node4)에는 nginx-proxy static pod가 배포되어 있다. CoreDNS, Flannel, kube-proxy, metrics-server는 시스템 컴포넌트다.

API Server 접근 확인

# Control Plane 노드 각각 API 접근 확인
for i in {1..3}; do 
  echo ">> k8s-node$i <<"
  curl -sk https://192.168.10.1$i:6443/version | grep gitVersion
  echo
done
>> k8s-node1 <<
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

>> k8s-node2 <<
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

>> k8s-node3 <<
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

admin-lb의 /etc/hostsk8s-node1~k8s-node5가 있으면 호스트명으로도 접근할 수 있다.

cat /etc/hosts
# ...
# 192.168.10.11 k8s-node1
# 192.168.10.12 k8s-node2
# ...

for i in {1..3}; do echo ">> k8s-node$i <<"; curl -sk https://k8s-node$i:6443/version | grep Version; echo; done
>> k8s-node1 <<
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

>> k8s-node2 <<
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

>> k8s-node3 <<
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

etcd 클러스터 확인

ssh k8s-node1 etcdctl.sh member list -w table
+------------------+---------+-------+----------------------------+----------------------------+------------+
|        ID        | STATUS  | NAME  |         PEER ADDRS         |        CLIENT ADDRS        | IS LEARNER |
+------------------+---------+-------+----------------------------+----------------------------+------------+
|  8b0ca30665374b0 | started | etcd3 | https://192.168.10.13:2380 | https://192.168.10.13:2379 |      false |
| 2106626b12a4099f | started | etcd2 | https://192.168.10.12:2380 | https://192.168.10.12:2379 |      false |
| c6702130d82d740f | started | etcd1 | https://192.168.10.11:2380 | https://192.168.10.11:2379 |      false |
+------------------+---------+-------+----------------------------+----------------------------+------------+

etcd endpoint status (노드별)

각 Control Plane 노드에서 로컬 etcd(127.0.0.1:2379) 상태를 보면, 리더/팔로워와 RAFT 인덱스가 맞는지 확인할 수 있다.

for i in {1..3}; do echo ">> k8s-node$i <<"; ssh k8s-node$i etcdctl.sh endpoint status -w table; echo; done
>> k8s-node1 <<
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | c6702130d82d740f |  3.5.25 |  6.6 MB |      true |      false |         5 |      10052 |              10052 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> k8s-node2 <<
| 127.0.0.1:2379 | 2106626b12a4099f |  3.5.25 |  6.6 MB |     false |      false |         5 |      10056 |              10056 |        |

>> k8s-node3 <<
| 127.0.0.1:2379 | 8b0ca30665374b0 |  3.5.25 |  6.6 MB |     false |      false |         5 |      10057 |              10057 |        |

k8s-node1에서만 IS LEADERtrue이고, node2·node3은 팔로워다. RAFT INDEX는 노드마다 약간씩 다를 수 있다.

etcd 백업 확인

for i in {1..3}; do echo ">> k8s-node$i <<"; ssh k8s-node$i tree /var/backups; echo; done
>> k8s-node1 <<
/var/backups
└── etcd-2026-02-05_21:47:00
    ├── member
    │   ├── snap
    │   │   └── db
    │   └── wal
    │       └── 0000000000000000-0000000000000000.wal
    └── snapshot.db

5 directories, 3 files

>> k8s-node2 <<
/var/backups
└── etcd-2026-02-05_21:47:01
    ├── member
    │   ├── snap
    │   │   └── db
    │   └── wal
    │       └── 0000000000000000-0000000000000000.wal
    └── snapshot.db

5 directories, 3 files

>> k8s-node3 <<
/var/backups
└── etcd-2026-02-05_21:47:00
    ├── member
    │   ├── snap
    │   │   └── db
    │   └── wal
    │       └── 0000000000000000-0000000000000000.wal
    └── snapshot.db

5 directories, 3 files

각 Control Plane 노드에 타임스탬프가 붙은 etcd 스냅샷 디렉터리(etcd-YYYY-MM-DD_HH:MM:SS)가 생성되어 있으면 배포 시점 백업이 수행된 것이다.

결과

Case 1 (Full Client-Side LB) 클러스터가 성공적으로 배포되었다.

구성 요소 상태
Control Plane (3대) 정상
etcd 클러스터 (3대) 정상
Worker Node (1대) 정상
nginx static pod 정상

다음 글에서는 Client-Side LB 환경의 동작을 확인해 본다.


참고 자료




hit count

댓글남기기