본문으로 건너뛰기

CKA 개념 노트: 클러스터 upgrade, etcd 백업, 인증서

  • kubernetes
  • cka
  • kubeadm
  • upgrade
  • etcd
  • certificates

기준: kubeadm 클러스터 v1.34.12 → v1.35.9 (libvirt VM 3대: control plane 1, worker 2), etcd 3.6.5 / 3.6.6, etcdctl·etcdutl 3.6.5에서 명령어 확인

목차

version skew

upgrade 순서는 “어떤 컴포넌트가 apiserver보다 얼마나 앞서거나 뒤처져도 되는가”에서 나옴.

컴포넌트kube-apiserver 대비 허용 범위
kube-apiserver (HA)apiserver끼리 minor 1 차이까지
kube-controller-manager, kube-scheduler같거나 1 minor 낮음. 높으면 안 됨
kubelet같거나 최대 3 minor 낮음. 높으면 안 됨
kube-proxy같거나 최대 3 minor 낮음. 높으면 안 됨
kubectl±1 minor

여기서 나오는 규칙:

  • control plane을 먼저, kubelet을 나중에 올림
  • minor 버전은 건너뛸 수 없음. 1.33 → 1.35는 1.34를 거쳐야 함. patch는 건너뛰어도 됨

upgrade 순서

순서노드명령
1첫 control plane저장소 변경 → kubeadm 설치 → kubeadm upgrade plan → kubeadm upgrade apply v1.35.9
2나머지 control planekubeadm 설치 → kubeadm upgrade node
3각 control planedrain → kubelet, kubectl 설치 → kubelet 재시작 → uncordon
4worker 하나씩kubeadm 설치 → kubeadm upgrade node → drain → kubelet, kubectl 설치 → kubelet 재시작 → uncordon

kubeadm upgrade apply는 클러스터 전체에서 한 번만. 나머지 노드는 전부 kubeadm upgrade node.

control plane upgrade

저장소와 kubeadm

pkgs.k8s.io는 minor 버전마다 저장소가 달라서, 저장소 줄의 버전부터 바꿔야 새 버전이 보임.

sudo sed -i 's#v1.34#v1.35#g' /etc/apt/sources.list.d/kubernetes.list
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key \
  | sudo gpg --dearmor --yes -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo apt-get update
sudo apt-cache madison kubeadm
#    kubeadm | 1.35.9-1.1 | https://pkgs.k8s.io/core:/stable:/v1.35/deb  Packages
#    kubeadm | 1.35.8-1.1 | ...

sudo apt-mark unhold kubeadm && \
sudo apt-get install -y kubeadm='1.35.9-*' && \
sudo apt-mark hold kubeadm
kubeadm version -o short   # v1.35.9

upgrade plan

sudo kubeadm upgrade plan
[upgrade/versions] Cluster version: 1.34.12
[upgrade/versions] kubeadm version: v1.35.9
remote version is much newer: v1.37.1; falling back to: stable-1.35
Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT   NODE      CURRENT    TARGET
kubelet     cka-cp1   v1.34.12   v1.35.9
kubelet     cka-w1    v1.34.12   v1.35.9
kubelet     cka-w2    v1.34.12   v1.35.9

COMPONENT                 NODE      CURRENT    TARGET
kube-apiserver            cka-cp1   v1.34.12   v1.35.9
kube-controller-manager   cka-cp1   v1.34.12   v1.35.9
kube-scheduler            cka-cp1   v1.34.12   v1.35.9
kube-proxy                          1.34.12    v1.35.9
CoreDNS                             v1.12.1    v1.13.1
etcd                      cka-cp1   3.6.5-0    3.6.6-0
  • kubelet은 kubeadm이 올려 주지 않음. 표 위쪽이 “직접 해야 하는 것”
  • control plane static Pod를 막 재시작한 직후에 plan을 돌렸더니 [ERROR CreateJob]: Job "upgrade-health-check-..." ... did not complete in 15s로 실패함. 30초 뒤 다시 실행하니 통과. plan과 apply는 시작할 때 health check Job을 띄워서 클러스터 상태를 봄

