본문 바로가기
Kubernetes

[Kubespray] Kubernetes HA 구성 실습

by interlude-3 2026. 2. 7.
본 글은 gasida님의 k8s-deploy 스터디 자료를 기반으로 작성되었습니다.

 

Kubernetes HA의 두 가지 로드밸런서


Kubernetes 고가용성 클러스터에는 두 종류의 로드밸런서가 존재합니다.

 

1.1 Worker Client-Side LB (워커 전용 내부 LB)

위치

  •  각 워커 노드 내부 (nginx-proxy static pod)

역할

  • 워커 노드의 kubelet, kube-proxy가 Control Plane API 서버에 접근할 때 사용
  • localhost:6443으로 수신하여 3개 Control Plane으로 분산

구성

[Worker Node]
┌─────────────────────────────────┐
│  kubelet                        │
│  kube-proxy                     │
│     ↓                           │
│  localhost:6443                 │
│     ↓                           │
│  nginx-proxy (static pod)       │
│     ├→ CP1:6443                 │
│     ├→ CP2:6443                 │
│     └→ CP3:6443                 │
└─────────────────────────────────┘

장점

  • 자동 구성 (Kubespray가 알아서 설치)
  • 별도 LB 서버 불필요
  • 워커별 독립적 (SPOF 없음)
  • 네트워크 홉 최소화

단점

  • kubectl 등 외부 관리 도구는 사용 불가
  • 워커 수만큼 nginx 인스턴스 실행 (리소스 소모)

 

1.2 Admin LB (관리자용 외부 LB)

 

위치

  • 독립 서버 (admin-lb 노드)

역할

  • kubectl, CI/CD, 모니터링 등 외부 사용자, 관리 도구의 API 접근용
  • 단일 엔드포인트 제공 (예: admin-lb.example.com:6443)

구성

[Client]
┌────────────────────────────────┐
│  kubectl                       │
│  CI/CD 파이프라인               │
│  모니터링 시스템                │
└──────────┬─────────────────────┘
           ↓
    admin-lb:6443 (HAProxy/Nginx)
           ├→ CP1:6443
           ├→ CP2:6443
           └→ CP3:6443

 

장점

  • 관리 도구들의 단일 진입점
  • Control Plane IP 변경 시 LB 설정만 수정
  • 접근 제어, 로깅 중앙화 가능

단점

  • 별도 서버 필요 (인프라 비용)
  • Admin LB 자체가 SPOF (HA 구성 필요)

 

실습 환경


Server Name Role OS IP CPU MEM Disk
admin-lb API LB Ubuntu 22.04 192.168.105.151 2 16G 20G
node1 control plane Ubuntu 22.04 192.168.105.152 4 16G 30G
node2 control plane Ubuntu 22.04 192.168.105.153 4 16G 30G
node3 control plane Ubuntu 22.04 192.168.105.154 4 16G 30G
node4 worker Ubuntu 22.04 192.168.105.155 4 16G 30G

 

최종 목표

kubectl [client] → admin-lb:6443 → HAProxy → control plane [node1, node2, node3]

kubelet [node4] → localhost:6443 → nginx → control plane [node1, node2, node3]

 

실습 1. Worker Client-Side LB로 기본 HA 구성


1) 노드 서버 설정 (node1~node4)

ssh 설정, hosts 도메인 설정
#!/usr/bin/env bash

set -e

echo ">>>> Initial Config Start <<<<"

########################################
# TASK 1. Timezone & NTP
########################################
echo "[TASK 1] Change Timezone and Enable NTP"
timedatectl set-local-rtc 0
timedatectl set-timezone Asia/Seoul
systemctl enable --now systemd-timesyncd >/dev/null 2>&1

########################################
# TASK 2. Disable SWAP (Kubernetes requirement)
########################################
echo "[TASK 2] Disable SWAP"
swapoff -a
sed -i.bak '/\sswap\s/d' /etc/fstab

########################################
# TASK 3. Kernel modules & sysctl
########################################
echo "[TASK 3] Config kernel & module"

cat << EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

modprobe overlay >/dev/null 2>&1 || true
modprobe br_netfilter >/dev/null 2>&1 || true

cat << EOF > /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

