[K8S Deploy] #5์ฃผ์ฐจ Kubespray HA & Upgrade

26๋…„๋„ K8S Deploy ์ •๋ฆฌ ๊ธ€์ž…๋‹ˆ๋‹ค.

 

kubespray ํ†ตํ•œ k8s ๋ฐฐํฌ

kubespray ๋ณ€์ˆ˜ ์šฐ์„ ์ˆœ์œ„

Kubespray๋Š” Ansible ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ, ์„ค์ • ๋Œ€๋ถ€๋ถ„์ด ๋ณ€์ˆ˜(variable) ๋กœ ์ œ์–ด๋œ๋‹ค.
๋”ฐ๋ผ์„œ Ansible ๋ณ€์ˆ˜ ์šฐ์„ ์ˆœ์œ„(variable precedence) ๊ตฌ์กฐ๋กœ ์ œ์–ด๋˜๋Š”๋ฐ, Ansible์€ ๋ณ€์ˆ˜๊ฐ€ ์„ ์–ธ๋˜๋Š” ์œ„์น˜์— ๋”ฐ๋ผ ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋ช…ํ™•ํžˆ ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ˆซ์ž๊ฐ€ ๋†’์„์ˆ˜๋ก ์šฐ์„  ์ ์šฉ๋œ๋‹ค.

 

Ansible ๊ณต์‹ ๋ฌธ์„œ ๊ธฐ์ค€์œผ๋กœ ๋ณ€์ˆ˜ ์šฐ์„ ์ˆœ์œ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค.

  • ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ ๋ณ€์ˆ˜๋Š” ๋†’์€ ์šฐ์„ ์ˆœ์œ„ ๋ณ€์ˆ˜์— ์˜ํ•ด ์–ธ์ œ๋“  ๋ฎ์–ด์”Œ์›Œ์งˆ ์ˆ˜ ์žˆ๋‹ค
  • ๊ฐ€์žฅ ๋†’์€ ์šฐ์„ ์ˆœ์œ„๋Š” ํ•ญ์ƒ Extra vars (-e) ์ด๋‹ค

๋งŒ์•ฝ ๊ฐ™์€ ์ด๋ฆ„์˜ ๋ณ€์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ ๊ณณ์— ์ •์˜๋˜์–ด ์žˆ๋‹ค๋ฉด ๊ฐ€์žฅ ๋‚˜์ค‘์—, ๊ฐ€์žฅ ๋†’์€ ๋‹จ๊ณ„์—์„œ ์ •์˜๋œ ๊ฐ’์ด ์‹ค์ œ ์ ์šฉ๋œ๋‹ค.

 

(1) Role defaults (์šฐ์„ ์ˆœ์œ„ ๋‚ฎ์Œ)