upgrade apply

sudo kubeadm upgrade apply v1.35.9

apply가 하는 일:

  • etcd, kube-apiserver, kube-controller-manager, kube-scheduler 순서로 manifest를 새로 쓰고, kubelet이 static Pod를 다시 띄울 때까지 기다림
  • 이전 manifest는 /etc/kubernetes/tmp/kubeadm-backup-manifests-<날짜>/에, etcd 데이터는 kubeadm-backup-etcd-<날짜>/에 백업
  • 컴포넌트마다 인증서 갱신(Renewing apiserver certificate 등)
  • kube-system/kubelet-config ConfigMap 갱신, CoreDNS와 kube-proxy addon upgrade
  • 마지막 줄: [upgrade] SUCCESS! A control plane node of your cluster was upgraded to "v1.35.9".

apply가 끝나도 kubectl get nodes의 VERSION은 v1.34.12 그대로. 이 컬럼은 kubelet 버전이라서, kubelet을 올리기 전까지는 안 바뀜.

kubelet, kubectl

kubectl drain cka-cp1 --ignore-daemonsets

sudo apt-mark unhold kubelet kubectl && \
sudo apt-get install -y kubelet='1.35.9-*' kubectl='1.35.9-*' && \
sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet

kubectl uncordon cka-cp1

drain 중에는 Ready,SchedulingDisabled, uncordon 후 Ready와 v1.35.9.

worker upgrade

# worker에서: 저장소 변경 후
sudo apt-mark unhold kubeadm && sudo apt-get install -y kubeadm='1.35.9-*' && sudo apt-mark hold kubeadm
sudo kubeadm upgrade node
# [upgrade/addon] Skipping the addon/kube-proxy phase. Not a control plane node.

# control plane(kubectl 있는 곳)에서
kubectl drain cka-w1 --ignore-daemonsets

# worker에서
sudo apt-mark unhold kubelet kubectl && sudo apt-get install -y kubelet='1.35.9-*' kubectl='1.35.9-*' && sudo apt-mark hold kubelet kubectl
sudo systemctl daemon-reload && sudo systemctl restart kubelet

# control plane에서
kubectl uncordon cka-w1

worker의 kubeadm upgrade node는 kubelet 설정(/var/lib/kubelet/config.yaml)만 새로 받음. drain은 worker가 아니라 kubeconfig가 있는 곳에서 실행.

drain, cordon, uncordon

명령스케줄 막기기존 Pod
kubectl cordon <node>O그대로
kubectl drain <node>O (먼저 cordon)eviction으로 내보냄
kubectl uncordon <node>해제변화 없음

옵션 없이 drain하면 거부되는 경우:

cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore):
  calico-system/calico-node-..., calico-system/csi-node-driver-..., kube-system/kube-proxy-...
cannot delete Pods that declare no controller (use --force to override): default/plain
상황옵션결과
DaemonSet Pod--ignore-daemonsets건너뜀. 노드에 그대로 남음
controller 없는 Pod--force삭제. 다시 만들어지지 않음
emptyDir 쓰는 Pod--delete-emptydir-data삭제. emptyDir 데이터는 사라짐
PodDisruptionBudget 위반(없음)계속 재시도
  • drain이 실패해도 cordon은 이미 걸려 있음. 다시 drain하면 node/cka-w2 already cordoned
  • minAvailable: 2인 PDB가 걸린 replicas 2 Deployment의 노드를 drain하면 Cannot evict pod as it would violate the pod's disruption budget.가 5초마다 반복됨. --timeout=10s를 주면 global timeout reached: 10s로 끝남
  • uncordon해도 Pod가 원래 노드로 돌아오지 않음. 실습에서는 worker 둘을 차례로 drain했더니 replicas 4개가 전부 cka-w1에 몰려 있었음

etcd 백업과 복원

도구

도구용도비고
etcdctl실행 중인 etcd에 네트워크로 접속snapshot save
etcdutletcd 데이터 파일을 직접 다룸snapshot status, snapshot restore