sysctl --system

########################################
# TASK 4. /etc/hosts
########################################
echo "[TASK 4] Setting Local DNS Using hosts file"

sed -i '/admin-lb/d;/node[1-4]/d' /etc/hosts

cat << EOF >> /etc/hosts
192.168.105.151 admin-lb
192.168.105.152 node1
192.168.105.153 node2
192.168.105.154 node3
192.168.105.155 node4
EOF

########################################
# TASK 5. SSH configuration
########################################
echo "[TASK 5] Setting SSHD"

echo "root:<PASSWORD>" | chpasswd

sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config

systemctl restart ssh >/dev/null 2>&1

########################################
# TASK 6. Base packages
########################################
echo "[TASK 6] Install packages"

apt-get update -qq
apt-get install -y \
  git \
  curl \
  ca-certificates \
  gnupg \
  nfs-common >/dev/null 2>&1

echo ">>>> Initial Config End <<<<"


2) admin-lb 서버 설정

HAProxy 설치/설정, NFS Server 설치/설정, ssh 설정, hosts 도메인 설정, kubespray git clone, kubectl/k9s/helm 설치
#!/usr/bin/env bash

">>>> Initial Config Start <<<<"

########################################
# TASK 1. Time & NTP
########################################
echo "[TASK 1] Timezone & NTP 설정"
timedatectl set-local-rtc 0
timedatectl set-timezone Asia/Seoul
systemctl enable --now systemd-timesyncd >/dev/null 2>&1
echo

########################################
# TASK 2. Disable Firewall (UFW)
########################################
echo "[TASK 2] UFW 비활성화"
ufw disable >/dev/null 2>&1 || true
echo

########################################
# TASK 3. /etc/hosts
########################################
echo "[TASK 3] /etc/hosts 설정"
sed -i '/admin-lb/d;/node[1-4]/d' /etc/hosts

cat << EOF >> /etc/hosts
192.168.105.151 admin-lb
192.168.105.152 node1
192.168.105.153 node2
192.168.105.154 node3
192.168.105.155 node4
EOF
echo

########################################
# TASK 4. Base Packages
########################################
echo "[TASK 4] 기본 패키지 설치"
apt-get update -qq
apt-get install -y \
  curl git sshpass ca-certificates \
  apt-transport-https gnupg lsb-release \
  python3-pip nfs-kernel-server >/dev/null 2>&1
echo

########################################
# TASK 5. kubectl
########################################
echo "[TASK 5] kubectl 설치"
mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key \
 | gpg --dearmor -o /etc/apt/keyrings/kubernetes.gpg

echo "deb [signed-by=/etc/apt/keyrings/kubernetes.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /" \
> /etc/apt/sources.list.d/kubernetes.list

apt-get update -qq
apt-get install -y kubectl >/dev/null 2>&1
echo

########################################
# TASK 6. HAProxy (API LB)
########################################
echo "[TASK 6] HAProxy 설치 및 설정"
apt-get install -y haproxy >/dev/null 2>&1

cat << EOF > /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    daemon
    maxconn 4000

defaults
    mode tcp
    log global
    option tcplog
    timeout connect 10s
    timeout client 1m
    timeout server 1m

frontend kube-apiserver
    bind *:6443
    default_backend kube-apiserver-backend

backend kube-apiserver-backend
    balance roundrobin
    option tcp-check
    server node1 192.168.105.152:6443 check
    server node2 192.168.105.153:6443 check
    server node3 192.168.105.154:6443 check

listen stats
    bind *:9000
    mode http
    stats enable
    stats uri /haproxy_stats
EOF

systemctl enable --now haproxy >/dev/null 2>&1
echo

########################################
# TASK 7. NFS Server
########################################
echo "[TASK 7] NFS 서버 설정"
mkdir -p /srv/nfs/share
chown nobody:nogroup /srv/nfs/share
chmod 755 /srv/nfs/share

echo "/srv/nfs/share *(rw,sync,no_root_squash,no_subtree_check)" > /etc/exports
exportfs -rav >/dev/null 2>&1
systemctl enable --now nfs-server >/dev/null 2>&1
echo

