카테고리 보관물: IT

Kubernetes cluster upgrade

개요

Kubernetes는 보안 패치 및 기능 개선을 위해 Kubernetes cluster upgrade를 주기적으로 수행해야 합니다. Kubernetes 업그레이드는 한 번에 한 개의 minor 버전씩만 가능하며, 예를 들어 1.32에서 1.35로 올리려면 1.32 → 1.33 → 1.34 → 1.35 순서로 각 버전을 단계별로 진행해야 합니다.

업그레이드 도구는 kubeadm이며, 진행 순서는 반드시 Control Plane 노드 먼저, 이후 Worker 노드 순차적으로 진행해야 합니다. Control Plane 노드가 여러 개인 HA 구성에서는 각 master 노드를 한 번에 하나씩 업그레이드합니다.

노드 업그레이드 전후에는 kubectl drainkubectl uncordon으로 해당 노드의 스케줄링을 일시 중지하고, 업그레이드 완료 후 다시 활성화합니다. Ubuntu 환경에서는 apt-mark hold로 kubeadm, kubelet, kubectl 패키지를 고정해 두었다가 업그레이드 시점에만 해제하는 방식을 권장합니다.

이 가이드는 Ubuntu 24.04 기반의 단일 Control Plane 클러스터(master 1대, worker 2대)에서 1.32.x → 1.33.7 업그레이드를 진행한 절차를 기록합니다. kubeadm upgrade plan으로 가능한 업그레이드 대상 버전을 확인하고, kubeadm upgrade apply로 Control Plane을 업그레이드한 뒤, 각 Worker 노드에서 kubelet과 kubectl을 교체합니다.

Kubernetes는 기능적/보안적인 이유로 주기적인 Cluster upgrade가 필연적입니다.

현재 버전인 1.32.*에서 1.33.* 버전으로 Upgrade가 목표이며 만일 1.35.* 같은 몇 단계 상위 버전 Upgrade를 계획하더라도 1.33, 1.34, 1.35 버전을 단계별로 순차 진행해야 되는 것은 동일합니다.

Master node가 이중화된 환경은 추가 과정이 있지만 과정 숙달을 목표로 메뉴얼을 기반하여 기본적인 순서로 진행해보겠습니다.

Master Node (Control Plane) 업그레이드

Control Plane 노드에서 kubeadm을 먼저 업그레이드하고, kubeadm upgrade plan으로 가능한 버전을 확인합니다. 이후 kubeadm upgrade apply로 API 서버, etcd, CoreDNS 등 핵심 컴포넌트를 일괄 업그레이드하고, kubelet/kubectl도 새 버전으로 교체합니다.

# 사용 명령어
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

sudo apt update
sudo apt-cache madison kubeadm

sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.33.7-*' && \
sudo apt-mark hold kubeadm

kubeadm version

sudo kubeadm upgrade plan

sudo kubeadm upgrade apply v1.33.7

kubectl drain  --ignore-daemonsets

sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.33.7-*' kubectl='1.33.7-*' && \
sudo apt-mark hold kubelet kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

kubectl uncordon 
# Log
test@:~$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /
test@:~$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
File '/etc/apt/keyrings/kubernetes-apt-keyring.gpg' exists. Overwrite? (y/N) y
test@:~$ sudo apt update
Hit:1 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:2 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:4 https://download.docker.com/linux/ubuntu noble InRelease
Hit:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
230 packages can be upgraded. Run 'apt list --upgradable' to see them.
test@:~$ sudo apt-cache madison kubeadm
   kubeadm | 1.33.7-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.6-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.5-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.4-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.3-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
test@:~$ sudo apt-mark unhold kubeadm && \
> sudo apt-get update && sudo apt-get install -y kubeadm='1.33.7-*' && \
> sudo apt-mark hold kubeadm
kubeadm was already not on hold.
Hit:1 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:2 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:4 https://download.docker.com/linux/ubuntu noble InRelease
Hit:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubeadm'
The following packages will be upgraded:
  kubeadm
1 upgraded, 0 newly installed, 0 to remove and 229 not upgraded.
Need to get 12.7 MB of archives.
After this operation, 3,600 kB of additional disk space will be used.
Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubeadm 1.33.7-1.1 [12.7 MB]
Fetched 12.7 MB in 0s (33.8 MB/s)
(Reading database ... 196041 files and directories currently installed.)
Preparing to unpack .../kubeadm_1.33.7-1.1_amd64.deb ...
Unpacking kubeadm (1.33.7-1.1) over (1.32.7-1.1) ...
Setting up kubeadm (1.33.7-1.1) ...
kubeadm set on hold.
test@:~$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"33", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.33.7", GitCommit:"a7245cdf3f69e11356c7e8f92b3e78ca4ee4e757", GitTreeState:"clean", BuildDate:"2025-12-09T14:41:01Z", GoVersion:"go1.24.11", Compiler:"gc", Platform:"linux/amd64"}
test@:~$ sudo kubeadm upgrade plan
[preflight] Running pre-flight checks.
[upgrade/config] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[upgrade/config] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it.
[upgrade] Running cluster health checks
[upgrade] Fetching available versions to upgrade to
[upgrade/versions] Cluster version: 1.32.7
[upgrade/versions] kubeadm version: v1.33.7
I0113 17:54:44.730671 2116008 version.go:261] remote version is much newer: v1.35.0; falling back to: stable-1.33
[upgrade/versions] Target version: v1.33.7
[upgrade/versions] Latest version in the v1.32 series: v1.32.11

Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   NODE                CURRENT   TARGET
kubelet        v1.32.7   v1.32.11
kubelet        v1.32.7   v1.32.11
kubelet        v1.32.7   v1.32.11

Upgrade to the latest version in the v1.32 series:

COMPONENT                 NODE                CURRENT    TARGET
kube-apiserver               v1.32.7    v1.32.11
kube-controller-manager      v1.32.7    v1.32.11
kube-scheduler               v1.32.7    v1.32.11
kube-proxy                                    1.32.7     v1.32.11
CoreDNS                                       v1.11.3    v1.12.0
etcd                         3.5.16-0   3.5.24-0

You can now apply the upgrade by executing the following command:

        kubeadm upgrade apply v1.32.11

_____________________________________________________________________

Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   NODE                CURRENT   TARGET
kubelet        v1.32.7   v1.33.7
kubelet        v1.32.7   v1.33.7
kubelet        v1.32.7   v1.33.7

Upgrade to the latest stable version:

COMPONENT                 NODE                CURRENT    TARGET
kube-apiserver               v1.32.7    v1.33.7
kube-controller-manager      v1.32.7    v1.33.7
kube-scheduler               v1.32.7    v1.33.7
kube-proxy                                    1.32.7     v1.33.7
CoreDNS                                       v1.11.3    v1.12.0
etcd                         3.5.16-0   3.5.24-0

You can now apply the upgrade by executing the following command:

        kubeadm upgrade apply v1.33.7

_____________________________________________________________________


The table below shows the current state of component configs as understood by this version of kubeadm.
Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or
resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually
upgrade to is denoted in the "PREFERRED VERSION" column.

API GROUP                 CURRENT VERSION   PREFERRED VERSION   MANUAL UPGRADE REQUIRED
kubeproxy.config.k8s.io   v1alpha1          v1alpha1            no
kubelet.config.k8s.io     v1beta1           v1beta1             no
_____________________________________________________________________