etcd 3.6.5의 etcdctl snapshot에는 save 하나만 남아 있음. status와 restore는 etcdutl로.

$ etcdctl snapshot --help
COMMANDS:
  save	Stores an etcd node backend snapshot to a given file

kubeadm 노드에는 둘 다 기본으로 설치되어 있지 않아서, etcd 이미지 버전(registry.k8s.io/etcd:3.6.5-0)에 맞춰 GitHub release에서 받음.

접속 정보 찾기

필요한 값은 전부 etcd static Pod manifest에 있음.

sudo grep -E "listen-client-urls|cert-file|key-file|trusted-ca-file|data-dir" /etc/kubernetes/manifests/etcd.yaml
#    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
#    - --data-dir=/var/lib/etcd
#    - --key-file=/etc/kubernetes/pki/etcd/server.key
#    - --listen-client-urls=https://127.0.0.1:2379,https://192.168.122.11:2379
#    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt

백업

sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /opt/etcd-backup.db
# Snapshot saved at /opt/etcd-backup.db

sudo etcdutl --write-out=table snapshot status /opt/etcd-backup.db
+----------+----------+------------+------------+---------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE | VERSION |
+----------+----------+------------+------------+---------+
| 2e711e8a |     2202 |        767 |      11 MB |   3.6.0 |
+----------+----------+------------+------------+---------+

복원

확인 방법: 백업 전에 ConfigMap before-backup, 백업 후에 after-backup을 만들고 복원. 복원 후에는 before-backup만 있어야 함.

# 1. 새 data dir에 복원
sudo etcdutl --data-dir /var/lib/etcd-from-backup snapshot restore /opt/etcd-backup.db

# 2. etcd manifest의 hostPath를 새 경로로
sudo vi /etc/kubernetes/manifests/etcd.yaml
volumes:
  - hostPath:
      path: /var/lib/etcd-from-backup # 원래 /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data
  • 컨테이너 안의 --data-dir=/var/lib/etcd와 mountPath는 그대로 둠. 호스트 쪽 경로만 바꾸면 됨
  • manifest를 고친 뒤 kubelet이 etcd Pod를 다시 띄우기까지 1분 넘게 걸렸음

여기서 한 번 헷갈렸음. etcd가 새 데이터로 뜬 뒤에도 kubectl get cm에는 after-backup이 계속 보였음. etcdctl get /registry/configmaps/backup-test --prefix --keys-only로 etcd를 직접 보면 after-backup은 이미 없었음. apiserver의 watch cache가 복원 전 상태를 들고 있었던 것.

# 3. control plane 컴포넌트 재시작 (static Pod는 manifest를 뺐다 넣으면 재시작됨)
cd /etc/kubernetes/manifests
sudo mv kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml /tmp/
# 컨테이너가 내려갈 때까지 기다린 뒤
sudo mv /tmp/kube-apiserver.yaml /tmp/kube-controller-manager.yaml /tmp/kube-scheduler.yaml .
sudo systemctl restart kubelet

재시작 후에는 before-backup만 남음. 공식 문서도 복원 후 모든 apiserver 재시작, scheduler·controller-manager·kubelet 재시작을 권장함.

복원 후 upgrade 주의

hostPath만 바꿔 놓은 채로 kubeadm upgrade apply를 했더니, kubeadm이 etcd manifest를 kubeadm-config의 etcd.local.dataDir(/var/lib/etcd) 기준으로 다시 써서 hostPath가 /var/lib/etcd로 돌아감. 복원 전 데이터로 etcd가 다시 떴고, 이어서 apiserver 단계에서 context deadline exceeded로 upgrade가 실패함.

실제 클러스터라면 복원 데이터를 원래 경로에 두거나, kubeadm-config의 dataDir도 같이 바꿔야 함. 이 실습은 VM snapshot으로 되돌려 깨끗한 상태에서 upgrade를 다시 함.

인증서

만료 확인