########################################
# TASK 8. SSH 설정
########################################
echo "[TASK 8] SSH 설정"
echo "root:<PASSWORD>" | chpasswd

sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/^#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart ssh >/dev/null 2>&1
echo

########################################
# TASK 9. SSH Key 배포
########################################
echo "[TASK 9] SSH Key 생성 및 배포"
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa >/dev/null 2>&1

for ip in 151 152 153 154 155; do
  echo "  - Copy key to 192.168.105.$ip"
  sshpass -p '<PASSWORD>' ssh-copy-id -o StrictHostKeyChecking=no root@192.168.105.$ip >/dev/null 2>&1
done
echo

########################################
# TASK 10. Kubespray
########################################
echo "[TASK 10] Kubespray 클론 및 inventory 설정"
git clone -b v2.29.1 https://github.com/kubernetes-sigs/kubespray.git /root/kubespray >/dev/null 2>&1
cp -rfp /root/kubespray/inventory/sample /root/kubespray/inventory/mycluster

cat << EOF > /root/kubespray/inventory/mycluster/inventory.ini
[kube_control_plane]
node1 ansible_host=192.168.105.152 ip=192.168.105.152 etcd_member_name=etcd1
node2 ansible_host=192.168.105.153 ip=192.168.105.153 etcd_member_name=etcd2
node3 ansible_host=192.168.105.154 ip=192.168.105.154 etcd_member_name=etcd3

[etcd:children]
kube_control_plane

[kube_node]
node4 ansible_host=192.168.105.155 ip=192.168.105.155
EOF
echo

########################################
# TASK 11. Python Deps
########################################
echo "[TASK 11] Python dependency 설치"
pip3 install -r /root/kubespray/requirements.txt >/dev/null 2>&1
echo

########################################
# TASK 12. Helm
########################################
echo "[TASK 12] Helm 설치"
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash >/dev/null 2>&1
echo

echo ">>>> Initial Config End <<<<"

 

3) 설정 환경 확인

root@admin-lb:~/kubespray-HA# for i in {1..4}; do echo ">> k8s-node$i <<"; ssh node$i hostname; echo; done
>> k8s-node1 <<
node1

>> k8s-node2 <<
node2

>> k8s-node3 <<
node3

>> k8s-node4 <<
node4

root@admin-lb:~/kubespray-HA# for i in {1..5}; do echo ">> k8s-node$i <<"; ssh 192.168.105.15$i hostname; echo; done
>> k8s-node1 <<
admin-lb

>> k8s-node2 <<
node1

>> k8s-node3 <<
node2

>> k8s-node4 <<
node3

>> k8s-node5 <<
node4

root@admin-lb:~/kubespray-HA# which python3 && python3 -V
/usr/bin/python3
Python 3.10.12

root@admin-lb:~/kubespray-HA# cat /root/kubespray/inventory/mycluster/inventory.ini
[kube_control_plane]
node1 ansible_host=192.168.105.152 ip=192.168.105.152 etcd_member_name=etcd1
node2 ansible_host=192.168.105.153 ip=192.168.105.153 etcd_member_name=etcd2
node3 ansible_host=192.168.105.154 ip=192.168.105.154 etcd_member_name=etcd3

[etcd:children]
kube_control_plane

[kube_node]
node4 ansible_host=192.168.105.155 ip=192.168.105.155

root@admin-lb:~/kubespray-HA# exportfs -rav
exporting *:/srv/nfs/share

root@admin-lb:~/kubespray-HA# cat /etc/exports
/srv/nfs/share *(rw,sync,no_root_squash,no_subtree_check)

 

4) cluster, flannel, addons 설정값 변경

# k8s_cluster.yml 설정

sed -i 's|kube_owner: kube|kube_owner: root|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|kube_network_plugin: calico|kube_network_plugin: flannel|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|kube_proxy_mode: ipvs|kube_proxy_mode: iptables|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|enable_nodelocaldns: true|enable_nodelocaldns: false|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
echo "enable_dns_autoscaler: false" >> inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

root@admin-lb:~/kubespray# 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

# flannel 설정
echo "flannel_interface: ens33" >> inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml

root@admin-lb:~/kubespray# grep "^[^#]" inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml
flannel_interface: ens33