test@:~$ sudo kubeadm upgrade apply v1.33.7
[upgrade] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[upgrade] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it.
[upgrade/preflight] Running preflight checks
[upgrade] Running cluster health checks
[upgrade/preflight] You have chosen to upgrade the cluster version to "v1.33.7"
[upgrade/versions] Cluster version: v1.32.7
[upgrade/versions] kubeadm version: v1.33.7
[upgrade] Are you sure you want to proceed? [y/N]: y
[upgrade/preflight] Pulling images required for setting up a Kubernetes cluster
[upgrade/preflight] This might take a minute or two, depending on the speed of your internet connection
[upgrade/preflight] You can also perform this action beforehand using 'kubeadm config images pull'
[upgrade/control-plane] Upgrading your static Pod-hosted control plane to version "v1.33.7" (timeout: 5m0s)...
[upgrade/staticpods] Writing new Static Pod manifests to "/etc/kubernetes/tmp/kubeadm-upgraded-manifests3418553132"
[upgrade/staticpods] Preparing for "etcd" upgrade
[upgrade/staticpods] Renewing etcd-server certificate
[upgrade/staticpods] Renewing etcd-peer certificate
[upgrade/staticpods] Renewing etcd-healthcheck-client certificate
[upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/etcd.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2026-01-13-17-57-46/etcd.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This can take up to 5m0s
[apiclient] Found 1 Pods for label selector component=etcd
[upgrade/staticpods] Component "etcd" upgraded successfully!
[upgrade/etcd] Waiting for etcd to become available
[upgrade/staticpods] Preparing for "kube-apiserver" upgrade
[upgrade/staticpods] Renewing apiserver certificate
[upgrade/staticpods] Renewing apiserver-kubelet-client certificate
[upgrade/staticpods] Renewing front-proxy-client certificate
[upgrade/staticpods] Renewing apiserver-etcd-client certificate
[upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/kube-apiserver.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2026-01-13-17-57-46/kube-apiserver.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This can take up to 5m0s
[apiclient] Found 1 Pods for label selector component=kube-apiserver
[upgrade/staticpods] Component "kube-apiserver" upgraded successfully!
[upgrade/staticpods] Preparing for "kube-controller-manager" upgrade
[upgrade/staticpods] Renewing controller-manager.conf certificate
[upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/kube-controller-manager.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2026-01-13-17-57-46/kube-controller-manager.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This can take up to 5m0s
[apiclient] Found 1 Pods for label selector component=kube-controller-manager
[upgrade/staticpods] Component "kube-controller-manager" upgraded successfully!
[upgrade/staticpods] Preparing for "kube-scheduler" upgrade
[upgrade/staticpods] Renewing scheduler.conf certificate
[upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/kube-scheduler.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2026-01-13-17-57-46/kube-scheduler.yaml"
[upgrade/staticpods] Waiting for the kubelet to restart the component
[upgrade/staticpods] This can take up to 5m0s
[apiclient] Found 1 Pods for label selector component=kube-scheduler
[upgrade/staticpods] Component "kube-scheduler" upgraded successfully!
[upgrade/control-plane] The control plane instance for this node was successfully upgraded!
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster
[upgrade/kubeconfig] The kubeconfig files for this node were successfully upgraded!
W0113 18:01:48.219276 2117131 postupgrade.go:117] Using temporary directory /etc/kubernetes/tmp/kubeadm-kubelet-config1781268223 for kubelet config. To override it set the environment variable KUBEADM_UPGRADE_DRYRUN_DIR
[upgrade] Backing up kubelet config file to /etc/kubernetes/tmp/kubeadm-kubelet-config1781268223/config.yaml
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[upgrade/kubelet-config] The kubelet configuration for this node was successfully upgraded!
[upgrade/bootstrap-token] Configuring bootstrap token and cluster-info RBAC rules
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

[upgrade] SUCCESS! A control plane node of your cluster was upgraded to "v1.33.7".

[upgrade] Now please proceed with upgrading the rest of the nodes by following the right order.
test@:~$ kubectl drain  --ignore-daemonsets
node/ cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/calico-node-km28t, kube-system/kube-proxy-ghl52
evicting pod kube-system/calico-kube-controllers-6d5bc68bd-vv654
pod/calico-kube-controllers-6d5bc68bd-vv654 evicted
node/ drained
test@:~$ kubectl get node -o wide
NAME                STATUS                     ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
   Ready,SchedulingDisabled   control-plane   166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready                      <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready                      <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
test@:~$ sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.33.7-*' kubectl='1.33.7-*' && \
sudo apt-mark hold kubelet kubectl
Canceled hold on kubelet.
Canceled hold on kubectl.
Hit:1 https://download.docker.com/linux/ubuntu noble InRelease
Hit:2 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:4 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Hit:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubelet'
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubectl'
The following package was automatically installed and is no longer required:
  conntrack
Use 'sudo apt autoremove' to remove it.
The following packages will be upgraded:
  kubectl kubelet
2 upgraded, 0 newly installed, 0 to remove and 227 not upgraded.
Need to get 27.6 MB of archives.
After this operation, 7,115 kB of additional disk space will be used.
Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubectl 1.33.7-1.1 [11.7 MB]
Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubelet 1.33.7-1.1 [15.9 MB]
Fetched 27.6 MB in 1s (51.6 MB/s)
(Reading database ... 196041 files and directories currently installed.)
Preparing to unpack .../kubectl_1.33.7-1.1_amd64.deb ...
Unpacking kubectl (1.33.7-1.1) over (1.32.7-1.1) ...
Preparing to unpack .../kubelet_1.33.7-1.1_amd64.deb ...
Unpacking kubelet (1.33.7-1.1) over (1.32.7-1.1) ...
Setting up kubectl (1.33.7-1.1) ...
Setting up kubelet (1.33.7-1.1) ...
kubelet set on hold.
kubectl set on hold.
test@:~$ sudo systemctl daemon-reload
test@:~$ sudo systemctl restart kubelet
test@:~$ kubectl uncordon 
node/ uncordoned
test@:~$ kubectl get node -o wide
NAME                STATUS   ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
   Ready    control-plane   166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready    <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready    <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3

Worker Node 업그레이드 (#1)

Worker 노드 업그레이드는 Control Plane에서 kubectl drain으로 해당 Worker 노드를 비운 뒤 진행합니다. Worker 노드에서 kubeadm/kubelet/kubectl을 새 버전으로 교체하고, kubelet 재시작 후 Control Plane에서 kubectl uncordon으로 복귀시킵니다. 노드가 여러 개인 경우 동일 절차를 반복합니다.

# 사용 명령어
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

sudo apt update
sudo apt-cache madison kubeadm

sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.33.7-*' && \
sudo apt-mark hold kubeadm

kubeadm version

kubectl drain  \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=15m

sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.33.7-*' kubectl='1.33.7-*' && \
sudo apt-mark hold kubelet kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

kubectl uncordon 
# Log
test@:~$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /
test@:~$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
File '/etc/apt/keyrings/kubernetes-apt-keyring.gpg' exists. Overwrite? (y/N) y
test@:~$ sudo apt update
Hit:1 https://download.docker.com/linux/ubuntu noble InRelease
Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease [1,230 B]
Get:3 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  Packages [11.3 kB]
Hit:4 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Get:5 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Get:6 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB]
Get:7 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Get:8 http://kr.archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [1,693 kB]
Get:9 http://kr.archive.ubuntu.com/ubuntu noble-updates/main Translation-en [313 kB]
Get:10 http://kr.archive.ubuntu.com/ubuntu noble-updates/main amd64 Components [175 kB]
Get:11 http://kr.archive.ubuntu.com/ubuntu noble-updates/main amd64 c-n-f Metadata [15.9 kB]
Get:12 http://kr.archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Packages [2,426 kB]
Get:13 http://kr.archive.ubuntu.com/ubuntu noble-updates/restricted Translation-en [554 kB]
Get:14 http://kr.archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Components [212 B]
Get:15 http://kr.archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [1,510 kB]
Get:16 http://kr.archive.ubuntu.com/ubuntu noble-updates/universe amd64 Components [377 kB]
Get:17 http://kr.archive.ubuntu.com/ubuntu noble-updates/universe amd64 c-n-f Metadata [31.4 kB]
Get:18 http://kr.archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Components [940 B]
Get:19 http://kr.archive.ubuntu.com/ubuntu noble-backports/main amd64 Components [7,300 B]
Get:20 http://kr.archive.ubuntu.com/ubuntu noble-backports/restricted amd64 Components [216 B]
Get:21 http://kr.archive.ubuntu.com/ubuntu noble-backports/universe amd64 Components [10.5 kB]
Get:22 http://kr.archive.ubuntu.com/ubuntu noble-backports/multiverse amd64 Components [212 B]
Get:23 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages [1,404 kB]
Get:24 http://security.ubuntu.com/ubuntu noble-security/main Translation-en [228 kB]
Get:25 http://security.ubuntu.com/ubuntu noble-security/main amd64 Components [21.5 kB]
Get:26 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Packages [2,302 kB]
Get:27 http://security.ubuntu.com/ubuntu noble-security/restricted Translation-en [527 kB]
Get:28 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Components [208 B]
Get:29 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Components [71.4 kB]
Get:30 http://security.ubuntu.com/ubuntu noble-security/multiverse amd64 Components [208 B]
Fetched 12.1 MB in 3s (3,513 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
241 packages can be upgraded. Run 'apt list --upgradable' to see them.
test@:~$ sudo apt-cache madison kubeadm
   kubeadm | 1.33.7-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.6-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.5-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.4-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.3-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
test@:~$ sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.33.7-*' && \
sudo apt-mark hold kubeadm
Canceled hold on kubeadm.
Hit:1 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:2 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:4 https://download.docker.com/linux/ubuntu noble InRelease
Hit:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubeadm'
The following packages will be upgraded:
  kubeadm
1 upgraded, 0 newly installed, 0 to remove and 240 not upgraded.
Need to get 12.7 MB of archives.
After this operation, 3,600 kB of additional disk space will be used.
Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubeadm 1.33.7-1.1 [12.7 MB]
Fetched 12.7 MB in 0s (28.4 MB/s)
(Reading database ... 196036 files and directories currently installed.)
Preparing to unpack .../kubeadm_1.33.7-1.1_amd64.deb ...
Unpacking kubeadm (1.33.7-1.1) over (1.32.7-1.1) ...
Setting up kubeadm (1.33.7-1.1) ...
kubeadm set on hold.
test@:~$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"33", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.33.7", GitCommit:"a7245cdf3f69e11356c7e8f92b3e78ca4ee4e757", GitTreeState:"clean", BuildDate:"2025-12-09T14:41:01Z", GoVersion:"go1.24.11", Compiler:"gc", Platform:"linux/amd64"}

test@:~$ kubectl drain  \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=15m
node/ cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/calico-node-fdff8, kube-system/kube-proxy-2rz9d
evicting pod kube-system/coredns-674b8bbfcf-7jxkk
pod/coredns-674b8bbfcf-7jxkk evicted
node/ drained
test@:~$ kubectl get node -o wide
NAME                STATUS                     ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
   Ready                      control-plane   166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready,SchedulingDisabled   <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready                      <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3

test@:~$ sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.33.7-*' kubectl='1.33.7-*' && \
sudo apt-mark hold kubelet kubectl
Canceled hold on kubelet.
Canceled hold on kubectl.
Hit:1 https://download.docker.com/linux/ubuntu noble InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:4 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:5 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubelet'
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubectl'
The following package was automatically installed and is no longer required:
  conntrack
Use 'sudo apt autoremove' to remove it.
The following packages will be upgraded:
  kubectl kubelet
2 upgraded, 0 newly installed, 0 to remove and 238 not upgraded.
Need to get 27.6 MB of archives.
After this operation, 7,115 kB of additional disk space will be used.
Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubectl 1.33.7-1.1 [11.7 MB]
Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubelet 1.33.7-1.1 [15.9 MB]
Fetched 27.6 MB in 1s (49.7 MB/s)
(Reading database ... 196036 files and directories currently installed.)
Preparing to unpack .../kubectl_1.33.7-1.1_amd64.deb ...
Unpacking kubectl (1.33.7-1.1) over (1.32.7-1.1) ...
Preparing to unpack .../kubelet_1.33.7-1.1_amd64.deb ...
Unpacking kubelet (1.33.7-1.1) over (1.32.7-1.1) ...
Setting up kubectl (1.33.7-1.1) ...
Setting up kubelet (1.33.7-1.1) ...
kubelet set on hold.
kubectl set on hold.
test@:~$ sudo systemctl daemon-reload
test@:~$ sudo systemctl restart kubelet
test@:~$ dpkg -l | grep kube
hi  kubeadm                                       1.33.7-1.1                               amd64        Command-line utility for administering a Kubernetes cluster
hi  kubectl                                       1.33.7-1.1                               amd64        Command-line utility for interacting with a Kubernetes cluster
hi  kubelet                                       1.33.7-1.1                               amd64        Node agent for Kubernetes clusters
ii  kubernetes-cni                                1.6.0-1.1                                amd64        Binaries required to provision kubernetes container networking

test@:~$ kubectl uncordon 
node/ uncordoned
test@:~$ kubectl get node -o wide
NAME                STATUS   ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
   Ready    control-plane   166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready    <none>          166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready    <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3

Worker Node 업그레이드 (#2)

Worker 노드 #1과 동일한 절차를 Worker 노드 #2에 반복합니다. Control Plane에서 kubectl drain <worker-node-02>로 노드를 비우고, Worker 노드에서 kubeadm/kubelet/kubectl을 업그레이드한 뒤 kubectl uncordon으로 복귀시킵니다. 모든 Worker 노드 업그레이드 완료 후 kubectl get nodes -o wide로 전체 노드 버전이 동일한지 확인합니다.

# 사용 명령어
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

sudo apt update
sudo apt-cache madison kubeadm

sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.33.7-*' && \
sudo apt-mark hold kubeadm

kubeadm version

kubectl drain  \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=15m

sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.33.7-*' kubectl='1.33.7-*' && \
sudo apt-mark hold kubelet kubectl

sudo systemctl daemon-reload
sudo systemctl restart kubelet

kubectl uncordon 
# Log
test@:~$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /
test@:~$ curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
File '/etc/apt/keyrings/kubernetes-apt-keyring.gpg' exists. Overwrite? (y/N) y
test@:~$ sudo apt update
Hit:1 https://download.docker.com/linux/ubuntu noble InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease [1,230 B]
Get:4 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Get:5 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB]
Get:6 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Get:7 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  Packages [11.3 kB]
Get:8 http://kr.archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [1,693 kB]
Get:9 http://kr.archive.ubuntu.com/ubuntu noble-updates/main Translation-en [313 kB]
Get:10 http://kr.archive.ubuntu.com/ubuntu noble-updates/main amd64 Components [175 kB]
Get:11 http://kr.archive.ubuntu.com/ubuntu noble-updates/main amd64 c-n-f Metadata [15.9 kB]
Get:12 http://kr.archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Packages [2,426 kB]
Get:13 http://kr.archive.ubuntu.com/ubuntu noble-updates/restricted Translation-en [554 kB]
Get:14 http://kr.archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Components [212 B]
Get:15 http://kr.archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [1,510 kB]
Get:16 http://kr.archive.ubuntu.com/ubuntu noble-updates/universe amd64 Components [377 kB]
Get:17 http://kr.archive.ubuntu.com/ubuntu noble-updates/universe amd64 c-n-f Metadata [31.4 kB]
Get:18 http://kr.archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Components [940 B]
Get:19 http://kr.archive.ubuntu.com/ubuntu noble-backports/main amd64 Components [7,300 B]
Get:20 http://kr.archive.ubuntu.com/ubuntu noble-backports/restricted amd64 Components [216 B]
Get:21 http://kr.archive.ubuntu.com/ubuntu noble-backports/universe amd64 Components [10.5 kB]
Get:22 http://kr.archive.ubuntu.com/ubuntu noble-backports/multiverse amd64 Components [212 B]
Get:23 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages [1,404 kB]
Get:24 http://security.ubuntu.com/ubuntu noble-security/main Translation-en [228 kB]
Get:25 http://security.ubuntu.com/ubuntu noble-security/main amd64 Components [21.5 kB]
Get:26 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Packages [2,302 kB]
Get:27 http://security.ubuntu.com/ubuntu noble-security/restricted Translation-en [527 kB]
Get:28 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Components [208 B]
Get:29 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Components [71.4 kB]
Get:30 http://security.ubuntu.com/ubuntu noble-security/universe amd64 c-n-f Metadata [19.7 kB]
Get:31 http://security.ubuntu.com/ubuntu noble-security/multiverse amd64 Components [208 B]
Fetched 12.1 MB in 4s (3,410 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
229 packages can be upgraded. Run 'apt list --upgradable' to see them.
test@:~$ sudo apt-cache madison kubeadm
   kubeadm | 1.33.7-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.6-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.5-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.4-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.3-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
   kubeadm | 1.33.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.33/deb  Packages
test@:~$ sudo apt-mark unhold kubeadm && \
sudo apt-get update && sudo apt-get install -y kubeadm='1.33.7-*' && \
sudo apt-mark hold kubeadm
Canceled hold on kubeadm.
Hit:1 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:2 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:4 https://download.docker.com/linux/ubuntu noble InRelease
Hit:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubeadm'
The following packages will be upgraded:
  kubeadm
1 upgraded, 0 newly installed, 0 to remove and 228 not upgraded.
Need to get 12.7 MB of archives.
After this operation, 3,600 kB of additional disk space will be used.
Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubeadm 1.33.7-1.1 [12.7 MB]
Fetched 12.7 MB in 1s (23.5 MB/s)
(Reading database ... 196021 files and directories currently installed.)
Preparing to unpack .../kubeadm_1.33.7-1.1_amd64.deb ...
Unpacking kubeadm (1.33.7-1.1) over (1.32.7-1.1) ...
Setting up kubeadm (1.33.7-1.1) ...
kubeadm set on hold.
test@:~$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"33", EmulationMajor:"", EmulationMinor:"", MinCompatibilityMajor:"", MinCompatibilityMinor:"", GitVersion:"v1.33.7", GitCommit:"a7245cdf3f69e11356c7e8f92b3e78ca4ee4e757", GitTreeState:"clean", BuildDate:"2025-12-09T14:41:01Z", GoVersion:"go1.24.11", Compiler:"gc", Platform:"linux/amd64"}

test@:~$ kubectl drain  \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=15m
node/ cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/calico-node-drhtt, kube-system/kube-proxy-hpkd8
evicting pod kube-system/coredns-674b8bbfcf-f6txf
evicting pod kube-system/calico-kube-controllers-6d5bc68bd-7mppz
pod/calico-kube-controllers-6d5bc68bd-7mppz evicted
pod/coredns-674b8bbfcf-f6txf evicted
node/ drained
test@:~$ kubectl get node -o wide
NAME                STATUS                     ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
   Ready                      control-plane   166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready                      <none>          166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready,SchedulingDisabled   <none>          166d   v1.32.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3

test@:~$ sudo apt-mark unhold kubelet kubectl && \
sudo apt-get update && sudo apt-get install -y kubelet='1.33.7-*' kubectl='1.33.7-*' && \
sudo apt-mark hold kubelet kubectl
Canceled hold on kubelet.
Canceled hold on kubectl.
Hit:1 http://kr.archive.ubuntu.com/ubuntu noble InRelease
Hit:2 http://kr.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:3 http://kr.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:4 https://download.docker.com/linux/ubuntu noble InRelease
Hit:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubelet'
Selected version '1.33.7-1.1' (isv:kubernetes:core:stable:v1.33:pkgs.k8s.io [amd64]) for 'kubectl'
The following package was automatically installed and is no longer required:
  conntrack
Use 'sudo apt autoremove' to remove it.
The following packages will be upgraded:
  kubectl kubelet
2 upgraded, 0 newly installed, 0 to remove and 226 not upgraded.
Need to get 27.6 MB of archives.
After this operation, 7,115 kB of additional disk space will be used.
Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubectl 1.33.7-1.1 [11.7 MB]
Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb  kubelet 1.33.7-1.1 [15.9 MB]
Fetched 27.6 MB in 1s (47.2 MB/s)
(Reading database ... 196021 files and directories currently installed.)
Preparing to unpack .../kubectl_1.33.7-1.1_amd64.deb ...
Unpacking kubectl (1.33.7-1.1) over (1.32.7-1.1) ...
Preparing to unpack .../kubelet_1.33.7-1.1_amd64.deb ...
Unpacking kubelet (1.33.7-1.1) over (1.32.7-1.1) ...
Setting up kubectl (1.33.7-1.1) ...
Setting up kubelet (1.33.7-1.1) ...
kubelet set on hold.
kubectl set on hold.
test@:~$ sudo systemctl daemon-reload
test@:~$ sudo systemctl restart kubelet
test@:~$ dpkg -l | grep kube
hi  kubeadm                                       1.33.7-1.1                               amd64        Command-line utility for administering a Kubernetes cluster
hi  kubectl                                       1.33.7-1.1                               amd64        Command-line utility for interacting with a Kubernetes cluster
hi  kubelet                                       1.33.7-1.1                               amd64        Node agent for Kubernetes clusters
ii  kubernetes-cni                                1.6.0-1.1                                amd64        Binaries required to provision kubernetes container networking

test@:~$ kubectl uncordon 
node/ uncordoned
test@:~$ kubectl get node -o wide
NAME                STATUS   ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
   Ready    control-plane   166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready    <none>          166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3
   Ready    <none>          166d   v1.33.7   x.x.x.x   <none>        Ubuntu 24.04.2 LTS   6.14.0-37-generic   docker://28.3.3

업그레이드 주의사항

Kubernetes 클러스터 업그레이드 시 주의해야 할 사항을 정리합니다.

  • 버전 건너뛰기 불가 — minor 버전은 반드시 한 단계씩 업그레이드해야 합니다. 예를 들어 1.31 → 1.33으로 직접 업그레이드할 수 없습니다.
  • etcd 백업 선행 — Control Plane 업그레이드 전 etcd 스냅샷을 백업해 두면 문제 발생 시 롤백이 가능합니다.
  • drain 전 PodDisruptionBudget 확인kubectl drain 시 PDB(PodDisruptionBudget)에 의해 eviction이 차단될 수 있습니다. --delete-emptydir-data--grace-period 옵션을 적절히 설정합니다.
  • CNI 플러그인 호환성 확인 — Calico, Flannel 등 CNI 플러그인이 업그레이드 대상 Kubernetes 버전과 호환되는지 릴리스 노트를 미리 확인합니다.
  • HA 클러스터 — master 노드가 여러 개인 경우 한 번에 하나씩 업그레이드하며, 각 master 업그레이드 후 etcd quorum이 유지되는지 확인합니다.

참고

관련 포스트:

참고 문서: kubeadm upgrade 공식 문서 (kubernetes.io) · Kubernetes 패키지 저장소 변경 가이드

Ceph MDS Pod Anti-Affinity Troubleshoot

개요

Kubernetes 클러스터에서 노드 업그레이드나 drain 작업을 수행할 때, Ceph MDS Pod Anti-Affinity 설정이 없으면 MDS 파드 두 개가 동일한 워커 노드에 집중 배치되는 문제가 발생할 수 있습니다. 이 글에서는 node drain 후 MDS 파드가 한 노드에 몰린 상황을 확인하고, CephFilesystem 리소스에 podAntiAffinity 규칙을 적용하여 MDS 파드를 분산 배치하는 과정을 정리합니다. 이어서 함께 발생한 mgr crash 알람 처리 방법도 설명합니다.

문제 상황

워커 노드 1번에 대해 drain을 수행한 후 Ceph 상태를 확인하면 HEALTH_WARN이 발생하고, MDS 파드 2개가 모두 워커 노드 2번에서 기동 중임을 확인할 수 있습니다.

# ceph 상태 확인
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph status
  cluster:
    id:     d874b4ea-8deb-4aa3-a3ac-e750180a6a5b
    health: HEALTH_WARN
            4 mgr modules have recently crashed

  services:
    mon: 3 daemons, quorum a,b,c (age 10h)
    mgr: b(active, since 5M), standbys: a
    mds: 1/1 daemons up, 1 hot standby
    osd: 3 osds: 3 up (since 10h), 3 in (since 18M)

# MDS pod 위치 확인 — 두 파드 모두 test-worker-02에 집중
test@test-master-01:~$ kubectl -n rook-ceph get pod -o wide | egrep 'mds'
rook-ceph-mds-myfs-a-77d484dc4-jddf9  2/2  Running  0  18s  172.16.x.x  test-worker-02  <none>  <none>
rook-ceph-mds-myfs-b-bd6ddc59b-l2b4t  2/2  Running  0  18s  172.16.x.x  test-worker-02  <none>  <none>

원인 분석

ceph fs status에서 확인하면 active MDS와 standby-replay MDS 모두 정상 동작 중이지만, 두 파드가 같은 노드에 배치되어 있어 해당 노드에 장애가 발생하면 CephFS 서비스 전체가 중단될 위험이 있습니다. Anti-Affinity 규칙이 설정되어 있지 않으면 Kubernetes 스케줄러가 가용 자원이 충분한 노드에 임의로 배치하기 때문에 이런 현상이 발생합니다.

# cephFS 상태 확인
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph fs status
myfs - 2 clients
====
RANK      STATE          MDS     ACTIVITY     DNS    INOS   DIRS   CAPS
 0        active        myfs-b  Reqs:  0 /s  35.9k  18.0k  4301      2
0-s   standby-replay   myfs-a  Evts:  0 /s  35.9k  18.0k  4301      0
MDS version: ceph version 18.2.2 reef (stable)

Ceph MDS Pod Anti-Affinity 적용

CephFilesystem 리소스에 podAntiAffinity를 설정하면 동일 레이블의 MDS 파드가 같은 노드에 배치되지 않도록 강제할 수 있습니다. requiredDuringSchedulingIgnoredDuringExecution을 사용하면 조건을 만족하지 못할 경우 파드가 아예 스케줄링되지 않으므로 강하게 분산을 보장합니다.

# CephFilesystem에 podAntiAffinity 패치 적용
test@test-master-01:~$ kubectl -n rook-ceph patch cephfilesystem myfs --type='merge' -p '
spec:
  metadataServer:
    placement:
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values: ["rook-ceph-mds"]
            - key: rook_file_system
              operator: In
              values: ["myfs"]
          topologyKey: kubernetes.io/hostname
'
cephfilesystem.ceph.rook.io/myfs patched

패치 후 kubectl -n rook-ceph get cephfilesystem myfs -o yaml에서 spec.metadataServer.placement.podAntiAffinity 항목이 반영되었는지 확인합니다.

결과 확인

워커 노드 1번을 uncordon하고 워커 노드 2번을 drain하면 MDS 파드가 Ceph MDS Pod Anti-Affinity 규칙에 따라 서로 다른 노드(worker-01, worker-03)에 분산 배치됩니다.

# Anti-Affinity 적용 후 MDS 파드 분산 확인
test@test-master-01:~$ kubectl -n rook-ceph get pod -o wide | egrep 'mds'
rook-ceph-mds-myfs-a-58846844d6-nd5mk  2/2  Running  0  53s  172.16.x.x  test-worker-01  <none>  <none>
rook-ceph-mds-myfs-b-6b4d9476cb-q6b6p  2/2  Running  0  38s  172.16.x.x  test-worker-03  <none>  <none>

# cephFS 상태 정상 확인
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph fs status
myfs - 2 clients
====
RANK      STATE          MDS     ACTIVITY     DNS    INOS   DIRS   CAPS
 0        active        myfs-a  Reqs:  0 /s  35.9k  18.0k  4301      2
0-s   standby-replay   myfs-b  Evts:  0 /s  35.9k  18.0k  4301      0
MDS version: ceph version 18.2.2 reef (stable)

mgr crash 알람 처리

MDS 분산 이후에도 4 mgr modules have recently crashed 알람이 남아 있을 수 있습니다. 이는 MDS 이슈와 무관하게 mgr 파드가 재시작되며 발생한 crash 이력으로, ceph mgr stat에서 available: true이면 서비스는 정상입니다. ceph crash archive-all로 이력을 정리하면 알람이 해소됩니다.

# mgr 상태 정상 확인 (available: true)
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph mgr stat
{
    "epoch": 476,
    "available": true,
    "active_name": "b",
    "num_standby": 1
}

# crash 이력 목록 확인
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph crash ls
ID                                                                ENTITY  NEW
2025-12-26T09:01:17.354121Z_c76c6eaf-4bf7-4cf9-a9ec-f646fe857b76  mgr.b    *
2025-12-26T09:01:32.345473Z_4dfd271c-3d5b-4c89-88cf-13ba096f327b  mgr.b    *
2025-12-26T09:01:47.357321Z_0f938fb6-4c50-4b58-815d-5990fbe4bbb7  mgr.b    *
2025-12-26T09:02:02.329492Z_43d344a7-b71f-442e-a664-1852dda3a3f3  mgr.b    *

# crash 이력 아카이브 후 HEALTH_OK 확인
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph crash archive-all
test@test-master-01:~$ kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph status
  cluster:
    health: HEALTH_OK
  services:
    mds: 1/1 daemons up, 1 hot standby
    osd: 3 osds: 3 up, 3 in

node drain / uncordon 작업 중 일시적으로 mon quorum 이탈이나 rebalancing이 발생할 수 있으나, 일정 시간 후 재확인하면 HEALTH_OK 상태로 복구됩니다.

참고

Ceph 관련 운영 내용은 아래 포스트도 참고하시기 바랍니다.

참고 문서: Rook CephFilesystem CRD 공식 문서 · Kubernetes Pod Anti-Affinity 공식 문서

ceph dashboard OSD alarm troubleshoot

개요

Rook-Ceph 운영 중 Ceph OSD down 알람 처리가 필요한 상황이 발생할 수 있습니다. Kubernetes 클러스터에서 Rook-Ceph를 운영하다 보면 대시보드에 OSD down 경고가 표시되는 경우가 있습니다. 이 글에서는 osd.3이 host 지정 없이 down 상태로 클러스터에 등록된 원인을 분석하고, 불필요한 OSD를 안전하게 제거하여 클러스터를 정상 상태로 복구하는 과정을 단계별로 정리합니다.

문제 상황

Ceph 대시보드에서 OSD 1 down 알람이 발생하였습니다.

Ceph 대시보드 OSD down 알람 경고 화면

ceph status를 확인하면 osd: 4 osds: 3 up으로 OSD 4개 중 3개만 정상 동작 중이며 HEALTH_WARN 상태입니다. 클러스터 용량(90 GiB) 대비 사용량(7.4 GiB)은 정상이며, 함께 표시된 mon b is low on available space는 Monitor 노드의 디스크 공간 문제로 OSD down 이슈와는 별개입니다.

bash-4.4$ ceph status
  cluster:
    id:     d874b4ea-8deb-4aa3-a3ac-e750180a6a5b
    health: HEALTH_WARN
            mon b is low on available space

  services:
    mon: 3 daemons, quorum a,b,c (age 4M)
    mgr: b(active, since 4M), standbys: a
    mds: 1/1 daemons up, 1 hot standby
    osd: 4 osds: 3 up (since 4M), 3 in (since 13M)

  data:
    volumes: 1/1 healthy
    pools: 5 pools, 113 pgs
    objects: 20.17k objects, 871 MiB
    usage: 7.4 GiB used, 83 GiB / 90 GiB avail
    pgs: 113 active+clean

bash-4.4$ ceph osd status
ID  HOST           USED  AVAIL  WR OPS  WR DATA  RD OPS  RD DATA  STATE
 0  <worker-node>  2515M  27.5G      0        0       1       90   exists,up
 1  <worker-node>  2511M  27.5G      0        0       0        0   exists,up
 2  <worker-node>  2544M  27.5G      0        0       1       16   exists,up
 3                     0      0      0        0       0        0   autoout,exists,new

원인 분석

ceph osd tree 결과를 보면 osd.3이 host 정보 없이 autoout, exists, new 상태로 등록되어 있습니다. 워커 노드 3대(<worker-node>/02/03)에 OSD가 각각 1개씩 정상 배치(osd.0~2)되어 있는 반면, osd.3은 노드에 연결되지 않은 채 클러스터에만 등록된 고아(orphan) 상태입니다. 노드 수보다 OSD 수가 많아진 경우 이런 현상이 발생할 수 있으며, 수동으로 제거해야 합니다.

bash-4.4$ ceph osd tree
ID  CLASS  WEIGHT   TYPE NAME            STATUS  REWEIGHT  PRI-AFF
-1         0.08789  root default
-3         0.02930      host <worker-node>
 0   ssd   0.02930          osd.0            up   1.00000  1.00000
-5         0.02930      host <worker-node>
 1   ssd   0.02930          osd.1            up   1.00000  1.00000
-7         0.02930      host <worker-node>
 2   ssd   0.02930          osd.2            up   1.00000  1.00000
 3            0               osd.3          down       0  1.00000

처리 절차

불필요한 OSD를 제거할 때는 반드시 CRUSH map 제거 → CephX 인증 키 삭제 → OSD 항목 삭제 순서로 진행합니다. 순서를 지키지 않으면 클러스터에 인증 키나 항목이 잔류하여 이후에도 알람이 발생할 수 있습니다.

# ① CRUSH map에서 제거 (이미 등록되지 않은 경우 아래 메시지가 출력되며 정상)
bash-4.4$ ceph osd crush remove osd.3
device 'osd.3' does not appear in the crush map

# ② CephX 인증 키 삭제
bash-4.4$ ceph auth del osd.3

# ③ OSD 항목 최종 삭제
bash-4.4$ ceph osd rm 3
removed osd.3
  • ceph osd crush remove: 데이터 배치 알고리즘인 CRUSH map에서 OSD를 제거합니다. 처음부터 CRUSH map에 등록되지 않았다면 “does not appear” 메시지가 출력되며 이는 정상입니다.
  • ceph auth del: 해당 OSD에 부여된 CephX 인증 키를 삭제합니다.
  • ceph osd rm: 클러스터 OSD 목록에서 항목을 최종 삭제합니다.

결과 확인

OSD 제거 후 ceph osd tree를 다시 확인하면 osd.3이 사라지고 워커 노드당 OSD 1개씩 총 3개가 정상 배치된 것을 확인할 수 있습니다. 이로써 Ceph OSD down 알람 처리가 완료되고 클러스터가 정상 상태로 복구되었습니다.

bash-4.4$ ceph osd tree
ID  CLASS  WEIGHT   TYPE NAME            STATUS  REWEIGHT  PRI-AFF
-1         0.08789  root default
-3         0.02930      host <worker-node>
 0   ssd   0.02930          osd.0            up   1.00000  1.00000
-5         0.02930      host <worker-node>
 1   ssd   0.02930          osd.1            up   1.00000  1.00000
-7         0.02930      host <worker-node>
 2   ssd   0.02930          osd.2            up   1.00000  1.00000

Ceph 관련 운영 내용은 아래 포스트도 참고하시기 바랍니다.

참고 문서: Rook Ceph OSD Management · Ceph OSD Operations (공식 문서)

ceph storage class 사용 wordpress, mysql pod 생성

개요

Rook-Ceph 구축 이후 Ceph StorageClass를 활용하면 Kubernetes Pod에서 PVC(PersistentVolumeClaim)를 통해 Ceph RBD(RADOS Block Device) 기반의 영구 볼륨을 동적으로 프로비저닝할 수 있습니다. Pod가 삭제되거나 재스케줄링되어도 데이터가 Ceph에 안전하게 보존됩니다.

이 글에서는 Ceph RBD StorageClass를 생성하고, PVC가 포함된 MySQL과 WordPress Deployment를 배포하는 전체 절차를 정리합니다. MetalLB LoadBalancer를 통해 WordPress에 외부 IP가 할당되어 실제 서비스 접근까지 확인합니다. 이 구성은 Rook-Ceph 구축이 완료된 환경을 전제합니다.

사전 준비

이 절차를 진행하기 전에 다음 구성 요소가 준비되어 있어야 합니다.

  • Rook-Ceph 클러스터가 정상 기동 중 (ceph status에서 HEALTH_OK)
  • MetalLB가 구성되어 있고 LoadBalancer IP 풀이 설정된 상태
  • Rook 저장소 클론이 로컬에 존재 (rook/deploy/examples/ 디렉토리 필요)

Rook 예제 디렉토리에는 mysql.yaml, wordpress.yaml, csi/rbd/storageclass.yaml이 포함되어 있습니다. 이 파일들을 기반으로 StorageClass를 생성하고 WordPress와 MySQL을 배포합니다.

Ceph StorageClass 생성

Rook 저장소의 csi/rbd/storageclass.yaml을 적용하면 rook-ceph-block StorageClass가 생성됩니다. 이후 Pod에서 storageClassName: rook-ceph-block으로 PVC를 요청하면 Ceph RBD 볼륨이 자동으로 생성·마운트됩니다.

StorageClass의 reclaimPolicy: Delete는 PVC 삭제 시 볼륨도 함께 삭제됨을 의미합니다. 데이터를 보존해야 하는 경우 Retain으로 변경해야 합니다. allowVolumeExpansion: true로 설정되어 있어 운영 중에도 PVC의 용량을 늘릴 수 있습니다.

kubectl apply -f csi/rbd/storageclass.yaml

kubectl get storageclass
NAME              PROVISIONER                  RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION
rook-ceph-block   rook-ceph.rbd.csi.ceph.com   Delete          Immediate           true
Ceph RBD StorageClass 적용 확인

MySQL Deployment 배포 (PVC 포함)

Service, PersistentVolumeClaim, Deployment가 포함된 MySQL manifest를 배포합니다. PVC가 생성되면 Rook CSI 드라이버가 Ceph 클러스터에 RBD 이미지를 자동으로 생성하고, kubelet이 해당 볼륨을 Pod에 마운트합니다. PVC Status가 Bound이면 Ceph RBD 볼륨이 정상 마운트된 것입니다.

MySQL Deployment의 환경변수(MYSQL_ROOT_PASSWORD 등)는 실제 운영 환경에서 Secret 리소스로 분리하는 것을 권장합니다. 또한 MySQL 데이터 디렉토리(/var/lib/mysql)를 PVC에 마운트하므로, Pod를 재시작하거나 노드가 변경되어도 데이터가 보존됩니다.

kubectl create -f mysql.yaml
service/wordpress-mysql created
persistentvolumeclaim/mysql-pv-claim created
deployment.apps/wordpress-mysql created

kubectl get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mysql-pv-claim   Bound    pvc-6d458ff1-54bf-4f34-b010-a0f4b2a0966e  20Gi       RWO            rook-ceph-block   94s

WordPress Deployment 배포 (PVC 포함)

MySQL과 동일하게 Service, PVC, Deployment가 포함된 WordPress manifest를 배포합니다. WordPress는 업로드 파일 등을 /var/www/html에 저장하는데, 이 디렉토리가 PVC에 마운트되어 Pod 재시작 후에도 데이터가 유지됩니다. WordPress 서비스 타입은 LoadBalancer로 설정되어 MetalLB로부터 외부 IP를 할당받습니다.

kubectl create -f wordpress.yaml
service/wordpress created
persistentvolumeclaim/wp-pv-claim created
deployment.apps/wordpress created

kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
wordpress-7cf5c5c8b-5cgqk          1/1     Running   0          42s
wordpress-mysql-6f99c59595-9vs7z   1/1     Running   0          4m8s

MetalLB로 외부 접근 확인

MetalLB가 구성된 환경에서는 WordPress Service에 외부 IP가 자동 할당됩니다. 해당 IP로 브라우저에서 접속하면 WordPress 설치 화면이 표시되며, Ceph 기반 영구 스토리지 구성이 완료된 것입니다.

이 구성에서 WordPress와 MySQL은 서로 다른 Ceph RBD PVC를 사용합니다. 스토리지 레이어가 Ceph로 추상화되어 있기 때문에, 향후 WordPress나 MySQL Pod를 다른 Worker 노드로 재스케줄링해도 동일한 데이터에 접근할 수 있습니다. PVC의 accessMode: ReadWriteOnce(RWO) 특성상 한 번에 한 Pod만 볼륨에 마운트할 수 있으며, 수평 확장이 필요한 경우에는 CephFS(RWX)를 사용해야 합니다.

kubectl get svc wordpress
NAME        TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
wordpress   LoadBalancer   x.x.x.x         x.x.x.x       80:31494/TCP   27s

배포 결과 및 볼륨 확인

모든 리소스 배포 후 아래 명령어로 최종 상태를 확인합니다. PVC가 Bound, Pod가 Running, Service에 외부 IP가 할당된 것이 정상 상태입니다.

# PVC 상태 확인 (Bound이면 정상)
kubectl get pvc

# Pod 상태 확인
kubectl get pods

# WordPress Service 외부 IP 확인
kubectl get svc wordpress

# Ceph에서 실제 RBD 이미지 확인 (Toolbox Pod 내에서)
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash
rbd ls replicapool

PVC가 Pending 상태에서 멈추는 경우 주요 원인으로는 Ceph 클러스터 상태 이상(HEALTH_WARN 이상), StorageClass 미적용, 또는 Rook CSI 드라이버 Pod 이상이 있습니다. kubectl describe pvc <pvc-name>으로 이벤트를 확인하고 조치합니다.

참고

관련 포스트:

참고 문서: Rook Ceph Block Storage 공식 문서 · Kubernetes Persistent Volumes 공식 문서

ceph 구축

개요

Rook은 Kubernetes용 스토리지 오케스트레이터로, Ceph 구축을 Kubernetes 네이티브 방식으로 자동화합니다. Rook-Ceph를 사용하면 Worker 노드에 연결된 Raw Disk를 기반으로 분산 오브젝트 스토리지를 구성하고, Kubernetes PersistentVolume(PV)을 동적으로 프로비저닝할 수 있습니다. Pod가 PersistentVolumeClaim(PVC)을 요청하면 Ceph RBD(Block Device) 또는 CephFS를 통해 영구 저장소가 자동으로 할당됩니다.

이 글에서는 Rook Operator를 배포하고 Ceph Cluster를 구성하는 전체 절차를 정리합니다. 최소 Worker 노드 3개와 각 노드에 파티션 또는 포맷이 되지 않은 Raw Disk가 필요합니다.

사전 준비 — Worker 노드 Raw Disk 확인

Ceph 스토리지는 각 Worker 노드에 할당된 초기화된(파티션/포맷 없는) Raw Disk를 OSD(Object Storage Daemon)로 사용합니다. 아래는 Worker 노드에 할당된 Raw Device 목록입니다.

OSD 외에도 Ceph는 여러 핵심 컴포넌트로 구성됩니다. MON(Monitor)은 클러스터 상태 맵을 관리하며 Quorum을 형성하기 위해 홀수(최소 3개)로 배포합니다. MGR(Manager)은 Ceph 대시보드와 메트릭 수집을 담당합니다. Rook Operator는 이 모든 컴포넌트를 Kubernetes CRD로 선언적으로 관리합니다.

Worker 노드에 할당된 raw device 목록

Rook Operator 배포

Rook 공식 저장소에서 특정 버전을 클론한 뒤, CRD(Custom Resource Definition), 공통 리소스, Operator를 순서대로 배포합니다. Operator Pod가 정상 기동된 것을 확인한 뒤 Ceph Cluster를 배포합니다.

crds.yaml은 CephCluster, CephBlockPool 등 Rook이 사용하는 커스텀 리소스 타입을 등록합니다. common.yaml은 RBAC, ServiceAccount 등 공통 리소스를 생성하며, operator.yaml은 Rook Operator Deployment를 배포합니다. Operator는 rook-ceph 네임스페이스에서 실행되며, 이후 적용되는 CephCluster CR을 감지하여 Ceph 컴포넌트를 자동으로 생성합니다.

git clone --single-branch --branch v1.10.10 https://github.com/rook/rook.git
cd rook/deploy/examples

kubectl create -f crds.yaml -f common.yaml -f operator.yaml

# Operator Pod 기동 확인
kubectl -n rook-ceph logs -l app=rook-ceph-operator -f

Ceph Cluster 배포

cluster.yaml을 적용하면 Rook Operator가 OSD, MON, MGR 등 Ceph 컴포넌트를 자동으로 생성합니다. 초기 배포 시 Raw Disk를 OSD로 포맷하는 작업이 포함되어 있어 5~10분 정도 소요될 수 있습니다.

기본 cluster.yaml은 클러스터 내 모든 Raw Disk를 자동으로 검색하여 OSD로 사용합니다. 특정 디바이스만 사용하려면 spec.storage.nodes에서 노드별로 사용할 디바이스를 명시해야 합니다. spec.mon.count: 3으로 MON을 3개 배포하여 Quorum을 유지하고, Worker 노드가 3개 미만이면 allowMultiplePerNode: true를 설정해야 합니다.

kubectl create -f cluster.yaml

# 배포 진행 상태 모니터링
kubectl -n rook-ceph get pods -w

Pod 상태 확인

Ceph 구축이 완료되면 rook-ceph 네임스페이스에 OSD, MON, MGR Pod가 Running 상태로 기동됩니다. OSD Pod 수는 사용 가능한 Raw Disk 수와 일치하며, MON은 3개(또는 설정값), MGR은 1개(액티브) + 1개(스탠바이)가 기동됩니다.

Ceph 구축 완료 후 rook-ceph 네임스페이스 Pod 상태

Ceph 클러스터 상태 확인

Rook Toolbox Pod를 배포하면 ceph status, ceph osd tree 등 Ceph 관리 명령어를 직접 실행할 수 있습니다. ceph status 출력에서 HEALTH_OK가 표시되면 Ceph 구축이 정상 완료된 것입니다.

HEALTH_WARN이 표시되는 경우 주요 원인으로는 OSD 개수가 PG(Placement Group) 비율에 맞지 않는 경우, CLOCK_SKEW(노드 간 시간 차이), 또는 OSD가 아직 초기화 중인 경우가 있습니다. ceph health detail 명령어로 구체적인 경고 내용을 확인하고 조치합니다.

Toolbox Pod에서 ceph status 확인 결과
# Toolbox 배포 (Rook 예제 디렉토리에서)
kubectl create -f toolbox.yaml

# Toolbox Pod에 접속
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- bash

# Ceph 클러스터 상태 확인
ceph status
ceph osd tree

참고

관련 포스트:

참고 문서: Rook Quickstart 공식 문서 · Ceph 공식 소개 문서

Mikrotik router & Cisco L2 Switch Trunking

개요

VLAN Trunking은 하나의 물리 링크에서 여러 VLAN 트래픽을 IEEE 802.1Q 태그로 구분해 전달하는 기술입니다. 이 글에서는 MikroTik RouterOS와 Cisco IOS 스위치를 혼용하는 환경에서 trunk 포트를 설정하여 VLAN 간 통신을 구성하는 방법을 정리합니다.

Scenario

Mikrotik router 와 Cisco L2 스위치 이기종간에 회선 이중화(bonding, etherchannel) 후 두 장비 모두 vlan 100(192.168.x.x/24), vlan 200(192.168.x.x/24)의 통신이 가능하도록 구성

# Mikrotik router side
# bridge 생성
/interface bridge
add name=bridge-lan vlan-filtering=yes

# vlan 생성 및 bridge 할당
/interface vlan
add interface=bridge-lan name=vlan100 vlan-id=100
add interface=bridge-lan name=vlan200 vlan-id=200

# bonding 생성(ether2, ether3 포트 대상)
/interface bonding
add mode=802.3ad name=bonding1 slaves=ether2,ether3 transmit-hash-policy=\
    layer-2-and-3

# trunk 처리, bridge-lan에 포함된 port 중 trunk 대상 아닌 경우 untagged port 처리
/interface bridge port
add bridge=bridge-lan frame-types=admit-only-vlan-tagged interface=bonding1
add bridge=bridge-lan tagged=bonding1,bridge-lan untagged=\
    ether4,ether5,ether6 vlan-ids=100,200

# vlan IP 및 대역 할당
/ip address
add address=192.168.x.x/24 interface=vlan100 network=192.168.x.x
add address=192.168.x.x/24 interface=vlan200 network=192.168.x.x
# Cisco L2 switch side

configure terminal

# vlan IP 및 대역 할당
interface Vlan100
 ip address 192.168.x.x 255.255.255.0
interface Vlan200
 ip address 192.168.x.x 255.255.255.0

# etherchannel 구성(GigabitEthernet0/1, GigabitEthernet0/2 포트 대상)
interface range GigabitEthernet0/1-2
 channel-protocol lacp
 channel-group 1 mode active

# 생성된 etherchannel에 trunk 모드 및 trunking할 vlan 적용
interface Port-channel1
 switchport trunk allowed vlan 100,200
 switchport mode trunk

end
write

링크 이중화(LACP) 방식 비교

이 구성에서는 MikroTik 측의 Bonding(802.3ad LACP)과 Cisco 측의 EtherChannel(LACP)을 상호 연결합니다. 두 장비 모두 LACP 프로토콜을 사용하여 물리 링크를 하나의 논리 인터페이스로 묶어 대역폭 확장과 링크 이중화를 동시에 달성합니다.

MikroTik은 mode=802.3ad로 LACP를 활성화하고 transmit-hash-policy=layer-2-and-3으로 로드밸런싱 해시를 L2+L3 기반으로 설정합니다. Cisco는 channel-protocol lacpchannel-group 1 mode active로 LACP 협상을 능동적으로 시작합니다. 양쪽 모두 LACP active 모드로 설정하면 협상이 정상적으로 진행됩니다.

VLAN Trunk 트러블슈팅

이기종 장비 간 VLAN Trunk 구성 시 통신 오류가 발생하면 아래 항목을 순서대로 확인합니다.

# MikroTik: LACP 협상 상태 및 bonding 멤버 확인
/interface bonding monitor bonding1

# MikroTik: bridge VLAN 설정 확인
/interface bridge vlan print

# MikroTik: bridge port frame-type 확인
/interface bridge port print

# Cisco: EtherChannel 상태 확인
show etherchannel summary
show interfaces port-channel 1 trunk

# Cisco: VLAN trunk 허용 목록 확인
show interfaces trunk

가장 흔한 오류는 MikroTik의 bridge VLAN filtering이 비활성화된 경우와 Cisco trunk의 allowed VLAN 목록이 일치하지 않는 경우입니다. 또한 LACP 협상이 실패하면 단일 링크만 활성화되거나 링크 자체가 down될 수 있으므로 양쪽의 LACP 상태를 동시에 확인하는 것이 중요합니다.

VLAN 구성 검증 절차

MikroTik과 Cisco 이기종 VLAN Trunk 구성 완료 후에는 단계적으로 통신을 검증합니다. 먼저 각 VLAN 인터페이스에 IP를 할당하고 동일 VLAN 내에서 핑이 통하는지 확인합니다. 다음으로 VLAN 간 통신은 MikroTik의 라우팅 또는 Cisco L3 스위치의 인터 VLAN 라우팅을 통해 확인합니다.

VLAN 100과 VLAN 200 사이의 통신이 필요한 경우 MikroTik에서 두 VLAN 인터페이스 간 라우팅이 활성화되어 있어야 합니다. MikroTik은 기본적으로 인터페이스 간 포워딩이 활성화되어 있어 별도 라우팅 설정 없이 VLAN 간 통신이 가능하지만, 방화벽 규칙에 따라 차단될 수 있습니다. /ip firewall filter print로 방화벽 규칙을 확인하고 필요한 경우 forward 체인에 허용 규칙을 추가합니다.

Cisco 측에서는 show interfaces trunk로 trunk 포트에서 허용된 VLAN 목록을 확인하고, show mac address-table로 각 VLAN의 MAC 주소가 올바른 포트에 학습되었는지 검증합니다. 링크 다운이나 VLAN 통신 오류 발생 시 양쪽 장비의 로그를 동시에 확인하면 원인 파악이 빠릅니다.

참고

참고 문서: MikroTik VLAN 공식 문서 · Cisco VLAN/VTP 설정 가이드