sudo kubeadm certs check-expiration
CERTIFICATE                EXPIRES                  RESIDUAL TIME   CERTIFICATE AUTHORITY
admin.conf                 Oct 13, 2027 16:21 UTC   364d            ca
apiserver                  Oct 13, 2027 16:21 UTC   364d            ca
apiserver-etcd-client      Oct 13, 2027 16:21 UTC   364d            etcd-ca
apiserver-kubelet-client   Oct 13, 2027 16:21 UTC   364d            ca
controller-manager.conf    Oct 13, 2027 16:21 UTC   364d            ca
etcd-healthcheck-client    Oct 13, 2027 16:21 UTC   364d            etcd-ca
etcd-peer                  Oct 13, 2027 16:21 UTC   364d            etcd-ca
etcd-server                Oct 13, 2027 16:21 UTC   364d            etcd-ca
front-proxy-client         Oct 13, 2027 16:21 UTC   364d            front-proxy-ca
scheduler.conf             Oct 13, 2027 16:21 UTC   364d            ca
super-admin.conf           Oct 13, 2027 16:21 UTC   364d            ca

CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME
ca                      Oct 10, 2036 16:21 UTC   9y
etcd-ca                 Oct 10, 2036 16:21 UTC   9y
front-proxy-ca          Oct 10, 2036 16:21 UTC   9y
  • leaf 인증서 1년, CA 10년
  • kubelet.conf는 목록에 없음. kubelet 클라이언트 인증서는 kubelet이 스스로 갱신하고, /var/lib/kubelet/pki/kubelet-client-current.pem symlink가 최신 파일을 가리킴
  • 개별 확인: sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddate

갱신

방법언제
kubeadm upgrade apply / upgrade nodeupgrade할 때 자동 갱신. 1년 안에 한 번씩 upgrade하면 따로 할 일 없음
kubeadm certs renew all수동 갱신. 이름을 주면 하나만 (kubeadm certs renew apiserver)
sudo kubeadm certs renew all
# Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager,
# kube-scheduler and etcd, so that they can use the new certificates.
  • 갱신 후 control plane static Pod 재시작 필요
  • ~/.kube/config는 admin.conf를 복사해 둔 것이라 자동으로 바뀌지 않음. 다시 복사

헷갈리는 것 비교

비교AB
kubeadm upgrade apply vs upgrade node첫 control plane에서 한 번. 클러스터 버전을 올림나머지 control plane과 worker. 그 노드의 manifest와 kubelet 설정만
kubectl get nodes VERSIONkubelet 버전control plane 버전은 kubectl version이나 static Pod 이미지
cordon vs drain스케줄만 막음스케줄 막고 Pod 내보냄
--ignore-daemonsets vs --forceDaemonSet Pod를 건너뜀controller 없는 Pod를 지움
etcdctl vs etcdutl실행 중인 etcd에 접속. save파일 직접. status, restore
--data-dir vs hostPath컨테이너 안 경로. 그대로 둠호스트 경로. 복원한 디렉터리로 바꿈
leaf vs CA 인증서1년. renew로 갱신10년. renew 대상 아님

시험 포인트

빨리 푸는 명령어

sudo kubeadm upgrade plan
sudo kubeadm upgrade apply v1.35.9
sudo kubeadm upgrade node
kubectl drain <node> --ignore-daemonsets [--delete-emptydir-data] [--force]
kubectl uncordon <node>

sudo cat /etc/kubernetes/manifests/etcd.yaml | grep -E "file|urls|data-dir"
sudo ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... snapshot save <file>
sudo etcdutl --data-dir <new-dir> snapshot restore <file>

sudo kubeadm certs check-expiration
sudo kubeadm certs renew all
  • upgrade 문제는 노드마다 어디서 실행하는지(ssh 대상 노드 vs kubectl 있는 노드)를 헷갈리기 쉬움
  • etcd 인증서 경로는 외우지 말고 etcd.yaml에서 복사
  • 복원 후 kubectl 결과가 그대로면 apiserver 재시작부터 의심

kubectl explain

kubectl explain poddisruptionbudget.spec   # minAvailable, maxUnavailable, selector
kubectl explain node.spec.unschedulable    # cordon이 바꾸는 필드
kubectl drain --help

참고 문서