# addons
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

root@admin-lb:~/kubespray# grep -iE 'metrics_server_enabled:' inventory/mycluster/group_vars/k8s_cluster/addons.yml
metrics_server_enabled: true

 

5) kubespray로 kubernetes 설치

ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml -e kube_version="1.32.9" | tee kubespray_install.log

root@admin-lb:~/kubespray# kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
node1   10.233.64.0/24
node2   10.233.65.0/24
node3   10.233.67.0/24
node4   10.233.66.0/24
root@admin-lb:~/kubespray# ssh node1 etcdctl.sh member list -w table
+------------------+---------+-------+------------------------------+------------------------------+------------+
|        ID        | STATUS  | NAME  |          PEER ADDRS          |         CLIENT ADDRS         | IS LEARNER |
+------------------+---------+-------+------------------------------+------------------------------+------------+
| 1d9709e3b87e94d1 | started | etcd2 | https://192.168.105.153:2380 | https://192.168.105.153:2379 |      false |
| b65184f1b302bcc4 | started | etcd1 | https://192.168.105.152:2380 | https://192.168.105.152:2379 |      false |
| dd3a126bd79df14b | started | etcd3 | https://192.168.105.154:2380 | https://192.168.105.154:2379 |      false |
+------------------+---------+-------+------------------------------+------------------------------+------------+
root@admin-lb:~/kubespray# for i in {1..3}; do echo ">> node$i <<"; ssh node$i etcdctl.sh endpoint status -w table; echo; done
>> node1 <<
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | b65184f1b302bcc4 |  3.5.25 |   11 MB |      true |      false |         4 |       7746 |               7746 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> node2 <<
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | 1d9709e3b87e94d1 |  3.5.25 |   12 MB |     false |      false |         4 |       7746 |               7746 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> node3 <<
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | dd3a126bd79df14b |  3.5.25 |   12 MB |     false |      false |         4 |       7752 |               7752 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

 

6) Worker Client-Side LB 자동 구성 확인

root@admin-lb:~/kubespray# ssh node4 crictl ps | grep nginx
ad5e49b1e4777       c44d76c3213ea       46 minutes ago      Running             ingress-nginx-controller           0                   523e6682ea310       ingress-nginx-controller-gpb84                      ingress-nginx
beebaa63167ed       c318e336065b1       About an hour ago   Running             nginx-proxy                        0                   1bddacee49bf0       nginx-proxy-node4                                   kube-system

root@admin-lb:~/kubespray# ssh node4 curl -s localhost:8081/healthz -I
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 08 Feb 2026 03:22:10 GMT
Content-Type: text/plain
Content-Length: 0
Connection: keep-alive

root@admin-lb:~/kubespray# ssh node4 cat /etc/kubernetes/kubelet.conf | grep server
    server: https://localhost:6443
root@admin-lb:~/kubespray# ssh node4 cat /etc/kubernetes/kubelet.conf | grep server
    server: https://localhost:6443
root@admin-lb:~/kubespray# kubectl get cm -n kube-system kube-proxy -o yaml | grep server
        server: https://127.0.0.1:6443
root@admin-lb:~/kubespray# ssh node4 ss -tnlp | grep :6443
LISTEN 0      511          127.0.0.1:6443       0.0.0.0:*    users:(("nginx",pid=44082,fd=5),("nginx",pid=44081,fd=5),("nginx",pid=44054,fd=5))

root@admin-lb:~/kubespray# ssh node4 cat /etc/nginx/nginx.conf
...
stream {
  upstream kube_apiserver {
    least_conn;
    server 192.168.105.152:6443;
    server 192.168.105.153:6443;
    server 192.168.105.154:6443;
    }

  server {
    listen        127.0.0.1:6443;
    proxy_pass    kube_apiserver;
    proxy_timeout 10m;
    proxy_connect_timeout 1s;
  }
}

...

 

7) HA 동작 테스트

# 워커노드 통신 상태 확인
root@admin-lb:~/kubespray# ssh node4 curl -sk https://localhost:6443/version
{
  "major": "1",
  "minor": "32",
  "gitVersion": "v1.32.9",
  "gitCommit": "cea7087b31eb788b75934d769a28f058ab309318",
  "gitTreeState": "clean",
  "buildDate": "2025-09-09T19:37:59Z",
  "goVersion": "go1.23.12",
  "compiler": "gc",
  "platform": "linux/amd64"
}