roles/*/defaults/main.yml
  • Kubespray์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ๊ฐ’
  • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ„๋„ ์„ค์ •์„ ํ•˜์ง€ ์•Š์œผ๋ฉด ์ด ๊ฐ’์ด ์ ์šฉ๋œ๋‹ค
  • ๊ฐ€์žฅ ๋จผ์ € ๋กœ๋”ฉ๋˜๋ฉฐ ์ž˜ ๋ฎ์–ด์”Œ์›Œ์ง„๋‹ค

(2) Inventory group_vars / host_vars (์‹ค์งˆ์ ์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฐ’)

inventory/mycluster/group_vars/all.yml
inventory/mycluster/group_vars/k8s_cluster.yml
inventory/mycluster/host_vars/k8s-node1.yml
  • Kubespray์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ •ํ•˜๋Š” ์˜์—ญ
  • Role defaults๋ฅผ ๋ฎ์–ด์”Œ์šฐ๋Š” ์ฃผ๋œ ์ˆ˜๋‹จ
  • ํด๋Ÿฌ์Šคํ„ฐ ๋‹จ์œ„ / ๊ทธ๋ฃน ๋‹จ์œ„ / ๋…ธ๋“œ ๋‹จ์œ„ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅ

(3) Play vars / Playbook ๋‚ด๋ถ€ vars (๊ฐ•์ œ ์„ค์ •)

 
- name: Install etcd
  vars:
    etcd_cluster_setup: false
    etcd_events_cluster_setup: false
  import_playbook: install_etcd.yml
  • vars: ๋Š” Play vars
  • inventory ๋ณ€์ˆ˜๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๋‹ค
  • ์‚ฌ์šฉ์ž๊ฐ€ inventory์—์„œ ๊ฐ’์„ ๋ฐ”๊ฟ”๋„ ์ด playbook ์•ˆ์—์„œ๋Š” ๋ฌด์‹œ๋œ๋‹ค

(4) Extra vars (-e)

ansible-playbook cluster.yml -e etcd_cluster_setup=true
  • ๊ฐ€์žฅ ์šฐ์„ ์ˆœ์œ„ ๋†’๋‹ค
  • ๋””๋ฒ„๊น…, ๊ฐ•์ œ override, ์ž„์‹œ ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉ๋œ๋‹ค
root@admin-lb:~# cd /root/kubespray/
root@admin-lb:~/kubespray# 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

 

๋ณ€์ˆ˜๊ฐ€ ์–ด๋””์„œ ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€ ์ถ”์ 

root@admin-lb:~/kubespray# grep -Rn "allow_unsupported_distribution_setup" inventory/mycluster/ playbooks/ roles/ -A1 -B1
inventory/mycluster/group_vars/all/all.yml-141-## If enabled it will allow kubespray to attempt setup even if the distribution is not supported. For unsupported distributions this can lead to unexpected failures in some cases.
inventory/mycluster/group_vars/all/all.yml:142:allow_unsupported_distribution_setup: false
--
roles/kubernetes/preinstall/tasks/0040-verify-settings.yml-22-  assert:
roles/kubernetes/preinstall/tasks/0040-verify-settings.yml:23:    that: (allow_unsupported_distribution_setup | default(false)) or ansible_distribution in supported_os_distributions
roles/kubernetes/preinstall/tasks/0040-verify-settings.yml-24-    msg: "{{ ansible_distribution }} is not a known OS"

 

 

์„ค์ • ๋ฐฐํฌ

root@admin-lb:~/kubespray# cat /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

    # utilize system-wide crypto-policies
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  tcplog
    option                  dontlognull
    option http-server-close
    #option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

# ---------------------------------------------------------------------
# Kubernetes API Server Load Balancer Configuration
# ---------------------------------------------------------------------
frontend k8s-api
    bind *:6443
    mode tcp
    option tcplog
    default_backend k8s-api-backend

backend k8s-api-backend
    mode tcp
    option tcp-check
    option log-health-checks
    timeout client 3h
    timeout server 3h
    balance roundrobin
    server k8s-node1 192.168.10.11:6443 check check-ssl verify none inter 10000
    server k8s-node2 192.168.10.12:6443 check check-ssl verify none inter 10000
    server k8s-node3 192.168.10.13:6443 check check-ssl verify none inter 10000

# ---------------------------------------------------------------------
# HAProxy Stats Dashboard - http://192.168.10.10:9000/haproxy_stats
# ---------------------------------------------------------------------
listen stats
    bind *:9000
    mode http
    stats enable
    stats uri /haproxy_stats
    stats realm HAProxy\ Statistic
    stats admin if TRUE

# ---------------------------------------------------------------------
# Configure the Prometheus exporter - curl http://192.168.10.10:8405/metrics
# ---------------------------------------------------------------------
frontend prometheus
    bind *:8405
    mode http
    http-request use-service prometheus-exporter if { path /metrics }
    no log
http://192.168.10.10:9000/haproxy_stats

 

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

 

ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml -e kube_version="1.32.9" | tee kubespray_install.log
...
Sunday 08 February 2026  00:43:34 +0900 (0:00:00.043)       0:05:33.369 *******
===============================================================================
kubernetes/kubeadm : Join to cluster if needed ------------------------- 16.06s
kubernetes/control-plane : Joining control plane node to the cluster. -- 14.54s
download : Download_container | Download image if required ------------- 11.63s
download : Download_container | Download image if required ------------- 11.35s
kubernetes/control-plane : Kubeadm | Initialize first control plane node (1st try) --- 7.24s
download : Download_container | Download image if required -------------- 7.20s
download : Download_file | Download item -------------------------------- 6.75s
download : Download_container | Download image if required -------------- 6.66s
download : Download_container | Download image if required -------------- 6.41s
download : Download_file | Download item -------------------------------- 6.23s
system_packages : Manage packages --------------------------------------- 5.71s
container-engine/containerd : Download_file | Download item ------------- 5.61s
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence --- 5.49s
etcd : Restart etcd ----------------------------------------------------- 5.41s
download : Download_container | Download image if required -------------- 5.21s
download : Download_container | Download image if required -------------- 5.17s
etcd : Configure | Check if etcd cluster is healthy --------------------- 5.15s
container-engine/crictl : Download_file | Download item ----------------- 4.59s
etcd : Gen_certs | Write etcd member/admin and kube_control_plane client certs to other etcd nodes --- 4.56s
container-engine/runc : Download_file | Download item ------------------- 4.48s

 

์•ค์„œ๋ธ” ๋ฐฐํฌ ์‹œ ์•ฝ 10๋ถ„ ์ •๋„ ์‹œ๊ฐ„์ด ์†Œ์š”๋œ๋‹ค.

 

api server 

root@admin-lb:~/kubespray# kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
E0208 01:32:25.680343   15124 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"http://localhost:8080/api?timeout=32s\": dial tcp [::1]:8080: connect: connection refused"

 

๋ฐฐํฌ ์งํ›„ kubeconfig๋ฅผ admin-lb ๋…ธ๋“œ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ด์ค€๋‹ค.

์œ„๋Š” ์ง€๊ธˆ ์„ค์ • ์•ˆํ•ด์ฃผ์–ด์„œ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๊ฐ€ ๋œฌ ์ƒํ™ฉ

 

root@admin-lb:~/kubespray# mkdir /root/.kube
scp k8s-node1:/root/.kube/config /root/.kube/
cat /root/.kube/config | grep server
config                             100% 5665     5.4MB/s   00:00
    server: https://127.0.0.1:6443
    
    
root@admin-lb:~/kubespray# sed -i 's/127.0.0.1/192.168.10.11/g' /root/.kube/config

 

control-plane ๋กœ์ปฌ ์ „์šฉ kubeconfig๋ฅผ ์™ธ๋ถ€ ๊ด€๋ฆฌ ๋…ธ๋“œ(admin-lb)์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก API Server endpoint๋ฅผ ์‹ค์ œ ๋„คํŠธ์›Œํฌ ์ฃผ์†Œ๋กœ ๊ต์ฒดํ•œ๋‹ค.

 

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

 

etcd ํ™•์ธ

root@admin-lb:~/kubespray# 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 |
+------------------+---------+-------+----------------------------+----------------------------+------------+
root@admin-lb:~/kubespray# 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.4 MB |      true |      false |         5 |      11188 |              11188 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> k8s-node2 <<
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | 2106626b12a4099f |  3.5.25 |  6.5 MB |     false |      false |         5 |      11189 |              11189 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> k8s-node3 <<
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |       ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | 8b0ca30665374b0 |  3.5.25 |  6.4 MB |     false |      false |         5 |      11189 |              11189 |        |
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

 

์ž๋™ ์™„์„ฑ ๋ฐ ๋‹จ์ถ•์–ด ์„ค์ •

source <(kubectl completion bash)
alias k=kubectl
alias kc=kubecolor
complete -F __start_kubectl k
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'alias k=kubectl' >> /etc/profile
echo 'alias kc=kubecolor' >> /etc/profile
echo 'complete -F __start_kubectl k' >> /etc/profile

 

 

control ์ปดํฌ๋„ŒํŠธ ํ™•์ธ

kube-apiserver.yaml

root@admin-lb:~/kubespray# ssh k8s-node1 cat /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=192.168.10.11
    - --allow-privileged=true
    - --anonymous-auth=True
    - --apiserver-count=3
    - --authorization-mode=Node,RBAC
    - '--bind-address=::'
    - --client-ca-file=/etc/kubernetes/ssl/ca.crt
    - --default-not-ready-toleration-seconds=300
    - --default-unreachable-toleration-seconds=300
    - --enable-admission-plugins=NodeRestriction
    - --enable-aggregator-routing=False
    - --enable-bootstrap-token-auth=true
    - --endpoint-reconciler-type=lease
    - --etcd-cafile=/etc/ssl/etcd/ssl/ca.pem
    - --etcd-certfile=/etc/ssl/etcd/ssl/node-k8s-node1.pem
    - --etcd-compaction-interval=5m0s
    - --etcd-keyfile=/etc/ssl/etcd/ssl/node-k8s-node1-key.pem
    - --etcd-servers=https://192.168.10.11:2379,https://192.168.10.12:2379,https://192.168.10.13:2379

 

kube-controller-manager

root@admin-lb:~/kubespray# ssh k8s-node1 cat /etc/kubernetes/manifests/kube-controller-manager.yaml
spec:
  containers:
  - command:
    - kube-controller-manager
    - --allocate-node-cidrs=true
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - '--bind-address=::'
    - --client-ca-file=/etc/kubernetes/ssl/ca.crt
    - --cluster-cidr=10.233.64.0/18
    - --cluster-name=cluster.local
    - --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.crt
    - --cluster-signing-key-file=/etc/kubernetes/ssl/ca.key
    - --configure-cloud-routes=false
    - --controllers=*,bootstrapsigner,tokencleaner
    - --kubeconfig=/etc/kubernetes/controller-manager.conf
    - --leader-elect=true
    - --leader-elect-lease-duration=15s
    - --leader-elect-renew-deadline=10s
    - --node-cidr-mask-size-ipv4=24
    - --node-monitor-grace-period=40s
    - --node-monitor-period=5s
    - --profiling=False
    - --requestheader-client-ca-file=/etc/kubernetes/ssl/front-proxy-ca.crt
    - --root-ca-file=/etc/kubernetes/ssl/ca.crt
    - --service-account-private-key-file=/etc/kubernetes/ssl/sa.key
    - --service-cluster-ip-range=10.233.0.0/18
    - --terminated-pod-gc-threshold=12500
    - --use-service-account-credentials=true

 

kube-scheduler

root@admin-lb:~/kubespray# ssh k8s-node1 cat /etc/kubernetes/manifests/kube-scheduler.yaml
spec:
  containers:
  - command:
    - kube-controller-manager
    - --allocate-node-cidrs=true
    - --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
    - --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
    - '--bind-address=::'
    - --client-ca-file=/etc/kubernetes/ssl/ca.crt
    - --cluster-cidr=10.233.64.0/18
    - --cluster-name=cluster.local
    - --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.crt
    - --cluster-signing-key-file=/etc/kubernetes/ssl/ca.key
    - --configure-cloud-routes=false
    - --controllers=*,bootstrapsigner,tokencleaner
    - --kubeconfig=/etc/kubernetes/controller-manager.conf
    - --leader-elect=true
    - --leader-elect-lease-duration=15s
    - --leader-elect-renew-deadline=10s
    - --node-cidr-mask-size-ipv4=24
    - --node-monitor-grace-period=40s
    - --node-monitor-period=5s
    - --profiling=False
    - --requestheader-client-ca-file=/etc/kubernetes/ssl/front-proxy-ca.crt
    - --root-ca-file=/etc/kubernetes/ssl/ca.crt
    - --service-account-private-key-file=/etc/kubernetes/ssl/sa.key
    - --service-cluster-ip-range=10.233.0.0/18
    - --terminated-pod-gc-threshold=12500
    - --use-service-account-credentials=true

 

 

์ธ์ฆ์„œ ์ •๋ณด ํ™•์ธ

์ปจํŠธ๋กค ํ”Œ๋ ˆ์ธ์€ 3๋Œ€์ธ๋ฐ ์ธ์ฆ์„œ ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋ฉด 1๋ฒˆ ๋…ธ๋“œ์—๋งŒ super-admin.conf๊ฐ€ ์žˆ๋Š”๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

root@admin-lb:~/kubespray# for i in {1..3}; do echo ">> k8s-node$i <<"; ssh k8s-node$i ls -l /etc/kubernetes/super-admin.conf ; echo; done
>> k8s-node1 <<
-rw-------. 1 root root 5689 Feb  8 00:42 /etc/kubernetes/super-admin.conf

>> k8s-node2 <<
ls: cannot access '/etc/kubernetes/super-admin.conf': No such file or directory

>> k8s-node3 <<
ls: cannot access '/etc/kubernetes/super-admin.conf': No such file or directory

 

 

  • k8s-node1
    • /etc/kubernetes/super-admin.conf ํŒŒ์ผ์ด ์กด์žฌ
    • root ์ „์šฉ ๊ถŒํ•œ(600)์œผ๋กœ ์ƒ์„ฑ๋จ
  • k8s-node2, k8s-node3
    • ๋™์ผ ๊ฒฝ๋กœ์— ํ•ด๋‹น ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ

/etc/kubernetes/super-admin.conf๋Š” Kubespray๊ฐ€ ์ƒ์„ฑํ•˜๋Š” superuser kubeconfig ํŒŒ์ผ๋กœ Kubernetes API์— ๋Œ€ํ•ด cluster-admin ์ˆ˜์ค€ ์ด์ƒ์˜ ๊ถŒํ•œ์„ ๊ฐ€์ง€๋ฉฐ, ์ผ๋ฐ˜์ ์ธ admin.conf๋ณด๋‹ค ์ž๋™ํ™”, bootstrap, ๋‚ด๋ถ€ ์ œ์–ด์šฉ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ์ž๋Š” system:masters ๊ทธ๋ฃน์— ์†ํ•œ๋‹ค.

 

Kubespray๋Š” ๋‹ค์ค‘ control-plane ํ™˜๊ฒฝ์—์„œ๋„ ๋ชจ๋“  ๊ด€๋ฆฌ์šฉ kubeconfig๋ฅผ ๋ชจ๋“  ๋…ธ๋“œ์— ๋ฟŒ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  control-plane ์ค‘ ํ•˜๋‚˜์˜ ๋…ธ๋“œ(primary, first master) ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ผ๊ณ  ์•„๋ž˜ ํŒŒ์ผ๋“ค์„ ํ•ด๋‹น ๋…ธ๋“œ์—๋งŒ ์ƒ์„ฑํ•œ๋‹ค.

 

  • /etc/kubernetes/admin.conf
  • /etc/kubernetes/super-admin.conf
  • /root/.kube/config

 

coredns ํ™•์ธ

root@admin-lb:~/kubespray# for i in {1..3}; do echo ">> k8s-node$i <<"; ssh k8s-node$i kubeadm certs check-expiration ; echo; done
>> k8s-node1 <<
[check-expiration] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[check-expiration] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.

W0208 02:19:43.293312   51059 utils.go:69] The recommended value for "clusterDNS" in "KubeletConfiguration" is: [10.233.0.10]; the provided value is: [10.233.0.3]
CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                 Feb 07, 2027 15:42 UTC   364d            ca                      no
apiserver                  Feb 07, 2027 15:42 UTC   364d            ca                      no
apiserver-kubelet-client   Feb 07, 2027 15:42 UTC   364d            ca                      no
controller-manager.conf    Feb 07, 2027 15:42 UTC   364d            ca                      no
front-proxy-client         Feb 07, 2027 15:42 UTC   364d            front-proxy-ca          no
scheduler.conf             Feb 07, 2027 15:42 UTC   364d            ca                      no
super-admin.conf           Feb 07, 2027 15:42 UTC   364d            ca                      no

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Feb 05, 2036 15:42 UTC   9y              no
front-proxy-ca          Feb 05, 2036 15:42 UTC   9y              no

>> k8s-node2 <<
[check-expiration] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[check-expiration] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.

W0208 02:19:43.751491   50100 utils.go:69] The recommended value for "clusterDNS" in "KubeletConfiguration" is: [10.233.0.10]; the provided value is: [10.233.0.3]
CERTIFICATE                  EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                   Feb 07, 2027 15:42 UTC   364d            ca                      no
apiserver                    Feb 07, 2027 15:42 UTC   364d            ca                      no
apiserver-kubelet-client     Feb 07, 2027 15:42 UTC   364d            ca                      no
controller-manager.conf      Feb 07, 2027 15:42 UTC   364d            ca                      no
front-proxy-client           Feb 07, 2027 15:42 UTC   364d            front-proxy-ca          no
scheduler.conf               Feb 07, 2027 15:42 UTC   364d            ca                      no
!MISSING! super-admin.conf

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Feb 05, 2036 15:42 UTC   9y              no
front-proxy-ca          Feb 05, 2036 15:42 UTC   9y              no

>> k8s-node3 <<
[check-expiration] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[check-expiration] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.

W0208 02:19:44.231769   50364 utils.go:69] The recommended value for "clusterDNS" in "KubeletConfiguration" is: [10.233.0.10]; the provided value is: [10.233.0.3]
CERTIFICATE                  EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY   EXTERNALLY MANAGED
admin.conf                   Feb 07, 2027 15:42 UTC   364d            ca                      no
apiserver                    Feb 07, 2027 15:42 UTC   364d            ca                      no
apiserver-kubelet-client     Feb 07, 2027 15:42 UTC   364d            ca                      no
controller-manager.conf      Feb 07, 2027 15:42 UTC   364d            ca                      no
front-proxy-client           Feb 07, 2027 15:42 UTC   364d            front-proxy-ca          no
scheduler.conf               Feb 07, 2027 15:42 UTC   364d            ca                      no
!MISSING! super-admin.conf

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
ca                      Feb 05, 2036 15:42 UTC   9y              no
front-proxy-ca          Feb 05, 2036 15:42 UTC   9y              no

 

 

 

 

๊ฐ control-plane ๋…ธ๋“œ๋Š” ํด๋Ÿฌ์Šคํ„ฐ ์ „์—ญ ์„ค์ •์„ ๊ณต์œ ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ธ์ฆ์„œ ์—ญ์‹œ ๋™์ผํ•œ CA ์ฒด๊ณ„ ํ•˜์—์„œ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋‹ค.

๊ตฌ์„ฑ๋œ ์ธ์ฆ์„œ์˜ ์ •๋ณด๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

  • ๋งŒ๋ฃŒ ์‹œ์ : 2027-02-07
  • ์ž”์—ฌ ๊ธฐ๊ฐ„: ์•ฝ 364์ผ
  • CA: ca ๋˜๋Š” front-proxy-ca
  • externally managed: no

 

2, 3๋ฒˆ ๋…ธ๋“œ์—์„œ ๋ณด์ด๋Š” !MISSING! super-admin.conf ๋ฉ”์„ธ์ง€๋Š” 1๋ฒˆ ๋…ธ๋“œ๊ฐ€ primary ๋…ธ๋“œ์ž„์„ ๋ณด์—ฌ์ฃผ๋Š” ๋‚ด์šฉ์ด๋‹ค.

 

kubeadm์ด ๊ถŒ์žฅํ•˜๋Š” clusterDNS ์ฃผ์†Œ๋Š” Service CIDR์˜ ์ฒซ ๋ฒˆ์งธ IP (๊ธฐ๋ณธ์ ์œผ๋กœ .10)์ธ๋ฐ ํ˜„์žฌ kubelet ์„ค์ •์—๋Š” 10.233.0.3 ์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค. Kubespray ํ™˜๊ฒฝ์—์„œ๋Š” CoreDNS Service IP๋ฅผ .3 ๋˜๋Š” .10 ๋“ฑ์œผ๋กœ ๋ผ์žˆ๋‹ค.

์ด๋Š” ๊ธฐ์กด ํด๋Ÿฌ์Šคํ„ฐ ํ˜ธํ™˜์„ฑ์„ ํ™•๋ณดํ•œ ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ ์„ค๊ณ„๋œ ๊ฒƒ์ด๋ฉฐ, ๊ณผ๊ฑฐ kube-dns ๊ตฌ์„ฑ๊ณผ์˜ ์—ฐ์†์„ฑ์„ ์œ„ํ•ด ์˜๋„์ ์œผ๋กœ ์กฐ์ •๋˜์—ˆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

root@admin-lb:~/kubespray# kubectl get svc -n kube-system coredns
NAME      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
coredns   ClusterIP   10.233.0.3   <none>        53/UDP,53/TCP,9153/TCP   102m


root@admin-lb:~/kubespray# kubectl get cm -n kube-system kubelet-config -o yaml | grep clusterDNS -A2
    clusterDNS:
    - 10.233.0.3
    clusterDomain: cluster.local

 

 

coredns ๋Š” Kubernetes ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€ DNS๋ฅผ ๋‹ด๋‹นํ•˜๋Š” Service์ด๋ฉฐ, Service ํƒ€์ž…์€ ClusterIP์ด๊ณ  ํด๋Ÿฌ์Šคํ„ฐ ๋‚ด๋ถ€์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ฐ€์ƒ IP๋ฅผ ๊ฐ€์ง„๋‹ค. ์ด ClusterIP๊ฐ€ ๋ฐ”๋กœ 10.233.0.3 ์ด๋‹ค

 

53/UDP, 53/TCP ํฌํŠธ๋Š” ์ผ๋ฐ˜์ ์ธ DNS ์งˆ์˜๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ , 9153/TCP๋Š” Prometheus metrics ์šฉ ํฌํŠธ๋กœ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜์—ฌ CoreDNS๊ฐ€ DNS ์„œ๋ฒ„ + ๊ด€์ธก ๋Œ€์ƒ ์ปดํฌ๋„ŒํŠธ ์—ญํ• ์„ ๋™์‹œ์— ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ์Œ์„ ๋ณด์—ฌ์ค€๋‹ค.

 

cluster ip ํ™•์ธ

root@admin-lb:~/kubespray# kubectl exec -it -n kube-system nginx-proxy-k8s-node4 -- cat /etc/resolv.conf
search kube-system.svc.cluster.local svc.cluster.local cluster.local default.svc.cluster.local
nameserver 10.233.0.3
options ndots:5

 

 

์ด ํด๋Ÿฌ์Šคํ„ฐ์—์„œ CoreDNS๋Š” 10.233.0.3 ์ฃผ์†Œ๋กœ ์„œ๋น„์Šค ๋˜๊ณ  ์žˆ๋‹ค.

ํ•ด๋‹น nginx ํŒŒ๋“œ์˜ ๋ชจ๋“  DNS ์งˆ์˜๋Š” 10.233.0.3 ์œผ๋กœ ์ „๋‹ฌ๋˜๊ณ , ์ด IP๋Š” CoreDNS Service์˜ ClusterIP ์ด๋‹ค.

 

 

์กฐ์ธ๋œ ๋…ธ๋“œ์™€ ์กฐ์ธ๋˜์ง€ ์•Š์€ ๋…ธ๋“œ dns ์„ค์ • ๋น„๊ต

root@admin-lb:~/kubespray# ssh k8s-node1 cat /etc/resolv.conf
# Generated by NetworkManager
search default.svc.cluster.local svc.cluster.local
nameserver 10.233.0.3
nameserver 168.126.63.1
nameserver 168.126.63.2
options ndots:2 timeout:2 attempts:2


root@admin-lb:~/kubespray# ssh k8s-node5 cat /etc/resolv.conf
# Generated by NetworkManager
search lan
nameserver 168.126.63.1
nameserver 168.126.63.2

 

์กฐ์ธ๋˜์ง€ ์•Š์€ 5๋ฒˆ ๋…ธ๋“œ๋Š” ๊ธฐ๋ณธ dns ์„ค์ •์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

 

kubeadm-config cm ์ •๋ณด ํ™•์ธ

Control Plane Endpoint

controlPlaneEndpoint: 192.168.10.11:6443

 

์ด ํ•ญ๋ชฉ์€ ํด๋Ÿฌ์Šคํ„ฐ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•  ๋•Œ์˜ kube-apiserver ์ง„์ž…์ ์„ ์˜๋ฏธํ•œ๋‹ค.

  • ํ˜„์žฌ ์„ค์ •: 192.168.10.11:6443
  • LB VIP ๋˜๋Š” ๋Œ€ํ‘œ control-plane ๋…ธ๋“œ ์ฃผ์†Œ๋กœ ์‚ฌ์šฉ ์ค‘
  • ์•ž์„œ admin-lb์—์„œ kubeconfig์˜ server: ๊ฐ’์„ ์ด ์ฃผ์†Œ๋กœ ์ˆ˜์ •ํ•œ ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•œ๋‹ค

 

API Server ์ธ์ฆ์„œ SAN ๊ตฌ์„ฑ

apiserver-count: "3"
authorization-mode: Node,RBAC
bind-address: '::'
service-cluster-ip-range: 10.233.0.0/18
 

kube-apiserver TLS ์ธ์ฆ์„œ๊ฐ€ ์‹ ๋ขฐํ•˜๋Š” ์ ‘์† ๋Œ€์ƒ ๋ชฉ๋ก์ด๋‹ค.

  • ์„œ๋น„์Šค DNS ์ด๋ฆ„ : kubernetes.default.svc.cluster.local
  • Service ClusterIP : 10.233.0.1
  • ๋กœ์ปฌ ์ ‘๊ทผ : 127.0.0.1, ::1
  • control-plane ๋…ธ๋“œ ์ด๋ฆ„ : k8s-node1~3
  • LB DNS / IP
    • lb-apiserver.kubernetes.local
    • 192.168.10.11~13

์ด ๊ตฌ์„ฑ ๋•๋ถ„์œผ๋กœ Pod ๋‚ด๋ถ€, control-plane ๋…ธ๋“œ, admin-lb, LB ๊ฒฝ์œ  ์ ‘๊ทผ ๋ชจ๋‘ tls ์˜ค๋ฅ˜ ์—†์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋œ๋‹ค.

 

API Server ์ฃผ์š” extraArgs

apiserver-count: "3"
authorization-mode: Node,RBAC
bind-address: '::'
service-cluster-ip-range: 10.233.0.0/18
 
  • apiserver-count: 3 : control-plane ๋…ธ๋“œ๊ฐ€ 3๋Œ€์ž„์„ ๋ช…์‹œ
  • authorization-mode: Node,RBAC : Kubernetes ํ‘œ์ค€ ๊ถŒํ•œ ๋ชจ๋ธ
  • bind-address: '::' : IPv4/IPv6 dual-stack ๋Œ€์‘
  • service-cluster-ip-range
    • Service CIDR = 10.233.0.0/18
    • CoreDNS Service IP(10.233.0.3)๊ฐ€ ์ด ๋ฒ”์œ„์— ํฌํ•จ

 

Networking ์„ค์ •

 
networking:
  dnsDomain: cluster.local
  podSubnet: 10.233.64.0/18
  serviceSubnet: 10.233.0.0/18
  • Service ๋„คํŠธ์›Œํฌ : 10.233.0.0/18
  • Pod ๋„คํŠธ์›Œํฌ : 10.233.64.0/18
  • DNS ๋„๋ฉ”์ธ : cluster.local

์•ž์—์„œ ํ™•์ธํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • CoreDNS Service IP = 10.233.0.3
  • kubelet clusterDNS = 10.233.0.3
  • Pod /etc/resolv.conf nameserver = 10.233.0.3

 

DNS ์„น์…˜: disabled: true์˜ ์˜๋ฏธ

dns:
  disabled: true

์ด ๋ถ€๋ถ„์€ kubeadm ๊ธฐ๋ณธ DNS ์„ค์น˜๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•œ๋‹ค๋Š” ์˜๋ฏธ๋‹ค.

kubeadm ์ž์ฒด์˜ CoreDNS addon์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋Œ€์‹  Kubespray๊ฐ€ Helm/Manifest ๊ธฐ๋ฐ˜์œผ๋กœ CoreDNS๋ฅผ ๋ณ„๋„ ๋ฐฐํฌ·๊ด€๋ฆฌํ•˜์—ฌ DNS ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

 

etcd ๊ตฌ์„ฑ (External etcd)

etcd:
  external:
    endpoints:
    - https://192.168.10.11:2379
    - https://192.168.10.12:2379
    - https://192.168.10.13:2379

 

control-plane static pod๊ฐ€ ์•„๋‹ˆ๋ผ ์™ธ๋ถ€(etcd cluster) 3๋…ธ๋“œ etcd HA ๊ตฌ์„ฑ๋˜์–ด ์žˆ์œผ๋ฉฐ TLS ์ธ์ฆ์„œ ๊ธฐ๋ฐ˜์œผ๋กœ ์ ‘๊ทผํ•œ๋‹ค.

์ด๋Š” control-plane๊ณผ etcd์˜ ์—ญํ• ์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•œ ๊ตฌ์กฐ๋‹ค.

 

์ธ์ฆ์„œ ์œ ํšจ๊ธฐ๊ฐ„ ์„ค์ •

certificateValidityPeriod: 8760h0m0s
caCertificateValidityPeriod: 87600h0m0s

 

kubeadm certs check-expiration ๊ฒฐ๊ณผ์™€ ์ผ์น˜ํ•˜๋ฉฐ, ์ธ์ฆ์„œ ์ˆ˜๋ช… ์ฃผ๊ธฐ๊ฐ€ ์„ค์ • → ์‹ค์ œ ์ƒํƒœ๊นŒ์ง€ ์ผ๊ด€๋˜๊ฒŒ ์œ ์ง€๋˜๊ณ  ์žˆ๋‹ค.

  • ์ผ๋ฐ˜ ์ธ์ฆ์„œ: ์•ฝ 1๋…„
  • CA ์ธ์ฆ์„œ: ์•ฝ 10๋…„

 

K8S API ์—”๋“œํฌ์ธํŠธ

 

 

Worker ๋…ธ๋“œ์—์„œ Kubernetes API์— ์ ‘๊ทผํ•˜๋Š” ์‹ค์ œ ๊ฒฝ๋กœ์™€ Kubespray์˜ Client-Side Load Balancing ๊ตฌ์กฐ๋ฅผ ์•Œ์•„๋ณธ๋‹ค.

 

์›Œ์ปค๋…ธ๋“œ ์ •๋ณด ํ™•์ธ

root@admin-lb:~/kubespray# ssh k8s-node4 crictl ps
CONTAINER           IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID              POD                               NAMESPACE
468e938c98d5c       bc6c1e09a843d       2 hours ago         Running             metrics-server      0                   09ff933093700       metrics-server-65fdf69dcb-jnvbd   kube-system
1b68d0c87c769       2f6c962e7b831       2 hours ago         Running             coredns             0                   b0027b8776163       coredns-664b99d7c7-5drtx          kube-system
37d48e61e4df1       cadcae92e6360       2 hours ago         Running             kube-flannel        0                   c8cdc9b9aff8e       kube-flannel-ds-arm64-wkwrg       kube-system
8117e271262f3       72b57ec14d31e       2 hours ago         Running             kube-proxy          0                   c02b63ef148dc       kube-proxy-bjqdx                  kube-system
6a22218ae0325       5a91d90f47ddf       2 hours ago         Running             nginx-proxy         0                   83316bf2859bd       nginx-proxy-k8s-node4             kube-system

 

nginx ์ปจํ”ผ๊ทธ

root@admin-lb:~/kubespray# ssh k8s-node4 cat /etc/nginx/nginx.conf
error_log stderr notice;

worker_processes 2;
worker_rlimit_nofile 130048;
worker_shutdown_timeout 10s;

events {
  multi_accept on;
  use epoll;
  worker_connections 16384;
}

stream {
  upstream kube_apiserver {
    least_conn;
    server 192.168.10.11:6443;
    server 192.168.10.12:6443;
    server 192.168.10.13:6443;
    }

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

http {
  aio threads;
  aio_write on;
  tcp_nopush on;
  tcp_nodelay on;

  keepalive_timeout 5m;
  keepalive_requests 100;
  reset_timedout_connection on;
  server_tokens off;
  autoindex off;

  server {
    listen 8081;
    location /healthz {
      access_log off;
      return 200;
    }
    location /stub_status {
      stub_status on;
      access_log off;
    }
  }
  }

 

 

nginx-proxy๋Š” Deployment๋‚˜ DaemonSet์ด ์•„๋‹ˆ๋ผ static pod๋กœ kubelet์ด /etc/kubernetes/manifests ํ•˜์œ„ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ๊ฐ์ง€ํ•ด ์ง์ ‘ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ๋œ๋‹ค.

 

nginx ์„ค์ •์€ worker ๋…ธ๋“œ๋Š” ํ•ญ์ƒ localhost๋งŒ ๋ฐ”๋ผ๋ณด๊ณ , ์‹ค์ œ control-plane ๋ถ„์‚ฐ์€ nginx๊ฐ€ ์ฑ…์ž„์ง„๋‹ค.

 kubelet / kube-proxy
        ↓
https://127.0.0.1:6443
        ↓
nginx-proxy (local)
        ↓
192.168.10.11~13:6443 (control-plane)

 

 

nginx ์ปจํ”ผ๊ทธ๋Š” Jinja2 ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋…ธ๋“œ๋ณ„ nginx.conf ์ž๋™ ์ƒ์„ฑ๋œ๋‹ค.

 

root@admin-lb:~/kubespray# tree roles/kubernetes/node/tasks/loadbalancer
roles/kubernetes/node/tasks/loadbalancer
โ”œโ”€โ”€ haproxy.yml
โ”œโ”€โ”€ kube-vip.yml
โ””โ”€โ”€ nginx-proxy.yml

1 directory, 3 files
root@admin-lb:~/kubespray# cat roles/kubernetes/node/tasks/loadbalancer/nginx-proxy.yml
---
- name: Haproxy | Cleanup potentially deployed haproxy
  file:
    path: "{{ kube_manifest_dir }}/haproxy.yml"
    state: absent

- name: Nginx-proxy | Make nginx directory
  file:
    path: "{{ nginx_config_dir }}"
    state: directory
    mode: "0700"
    owner: root

- name: Nginx-proxy | Write nginx-proxy configuration
  template:
    src: "loadbalancer/nginx.conf.j2"
    dest: "{{ nginx_config_dir }}/nginx.conf"
    owner: root
    mode: "0755"
    backup: true

- name: Nginx-proxy | Get checksum from config
  stat:
    path: "{{ nginx_config_dir }}/nginx.conf"
    get_attributes: false
    get_checksum: true
    get_mime: false
  register: nginx_stat

- name: Nginx-proxy | Write static pod
  template:
    src: manifests/nginx-proxy.manifest.j2
    dest: "{{ kube_manifest_dir }}/nginx-proxy.yml"
    mode: "0640"
root@admin-lb:~/kubespray# cat roles/kubernetes/node/templates/loadbalancer/nginx.conf.j2
error_log stderr notice;

worker_processes 2;
worker_rlimit_nofile 130048;
worker_shutdown_timeout 10s;

events {
  multi_accept on;
  use epoll;
  worker_connections 16384;
}

stream {
  upstream kube_apiserver {
    least_conn;
    {% for host in groups['kube_control_plane'] -%}
    server {{ hostvars[host]['main_access_ip'] | ansible.utils.ipwrap }}:{{ kube_apiserver_port }};
    {% endfor -%}
  }

  server {
    listen        127.0.0.1:{{ loadbalancer_apiserver_port|default(kube_apiserver_port) }};
    {% if ipv6_stack -%}
    listen        [::1]:{{ loadbalancer_apiserver_port|default(kube_apiserver_port) }};
    {% endif -%}
    proxy_pass    kube_apiserver;
    proxy_timeout 10m;
    proxy_connect_timeout 1s;
  }
}

http {
  aio threads;
  aio_write on;
  tcp_nopush on;
  tcp_nodelay on;

  keepalive_timeout {{ loadbalancer_apiserver_keepalive_timeout }};
  keepalive_requests 100;
  reset_timedout_connection on;
  server_tokens off;
  autoindex off;

  {% if loadbalancer_apiserver_healthcheck_port is defined -%}
  server {
    listen {{ loadbalancer_apiserver_healthcheck_port }};
    {% if ipv6_stack -%}
    listen [::]:{{ loadbalancer_apiserver_healthcheck_port }};
    {% endif -%}
    location /healthz {
      access_log off;
      return 200;
    }
    location /stub_status {
      stub_status on;
      access_log off;
    }
  }
  {% endif %}
}
root@admin-lb:~/kubespray# cat roles/kubernetes/node/templates/manifests/nginx-proxy.manifest.j2
apiVersion: v1
kind: Pod
metadata:
  name: {{ loadbalancer_apiserver_pod_name }}
  namespace: kube-system
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    k8s-app: kube-nginx
  annotations:
    nginx-cfg-checksum: "{{ nginx_stat.stat.checksum }}"
spec:
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet
  nodeSelector:
    kubernetes.io/os: linux
  priorityClassName: system-node-critical
  containers:
  - name: nginx-proxy
    image: {{ nginx_image_repo }}:{{ nginx_image_tag }}
    imagePullPolicy: {{ k8s_image_pull_policy }}
    resources:
      requests:
        cpu: {{ loadbalancer_apiserver_cpu_requests }}
        memory: {{ loadbalancer_apiserver_memory_requests }}
    {% if loadbalancer_apiserver_healthcheck_port is defined -%}
    livenessProbe:
      httpGet:
        path: /healthz
        port: {{ loadbalancer_apiserver_healthcheck_port }}
    readinessProbe:
      httpGet:
        path: /healthz
        port: {{ loadbalancer_apiserver_healthcheck_port }}
    {% endif -%}
    volumeMounts:
    - mountPath: /etc/nginx
      name: etc-nginx
      readOnly: true
  volumes:
  - name: etc-nginx
    hostPath:
      path: {{ nginx_config_dir }}

 

api ํ˜ธ์ถœ ๊ฒ€์ฆ

root@admin-lb:~/kubespray# ssh k8s-node4 curl -s localhost:8081/healthz -I
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 07 Feb 2026 18:12:23 GMT
Content-Type: text/plain
Content-Length: 0
Connection: keep-alive

 

root@admin-lb:~/kubespray# ssh k8s-node4 curl -sk https://127.0.0.1:6443/version | grep Version
  "gitVersion": "v1.32.9",
  "goVersion": "go1.23.12",

root@admin-lb:~/kubespray# ssh k8s-node4 ss -tnlp | grep nginx
LISTEN 0      511          0.0.0.0:8081       0.0.0.0:*    users:(("nginx",pid=17561,fd=6),("nginx",pid=17560,fd=6),("nginx",pid=17532,fd=6))
LISTEN 0      511        127.0.0.1:6443       0.0.0.0:*    users:(("nginx",pid=17561,fd=5),("nginx",pid=17560,fd=5),("nginx",pid=17532,fd=5))

 

worker ๋…ธ๋“œ์—์„œ localhost(127.0.0.1)๋กœ ์š”์ฒญํ–ˆ์ง€๋งŒ ์‹ค์ œ ์‘๋‹ต์€ control-plane API Server๊ฐ€ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด๋Š” nginx-proxy๋ฅผ ํ†ตํ•œ Client-Side LB๊ฐ€ ์ •์ƒ ๋™์ž‘ ์ค‘์ž„์„ ์ฆ๋ช…ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

kubelet์ด ๋ฐ”๋ผ๋ณด๋Š” ์—”๋“œํฌ์ธํŠธ

root@admin-lb:~/kubespray# ssh k8s-node4 cat /etc/kubernetes/kubelet.conf | grep server
    server: https://localhost:6443

 

kube-proxy ๊ตฌ์กฐ

root@admin-lb:~/kubespray# kubectl get cm -n kube-system kube-proxy -o yaml | grep 'kubeconfig.conf:' -A18
  kubeconfig.conf: |-
    apiVersion: v1
    kind: Config
    clusters:
    - cluster:
        certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
        server: https://127.0.0.1:6443
      name: default
    contexts:
    - context:
        cluster: default
        namespace: default
        user: default
      name: default
    current-context: default
    users:
    - name: default
      user:
        tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token

 

 

nginx ๋กœ๊ทธ ์•Œ๋Ÿฟ์„ ํ•ด๊ฒฐํ•ด๋ณด๊ธฐ

root@admin-lb:~/kubespray# kubectl logs -n kube-system nginx-proxy-k8s-node4
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/02/07 15:42:53 [notice] 1#1: using the "epoll" event method
2026/02/07 15:42:53 [notice] 1#1: nginx/1.28.0
2026/02/07 15:42:53 [notice] 1#1: built by gcc 14.2.0 (Alpine 14.2.0)
2026/02/07 15:42:53 [notice] 1#1: OS: Linux 6.12.0-55.39.1.el10_0.aarch64
2026/02/07 15:42:53 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 65535:65535
2026/02/07 15:42:53 [notice] 1#1: start worker processes
2026/02/07 15:42:53 [notice] 1#1: start worker process 20
2026/02/07 15:42:53 [notice] 1#1: start worker process 21
2026/02/07 15:42:53 [alert] 20#20: setrlimit(RLIMIT_NOFILE, 130048) failed (1: Operation not permitted)
2026/02/07 15:42:53 [alert] 21#21: setrlimit(RLIMIT_NOFILE, 130048) failed (1: Operation not permitted)
2026/02/07 15:43:33 [error] 20#20: *37 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.10.12:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489
2026/02/07 15:43:33 [error] 21#21: *31 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.10.12:6443", bytes from/to client:1489/0, bytes from/to upstream:0/1489

 

nginx ์„ค์ •์—์„œ๋Š” ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ ํ•œ๊ณ„๋ฅผ 130048๋กœ ์˜ฌ๋ฆฌ๋ ค ํ–ˆ์œผ๋‚˜, ์‹ค์ œ ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„ ํ™˜๊ฒฝ์—์„œ๋Š” ์ด๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š์•„ ์‹คํŒจํ•œ ๊ฒƒ์ด๋‹ค.

 

๋…ธ๋“œ4๋ฒˆ containerd ์„ค์ • ํ™•์ธ

root@admin-lb:~/kubespray# ssh k8s-node4 cat /etc/containerd/cri-base.json | jq | grep rlimits -A 6
    "rlimits": [
      {
        "type": "RLIMIT_NOFILE",
        "hard": 65535,
        "soft": 65535
      }
    ],

 

containerd๊ฐ€ OCI Runtime Spec ๋‹จ๊ณ„์—์„œ ์ด๋ฏธ RLIMIT_NOFILE์„ 65535๋กœ ๊ณ ์ •๋ผ์„œ nginx ๋‚ด๋ถ€ ์„ค์ •(worker_rlimit_nofile 130048)์€ ๋Ÿฐํƒ€์ž„ ๋ ˆ๋ฒจ์—์„œ ์ฐจ๋‹จ๋  ์ˆ˜๋ฐ–์— ์—†๋Š” ๊ตฌ์กฐ์ธ ์ƒํƒœ์ด๋‹ค.

 

root@admin-lb:~/kubespray# ssh k8s-node4 crictl inspect --name nginx-proxy | grep rlimits -A6
        "rlimits": [
          {
            "hard": 65535,
            "soft": 65535,
            "type": "RLIMIT_NOFILE"
          }
        ],

 

Kubespray๋Š” containerd ์„ค์น˜ ์‹œ containerd_base_runtime_spec_rlimit_nofile: 65535 ๋ฅผ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ด ์„ค์ •์€ ๊ณผ๋„ํ•œ FD ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ ๋…ธ๋“œ ์ž์› ๊ณ ๊ฐˆ์„ ๋ฐฉ์ง€ํ•˜๊ณ , ๋Œ€๋ถ€๋ถ„์˜ ์›Œํฌ๋กœ๋“œ์— ์ถฉ๋ถ„ํ•œ ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

ํ•˜์ง€๋งŒ nginx-proxy์ฒ˜๋Ÿผ ์ปค๋„ฅ์…˜ ์ˆ˜๊ฐ€ ๋งŽ์€ ์ธํ”„๋ผ ์ปดํฌ๋„ŒํŠธ์—๋Š” ์ด ์ œํ•œ์ด nginx ์„ค์ •๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ๋‹ค.

 

OCI Spec ์ˆ˜์ •ํ•˜์—ฌ ์•ค์„œ๋ธ” ํ”Œ๋ ˆ์ด๋ถ ์ˆ˜ํ–‰

root@admin-lb:~/kubespray# cat << EOF >> inventory/mycluster/group_vars/all/containerd.yml
containerd_default_base_runtime_spec_patch:
  process:
    rlimits: []
EOF
grep "^[^#]" inventory/mycluster/group_vars/all/containerd.yml
---
containerd_default_base_runtime_spec_patch:
  process:
    rlimits: []

 

root@k8s-node4:~# journalctl -u containerd.service -f
ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml --tags "containerd" --limit k8s-node4 -e kube_version="1.32.9"

 

 

๋ฐฐํฌํ•  ๋•Œ 4๋ฒˆ ๋…ธ๋“œ์—์„œ containerd ์„œ๋น„์Šค๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋ฉด์„œ ๋ณด๋ฉด containerd ์„œ๋น„์Šค๊ฐ€ ์žฌ์‹œ์ž‘๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

containerd ์žฌ์‹œ์ž‘ ์‹œ ํ•ด๋‹น ๋…ธ๋“œ์— ์Šค์ผ€์ค„ ๋œ ๋ชจ๋“  ํŒŒ๋“œ ์ƒํƒœ๊ฐ€ ์ผ์‹œ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค

  • NotReady
  • ContainerCreating
  • CrashLoopBackOff (์ผ๋ถ€ ์ผ€์ด์Šค)
  • static pod (nginx-proxy ๋“ฑ)์€ kubelet์ด ์ฆ‰์‹œ ์žฌ์ƒ์„ฑ

๊ทธ๋Ÿฌ๋ฏ€๋กœ ์‹ค์ œ ์ž‘์—… ์ „์—๋Š” ์•ค์„œ๋ธ” ํ”Œ๋ ˆ์ด๋ถ ์˜ํ–ฅ๋„๋ฅผ ๋ฒ ํƒ€ ํ™˜๊ฒฝ์—์„œ ๋ฏธ๋ฆฌ ํ™•์ธํ•˜๊ณ 

kubectl cordon k8s-node4
kubectl drain k8s-node4 --ignore-daemonsets --delete-emptydir-data

 

์•ˆ์ „ํ•˜๊ฒŒ ํŒŒ๋“œ๋ฅผ ๊ฒฉ๋ฆฌ์‹œํ‚ค๊ณ  ์ž‘์—… ๋Œ€์ƒ ๋…ธ๋“œ๋ฅผ drain ํ•œ ํ›„ ์ž‘์—…ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•˜๋‹ค!

 

 

ํ”Œ๋ ˆ์ด๋ถ ๋ฐฐํฌ ํ›„

root@admin-lb:~/kubespray# ssh k8s-node4 cat /etc/containerd/cri-base.json | jq | grep rlimits
    "rlimits": [],
root@admin-lb:~/kubespray# ssh k8s-node4 crictl inspect --name nginx-proxy | grep rlimits -A6
        "rlimits": [
          {
            "hard": 65535,
            "soft": 65535,
            "type": "RLIMIT_NOFILE"
          }
        ],

 

 

cri-base.json ์•ž์œผ๋กœ ์ƒ์„ฑ๋  ์ปจํ…Œ์ด๋„ˆ์— ์ ์šฉ๋˜๋Š” base runtime spec์œผ๋กœ ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ด๋˜ nginx-proxy Pod๋Š” ๊ธฐ์กด runtime spec์„ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜๋ฉฐ, containerd ์žฌ์‹œ์ž‘๋งŒ์œผ๋กœ๋Š” ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š”๋‹ค.

 

root@k8s-node4:~# crictl pods --namespace kube-system --name 'nginx-proxy-*' -q | xargs crictl rmp -f
Stopped sandbox 83316bf2859bd6177593dd4b04a662af5f14891602c4af42b887199638229e66
Removed sandbox 83316bf2859bd6177593dd4b04a662af5f14891602c4af42b887199638229e66

 

 

nginx-proxy๋Š” static pod๋กœ kubelet์ด /etc/kubernetes/manifests ๊ฐ์‹œ ์ค‘์ธ๋ฐ,

Pod๋ฅผ ์‚ญ์ œํ•˜์—ฌ kubelet์ด ์ฆ‰์‹œ ๋‹ค์‹œ ์ƒ์„ฑ๋˜๊ณ  ์ƒˆ๋กœ์šด OCI runtime spec ์ ์šฉ๋˜๊ฒŒ ํ•œ๋‹ค.

 

 

root@admin-lb:~/kubespray# ssh k8s-node4 crictl inspect --name nginx-proxy | grep rlimits -A6

 

root@admin-lb:~/kubespray# kubectl logs -n kube-system nginx-proxy-k8s-node4 -f
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
/docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/02/07 18:30:26 [notice] 1#1: using the "epoll" event method
2026/02/07 18:30:26 [notice] 1#1: nginx/1.28.0
2026/02/07 18:30:26 [notice] 1#1: built by gcc 14.2.0 (Alpine 14.2.0)
2026/02/07 18:30:26 [notice] 1#1: OS: Linux 6.12.0-55.39.1.el10_0.aarch64
2026/02/07 18:30:26 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2026/02/07 18:30:26 [notice] 1#1: start worker processes
2026/02/07 18:30:26 [notice] 1#1: start worker process 20
2026/02/07 18:30:26 [notice] 1#1: start worker process 21

 

containerd base runtime spec์—์„œ RLIMIT_NOFILE ์„ค์ •์„ ์ œ๊ฑฐํ•œ ๋’ค nginx-proxy static pod๋ฅผ ์žฌ๊ธฐ๋™ํ•จ์œผ๋กœ์จ, nginx๊ฐ€ ํ˜ธ์ŠคํŠธ ulimit์„ ๊ทธ๋Œ€๋กœ ์ƒ์†๋ฐ›์•„ nginx ๊ฒฝ๊ณ  ์—†์ด ์ •์ƒ ๋™์ž‘ํ•˜๋„๋ก ๊ตฌ์„ฑํ•˜์˜€๋‹ค.

 

 

 

์ปจํŠธ๋กค ํ”Œ๋ ˆ์ธ ๋…ธ๋“œ -> K8S Api Endpoint 

 

 

kubeops view ์„ค์น˜

root@admin-lb:~/kubesprayhelm repo add geek-cookbook https://geek-cookbook.github.io/charts/s/
"geek-cookbook" has been added to your repositories
root@admin-lb:~/kubespray# helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 \
  --set service.main.type=NodePort,service.main.ports.http.nodePort=30000 \
  --set env.TZ="Asia/Seoul" --namespace kube-system \
  --set image.repository="abihf/kube-ops-view" --set image.tag="latest"
NAME: kube-ops-view
LAST DEPLOYED: Sun Feb  8 03:40:35 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace kube-system -o jsonpath="{.spec.ports[0].nodePort}" services kube-ops-view)
  export NODE_IP=$(kubectl get nodes --namespace kube-system -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

 

ํ˜„์žฌ๋Š” 1๋ฒˆ ๋…ธ๋“œ๋งŒ ํŠธ๋ž˜ํ”ฝ์ด ๋“ค์–ด์˜ค๋Š” ๊ฒƒ์„ 3๋Œ€ ๋…ธ๋“œ ๋ชจ๋‘ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

 

์ƒ˜ํ”Œ ์•ฑ ๋ฐฐํฌ

# ์ƒ˜ํ”Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐฐํฌ
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30003
  type: NodePort
EOF

 

root@admin-lb:~/kubespray# kubectl get deploy,svc,ep webpod -owide
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES           SELECTOR
deployment.apps/webpod   2/2     2            2           23s   webpod       traefik/whoami   app=webpod

NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE   SELECTOR
service/webpod   NodePort   10.233.2.178   <none>        80:30003/TCP   23s   app=webpod

NAME               ENDPOINTS                       AGE
endpoints/webpod   10.233.67.5:80,10.233.67.6:80   23s

 

์žฅ์•  ์ƒํ™ฉ ์žฌํ˜„

 

1๋ฒˆ ๋…ธ๋“œ๋ฅผ ์ „์›์„ ๊บผ์„œ ์žฅ์• ๋ฅผ ์žฌํ˜„ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฐฑ์—”๋“œ ๋Œ€์ƒ ์„œ๋ฒ„๊ฐ€ 2๋Œ€๊ฐ€ ๋” ์žˆ๊ธฐ ๋•Œ๋ฌธ์— 2๋ฒˆ ๋…ธ๋“œ์—์„œ ์•„๋ž˜ ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋Š” ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

while true; do curl -sk https://127.0.0.1:6443/version | grep gitVersion ; date; sleep 1; echo ; done

 

 

external LB -> HA 3๋Œ€๋กœ ์ง€์ •

sed -i 's/192.168.10.12/192.168.10.10/g' /root/.kube/config

root@admin-lb:~/kubespray# ssh k8s-node1 kubectl get cm -n kube-system kubeadm-config -o yaml

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
      - k8s-node1
      - k8s-node2
      - k8s-node3
      - lb-apiserver.kubernetes.local
      - 192.168.10.11
      - 192.168.10.12
      - 192.168.10.13
      - 10.0.2.15
      - fd17:625c:f037:2:a00:27ff:fe90:eaeb


ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml --tags "control-plane" --limit kube_control_plane -e kube_version="1.32.9"
...
Sunday 08 February 2026  04:25:31 +0900 (0:00:00.033)       0:00:20.897 *******
===============================================================================
Gather minimal facts ---------------------------------------------------- 1.99s
kubernetes/control-plane : Kubeadm | Check apiserver.crt SAN hosts ------ 1.36s
kubernetes/control-plane : Backup old certs and keys -------------------- 1.29s
kubernetes/control-plane : Install | Copy kubectl binary from download dir --- 1.24s
kubernetes/control-plane : Kubeadm | Check apiserver.crt SAN IPs -------- 1.07s
Gather necessary facts (hardware) --------------------------------------- 0.98s
kubernetes/preinstall : Create other directories of root owner ---------- 0.74s
kubernetes/preinstall : Create kubernetes directories ------------------- 0.72s
kubernetes/control-plane : Backup old confs ----------------------------- 0.70s
kubernetes/control-plane : Update server field in component kubeconfigs --- 0.67s
win_nodes/kubernetes_patch : debug -------------------------------------- 0.66s
kubernetes/control-plane : Kubeadm | Create kubeadm config -------------- 0.64s
kubernetes/control-plane : Kubeadm | regenerate apiserver cert 2/2 ------ 0.42s
kubernetes/control-plane : Install kubectl bash completion -------------- 0.37s
kubernetes/control-plane : Create kube-scheduler config ----------------- 0.36s
kubernetes/control-plane : Renew K8S control plane certificates monthly 2/2 --- 0.35s
Gather necessary facts (network) ---------------------------------------- 0.34s
kubernetes/control-plane : Kubeadm | aggregate all SANs ----------------- 0.34s
kubernetes/control-plane : Set kubectl bash completion file permissions --- 0.34s
kubernetes/control-plane : Install script to renew K8S control plane certificates --- 0.30s
kubectl edit cm -n kube-system kubeadm-config
data:
  ClusterConfiguration: |
    apiServer:
      certSANs:
        - k8s-api-srv.admin-lb.com
        - 192.168.10.10
        - k8s-node1
        - k8s-node2
        - k8s-node3
        - kubernetes
        - kubernetes.default
        - kubernetes.default.svc
        - kubernetes.default.svc.cluster.local
        - lb-apiserver.kubernetes.local
        - localhost
        - 127.0.0.1
        - ::1
        - 10.233.0.1
        - 192.168.10.11
        - 192.168.10.12
        - 192.168.10.13
        - 10.0.2.15
    controlPlaneEndpoint: 192.168.10.10:6443

 

kubeadm-config ConfigMap์—๋Š” controlPlaneEndpoint์™€ apiServer.certSANs๋ฅผ ํ†ตํ•ด API Server์˜ ๊ณต์‹ ์ ‘๊ทผ ์—”๋“œํฌ์ธํŠธ(IP/DNS)๋ฅผ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Š” ์ธ์ฆ์„œ ๊ฐฑ์‹  ๋ฐ ํด๋Ÿฌ์Šคํ„ฐ ์—…๊ทธ๋ ˆ์ด๋“œ ์‹œ ๊ธฐ์ค€๊ฐ’์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 

๋”ฐ๋ผ์„œ ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ VIP ๋˜๋Š” DNS ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ถ”๊ฐ€ํ•ด๋‘์–ด์•ผ ํ•œ๋‹ค.

 

root@admin-lb:~/kubespray# kubectl get node -v=6
I0208 04:26:22.096297   21384 loader.go:402] Config loaded from file:  /root/.kube/config
I0208 04:26:22.097979   21384 envvar.go:172] "Feature gate default state" feature="ClientsAllowCBOR" enabled=false
I0208 04:26:22.097992   21384 envvar.go:172] "Feature gate default state" feature="ClientsPreferCBOR" enabled=false
I0208 04:26:22.097995   21384 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=false
I0208 04:26:22.097997   21384 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=false
I0208 04:26:22.111997   21384 round_trippers.go:560] GET https://192.168.10.11:6443/api/v1/nodes?limit=500 200 OK in 7 milliseconds
NAME        STATUS   ROLES           AGE     VERSION
k8s-node1   Ready    control-plane   3h44m   v1.32.9
k8s-node2   Ready    control-plane   3h43m   v1.32.9
k8s-node3   Ready    control-plane   3h43m   v1.32.9
k8s-node4   Ready    <none>          3h43m   v1.32.9
sed -i 's/192.168.10.10/k8s-api-srv.admin-lb.com/g' /root/.kube/config
root@admin-lb:~/kubespray# ssh k8s-node1 cat /etc/kubernetes/ssl/apiserver.crt | openssl x509 -text -noout
...
X509v3 Subject Alternative Name:
                DNS:k8s-api-srv.admin-lb.com, DNS:k8s-node1, DNS:k8s-node2, DNS:k8s-node3, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:lb-apiserver.kubernetes.local, DNS:localhost, IP Address:10.233.0.1, IP Address:192.168.10.11, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, IP Address:192.168.10.10, IP Address:192.168.10.12, IP Address:192.168.10.13, IP Address:10.0.2.15, IP Address:FD17:625C:F037:2:A00:27FF:FE90:EAEB

 

1๋ฒˆ ๋…ธ๋“œ๋ฅผ ์ฃฝ์˜€์ง€๋งŒ 2,3๋ฒˆ ๋…ธ๋“œ๋กœ ํŠธ๋ž˜ํ”ฝ์ด ๋ถ„๋ฐฐ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค!

 

 

๋ฒ„์ธ„์–ผ๋ฐ•์Šค์—์„œ 1๋ฒˆ ๋…ธ๋“œ๋ฅผ ๋‹ค์‹œ ์žฌ๊ธฐ๋™ํ•œ๋‹ค.

 

 

 

๋…ธ๋“œ ๊ด€๋ฆฌ

๋…ธ๋“œ ์ถ”๊ฐ€

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

root@admin-lb:~/kubespray# ansible -i inventory/mycluster/inventory.ini k8s-node5 -m ping

[WARNING]: Platform linux on host k8s-node5 is using the discovered Python
interpreter at /usr/bin/python3.12, but future installation of another Python
interpreter could change the meaning of that path. See
https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
k8s-node5 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.12"
    },
    "changed": false,
    "ping": "pong"
}

 

์ธ๋ฒคํ† ๋ฆฌ์— 5๋ฒˆ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ํ•‘ํ ์ฒดํฌํ•œ๋‹ค.

 

ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini -v scale.yml --limit=k8s-node5 -e kube_version="1.32.9" | tee kubespray_add_worker_node.log
...
PLAY RECAP *********************************************************************
k8s-node5                  : ok=411  changed=87   unreachable=0    failed=0    skipped=591  rescued=0    ignored=0

Sunday 08 February 2026  04:45:41 +0900 (0:00:00.014)       0:02:21.385 *******
===============================================================================
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence -- 30.24s
download : Download_container | Download image if required -------------- 6.15s
system_packages : Manage packages --------------------------------------- 6.13s
download : Download_container | Download image if required -------------- 5.89s
download : Download_container | Download image if required -------------- 4.63s
container-engine/containerd : Download_file | Download item ------------- 3.54s
download : Download_file | Download item -------------------------------- 3.21s
container-engine/crictl : Download_file | Download item ----------------- 3.15s
container-engine/runc : Download_file | Download item ------------------- 3.12s
download : Download_container | Download image if required -------------- 2.54s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 2.48s
container-engine/nerdctl : Download_file | Download item ---------------- 2.37s
download : Download_file | Download item -------------------------------- 2.04s
download : Download_file | Download item -------------------------------- 1.95s
container-engine/containerd : Containerd | Unpack containerd archive ---- 1.91s
download : Download_container | Download image if required -------------- 1.78s
container-engine/nerdctl : Extract_file | Unpacking archive ------------- 1.66s
container-engine/crictl : Extract_file | Unpacking archive -------------- 1.65s
etcd : Check_certs | Register certs that have already been generated on first etcd node --- 1.60s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 1.43s
root@admin-lb:~/kubespray# kubectl get node -owide
NAME        STATUS   ROLES           AGE    VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                        KERNEL-VERSION                  CONTAINER-RUNTIME
k8s-node1   Ready    control-plane   4h4m   v1.32.9   192.168.10.11   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node2   Ready    control-plane   4h3m   v1.32.9   192.168.10.12   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node3   Ready    control-plane   4h3m   v1.32.9   192.168.10.13   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node4   Ready    <none>          4h3m   v1.32.9   192.168.10.14   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node5   Ready    <none>          82s    v1.32.9   192.168.10.15   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5

 

๋…ธ๋“œ ์‚ญ์ œ

์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ๊ฒƒ์€ ๋น„์ •์ƒ ๋…ธ๋“œ ์‚ญ์ œ์ธ remove-node.yaml์€ ํด๋Ÿฌ์Šคํ„ฐ๊ฐ€ ๊นจ์ง€๊ฒŒ ๋˜๊ณ ,

ํด๋Ÿฌ์Šคํ„ฐ ๋ฆฌ์…‹ reset.yaml์€ k8s ํด๋Ÿฌ์Šคํ„ฐ ์ „์ฒด๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ... ์‹คํ–‰ ํ›„ ๋ณต๊ตฌ๊ฐ€ ๋ถˆ๊ฐ€์ธ ์Šคํฌ๋ฆฝํŠธ์ด๋ฏ€๋กœ ์‚ฌ์šฉํ•˜์ง€์•Š๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. ๊ฐ€๊ธ‰์  ์šด์˜์ค‘์ธ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ๋Š” ํ•ด๋‹น ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์•„์˜ˆ ์ง€์›Œ๋ฒ„๋ฆฌ๊ธฐ

 

https://github.com/kubernetes-sigs/kubespray/blob/master/playbooks/remove_node.yml

 

kubespray/playbooks/remove_node.yml at master · kubernetes-sigs/kubespray

Deploy a Production Ready Kubernetes Cluster. Contribute to kubernetes-sigs/kubespray development by creating an account on GitHub.

github.com

 

๋…ธ๋“œ ์‚ญ์ œ ์‹œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ํ”Œ๋ ˆ์ด๋ถ์€ remove-node.yml ์ด๋‹ค.

์ด ์Šคํฌ๋ฆฝํŠธ๋Š” ์ง€์›Œ์•ผ ๋  ๋…ธ๋“œ๊ฐ€ ์ •์ƒ์ ์ธ ์ƒํƒœ์ผ ๋•Œ ์ˆ˜ํ–‰๋œ๋‹ค.

 

์ด ํ”Œ๋ ˆ์ด๋ถ์€ ๋…ธ๋“œ ์ œ๊ฑฐ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ํด๋Ÿฌ์Šคํ„ฐ ๋ถˆ์ผ์น˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ์ž…๋ ฅ ๊ฒ€์ฆ๊ณผ ์‚ฌ์šฉ์ž ํ™•์ธ์„ ์„ ํ–‰ํ•˜๊ณ , etcd ๋ฐ control-plane ์—ญํ• ์„ ๊ณ ๋ คํ•œ ๋‹จ๊ณ„์  ์ œ๊ฑฐ(pre-remove → reset → post-remove) ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ๋‹ค.

์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์•ˆ์ „ํ•œ ์ด์œ ๋Š” ์ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „ ์‚ฌ์šฉ์ž์—๊ฒŒ yes๋ฅผ ์ž…๋ ฅ์„ ํ•œ ๋ฒˆ ๋” ๋ฐ›๊ฒŒ ๋ผ์žˆ๊ณ , ๋…ธ๋“œ ์ œ๊ฑฐ ์‹œ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋จผ์ € ๋ถ„๋ฆฌํ•˜๊ณ  ๋‚œ ํ›„ ๋…ธ๋“œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊นŒ์ง€ ์ •๋ฆฌํ•˜๋Š” ๊ณผ์ •์„ ์ง€์ผœ์„œ ์ƒํƒœ๊ฐ’์„ ์ผ๊ด€๋˜๊ฒŒ ๋งŒ๋“ ๋‹ค.

๊ทธ๋ฆฌ๊ณ  etcd ์ฟผ๋Ÿผ์„ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š๋„๋ก ๋ณ„๋„ ์ œ๊ฑฐ ์ ˆ์ฐจ๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ์ง€ํ‚ค๊ฒŒ ๋œ๋‹ค. ๊ต‰์žฅํžˆ ๋ณด์ˆ˜์ ์œผ๋กœ ์ž˜ ์„ค๊ณ„๋ผ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค!

 

Validate nodes for removal

  • ๋…ธ๋“œ ์ œ๊ฑฐ ๋Œ€์ƒ์ด ๋ช…์‹œ๋˜์—ˆ๋Š”์ง€ ์‚ฌ์ „์— ๊ฒ€์ฆํ•˜์—ฌ, ์ž˜๋ชป๋œ ์‹คํ–‰์œผ๋กœ ์ „์ฒด ํด๋Ÿฌ์Šคํ„ฐ์— ์˜ํ–ฅ์„ ์ฃผ๋Š” ์‚ฌ๊ณ ๋ฅผ ๋ฐฉ์ง€ํ•œ๋‹ค.

Common tasks for every playbooks

  • ๋ชจ๋“  ํ”Œ๋ ˆ์ด๋ถ์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ ์„ค์ •๊ณผ ํ™˜๊ฒฝ ์ดˆ๊ธฐํ™” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

Confirm node removal

  • ์‹ค์ œ ๋…ธ๋“œ ์ œ๊ฑฐ ์ž‘์—… ์ „์— ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋ช…์‹œ์ ์ธ ํ™•์ธ์„ ๋ฐ›์•„, ์˜๋„ํ•˜์ง€ ์•Š์€ ํŒŒ๊ดด์  ์ž‘์—…์„ ํ•œ ๋ฒˆ ๋” ์ฐจ๋‹จํ•œ๋‹ค.

Gather facts

  • ์ œ๊ฑฐ ๋Œ€์ƒ ๋…ธ๋“œ์˜ ์—ญํ• (control-plane, worker, etcd ๋“ฑ)์„ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•œ ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•œ๋‹ค.

Reset node

  • ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋…ธ๋“œ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•œ ๋’ค, ํ•ด๋‹น ๋…ธ๋“œ์˜ Kubernetes ๊ตฌ์„ฑ ์š”์†Œ์™€ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.
  • ๋งŒ์•ฝ ๋…ธ๋“œ๊ฐ€ etcd ๋ฉค๋ฒ„๋ผ๋ฉด etcd ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋ฉค๋ฒ„๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  resetํ•œ๋‹ค.
  • kubeadm reset, CNI ์ œ๊ฑฐ, kubelet / container runtime ์„ค์ • ์ œ๊ฑฐ, ์ธ์ฆ์„œ, kubeconfig ์‚ญ์ œํ•˜์—ฌ ๋…ธ๋“œ๋ฅผ ์™„์ „ํžˆ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค.

Post node removal

  • ๋…ธ๋“œ ์ œ๊ฑฐ ์ดํ›„ ํด๋Ÿฌ์Šคํ„ฐ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ์ž”์—ฌ ์„ค์ •์„ ์ •๋ฆฌํ•˜์—ฌ ์ƒํƒœ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค

 

ansible-playbook -i inventory/mycluster/inventory.ini -v remove-node.yml -e node=k8s-node5
> yes
...
Sunday 08 February 2026  04:58:58 +0900 (0:00:00.252)       0:00:38.802 *******
===============================================================================
reset : Reset | delete some files and directories ----------------------- 7.35s
system_packages : Manage packages --------------------------------------- 6.35s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 2.71s
bootstrap_os : Fetch /etc/os-release ------------------------------------ 1.54s
Gather necessary facts (hardware) --------------------------------------- 1.20s
Confirm Execution ------------------------------------------------------- 1.11s
reset : Reset | stop services ------------------------------------------- 1.07s
reset : Gather active network services ---------------------------------- 1.04s
reset : Reset | stop containerd and etcd services ----------------------- 1.00s
reset : Reset | remove containerd binary files -------------------------- 0.97s
Gather information about installed services ----------------------------- 0.96s
reset : Reset | remove services ----------------------------------------- 0.70s
bootstrap_os : Assign inventory name to unconfigured hostnames (non-CoreOS, non-Flatcar, Suse and ClearLinux, non-Fedora) --- 0.68s
bootstrap_os : Gather host facts to get ansible_distribution_version ansible_distribution_major_version --- 0.67s
reset : Flush iptables -------------------------------------------------- 0.45s
reset : Reset | systemctl daemon-reload --------------------------------- 0.45s
reset : Restart active network services --------------------------------- 0.42s
system_packages : Gather OS information --------------------------------- 0.41s
Gather necessary facts (network) ---------------------------------------- 0.40s
reset : Reset | stop all cri containers --------------------------------- 0.36s
root@admin-lb:~/kubespray# kubectl get node -owide
NAME        STATUS   ROLES           AGE     VERSION   INTERNAL-IP     EXTERNAL-IP   OS-IMAGE                        KERNEL-VERSION                  CONTAINER-RUNTIME
k8s-node1   Ready    control-plane   4h17m   v1.32.9   192.168.10.11   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node2   Ready    control-plane   4h16m   v1.32.9   192.168.10.12   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node3   Ready    control-plane   4h16m   v1.32.9   192.168.10.13   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5
k8s-node4   Ready    <none>          4h16m   v1.32.9   192.168.10.14   <none>        Rocky Linux 10.0 (Red Quartz)   6.12.0-55.39.1.el10_0.aarch64   containerd://2.1.5

 

 

 

๋ชจ๋‹ˆํ„ฐ๋ง ์„ค์ •

nfs ํ”„๋กœ๋น„์ €๋„ˆ

root@admin-lb:~/kubespray# kubectl create ns nfs-provisioner
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner -n nfs-provisioner \
    --set nfs.server=192.168.10.10 \
    --set nfs.path=/srv/nfs/share \
    --set storageClass.defaultClass=true
namespace/nfs-provisioner created
"nfs-subdir-external-provisioner" has been added to your repositories
NAME: nfs-provisioner
LAST DEPLOYED: Sun Feb  8 05:08:57 2026
NAMESPACE: nfs-provisioner
STATUS: deployed
REVISION: 1
TEST SUITE: None

 

ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค

root@admin-lb:~/kubespray# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
"prometheus-community" has been added to your repositories

root@admin-lb:~/kubespray# cat <<EOT > monitor-values.yaml
prometheus:
  prometheusSpec:
    scrapeInterval: "20s"
    evaluationInterval: "20s"
    storageSpec:
      volumeClaimTemplate:
        spec:
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 10Gi
    additionalScrapeConfigs:
      - job_name: 'haproxy-metrics'
        static_configs:
          - targets:
              - '192.168.10.10:8405'
    externalLabels:
      cluster: "myk8s-cluster"
  service:
    type: NodePort
    nodePort: 30001

grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator
  service:
    type: NodePort
    nodePort: 30002

alertmanager:
  enabled: false
defaultRules:
  create: false
kubeProxy:
EOT   enabled: falsexporter:
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 80.13.3 \
-f monitor-values.yaml --create-namespace --namespace monitoring

 

๊ทธ๋ผํŒŒ๋‚˜ ๋Œ€์‹œ๋ณด๋“œ ๋‹ค์šด๋กœ๋“œ

curl -o 12693_rev12.json https://grafana.com/api/dashboards/12693/revisions/12/download
curl -o 15661_rev2.json https://grafana.com/api/dashboards/15661/revisions/2/download
curl -o k8s-system-api-server.json https://raw.githubusercontent.com/dotdc/grafana-dashboards-kubernetes/refs/heads/master/dashboards/k8s-system-api-server.json
sed -i -e 's/${DS_PROMETHEUS}/prometheus/g' 12693_rev12.json
sed -i -e 's/${DS__VICTORIAMETRICS-PROD-ALL}/prometheus/g' 15661_rev2.json
sed -i -e 's/${DS_PROMETHEUS}/prometheus/g' k8s-system-api-server.json
kubectl create configmap my-dashboard --from-file=12693_rev12.json --from-file=15661_rev2.json --from-file=k8s-system-api-server.json -n monitoring
kubectl label configmap my-dashboard grafana_dashboard="1" -n monitoring
root@admin-lb:~# kubectl exec -it -n monitoring deploy/kube-prometheus-stack-grafana -- ls -l /tmp/dashboards
total 976
-rw-r--r--    1 grafana  472         333790 Feb  7 20:13 12693_rev12.json
-rw-r--r--    1 grafana  472         198839 Feb  7 20:13 15661_rev2.json
-rw-r--r--    1 grafana  472          12367 Feb  7 20:11 apiserver.json
-rw-r--r--    1 grafana  472          15598 Feb  7 20:11 cluster-total.json
-rw-r--r--    1 grafana  472           8600 Feb  7 20:11 controller-manager.json
-rw-r--r--    1 grafana  472           8340 Feb  7 20:11 etcd.json
-rw-r--r--    1 grafana  472           7282 Feb  7 20:11 grafana-overview.json
-rw-r--r--    1 grafana  472          25210 Feb  7 20:11 k8s-coredns.json
-rw-r--r--    1 grafana  472          26811 Feb  7 20:11 k8s-resources-cluster.json
-rw-r--r--    1 grafana  472           9837 Feb  7 20:11 k8s-resources-multicluster.json
-rw-r--r--    1 grafana  472          27310 Feb  7 20:11 k8s-resources-namespace.json
-rw-r--r--    1 grafana  472          11208 Feb  7 20:11 k8s-resources-node.json
-rw-r--r--    1 grafana  472          25881 Feb  7 20:11 k8s-resources-pod.json
-rw-r--r--    1 grafana  472          24677 Feb  7 20:11 k8s-resources-workload.json
-rw-r--r--    1 grafana  472          27747 Feb  7 20:11 k8s-resources-workloads-namespace.json
-rw-r--r--    1 grafana  472          35173 Feb  7 20:13 k8s-system-api-server.json
-rw-r--r--    1 grafana  472          19009 Feb  7 20:11 kubelet.json
-rw-r--r--    1 grafana  472          11767 Feb  7 20:11 namespace-by-pod.json
-rw-r--r--    1 grafana  472          19013 Feb  7 20:11 namespace-by-workload.json
-rw-r--r--    1 grafana  472           8222 Feb  7 20:11 node-cluster-rsrc-use.json
-rw-r--r--    1 grafana  472           7833 Feb  7 20:11 node-rsrc-use.json
-rw-r--r--    1 grafana  472           9750 Feb  7 20:11 nodes-aix.json
-rw-r--r--    1 grafana  472          10987 Feb  7 20:11 nodes-darwin.json
-rw-r--r--    1 grafana  472          10339 Feb  7 20:11 nodes.json
-rw-r--r--    1 grafana  472           5971 Feb  7 20:11 persistentvolumesusage.json
-rw-r--r--    1 grafana  472           6775 Feb  7 20:11 pod-total.json
-rw-r--r--    1 grafana  472          11115 Feb  7 20:11 prometheus.json
-rw-r--r--    1 grafana  472           9731 Feb  7 20:11 scheduler.json
-rw-r--r--    1 grafana  472          11025 Feb  7 20:11 workload-total.json

 

etcd ๋งคํŠธ๋ฆญ ์ˆ˜์ง‘ ์„ค์ • ์ถ”๊ฐ€

cat << EOF >> inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
etcd_metrics: true
etcd_listen_metrics_urls: "http://0.0.0.0:2381"
EOF

ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml --tags "etcd" --limit etcd -e kube_version="1.32.9"
cat <<EOF > monitor-add-values.yaml
prometheus:
  prometheusSpec:
    additionalScrapeConfigs:
      - job_name: 'etcd'
        metrics_path: /metrics 
        static_configs:
          - targets:
              - '192.168.10.11:2381'
              - '192.168.10.12:2381'
              - '192.168.10.13:2381'
EOF
helm get values -n monitoring kube-prometheus-stack
helm upgrade kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 80.13.3 \
--reuse-values -f monitor-add-values.yaml --namespace monitoring

 

 

k8s ์—…๊ทธ๋ ˆ์ด๋“œ (1.32.9 -> 1.32.10)

upgrade_cluster.yaml

https://github.com/kubernetes-sigs/kubespray/blob/master/playbooks/upgrade_cluster.yml

 

kubespray/playbooks/upgrade_cluster.yml at master · kubernetes-sigs/kubespray

Deploy a Production Ready Kubernetes Cluster. Contribute to kubernetes-sigs/kubespray development by creating an account on GitHub.

github.com

 

Common tasks for every playbooks

- name: Common tasks for every playbooks
  import_playbook: boilerplate.yml

 

  • ๋ชจ๋“  Kubespray ํ”Œ๋ ˆ์ด๋ถ์—์„œ ๊ณตํ†ต์œผ๋กœ ํ•„์š”ํ•œ ์„ค์ •์„ ๋จผ์ € ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
  • ํ”„๋ก์‹œ, ๊ธฐ๋ณธ ๋ณ€์ˆ˜, ํ™˜๊ฒฝ ์„ค์ • ๋“ฑ์ด ์—ฌ๊ธฐ์„œ ์ดˆ๊ธฐํ™”๋œ๋‹ค.
  • ์ดํ›„ ๋ชจ๋“  play๊ฐ€ ๋™์ผํ•œ ๊ธฐ์ค€ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค.
  • ์—…๊ทธ๋ ˆ์ด๋“œ ๋„์ค‘ ํ™˜๊ฒฝ ์ฐจ์ด๋กœ ์ธํ•œ ์˜ˆ์™ธ๋ฅผ ๋ง‰๋Š”๋‹ค.

Gather facts

- name: Gather facts
  import_playbook: internal_facts.yml

 

  • ๋…ธ๋“œ ์—ญํ• (control-plane, worker, etcd, calico_rr ๋“ฑ)์„ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๋‚ด๋ถ€ fact๋ฅผ ์ˆ˜์ง‘ํ•œ๋‹ค.
  • ํ•ด๋‹น ๋‹จ๊ณ„์—์„œ ์ˆ˜์ง‘๋œ facts๋“ค์€ hosts: kube_control_plane, kube_node, etcd ๊ฐ™์€ ๊ทธ๋ฃน ๋ถ„๊ธฐ๊ฐ€ ์ •ํ™•ํžˆ ๋™์ž‘ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐ์ดํ„ฐ์˜ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 

Download images to ansible host cache via first kube_control_plane node

- hosts: kube_control_plane[0]
  roles:
    - kubespray_defaults
    - kubernetes/preinstall
    - download

 

  • ์ฒซ ๋ฒˆ์งธ control-plane ๋…ธ๋“œ ํ•œ ๋Œ€๋งŒ ์‚ฌ์šฉํ•ด ์ด๋ฏธ์ง€/๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค.
  • download_run_once ์กฐ๊ฑด์œผ๋กœ ์ค‘๋ณต ๋‹ค์šด๋กœ๋“œ ๋ฐฉ์ง€ํ•œ๋‹ค.
  • ์—…๊ทธ๋ ˆ์ด๋“œ ์ค‘ ๋„คํŠธ์›Œํฌ ์ด์Šˆ๋กœ ์‹คํŒจํ•˜๋Š” ๊ฑธ ๋ง‰๋Š”๋‹ค.
  • ์ดํ›„ ๋…ธ๋“œ๋“ค์€ ๋กœ์ปฌ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ์†๋„์™€ ์•ˆ์ •์„ฑ์ด ์ข‹์•„์ง„๋‹ค.

 

Prepare nodes for upgrade

- hosts: k8s_cluster:etcd:calico_rr
  roles:
    - kubespray_defaults
    - kubernetes/preinstall
    - download

 

  • ํด๋Ÿฌ์Šคํ„ฐ์— ํฌํ•จ๋œ ๋ชจ๋“  ๋…ธ๋“œ๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋กœ ์ค€๋น„ํ•œ๋‹ค.
  • OS ์„ค์ •, ํ•„์ˆ˜ ํŒจํ‚ค์ง€, ์ด๋ฏธ์ง€ ์ค€๋น„ ๋‹จ๊ณ„์ด๋‹ค.
  • ์‹ค์ œ ์—…๊ทธ๋ ˆ์ด๋“œ ์ „์— ํ™˜๊ฒฝ ์ฐจ์ด๋กœ ์ธํ•œ ์‹คํŒจ๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.

Upgrade container engine on non-cluster nodes

- hosts: etcd:calico_rr:!k8s_cluster
  serial: 20%
  roles:
    - container-engine

 

  • Kubernetes ์›Œํฌ๋กœ๋“œ๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•˜์ง€ ์•Š๋Š” ๋…ธ๋“œ๋“ค์˜ ์ปจํ…Œ์ด๋„ˆ ๋Ÿฐํƒ€์ž„์„ ๋จผ์ € ์—…๊ทธ๋ ˆ์ด๋“œํ•œ๋‹ค.
  • ํด๋Ÿฌ์Šคํ„ฐ ํ•ต์‹ฌ ๋…ธ๋“œ์™€ ๋ถ„๋ฆฌํ•ด์„œ ์˜ํ–ฅ ๋ฒ”์œ„๋ฅผ ์ตœ์†Œํ™”ํ•œ๋‹ค.
  • serial๋กœ ํ•œ ๋ฒˆ์— ์ผ๋ถ€๋งŒ ์ฒ˜๋ฆฌํ•ด ๋ฆฌ์Šคํฌ๋ฅผ ๋‚ฎ์ถ˜๋‹ค.

 

Install etcd

- name: Install etcd
  import_playbook: install_etcd.yml

 

  • etcd ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๊ฑฐ๋‚˜ ์žฌ๊ตฌ์„ฑํ•œ๋‹ค.
  • ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ณด๋‹ค ๋จผ์ €, ๊ทธ๋ฆฌ๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ๋‹ค๋ค„์ง„๋‹ค.

Handle upgrades to control plane components first

- hosts: kube_control_plane
  serial: 1
  roles:
    - upgrade/pre-upgrade
    - upgrade/system-upgrade
    - kubernetes/control-plane
    - kubernetes/client

 

  • control-plane ๋…ธ๋“œ๋ฅผ 1๋Œ€์”ฉ(serial: 1) ์—…๊ทธ๋ ˆ์ด๋“œํ•œ๋‹ค.
  • kube-apiserver, controller-manager, scheduler ํฌํ•จ๋œ๋‹ค.
  • API ํ˜ธํ™˜์„ฑ์„ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š๊ธฐ ์œ„ํ•ด 1๋Œ€์”ฉ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜๋ฉฐ, ๋งŒ์•ฝ ๋™์‹œ์— ์—ฌ๋Ÿฌ control-plane์„ ๊ฑด๋“œ๋ฆฌ๋ฉด ํด๋Ÿฌ์Šคํ„ฐ์˜ ์ •ํ•ฉ์„ฑ์ด ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค.

Upgrade calico and external cloud provider

- hosts: kube_control_plane:calico_rr:kube_node
  roles:
    - kubernetes-apps/external_cloud_controller
    - network_plugin

 

  • ๋„คํŠธ์›Œํฌ ํ”Œ๋Ÿฌ๊ทธ์ธ(Calico)๊ณผ ํด๋ผ์šฐ๋“œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ „์ฒด ๋…ธ๋“œ์— ์ ์šฉํ•œ๋‹ค.
  • control-plane ์—…๊ทธ๋ ˆ์ด๋“œ ํ›„์— ์‹คํ–‰ํ•ด ๋„คํŠธ์›Œํฌ ๋‹จ์ ˆ ๊ฐ€๋Šฅ์„ฑ์„ ์ตœ์†Œํ™”ํ•œ๋‹ค.

 

Finally handle worker upgrades

- hosts: kube_node:calico_rr:!kube_control_plane
  serial: 20%
  roles:
    - upgrade/pre-upgrade
    - kubernetes/node
    - kubernetes/kubeadm

 

  • ์›Œ์ปค ๋…ธ๋“œ๋ฅผ ๋ฐฐ์น˜ ๋‹จ์œ„๋กœ ์ˆœ์ฐจ ์—…๊ทธ๋ ˆ์ด๋“œํ•œ๋‹ค.
  • ์„œ๋น„์Šค ๊ฐ€์šฉ์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํ•œ ๋ฒˆ์— ์ „๋ถ€ ์—…๊ทธ๋ ˆ์ด๋“œํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • control-plane์€ ์ด๋ฏธ ์•ˆ์ •ํ™”๋œ ์ƒํƒœ.

 

Patch Kubernetes for Windows

- hosts: kube_control_plane[0]
  roles:
    - win_nodes/kubernetes_patch

 

  • ์œˆ๋„์šฐ ๋…ธ๋“œ์ผ ๊ฒฝ์šฐ๋„ ๊ณ ๋ ค๋˜์–ด ์žˆ๋‹ค ์‹ ๊ธฐ

Install Calico Route Reflector

- hosts: calico_rr
  roles:
    - network_plugin/calico/rr

 

  • BGP ๊ธฐ๋ฐ˜ ๋„คํŠธ์›Œํฌ ํ™•์žฅ์„ฑ์„ ์œ„ํ•œ Route Reflector๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.
  • ๋Œ€๊ทœ๋ชจ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋„คํŠธ์›Œํฌ ์„ฑ๋Šฅ๊ณผ ์•ˆ์ •์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค.

 

Install Kubernetes apps

- hosts: kube_control_plane
  roles:
    - kubernetes-apps/ingress_controller
    - kubernetes-apps
  • Ingress, CSI ๋“ฑ ํด๋Ÿฌ์Šคํ„ฐ ๋ถ€๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ค์น˜/์—…๊ทธ๋ ˆ์ด๋“œํ•œ๋‹ค.

 

Apply resolv.conf changes now that cluster DNS is up

- hosts: k8s_cluster
  roles:
    - kubernetes/preinstall

 

  • CoreDNS๊ฐ€ ์ •์ƒ ๋™์ž‘ํ•œ ์ดํ›„์— DNS ์„ค์ •์„ ์ตœ์ข… ๋ฐ˜์˜ํ•œ๋‹ค.
  • DNS ์„ค์ •์„ ๋„ˆ๋ฌด ์ผ์ฐ ์ ์šฉํ•˜๋ฉด ์—…๊ทธ๋ ˆ์ด๋“œ ๋„์ค‘ name resolution ์‹คํŒจ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ”Œ๋ ˆ์ด๋ถ ์‹คํ–‰

ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini -v upgrade-cluster.yml -e kube_version="1.32.10" --limit "kube_control_plane:etcd" | tee kubespray_upgrade.log

 

 

์ˆœ์ฐจ์ ์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ์ง„ํ–‰๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

PLAY RECAP *********************************************************************
k8s-node1                  : ok=533  changed=49   unreachable=0    failed=0    skipped=1091 rescued=0    ignored=0
k8s-node2                  : ok=483  changed=35   unreachable=0    failed=0    skipped=994  rescued=0    ignored=0
k8s-node3                  : ok=489  changed=35   unreachable=0    failed=0    skipped=1026 rescued=0    ignored=0

Sunday 08 February 2026  05:23:39 +0900 (0:00:00.041)       0:11:35.319 *******
===============================================================================
kubernetes/control-plane : Kubeadm | Upgrade first control plane node to 1.32.10 - 105.01s
kubernetes/control-plane : Kubeadm | Upgrade other control plane nodes to 1.32.10 -- 92.19s
kubernetes/control-plane : Kubeadm | Upgrade other control plane nodes to 1.32.10 -- 81.87s
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence -- 15.86s
kubernetes/control-plane : Control plane | wait for the apiserver to be running -- 10.46s
download : Download_container | Download image if required -------------- 9.82s
upgrade/pre-upgrade : Drain node ---------------------------------------- 6.83s
download : Download_file | Download item -------------------------------- 6.78s
etcd : Gen_certs | Write etcd member/admin and kube_control_plane client certs to other etcd nodes --- 6.59s
download : Download_container | Download image if required -------------- 5.67s
system_packages : Manage packages --------------------------------------- 5.54s
download : Download_container | Download image if required -------------- 5.50s
download : Download_file | Download item -------------------------------- 5.29s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 5.20s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 5.13s
download : Download_container | Download image if required -------------- 5.00s
kubernetes/control-plane : Backup old certs and keys -------------------- 4.85s
container-engine/containerd : Containerd | Unpack containerd archive ---- 4.83s
kubernetes/control-plane : Kubeadm | Check apiserver.crt SAN IPs -------- 4.17s
etcdctl_etcdutl : Extract_file | Unpacking archive ---------------------- 4.06s
NAME        STATUS   ROLES           AGE     VERSION
k8s-node1   Ready    control-plane   4h45m   v1.32.10
k8s-node2   Ready    control-plane   4h45m   v1.32.10
k8s-node3   Ready    control-plane   4h45m   v1.32.10
k8s-node4   Ready    <none>          4h44m   v1.32.9
k8s-node5   Ready    <none>          26m     v1.32.9

 

์•ฝ 11๋ถ„ ์ •๋„ ์†Œ์š”๋˜๊ณ  ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค.

 

root@admin-lb:~# ssh k8s-node1 crictl images
IMAGE                                           TAG                 IMAGE ID            SIZE
docker.io/flannel/flannel-cni-plugin            v1.7.1-flannel1     e5bf9679ea8c3       5.14MB
docker.io/flannel/flannel                       v0.27.3             cadcae92e6360       33.1MB
quay.io/prometheus/node-exporter                v1.10.2             6b5bc413b280c       12.1MB
registry.k8s.io/coredns/coredns                 v1.11.3             2f6c962e7b831       16.9MB
registry.k8s.io/kube-apiserver                  v1.32.10            03aec5fd5841e       26.4MB
registry.k8s.io/kube-apiserver                  v1.32.9             02ea53851f07d       26.4MB
registry.k8s.io/kube-controller-manager         v1.32.10            66490a6490dde       24.2MB
registry.k8s.io/kube-controller-manager         v1.32.9             f0bcbad5082c9       24.1MB
registry.k8s.io/kube-proxy                      v1.32.10            8b57c1f8bd2dd       27.6MB
registry.k8s.io/kube-proxy                      v1.32.9             72b57ec14d31e       27.4MB
registry.k8s.io/kube-scheduler                  v1.32.10            fcf368a1abd0b       19.2MB
registry.k8s.io/kube-scheduler                  v1.32.9             1d625baf81b59       19.1MB
registry.k8s.io/metrics-server/metrics-server   v0.8.0              bc6c1e09a843d       20.6MB
registry.k8s.io/pause                           3.10                afb61768ce381       268kB

 

etcd ํ™•์ธ

root@admin-lb:~# ssh k8s-node1 systemctl status etcd --no-pager | grep active
     Active: active (running) since Sun 2026-02-08 04:40:35 KST; 47min ago
root@admin-lb:~# 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 |
+------------------+---------+-------+----------------------------+----------------------------+------------+
root@admin-lb:~# 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 |   23 MB |     false |      false |         6 |      55240 |              55240 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> k8s-node2 <<
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | 2106626b12a4099f |  3.5.25 |   22 MB |      true |      false |         6 |      55243 |              55243 |        |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

>> k8s-node3 <<
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|    ENDPOINT    |       ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 127.0.0.1:2379 | 8b0ca30665374b0 |  3.5.25 |   22 MB |     false |      false |         6 |      55243 |              55243 |        |
+----------------+-----------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

root@admin-lb:~# for i in {1..3}; do echo ">> k8s-node$i <<"; ssh k8s-node$i tree /var/backups; echo; done # etcd ๋ฐฑ์—… ํ™•์ธ
>> k8s-node1 <<
/var/backups
โ””โ”€โ”€ etcd-2026-02-08_00:41:43
    โ”œโ”€โ”€ member
    โ”‚   โ”œโ”€โ”€ snap
    โ”‚   โ”‚   โ””โ”€โ”€ db
    โ”‚   โ””โ”€โ”€ wal
    โ”‚       โ””โ”€โ”€ 0000000000000000-0000000000000000.wal
    โ””โ”€โ”€ snapshot.db

5 directories, 3 files

>> k8s-node2 <<
/var/backups
โ””โ”€โ”€ etcd-2026-02-08_00:41:43
    โ”œโ”€โ”€ member
    โ”‚   โ”œโ”€โ”€ snap
    โ”‚   โ”‚   โ””โ”€โ”€ db
    โ”‚   โ””โ”€โ”€ wal
    โ”‚       โ””โ”€โ”€ 0000000000000000-0000000000000000.wal
    โ””โ”€โ”€ snapshot.db

5 directories, 3 files

>> k8s-node3 <<
/var/backups
โ””โ”€โ”€ etcd-2026-02-08_00:41:43
    โ”œโ”€โ”€ member
    โ”‚   โ”œโ”€โ”€ snap
    โ”‚   โ”‚   โ””โ”€โ”€ db
    โ”‚   โ””โ”€โ”€ wal
    โ”‚       โ””โ”€โ”€ 0000000000000000-0000000000000000.wal
    โ””โ”€โ”€ snapshot.db

5 directories, 3 files

 

etcd๋Š” ์˜ํ–ฅ์ด ์—†๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์›Œ์ปค ๋…ธ๋“œ ์—…๊ทธ๋ ˆ์ด๋“œ

root@admin-lb:~/kubespray# kubectl get pod -A -owide | grep node4
default           webpod-697b545f57-m95gv                                           1/1     Running   0             81m     10.233.67.6     k8s-node4   <none>           <none>
default           webpod-697b545f57-prbjp                                           1/1     Running   0             81m     10.233.67.5     k8s-node4   <none>           <none>
kube-system       coredns-664b99d7c7-5drtx                                          1/1     Running   0             4h47m   10.233.67.2     k8s-node4   <none>           <none>
kube-system       kube-flannel-ds-arm64-wkwrg                                       1/1     Running   0             4h47m   192.168.10.14   k8s-node4   <none>           <none>
kube-system       kube-ops-view-8484bdc5df-8xf2c                                    1/1     Running   0             109m    10.233.67.4     k8s-node4   <none>           <none>
kube-system       kube-proxy-qw84v                                                  1/1     Running   0             12m     192.168.10.14   k8s-node4   <none>           <none>
kube-system       metrics-server-65fdf69dcb-jnvbd                                   1/1     Running   0             4h47m   10.233.67.3     k8s-node4   <none>           <none>
kube-system       nginx-proxy-k8s-node4                                             1/1     Running   1             4h47m   192.168.10.14   k8s-node4   <none>           <none>
monitoring        kube-prometheus-stack-prometheus-node-exporter-8rcnt              1/1     Running   0             19m     192.168.10.14   k8s-node4   <none>           <none>
monitoring        prometheus-kube-prometheus-stack-prometheus-0                     2/2     Running   0             19m     10.233.67.8     k8s-node4   <none>           <none>
root@admin-lb:~/kubespray# kubectl get pod -A -owide | grep node5
kube-system       kube-flannel-ds-arm64-4plpp                                       1/1     Running   1 (28m ago)   29m     192.168.10.15   k8s-node5   <none>           <none>
kube-system       kube-proxy-5lzz6                                                  1/1     Running   0             12m     192.168.10.15   k8s-node5   <none>           <none>
kube-system       nginx-proxy-k8s-node5                                             1/1     Running   0             29m     192.168.10.15   k8s-node5   <none>           <none>
monitoring        kube-prometheus-stack-grafana-5cb7c586f9-bnfd2                    3/3     Running   0             19m     10.233.69.6     k8s-node5   <none>           <none>
monitoring        kube-prometheus-stack-kube-state-metrics-7846957b5b-fqbf2         1/1     Running   0             19m     10.233.69.5     k8s-node5   <none>           <none>
monitoring        kube-prometheus-stack-operator-584f446c98-tjxfj                   1/1     Running   0             19m     10.233.69.4     k8s-node5   <none>           <none>
monitoring        kube-prometheus-stack-prometheus-node-exporter-pkxgl              1/1     Running   0             19m     192.168.10.15   k8s-node5   <none>           <none>
nfs-provisioner   nfs-provisioner-nfs-subdir-external-provisioner-b549b9dff-tbddr   1/1     Running   0             21m     10.233.69.2     k8s-node5   <none>           <none>

 

root@admin-lb:~/kubespray# ansible-playbook -i inventory/mycluster/inventory.ini -v upgrade-cluster.yml -e kube_version="1.32.10" --limit "k8s-node5"
...
***********
k8s-node5                  : ok=369  changed=20   unreachable=0    failed=0    skipped=608  rescued=0    ignored=0

Sunday 08 February 2026  05:33:05 +0900 (0:00:00.049)       0:02:15.924 *******
===============================================================================
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence --- 5.23s
system_packages : Manage packages --------------------------------------- 5.04s
container-engine/containerd : Containerd | Unpack containerd archive ---- 4.89s
container-engine/containerd : Download_file | Download item ------------- 4.71s
container-engine/nerdctl : Extract_file | Unpacking archive ------------- 3.37s
upgrade/pre-upgrade : Drain node ---------------------------------------- 3.37s
container-engine/nerdctl : Download_file | Download item ---------------- 3.21s
container-engine/containerd : Containerd | Copy containerd config file --- 2.21s
kubernetes/kubeadm : Restart all kube-proxy pods to ensure that they load the new configmap --- 2.20s
container-engine/runc : Download_file | Download item ------------------- 2.17s
download : Download_file | Download item -------------------------------- 2.09s
download : Download_file | Download item -------------------------------- 2.05s
container-engine/crictl : Download_file | Download item ----------------- 1.90s
container-engine/crictl : Extract_file | Unpacking archive -------------- 1.88s
container-engine/containerd : Download_file | Create dest directory on node --- 1.73s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 1.62s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 1.53s
container-engine/containerd : Extract_file | Unpacking archive ---------- 1.50s
kubernetes/node : Install | Copy kubeadm binary from download dir ------- 1.27s
container-engine/crictl : Download_file | Create dest directory on node --- 1.25s


root@admin-lb:~/kubespray# ansible-playbook -i inventory/mycluster/inventory.ini -v upgrade-cluster.yml -e kube_version="1.32.10" --limit "k8s-node4"
...
PLAY RECAP *********************************************************************
k8s-node4                  : ok=369  changed=19   unreachable=0    failed=0    skipped=608  rescued=0    ignored=0

Sunday 08 February 2026  05:36:05 +0900 (0:00:00.015)       0:02:11.744 *******
===============================================================================
upgrade/pre-upgrade : Drain node --------------------------------------- 12.62s
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence --- 5.34s
system_packages : Manage packages --------------------------------------- 5.26s
download : Download_file | Download item -------------------------------- 3.45s
download : Download_file | Download item -------------------------------- 3.23s
container-engine/nerdctl : Download_file | Download item ---------------- 2.87s
kubernetes/preinstall : NetworkManager | Prevent NetworkManager from managing K8S interfaces (kube-ipvs0/nodelocaldns) --- 2.78s
container-engine/runc : Download_file | Download item ------------------- 2.65s
container-engine/validate-container-engine : Populate service facts ----- 2.56s
container-engine/crictl : Download_file | Download item ----------------- 2.50s
container-engine/containerd : Download_file | Download item ------------- 2.42s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 2.00s
container-engine/crictl : Extract_file | Unpacking archive -------------- 1.91s
container-engine/nerdctl : Extract_file | Unpacking archive ------------- 1.91s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 1.85s
container-engine/containerd : Containerd | Unpack containerd archive ---- 1.64s
container-engine/crictl : Download_file | Create dest directory on node --- 1.27s
download : Download | Download files / images --------------------------- 1.17s
kubernetes/preinstall : Stop if access_ip is not pingable --------------- 1.15s
container-engine/runc : Copy runc binary from download dir -------------- 1.05s
NAME        STATUS   ROLES           AGE     VERSION
k8s-node1   Ready    control-plane   4h51m   v1.32.10
k8s-node2   Ready    control-plane   4h51m   v1.32.10
k8s-node3   Ready    control-plane   4h51m   v1.32.10
k8s-node4   Ready    <none>          4h50m   v1.32.9
k8s-node5   Ready    <none>          32m     v1.32.10


NAME        STATUS   ROLES           AGE     VERSION
k8s-node1   Ready    control-plane   4h54m   v1.32.10
k8s-node2   Ready    control-plane   4h54m   v1.32.10
k8s-node3   Ready    control-plane   4h54m   v1.32.10
k8s-node4   Ready    <none>          4h53m   v1.32.10
k8s-node5   Ready    <none>          35m     v1.32.10

 

 

k8s ์—…๊ทธ๋ ˆ์ด๋“œ (1.32.10 -> 1.33.7)

์ปจํŠธ๋กค ํ”Œ๋ ˆ์ธ

ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini -v upgrade-cluster.yml -e kube_version="1.33.7" --limit "kube_control_plane:etcd" | tee kubespray_upgrade-2.log
PLAY RECAP *********************************************************************
k8s-node1                  : ok=536  changed=50   unreachable=0    failed=0    skipped=1126 rescued=0    ignored=0
k8s-node2                  : ok=482  changed=36   unreachable=0    failed=0    skipped=995  rescued=0    ignored=0
k8s-node3                  : ok=483  changed=36   unreachable=0    failed=0    skipped=994  rescued=0    ignored=1

Sunday 08 February 2026  05:48:01 +0900 (0:00:00.031)       0:11:02.537 *******
===============================================================================
kubernetes/control-plane : Kubeadm | Upgrade first control plane node to 1.33.7 -- 81.21s
kubernetes/control-plane : Kubeadm | Upgrade other control plane nodes to 1.33.7 -- 77.53s
kubernetes/control-plane : Kubeadm | Upgrade other control plane nodes to 1.33.7 -- 72.58s
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence -- 15.65s
upgrade/pre-upgrade : Drain node --------------------------------------- 14.99s
download : Download_file | Download item ------------------------------- 11.00s
download : Download_container | Download image if required ------------- 10.31s
download : Download_container | Download image if required -------------- 9.18s
etcd : Gen_certs | Write etcd member/admin and kube_control_plane client certs to other etcd nodes --- 7.03s
kubernetes/control-plane : Control plane | wait for the apiserver to be running --- 6.79s
download : Download_file | Download item -------------------------------- 6.16s
etcdctl_etcdutl : Extract_file | Unpacking archive ---------------------- 5.95s
system_packages : Manage packages --------------------------------------- 5.79s
download : Download_file | Download item -------------------------------- 5.63s
download : Download_container | Download image if required -------------- 5.39s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 4.85s
download : Download_container | Download image if required -------------- 4.76s
container-engine/containerd : Containerd | Unpack containerd archive ---- 4.75s
download : Download_container | Download image if required -------------- 4.72s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 4.51s
NAME        STATUS   ROLES           AGE    VERSION
k8s-node1   Ready    control-plane   5h6m   v1.33.7
k8s-node2   Ready    control-plane   5h5m   v1.33.7
k8s-node3   Ready    control-plane   5h5m   v1.33.7
k8s-node4   Ready    <none>          5h5m   v1.32.10
k8s-node5   Ready    <none>          47m    v1.32.10

 

 

์›Œ์ปค ๋…ธ๋“œ

ansible-playbook -i inventory/mycluster/inventory.ini -v upgrade-cluster.yml -e kube_version="1.33.7" --limit "kube_node"
PLAY RECAP *********************************************************************
k8s-node4                  : ok=370  changed=23   unreachable=0    failed=0    skipped=609  rescued=0    ignored=0
k8s-node5                  : ok=348  changed=20   unreachable=0    failed=0    skipped=580  rescued=0    ignored=0

Sunday 08 February 2026  05:52:08 +0900 (0:00:00.039)       0:03:26.512 *******
===============================================================================
upgrade/pre-upgrade : Drain node --------------------------------------- 19.77s
network_plugin/flannel : Flannel | Wait for flannel subnet.env file presence -- 10.67s
download : Download_file | Download item -------------------------------- 5.86s
system_packages : Manage packages --------------------------------------- 5.02s
container-engine/validate-container-engine : Populate service facts ----- 4.69s
download : Download_file | Download item -------------------------------- 3.68s
container-engine/containerd : Containerd | Unpack containerd archive ---- 3.62s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 3.38s
network_plugin/cni : CNI | Copy cni plugins ----------------------------- 2.91s
container-engine/nerdctl : Download_file | Download item ---------------- 2.30s
kubernetes/node : Install | Copy kubelet binary from download dir ------- 2.27s
kubernetes/preinstall : NetworkManager | Prevent NetworkManager from managing K8S interfaces (kube-ipvs0/nodelocaldns) --- 2.25s
container-engine/nerdctl : Extract_file | Unpacking archive ------------- 1.97s
container-engine/containerd : Download_file | Download item ------------- 1.89s
container-engine/runc : Download_file | Download item ------------------- 1.87s
container-engine/crictl : Download_file | Download item ----------------- 1.85s
container-engine/crictl : Extract_file | Unpacking archive -------------- 1.84s
kubernetes/node : Install | Copy kubeadm binary from download dir ------- 1.75s
container-engine/runc : Download_file | Download item ------------------- 1.69s
container-engine/crictl : Extract_file | Unpacking archive -------------- 1.68s
NAME        STATUS   ROLES           AGE     VERSION
k8s-node1   Ready    control-plane   5h10m   v1.33.7
k8s-node2   Ready    control-plane   5h10m   v1.33.7
k8s-node3   Ready    control-plane   5h10m   v1.33.7
k8s-node4   Ready    <none>          5h10m   v1.33.7
k8s-node5   Ready    <none>          51m     v1.33.7