# 장애 재현 - Control Plane 1번 노드 poweroff
root@admin-lb:~/kubespray# ssh node1 poweroff

# 워커노드 통신 상태 확인
root@admin-lb:/home/ria# ssh node4 curl -sk https://localhost:6443/version
{
  "major": "1",
  "minor": "32",
  "gitVersion": "v1.32.9",
  "gitCommit": "cea7087b31eb788b75934d769a28f058ab309318",
  "gitTreeState": "clean",
  "buildDate": "2025-09-09T19:37:59Z",
  "goVersion": "go1.23.12",
  "compiler": "gc",
  "platform": "linux/amd64"
}

# node1 upstream pool에서 제외 확인
root@node2:~# kubectl logs -f nginx-proxy-node4 -n kube-system
...
2026/02/08 03:42:00 [error] 20#20: *1051 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/08 03:42:00 [error] 21#21: *1071 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/08 03:42:00 [error] 21#21: *1077 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/08 03:42:00 [error] 21#21: *1065 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/08 03:42:00 [error] 20#20: *1049 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1471/0, bytes from/to upstream:0/1471
2026/02/08 03:42:00 [error] 20#20: *1082 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/08 03:42:00 [error] 20#20: *1061 recv() failed (104: Connection reset by peer) while proxying and reading from upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/08 03:43:19 [error] 21#21: *1105 upstream timed out (110: Operation timed out) while connecting to upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:0/0, bytes from/to upstream:0/0
2026/02/08 03:43:19 [warn] 21#21: *1105 upstream server temporarily disabled while connecting to upstream, client: 127.0.0.1, server: 127.0.0.1:6443, upstream: "192.168.105.152:6443", bytes from/to client:0/0, bytes from/to upstream:0/0
...

# kubectl은 실패  => 실습 2에서 admin lb 구성
root@admin-lb:/home/ria# kubectl get pods -A
Unable to connect to the server: dial tcp 192.168.105.152:6443: connect: no route to host

 

 

실습 2. Admin LB 추가로 완전한 HA 구성


1) k8s apiserver 호출 설정 (kube-config 수정)

root@admin-lb:/home/ria# sed -i 's/192.168.105.152/192.168.105.151/g' ~/.kube/config

root@admin-lb:/home/ria# cat ~/.kube/config | grep server
    server: https://192.168.105.151:6443
    
root@admin-lb:/home/ria# kubectl get node
E0208 12:55:24.571091 2672733 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://192.168.105.151:6443/api?timeout=32s\": tls: failed to verify certificate: x509: certificate is valid for 10.233.0.1, 192.168.105.154, 192.168.105.152, 127.0.0.1, ::1, 192.168.105.153, not 192.168.105.151"
E0208 12:55:24.574813 2672733 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://192.168.105.151:6443/api?timeout=32s\": tls: failed to verify certificate: x509: certificate is valid for 10.233.0.1, 192.168.105.153, 192.168.105.152, 127.0.0.1, ::1, 192.168.105.154, not 192.168.105.151"
E0208 12:55:24.578513 2672733 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://192.168.105.151:6443/api?timeout=32s\": tls: failed to verify certificate: x509: certificate is valid for 10.233.0.1, 192.168.105.154, 192.168.105.152, 127.0.0.1, ::1, 192.168.105.153, not 192.168.105.151"
E0208 12:55:24.582311 2672733 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://192.168.105.151:6443/api?timeout=32s\": tls: failed to verify certificate: x509: certificate is valid for 10.233.0.1, 192.168.105.153, 192.168.105.152, 127.0.0.1, ::1, 192.168.105.154, not 192.168.105.151"
E0208 12:55:24.586192 2672733 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://192.168.105.151:6443/api?timeout=32s\": tls: failed to verify certificate: x509: certificate is valid for 10.233.0.1, 192.168.105.154, 192.168.105.152, 127.0.0.1, ::1, 192.168.105.153, not 192.168.105.151"
Unable to connect to the server: tls: failed to verify certificate: x509: certificate is valid for 10.233.0.1, 192.168.105.154, 192.168.105.152, 127.0.0.1, ::1, 192.168.105.153, not 192.168.105.151
  • TLS 검증 오류 - Kubernetes API 서버의 인증서는 특정 IP와 도메인만 허용

 

2) Control Plane 서버에 SAN 적용 후 인증서 재발급

  • supplementary_addresses_in_ssl_keys 변수에 새 IP와 도메인 추가
  •  kube-apiserver 인증서 재발급
  • 새로 추가된 IP/도메인이 SAN에 반영되어 TLS 오류 해결
root@admin-lb:~/kubespray# echo "supplementary_addresses_in_ssl_keys: [192.168.105.151, k8s-api-srv.admin-lb.com]" >> inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
root@admin-lb:~/kubespray# ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml --tags "control-plane" --list-tasks
root@admin-lb:~/kubespray# ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml --tags "control-plane" --limit kube_control_plane -e kube_version="1.32.9"

# 적용 후
root@admin-lb:~/kubespray# kubectl get node -v=6
I0208 13:25:28.086285 2674444 loader.go:402] Config loaded from file:  /root/.kube/config
I0208 13:25:28.086879 2674444 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=false
I0208 13:25:28.086899 2674444 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=false
I0208 13:25:28.086918 2674444 envvar.go:172] "Feature gate default state" feature="ClientsAllowCBOR" enabled=false
I0208 13:25:28.086926 2674444 envvar.go:172] "Feature gate default state" feature="ClientsPreferCBOR" enabled=false
I0208 13:25:28.098864 2674444 round_trippers.go:560] GET https://192.168.105.151:6443/api/v1/nodes?limit=500 200 OK in 8 milliseconds
NAME    STATUS   ROLES           AGE    VERSION
node1   Ready    control-plane   128m   v1.32.9
node2   Ready    control-plane   127m   v1.32.9
node3   Ready    control-plane   127m   v1.32.9
node4   Ready    <none>          127m   v1.32.9

root@admin-lb:~/kubespray# echo "192.168.105.151 k8s-api-srv.admin-lb.com" >> /etc/hosts
root@admin-lb:~/kubespray# sed -i 's/192.168.105.151/k8s-api-srv.admin-lb.com/g' /root/.kube/config

root@admin-lb:~/kubespray# kubectl get node -v=6
I0208 13:26:05.581396 2674450 loader.go:402] Config loaded from file:  /root/.kube/config
I0208 13:26:05.581846 2674450 envvar.go:172] "Feature gate default state" feature="ClientsAllowCBOR" enabled=false
I0208 13:26:05.581869 2674450 envvar.go:172] "Feature gate default state" feature="ClientsPreferCBOR" enabled=false
I0208 13:26:05.581879 2674450 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=false
I0208 13:26:05.581885 2674450 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=false
I0208 13:26:05.595140 2674450 round_trippers.go:560] GET https://k8s-api-srv.admin-lb.com:6443/api/v1/nodes?limit=500 200 OK in 8 milliseconds
NAME    STATUS   ROLES           AGE    VERSION
node1   Ready    control-plane   128m   v1.32.9
node2   Ready    control-plane   128m   v1.32.9
node3   Ready    control-plane   128m   v1.32.9
node4   Ready    <none>          127m   v1.32.9

root@admin-lb:~/kubespray# ssh node2 cat /etc/kubernetes/ssl/apiserver.crt | openssl x509 -text -noout
...
X509v3 Subject Alternative Name:
  DNS:k8s-api-srv.admin-lb.com, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:lb-apiserver.kubernetes.local, DNS:localhost, DNS:node-0.kubernetes.local, DNS:node-1.kubernetes.local, DNS:node1, DNS:node2, DNS:node3, DNS:server.kubernetes.local, IP Address:10.233.0.1, IP Address:192.168.105.153, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, IP Address:192.168.105.151, IP Address:192.168.105.152, IP Address:192.168.105.154  
...

 

3) configmap 수정

  • kubeadm-config ConfigMap은 자동 업데이트 되지 않음
  • SAN을 직접 ConfigMap에 등록하여
    kubeadm 업그레이드나 추가 Control Plane 노드 설치 시 새 SAN이 유지되도록 설정
root@admin-lb:~/kubespray# kubectl edit cm -n kube-system kubeadm-config
apiVersion: v1
data:
  ClusterConfiguration: |
    apiServer:
      certSANs:
      - kubernetes
      - kubernetes.default
      - kubernetes.default.svc
      - kubernetes.default.svc.cluster.local
      - 10.233.0.1
      - localhost
      - 127.0.0.1
      - ::1
      - node1
      - node2
      - node3
      - lb-apiserver.kubernetes.local
      - 192.168.105.152
      - 192.168.105.153
      - 192.168.105.154
      - server.kubernetes.local
      - node-0.kubernetes.local
      - node-1.kubernetes.local
      - 192.168.105.151
      - k8s-api-srv.admin-lb.com
...

 

4) HA 동작 테스트

# admin-lb 서버
root@admin-lb:~/kubespray# while true; do kubectl get node ; echo ; kubectl get pod -n kube-system; sleep 1; echo ; done

# 워커 노드 (node4)
root@node4:/home/ria# while true; do curl -sk https://127.0.0.1:6443/version | grep gitVersion ; date; sleep 1; echo ; done

# 장애 재현 - 3대의 컨트롤 플레인 서버 중 node1 강제 종료
root@node1:~# poweroff

 

결과

  • kubectl   ->  TLS handshake timeout 발생. 5초 중단 후 다시 연결
  • curl API 요청  ->  nginx proxy 기반 통신으로 지연 거의 없음
  • 결과적으로 외부 사용자 요청과 워커노드 요청 모두 가용 노드로 전환되어 서비스 지속 (HA 동작 확인)
# admin-lb 서버
...

Unable to connect to the server: net/http: TLS handshake timeout

NAME                              READY   STATUS    RESTARTS      AGE
coredns-664b99d7c7-lgw7x          1/1     Running   0             114m
coredns-664b99d7c7-zxhhl          1/1     Running   0             49m
kube-apiserver-node1              1/1     Running   1 (26m ago)   131m
kube-apiserver-node2              1/1     Running   1             131m
kube-apiserver-node3              1/1     Running   1             131m
kube-controller-manager-node1     1/1     Running   3 (26m ago)   131m
kube-controller-manager-node2     1/1     Running   2             131m
kube-controller-manager-node3     1/1     Running   2             131m
kube-flannel-4jb7p                1/1     Running   0             114m
kube-flannel-6c986                1/1     Running   1 (26m ago)   114m
kube-flannel-6v7sd                1/1     Running   0             114m
kube-flannel-jdfdd                1/1     Running   0             114m
kube-proxy-7fphd                  1/1     Running   0             130m
kube-proxy-8qcl5                  1/1     Running   0             130m
kube-proxy-j7wzk                  1/1     Running   1 (26m ago)   130m
kube-proxy-jkdp4                  1/1     Running   0             130m
kube-scheduler-node1              1/1     Running   2 (26m ago)   131m
kube-scheduler-node2              1/1     Running   1             131m
kube-scheduler-node3              1/1     Running   1             131m
metrics-server-65fdf69dcb-7fvnh   1/1     Running   0             113m
nginx-proxy-node4                 1/1     Running   0             130m


# 워커 노드 (node4)
...
  "gitVersion": "v1.32.9",
Sun Feb  8 01:28:44 PM KST 2026

  "gitVersion": "v1.32.9",
Sun Feb  8 01:28:45 PM KST 2026

  "gitVersion": "v1.32.9",
Sun Feb  8 01:28:46 PM KST 2026
...

 

운영 환경 추천


  • Worker Client-Side LB  => 활성화
  • Admin LB  => HA 구성 (Admin LB 2대 + Keepalived VIP)
# Admin LB HA 구성 예시
VIP:        192.168.105.148 (Keepalived Virtual IP)
admin-lb-1: 192.168.105.149 (MASTER)
admin-lb-2: 192.168.105.150 (BACKUP)

# kubectl(외부 사용자) 설정  ~/.kube/config
clusters:
- cluster:
    server: https://192.168.105.148:6443
  name: kubernetes