티스토리 뷰
기시다님의 3주차 스터디 내용을 정리한 내용입니다.
kubeadm을 사용하여 쿠버네티스 클러스터를 구축하고 업그레이드하는 방법
kubeadm의 기본 개념부터 클러스터 구축, CNI 설치, 모니터링 툴 설치, 그리고 클러스터 업그레이드까지 유용한 스터디
EKS 버전 업그레이드 등 쿠버네티스 업데이트에 대한 절차를 배울수 있는 시간이었습니다.
금주도 건강 이슈로 실습보다는 정리에 초점을 둔 포스팅이라 향후에 실습이 필요할것 같다.
- kubeadm 으로 k8s 구성 절차
- [공통] 사전 설정
- [공통] CRI 설치 : containerd
- [공통] kubeadm, kubelet 및 kubectl 설치
- [Controlplane node] kubeadm 으로 k8s 클러스터 구성 → Flannel CNI 설치 → 편의성 설치 및 확인
- [Worker nodes] kubeadm 으로 k8s 클러스터 join → 확인
- 모니터링 툴 설치 : 프로메테우스-스택 설치 → 인증서 익스포터 설치 → 그라파나 대시보드 확인
- 샘플 애플리케이션 배포
- kubeadm 인증서 갱신
-
주요 설치 버전 정보: v1.32.11
항목 버전 k8s 버전 호환성 Rocky Linux 10.0-1.6 RHEL 10 소스 기반 배포판으로 RHEL 정보 참고 containerd v2.1.5 CRI Version(v1), k8s 1.32~1.35 지원 - Link runc v1.3.3 정보 조사 필요 https://github.com/opencontainers/runc kubelet v1.32.11 k8s 버전 정책 문서 참고 - Docs kubeadm v1.32.11 상동 kubectl v1.32.11 상동 helm v3.18.6 k8s 1.30.x ~ 1.33.x 지원 - Docs flannel cni v0.27.3 k8s 1.28~ 이후 - Release - [공통] 사전 설정 - K8S_1.32_Docs , CRI_Docs
- 기본 정보 확인 : vagrant ssh k8s-ctr
- # User 정보 whoami id # uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant) pwd # /home/vagrant # cpu, mem lscpu free -h # Disk lsblk # sda 8:0 0 64G 0 disk df -hT # Network ip -br -c -4 addr # enp0s9 UP 192.168.10.100/24 ip -c route ip addr # Host Info, Kernel hostnamectl # Kernel: Linux 6.12.0-55.39.1.el10_0.aarch64 uname -r **rpm -aq | grep release** rocky-release-**10.0-1**.6.el10.noarch # cgroup 버전 확인 **stat -fc %T /sys/fs/cgroup** *cgroup2fs* **findmnt** **mount | grep cgroup** *cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot)* ## systemd cgroup 계층 구조 확인 systemd-cgls --no-pager # Process pstree lsns
- root 권한(로그인 환경) 전환 : sudo su -
- Time, NTP 설정 : 인증서 만료 시간, 로그 타임스탬프 등 모든 노드에 동기화된 시간 필요
- # timedatectl 정보 확인 timedatectl status # RTC in local TZ: yes -> Warning:... **timedatectl set-local-rtc 0** timedatectl status # 시스템 타임존(Timezone)을 한국(KST, UTC+9) 으로 설정 : 시스템 시간은 UTC 기준 유지, 표시만 KST로 변환 date **timedatectl set-timezone Asia/Seoul** date # systemd가 시간 동기화 서비스(chronyd) 를 관리하도록 설정되어 있음 : ntpd 대신 chrony 사용 (Rocky 9/10 기본) **timedatectl status** timedatectl set-ntp true # System clock synchronized: yes -> NTP service: active # chronyc 확인 # chrony가 어떤 NTP 서버들을 알고 있고, 그중 어떤 서버를 기준으로 시간을 맞추는지를 보여줍니다. ## Stratum 2: 매우 신뢰도 높은 서버 ## Reach 377: 최근 8회 연속 응답 성공 (최대값) **chronyc sources -v** ***MS** **Name/IP address** Stratum Poll **Reach** LastRx Last sample =============================================================================== **^*** 211.108.117.211 2 6 **377** 9 -56us[ -31us] +/- 3253us* ... # 현재 시스템 시간이 얼마나 정확한지 종합 성적표 **chronyc tracking** *Reference ID : D36C75D3 (211.108.117.211) ...*
- SELinux 설정, firewalld(방화벽) 끄기
- # SELinux 설정 : Kubernetes는 Permissive 권장 getenforce sestatus # Current mode: enforcing **setenforce 0** getenforce sestatus # Current mode: permissive # 재부팅 시에도 Permissive 적용 cat /etc/selinux/config | grep ^SELINUX **sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config** cat /etc/selinux/config | grep ^SELINUX # firewalld(방화벽) 끄기 systemctl status firewalld **systemctl disable --now firewalld** systemctl status firewalld
- Swap 비활성화 ⇒ 아래 ‘(TS) Rocky Linux 10 vagrant image 에 SWAP off 해결’ 실습 내용 참고Kubernetes 설치 전 swap을 끄세요 (swap off) - 출처 : https://interlude-3.tistory.com/47
- Swap = 가상 메모리
- RAM(물리 메모리)이 부족할 때 디스크의 일부를 메모리처럼 사용하는 기능
- RAM이 8GB인데 10GB가 필요한 경우, 2GB를 디스크(Swap)에 임시 저장
- Kubernetes 설치 시 왜 swap을 꺼야 할까요?
- 쿠버네티스는 노드 자원을 직접 통제하고 관리할 수 있어야 합니다.
- Swap은 이 통제권을 OS에게 넘겨버리는 행위이므로 클러스터의 안정성을 위해 반드시 비활성화해야 합니다.
- 리소스/성능 예측 불가능
- Pod가 메모리를 초과 사용해도 죽지 않고 디스크를 쓰면서 버팀
- Kubernetes는 정상 Pod로 인식하지만 실제로는 클러스터 성능을 낮추는 중
- resources: requests: memory: 1Gi # 최소 필요 메모리 limits: memory: 2Gi # 최대 사용 가능 메모리
- Kubernetes의 리소스 관리 철학
- 기본 관리 철학 : 메모리가 부족하면, Pod를 즉시 종료(OOMKilled) 시키고 다른 노드에서 재시작
- Swap 켜져 있으면 죽지 않고 느린 상태로 계속 실행 → K8s 입장에서는 정상 Pod 인건지 판단 불가
- 스케줄링 판단 오류 -> 노드 병목 지점 -> 전체 클러스터 성능 저하
- Swap = 가상 메모리
- # Swap 비활성화 lsblk free -h free -h | grep Swap **swapoff -a** lsblk free -h | grep Swap # 재부팅 시에도 'Swap 비활성화' 적용되도록 /etc/fstab에서 swap 라인 삭제 cat /etc/fstab | grep swap **sed -i '/swap/d' /etc/fstab** cat /etc/fstab | grep swap
- 커널 모듈 및 커널 파라미터 설정(네트워크 설정)OverlayFS 모듈을 활성화 하세요 - 출처 : https://interlude-3.tistory.com/47
- OverlayFS = 여러 파일시스템 레이어를 하나로 겹쳐 보이게 만드는 기술
- 동작 방식
- Base Image(Lower Layer)는 수정되지 않음
- 컨테이너에서 파일을 새로 만들면, Upper Layer에만 생성
- 기존 파일을 수정하면, Upper Layer에 복사 후 수정
- 컨테이너가 삭제되면, Uppder Layer만 사라지고 Lower Layer는 그대로 유지
- ⇒ 이미지 재사용이 쉬워지고, 컨테이너는 가벼워집니다.
- 동작 방식
- Kubernetes / Docker에서 OverlayFS가 필요한 이유
- 컨테이너 이미지 생성
- 컨테이너 이미지는 처음부터 레이어 단위로 만들어지고, 각 명령어가 하나의 레이어가 됨 (모든 레이어는 읽기 전용)
- OverlayFS는 이 레이어들을 하나로 합쳐서 보여주는 역할을 합니다.
FROM ubuntu:20.04 # Layer 1 (100MB) RUN apt-get update # Layer 2 (50MB) RUN apt-get install nginx # Layer 3 (30MB) COPY app.py /app/ # Layer 4 (1MB) - 디스크 공간 절약
- 같은 이미지를 쓰는 컨테이너가 많아질수록 디스크 효율 차이가 커집니다.
- # 같은 Base Image 사용하는 컨테이너 100개 ubuntu:20.04 (100MB) ← 이건 한번만 저장 ↓ Container 1 (변경사항만 1MB) Container 2 (변경사항만 2MB) Container 3 (변경사항만 1MB) ...
- OverlayFS 없는 경우,
- 100MB × 컨테이너 수
- OverlayFS 있는 경우,
- 100MB + 각 컨테이너의 변경분만 추가
- 컨테이너 시작 속도
- OverlayFS를 사용하면 전체 파일 시스템을 복사하지 않아도 됩니다.
- 컨테이너가 시작하면 레이어 마운트를 수행하여 수백개 컨테이너도 빠르게 기동시킬 수 있습니다.
- 컨테이너 이미지 생성
- OverlayFS 모듈 활성화란?
- OverlayFS는 Linux 커널 기능입니다.
- 커널에 overlay 모듈이 있어야 containerd가 OverlayFS를 사용할 수 있습니다.
- (참고) 일반 서버에서 OverlayFS가 활성화 되어 있지 않은 이유
- 기본적으로 컨테이너를 안 쓰는 서버가 많음
- 불필요한 커널 기능은 로드하지 않는 게 기본 정책
- 메모리 절약과 안정성을 위해 OverlayFS 비활성화
- 컨테이너 런타임(Docker, containerd)은 반드시 필요
- Docker나 containerd는 OverlayFS 없이 정상 동작 불가
- /merged 디렉터리 : 컨테이너 안에서 보이는 최종 파일시스템
# Docker에서 overlay 사용 확인 docker info | grep -i storage Storage Driver: overlay2 # Overlay 마운트 확인 mount | grep overlay overlay on /var/lib/docker/overlay2/xxxxx/merged type overlay ...
- OverlayFS 모듈 확인하고 활성화하기
- # 커널 모듈 로드 여부 확인 lsmod | grep overlay # 없으면 로드 sudo modprobe overlay # 부팅 시 자동 로드 설정 cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF
- Snapshotter = 컨테이너 이미지의 레이어를 스냅샷 단위로 관리하는 시스템
- 이미지 레이어가 추가될 때마다 기존 스냅샷을 부모로 참조하는 새로운 스냅샷이 생성되며, 기존 스냅샷은 변경되지 않고 그대로 유지됩니다.
- Docker Image ├─ Layer 1 (Ubuntu base) ← Snapshot 1 ├─ Layer 2 (apt update) ← Snapshot 2 ├─ Layer 3 (install nginx) ← Snapshot 3 └─ Layer 4 (copy app) ← Snapshot 4
- Snapshotter는 언제 필요할까?
- 이미지 Pull 할 때
- Layer = 스냅샷 체인
- Layer가 3개인 nginx 이미지를 pull 하는 경우, Snopshotter은 다음과 같은 일을 수행합니다.
# nginx 이미지 pull docker pull nginx # Snapshotter가 하는 일 1. Layer 1 다운로드 → 스냅샷 생성 2. Layer 2 다운로드 → 이전 스냅샷 위에 추가 3. Layer 3 다운로드 → 이전 스냅샷 위에 추가 - 컨테이너 실행할 때
- OverlayFS - Layer를 겹쳐서 보여주는 기술
- Snapshotter - 어떤 Layer를 어떻게 쌓을지 결정하는 관리자
docker run nginx # Snapshotter가 하는 일: 1. nginx 이미지의 모든 읽기 전용 스냅샷 가져오기 2. 스냅샷 순서대로 쌓기 3. 맨 위에 쓰기 가능한 컨테이너 레이어(스냅샷) 추가 4. 이 구조를 컨테이너 rootfs로 마운트
- 이미지 Pull 할 때
- Containerd의 Snapshotter 종류
- containerd는 여러 Snapshotter 구현체를 제공합니다.
- 대부분 기본값으로 'overlayfs'가 선택되지만, 운영 환경에서는 어떤 기술이 사용되는지 직접 확인하고 명시하는 것이 좋습니다.
- (주의) 커널에 overlayfs 모듈이 없거나 특정 제약에 걸려 'native'로 fallback 하는 경우, 컨테이너 생성이 느려지거나 디스크 사용량이 급증할 수 있습니다.
Snapshotter 특징 사용 환경# containerd 설정 확인 cat /etc/containerd/config.toml | grep snapshotteroverlayfs 빠르고 효율적 (사실상 표준) 리눅스 대부분 native 파일 복사 방식 (느림) overlayfs 불가 환경 btrfs Btrfs 스냅샷 기능 활용 Btrfs 사용 시 zfs ZFS 스냅샷 기능 활용 ZFS 사용 시 devmapper Device Mapper 기반 구형 RHEL - 'overlayfs' Snapshotter 실제 동작
- 각 레이어는 부모 스냅샷을 가리키는 체인 구조로, 이미지 레이어 순서를 그대로 볼 수 있습니다.
- # containerd 스냅샷 목록 보기 sudo ctr -n k8s.io snapshots ls # 예시 출력: KEY PARENT sha256:abc123... - sha256:def456... sha256:abc123... sha256:ghi789... sha256:def456...
- 실제 파일 위치
- ls -la /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ 1/ ├─ fs/ # 실제 파일 데이터 └─ work/ # Overlay 작업용 2/ ├─ fs/ └─ work/ ... # 이 디렉터리들이 OverlayFS의 Lower / Upper 레이어 재료가 됩니다.
- Kubernetes에서의 Snapshotter의 역할
- Pod 실행 시, Snapshotter가 하는 일은 다음과 같습니다.
- apiVersion: v1 kind: Pod spec: containers: - name: nginx image: nginx:latest --- # Pod 생성 시 Snapshotter가 하는 일 1. Image Pull kubelet → containerd → snapshotter "nginx 이미지 받아서 스냅샷으로 저장해" (각 레이어를 스냅샷으로 관리) 2. Container 생성 kubelet → containerd → snapshotter "이 스냅샷들로 rootfs 만들어줘" (읽기 전용 스냅샷 쌓고 쓰기 가능한 컨테이너 스냅샷 추가) 3. Container 삭제 kubelet → containerd → snapshotter "컨테이너 지워줘" (컨테이너 쓰기 레이어만 삭제하고, 이미지 스냅샷은 그대로 유지)
- Snapshotter 설정 방법
- overlayfs 기반 snapshotter를 사용하려면 다음과 같이 설정합니다.
# containerd config 파일 생성 sudo containerd config default | sudo tee /etc/containerd/config.toml # 버전 확인 containerd --version # 적용 ## containerd 2.x version = 3 [plugins.'io.containerd.cri.v1.images'] snapshotter = "overlayfs" ## containerd 1.x version = 2 [plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "overlayfs" # containerd 재시작 sudo systemctl restart containerd
- OverlayFS = 여러 파일시스템 레이어를 하나로 겹쳐 보이게 만드는 기술
- # 커널 모듈 확인 lsmod lsmod | grep -iE 'overlay|br_netfilter' # 커널 모듈 로드 **modprobe overlay modprobe br_netfilter** lsmod | grep -iE 'overlay|br_netfilter' # **cat <<EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF** tree /etc/modules-load.d/ # 커널 파라미터 설정 : 네트워크 설정 - 브릿지 트래픽이 iptables를 거치도록 함 **cat <<EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF** tree /etc/sysctl.d/ # 설정 적용 **sysctl --system** # 적용 확인 sysctl net.bridge.bridge-nf-call-iptables sysctl net.ipv4.ip_forward
- hosts 설정
- # hosts 설정 cat /etc/hosts **sed -i '/^127\\.0\\.\\(1\\|2\\)\\.1/d' /etc/hosts** **cat << EOF >> /etc/hosts 192.168.10.100 k8s-ctr 192.168.10.101 k8s-w1 192.168.10.102 k8s-w2 EOF** cat /etc/hosts # 확인 ping -c 1 k8s-ctr ping -c 1 k8s-w1 ping -c 1 k8s-w2
- [공통] CRI 설치 : containerd(runc) v2.1.5 - Docs , K8S_1.32_CRI_Docs
- containerd is a member of CNCF with 'graduated' status. : An open and reliable container runtim

- containerd(runc) 설치 v2.1.5 - Getting-started dssds
- [공통] kubeadm, kubelet 및 kubectl 설치 v1.32.11 - Docs
- kubeadm, kubelet 및 kubectl 설치 v1.32.11

-
# repo 추가 ## exclude=... : 실수로 dnf update 시 kubelet 자동 업그레이드 방지 dnf repolist tree /etc/yum.repos.d/ cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni EOF dnf makecache # 설치 ## --disableexcludes=... kubernetes repo에 설정된 exclude 규칙을 이번 설치에서만 무시(1회성 옵션 처럼 사용) ## 설치 가능 버전 확인 dnf list --showduplicates kubelet # '--disableexcludes=kubernetes' 아래 처럼 있는 경우와 없는 경우 비교해보기 dnf list --showduplicates kubelet --disableexcludes=kubernetes dnf list --showduplicates kubeadm --disableexcludes=kubernetes dnf list --showduplicates kubectl --disableexcludes=kubernetes ... kubectl.x86_64 1.32.10-150500.1.1 kubernetes kubectl.aarch64 1.32.11-150500.1.1 kubernetes ... ## 버전 정보 미지정 시, 제공 가능 최신 버전 설치됨. dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes Installing: kubeadm aarch64 1.32.11-150500.1.1 kubernetes 10 M kubectl aarch64 1.32.11-150500.1.1 kubernetes 9.4 M kubelet aarch64 1.32.11-150500.1.1 kubernetes 13 M Installing dependencies: cri-tools aarch64 1.32.0-150500.1.1 kubernetes 6.2 M kubernetes-cni aarch64 1.6.0-150500.1.1 kubernetes 7.2 M # kubelet 활성화 (실제 기동은 kubeadm init 후에 시작됨) systemctl enable --now kubelet ps -ef |grep kubelet # 설치 파일들 확인 which kubeadm && kubeadm version -o yaml which kubectl && kubectl version --client=true Client Version: v1.32.11 Kustomize Version: v5.5.0 which kubelet && kubelet --version Kubernetes v1.32.11 # cri-tools which crictl && crictl version WARN[0000] Config "/etc/crictl.yaml" does not exist, trying next: "/usr/bin/crictl.yaml" # /etc/crictl.yaml 파일 작성 cat << EOF > /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock EOF crictl info | jq { "cniconfig": { "Networks": [ { "Config": { "CNIVersion": "0.3.1", "Name": "cni-loopback", "Plugins": [ { "Network": { "ipam": {}, "type": "loopback" }, "Source": "{\"type\":\"loopback\"}" } ], "Source": "{\n\"cniVersion\": \"0.3.1\",\n\"name\": \"cni-loopback\",\n\"plugins\": [{\n \"type\": \"loopback\"\n}]\n}" }, "IFName": "lo" } ], "PluginConfDir": "/etc/cni/net.d", "PluginDirs": [ "/opt/cni/bin" ], "PluginMaxConfNum": 1, "Prefix": "eth" }, ... "containerdEndpoint": "/run/containerd/containerd.sock", "containerdRootDir": "/var/lib/containerd", ... "status": { ... { "message": "Network plugin returns error: cni plugin not initialized", "reason": "NetworkPluginNotReady", "status": false, "type": "NetworkReady" }, # kubernetes-cni : 파드 네트워크 구성을 위한 CNI 바이너리 파일 확인 ls -al /opt/cni/bin tree /opt/cni /opt/cni └── bin ├── bandwidth ├── bridge ├── portmap ... tree /etc/cni/ /etc/cni/ └── net.d # systemctl is-active kubelet systemctl status kubelet --no-pager journalctl -u kubelet --no-pager tree /usr/lib/systemd/system | grep kubelet -A1 ├── kubelet.service ├── kubelet.service.d │ └── 10-kubeadm.conf cat /usr/lib/systemd/system/kubelet.service cat /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf ... tree /etc/kubernetes tree /var/lib/kubelet cat /etc/sysconfig/kubelet KUBELET_EXTRA_ARGS= # cgroup , namespace 정보 확인 systemd-cgls --no-pager lsns # containerd의 유닉스 도메인 소켓 확인 : kubelet에서 사용 , containerd client 3종(ctr, nerdctr, crictl)도 사용 ls -l /run/containerd/containerd.sock ss -xl | grep containerd ss -xnp | grep containerd
- kubeadm, kubelet 및 kubectl 설치 v1.32.11
- [k8s-ctr] kubeadm 으로 k8s 클러스터 구성 & Flannel CNI 설치 v0.27.3 & 편의성 설정 등
- Customizing components with the kubeadm API - Docs
- kubeadm Configuration (v1beta4)
- apiVersion: kubeadhttp://m.k8s.io/v1beta4
kind: InitConfiguration
bootstrapTokens:
- token: "9a08jv.c0izixklcxtmnze7"
description: "kubeadm bootstrap token"
ttl: "24h"
- token: "783bde.3f89s0fje9f38fhf"
description: "another bootstrap token"
usages:
- authentication
- signing
groups:
- system:bootstrappers:kubeadm:default-node-token
nodeRegistration:
name: "ec2-10-100-0-1"
criSocket: "unix:///var/run/containerd/containerd.sock"
taints:
- key: "kubeadmNode"
value: "someValue"
effect: "NoSchedule"
kubeletExtraArgs:
- name: v
value: "5"
ignorePreflightErrors:
- IsPrivilegedUser
imagePullPolicy: "IfNotPresent"
imagePullSerial: true
localAPIEndpoint:
advertiseAddress: "10.100.0.1"
bindPort: 6443
certificateKey: "e6a2eb8581237ab72a4f494f30285ec12a9694d750b9785706a83bfcbbbd2204"
skipPhases:
- preflight
timeouts:
controlPlaneComponentHealthCheck: "60s"
kubenetesAPICall: "40s"
---
apiVersion: kubeadhttp://m.k8s.io/v1beta4
kind: ClusterConfiguration
etcd:
# one of local or external
local:
imageRepository: "registry.k8s.io"
imageTag: "3.2.24"
dataDir: "/var/lib/etcd"
extraArgs:
- name: listen-client-urls
value: http://10.100.0.1:2379
extraEnvs:
- name: SOME_VAR
value: SOME_VALUE
serverCertSANs:
- ec2-10-100-0-1.compute-1.amazonaws.com
peerCertSANs:
- 10.100.0.1
# external:
# endpoints:
# - 10.100.0.1:2379
# - 10.100.0.2:2379
# caFile: "/etcd/kubernetes/pki/etcd/etcd-ca.crt"
# certFile: "/etcd/kubernetes/pki/etcd/etcd.crt"
# keyFile: "/etcd/kubernetes/pki/etcd/etcd.key"
networking:
serviceSubnet: "10.96.0.0/16"
podSubnet: "10.244.0.0/24"
dnsDomain: "cluster.local"
kubernetesVersion: "v1.21.0"
controlPlaneEndpoint: "10.100.0.1:6443"
apiServer:
extraArgs:
- name: authorization-mode
value: Node,RBAC
extraEnvs:
- name: SOME_VAR
value: SOME_VALUE
extraVolumes:
- name: "some-volume"
hostPath: "/etc/some-path"
mountPath: "/etc/some-pod-path"
readOnly: false
pathType: File
certSANs:
- "10.100.1.1"
- "ec2-10-100-0-1.compute-1.amazonaws.com"
controllerManager:
extraArgs:
- name: node-cidr-mask-size
value: "20"
extraVolumes:
- name: "some-volume"
hostPath: "/etc/some-path"
mountPath: "/etc/some-pod-path"
readOnly: false
pathType: File
scheduler:
extraArgs:
- name: address
value: 10.100.0.1
extraVolumes:
- name: "some-volume"
hostPath: "/etc/some-path"
mountPath: "/etc/some-pod-path"
readOnly: false
pathType: File
certificatesDir: "/etc/kubernetes/pki"
imageRepository: "registry.k8s.io"
clusterName: "example-cluster"
encryptionAlgorithm: ECDSA-P256
dns:
disabled: true # disable CoreDNS
proxy:
disabled: true # disable kube-proxy
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# kubelet specific options here
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
# kube-proxy specific options here
- kubeadm Configuration (v1beta4) - Docs
-
apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration bootstrapTokens: - token: "9a08jv.c0izixklcxtmnze7" description: "kubeadm bootstrap token" ttl: "24h" - token: "783bde.3f89s0fje9f38fhf" description: "another bootstrap token" usages: - authentication - signing groups: - system:bootstrappers:kubeadm:default-node-token nodeRegistration: name: "ec2-10-100-0-1" criSocket: "unix:///var/run/containerd/containerd.sock" taints: - key: "kubeadmNode" value: "someValue" effect: "NoSchedule" kubeletExtraArgs: - name: v value: "5" ignorePreflightErrors: - IsPrivilegedUser imagePullPolicy: "IfNotPresent" imagePullSerial: true localAPIEndpoint: advertiseAddress: "10.100.0.1" bindPort: 6443 certificateKey: "e6a2eb8581237ab72a4f494f30285ec12a9694d750b9785706a83bfcbbbd2204" skipPhases: - preflight timeouts: controlPlaneComponentHealthCheck: "60s" kubenetesAPICall: "40s" --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration etcd: # one of local or external local: imageRepository: "registry.k8s.io" imageTag: "3.2.24" dataDir: "/var/lib/etcd" extraArgs: - name: listen-client-urls value: http://10.100.0.1:2379 extraEnvs: - name: SOME_VAR value: SOME_VALUE serverCertSANs: - ec2-10-100-0-1.compute-1.amazonaws.com peerCertSANs: - 10.100.0.1 # external: # endpoints: # - 10.100.0.1:2379 # - 10.100.0.2:2379 # caFile: "/etcd/kubernetes/pki/etcd/etcd-ca.crt" # certFile: "/etcd/kubernetes/pki/etcd/etcd.crt" # keyFile: "/etcd/kubernetes/pki/etcd/etcd.key" networking: serviceSubnet: "10.96.0.0/16" podSubnet: "10.244.0.0/24" dnsDomain: "cluster.local" kubernetesVersion: "v1.21.0" controlPlaneEndpoint: "10.100.0.1:6443" apiServer: extraArgs: - name: authorization-mode value: Node,RBAC extraEnvs: - name: SOME_VAR value: SOME_VALUE extraVolumes: - name: "some-volume" hostPath: "/etc/some-path" mountPath: "/etc/some-pod-path" readOnly: false pathType: File certSANs: - "10.100.1.1" - "ec2-10-100-0-1.compute-1.amazonaws.com" controllerManager: extraArgs: - name: node-cidr-mask-size value: "20" extraVolumes: - name: "some-volume" hostPath: "/etc/some-path" mountPath: "/etc/some-pod-path" readOnly: false pathType: File scheduler: extraArgs: - name: address value: 10.100.0.1 extraVolumes: - name: "some-volume" hostPath: "/etc/some-path" mountPath: "/etc/some-pod-path" readOnly: false pathType: File certificatesDir: "/etc/kubernetes/pki" imageRepository: "registry.k8s.io" clusterName: "example-cluster" encryptionAlgorithm: ECDSA-P256 dns: disabled: true # disable CoreDNS proxy: disabled: true # disable kube-proxy --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration # kubelet specific options here --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration # kube-proxy specific options here - # 기본 환경 정보 출력 저장
crictl images
crictl ps
cat /etc/sysconfig/kubelet
tree /etc/kubernetes | tee -a etc_kubernetes-1.txt
tree /var/lib/kubelet | tee -a var_lib_kubelet-1.txt
tree /run/containerd/ -L 3 | tee -a run_containerd-1.txt
pstree -alnp | tee -a pstree-1.txt
systemd-cgls --no-pager | tee -a systemd-cgls-1.txt
lsns | tee -a lsns-1.txt
ip addr | tee -a ip_addr-1.txt
ss -tnlp | tee -a ss-1.txt
df -hT | tee -a df-1.txt
findmnt | tee -a findmnt-1.txt
sysctl -a | tee -a sysctl-1.txt
# kubeadm Configuration 파일 작성
cat << EOF > kubeadm-init.yaml
apiVersion: kubeadhttp://m.k8s.io/v1beta4
kind: InitConfiguration
bootstrapTokens:
- token: "123456.1234567890123456"
ttl: "0s"
usages:
- signing
- authentication
nodeRegistration:
kubeletExtraArgs:
- name: node-ip
value: "192.168.10.100" # 미설정 시 10.0.2.15 맵핑
criSocket: "unix:///run/containerd/containerd.sock"
localAPIEndpoint:
advertiseAddress: "192.168.10.100"
---
apiVersion: kubeadhttp://m.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: "1.32.11"
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/16"
EOF
cat kubeadm-init.yaml
# (옵션) 컨테이너 이미지 미리 다운로드 : 특히 업그레이드 작업 시, 작업 시간 단축을 위해서 수행할 것
kubeadm config images pull
# k8s controlplane 초기화 설정 수행
kubeadm init --config="kubeadm-init.yaml" --dry-run
tree /etc/kubernetes
kubeadm init --config="kubeadm-init.yaml"
[init] Using Kubernetes version: v1.32.11
...
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-ctr kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.10.100]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-ctr localhost] and IPs [192.168.10.100 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-ctr localhost] and IPs [192.168.10.100 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "super-admin.conf" kubeconfig file
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
[control-plane] Creating static Pod manifest for "kube-scheduler"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests"
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 1.003793571s
[api-check] Waiting for a healthy API server. This can take up to 4m0s
[api-check] The API server is healthy after 3.004974627s
[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
[upload-certs] Skipping phase. Please see --upload-certs
[mark-control-plane] Marking the node k8s-ctr as control-plane by adding the labels: [node-role.kubernetes.io/control-plane node.kubernetes.io/exclude-from-external-load-balancers]
[mark-control-plane] Marking the node k8s-ctr as control-plane by adding the taints [node-role.kubernetes.io/control-plane:NoSchedule]
[bootstrap-token] Using token: 123456.1234567890123456
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[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
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
# crictl 확인
crictl images
IMAGE TAG IMAGE ID SIZE
registry.k8s.io/coredns/coredns v1.11.3 2f6c962e7b831 16.9MB
registry.k8s.io/etcd 3.5.24-0 1211402d28f58 21.9MB
registry.k8s.io/kube-apiserver v1.32.11 58951ea1a0b5d 26.4MB
registry.k8s.io/kube-controller-manager v1.32.11 82766e5f2d560 24.2MB
registry.k8s.io/kube-proxy v1.32.11 dcdb790dc2bfe 27.6MB
registry.k8s.io/kube-scheduler v1.32.11 cfa17ff3d6634 19.2MB
registry.k8s.io/pause 3.10 afb61768ce381 268kB
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
a04be00090580 dcdb790dc2bfe 26 seconds ago Running kube-proxy 0 1fd91b0a982bb kube-proxy-7w44b kube-system
b005f34739da5 82766e5f2d560 37 seconds ago Running kube-controller-manager 0 555d146c3ec07 kube-controller-manager-k8s-ctr kube-system
eb42b9c47fdce cfa17ff3d6634 37 seconds ago Running kube-scheduler 0 e649514d0a1b7 kube-scheduler-k8s-ctr kube-system
bbe8495d2a205 58951ea1a0b5d 37 seconds ago Running kube-apiserver 0 be25c00dd555c kube-apiserver-k8s-ctr kube-system
c00a944599500 1211402d28f58 37 seconds ago Running etcd 0 ce6b89dea28da etcd-k8s-ctr kube-system
# kubeconfig 작성
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config
chown $(id -u):$(id -g) /root/.kube/config
# 확인
kubectl cluster-info
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr NotReady control-plane 6m45s v1.32.11 192.168.10.100 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
kubectl get nodes -o json | jq ".items[] | {name:.metadata.name} + .status.capacity"
...
kubectl get pod -n kube-system -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-668d6bf9bc-bmdjw 0/1 Pending 0 6m55s <none> <none> <none> <none>
coredns-668d6bf9bc-cbtn9 0/1 Pending 0 6m55s <none> <none> <none> <none>
etcd-k8s-ctr 1/1 Running 0 7m2s 192.168.10.100 k8s-ctr <none> <none>
kube-apiserver-k8s-ctr 1/1 Running 0 7m4s 192.168.10.100 k8s-ctr <none> <none>
kube-controller-manager-k8s-ctr 1/1 Running 0 7m2s 192.168.10.100 k8s-ctr <none> <none>
kube-proxy-zfr9d 1/1 Running 0 6m55s 192.168.10.100 k8s-ctr <none> <none>
kube-scheduler-k8s-ctr 1/1 Running 0 7m3s 192.168.10.100 k8s-ctr <none> <none>
# coredns 의 service name 확인 : kube-dns
kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 3h26m
# cluster-info ConfigMap 공개 : cluster-info는 '신원 확인 전, 최소한의 신뢰 부트스트랩 데이터'
kubectl -n kube-public get configmap cluster-info
kubectl -n kube-public get configmap cluster-info -o yaml
kubectl -n kube-public get configmap cluster-info -o jsonpath='{.data.kubeconfig}' | grep certificate-authority-data | cut -d ':' -f2 | tr -d ' ' | base64 -d | openssl x509 -text -noout
curl -s -k https://192.168.10.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info | jq
curl -s -k https://192.168.10.100:6443/api/v1/namespaces/default/pods # X
kubectl -n kube-public get role
kubectl -n kube-public get rolebinding
# kubeadm init 시 생성되는 객체
- Namespace: kube-public
- ConfigMap: cluster-info
- Role + RoleBinding
>> 대상: system:unauthenticated (인증 안 된 사용자)
>> 권한: get on configmaps/cluster-info
👉 아직 클러스터 인증서가 없는 노드(worker) 가 (kubeadm join 전) API Server에 처음 접속해서 최소 정보(엔드포인트 + CA)를 얻기 위해 필요
-
- [k8s-ctr] kubeadm init 수행
-
- 사전 검사 (Preflight Checks)
- [Error] if the CRI endpoint does not answer ← CRI(Container Runtime Interface) 엔드포인트 연결 확인
- [Error] if user is not root ← 루트 권한 확인
- [Error] if kubelet version is lower that the minimum kubelet version supported by kubeadm (current minor -1) ← 버전 확인
- 인증서 및 키 생성 (Certificates) : Control Plane이 안전하게 통신할 수 있도록 인증서를 /etc/kubernetes/pki 생성
- CA 인증서 (클러스터 전체 기본 신뢰체계)
- API Server용 서버 인증서
- API Server ↔ kubelet 통신용 인증서
- Front-Proxy CA / Front-Proxy 클라이언트 인증서
- etcd 관련 인증서 및 키 (local etcd 사용 시)
- control plane components 를 위한 kubeconfig 파일을 /etc/kubernetes 생성
- A kubeconfig file for the kubelet to use during TLS bootstrap - /etc/kubernetes/bootstrap-kubelet.conf
- A kubeconfig file for controller-manager, /etc/kubernetes/controller-manager.conf
- A kubeconfig file for scheduler, /etc/kubernetes/scheduler.conf
- /etc/kubernetes/admin.conf , /etc/kubernetes/super-admin.conf
- Control Plane 구성 요소 생성 (Static Pods)
- 공통 사항
- All static Pods are deployed on kube-system namespace
- All static Pods get tier:control-plane and component:{component-name} labels
- All static Pods use the system-node-critical priority class
- hostNetwork: true is set on all static Pods to allow control plane startup
- kube-apiserver
- kube-controller-manager
- kube-scheduler
- etcd (local etcd 사용하는 경우)
- 공통 사항
- kubelet 시작 및 대기
- kubeadm은 manifest 적용 후 kubelet을 재시작하거나 설정을 적용하고 , Control Plane이 성공적으로 시작될 때까지 기다립니다.
- API Server가 정상적으로 건강 상태(healthz)를 반환할 때까지 대기합니다 : /healthz or /livez endpoints.
- Save the kubeadm ClusterConfiguration in a ConfigMap for later reference
- kubectl get cm -n kube-system **kubeadm-config**
- control-plane 노드 라벨링 및 태인트 Mark the node as control-plane
- Labels the node as control-plane with node-role.kubernetes.io/control-plane=""
- Taints the node with node-role.kubernetes.io/control-plane:NoSchedule ← 일반 워크로드가 Control Plane에 스케줄 X
- Bootstrap 토큰 생성 및 클러스터 부트스트랩 설정 Configure TLS-Bootstrapping for node joining
- Control Plane에 부트스트랩 토큰을 생성합니다. Create a bootstrap token
- 각 노드가 클러스터에 합류할 수 있도록 ConfigMap, RBAC 규칙, CSR 접근 권한 등을 설정합니다.
- Create the public cluster-info ConfigMap : '신원 확인 전, 최소한의 신뢰 부트스트랩 데이터'
- This phase creates the cluster-info ConfigMap in the kube-public namespace.
- Additionally, it creates a Role and a RoleBinding granting access to the ConfigMap for unauthenticated users (i.e. users in RBAC group system:unauthenticated).
- Allow joining nodes to call CSR API …(생략)…
- Create the public cluster-info ConfigMap : '신원 확인 전, 최소한의 신뢰 부트스트랩 데이터'
- 부트스트랩 토큰은 기본적으로 24시간 유효하며, kubeadm join 시 사용할 수 있습니다.
- 필수 애드온 설치 (CoreDNS, kube-proxy)
- kube-proxy
- A ServiceAccount for kube-proxy is created in the kube-system namespace
- then kube-proxy is deployed as a DaemonSet
- CoreDNS
- The CoreDNS service is named kube-dns for compatibility reasons with the legacy kube-dns addon.
- A ServiceAccount for CoreDNS is created in the kube-system namespace.
- The coredns ServiceAccount is bound to the privileges in the system:coredns ClusterRole.
- In Kubernetes version 1.21, support for using kube-dns with kubeadm was removed.
- kube-proxy
- 사전 검사 (Preflight Checks)
-
- [k8s-ctr] k8s 관련 작업 편의성 설정 : 자동 완성, kubecolor, kubectx, kubens, kube-ps, helm, k9s 설치
- #
echo "sudo su -" >> /home/vagrant/.bashrc
# Source the completion
source <(kubectl completion bash)
source <(kubeadm completion bash)
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'source <(kubeadm completion bash)' >> /etc/profile
kubectl get <tab 2번>
# Alias kubectl to k
alias k=kubectl
complete -o default -F __start_kubectl k
echo 'alias k=kubectl' >> /etc/profile
echo 'complete -o default -F __start_kubectl k' >> /etc/profile
k get node
# kubecolor 설치 : https://kubecolor.github.io/setup/install/
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --add-repo https://kubecolor.github.io/packages/rpm/kubecolor.repo
dnf repolist
dnf install -y kubecolor
kubecolor get node
alias kc=kubecolor
echo 'alias kc=kubecolor' >> /etc/profile
kc get node
kc describe node
# Install Kubectx & Kubens"
dnf install -y git
git clone https://github.com/ahmetb/kubectx /opt/kubectx
ln -s /opt/kubectx/kubens /usr/local/bin/kubens
ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
# Install Kubeps & Setting PS1
git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
cat << "EOT" >> /root/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=true
function get_cluster_short() {
echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT
# 빠져나오기
exit
exit
# 다시 접속
vagrant ssh k8s-ctr
whoami
pwd
kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab"
kubens default
# helm 3 설치 : https://helm.sh/docs/intro/install
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | DESIRED_VERSION=v3.18.6 bash
helm version
# k9s 설치 : https://github.com/derailed/k9s
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_${CLI_ARCH}.tar.gz
tar -xzf k9s_linux_*.tar.gz
ls -al k9s
chown root:root k9s
mv k9s /usr/local/bin/
chmod +x /usr/local/bin/k9s
k9s
- #
- [k8s-ctr] Flannel CNI 설치 v0.27.3
- cni0 브리지에 veth 와 flannel.1 연동
- # 현재 k8s 클러스터에 파드 전체 CIDR 확인
kc describe pod -n kube-system kube-controller-manager-k8s-ctr
...
Command:
kube-controller-manager
--allocate-node-cidrs=true
--cluster-cidr=10.244.0.0/16
--service-cluster-ip-range=10.96.0.0/16
...
# 노드별 파드 CIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
k8s-ctr 10.244.0.0/24
# Deploying Flannel with Helm
# https://github.com/flannel-io/flannel/blob/master/Documentation/configuration.md
helm repo add flannel https://flannel-io.github.io/flannel
helm repo update
kubectl create namespace kube-flannel
cat << EOF > flannel.yaml
podCidr: "10.244.0.0/16"
flannel:
cniBinDir: "/opt/cni/bin"
cniConfDir: "/etc/cni/net.d"
args:
- "--ip-masq"
- "--kube-subnet-mgr"
- "--iface=enp0s9"
backend: "vxlan"
EOF
helm install flannel flannel/flannel --namespace kube-flannel --version 0.27.3 -f flannel.yaml
# 확인
helm list -A
helm get values -n kube-flannel flannel
kubectl get ds,pod,cm -n kube-flannel -owide
kc describe cm -n kube-flannel kube-flannel-cfg
kc describe ds -n kube-flannel
...
Command:
/opt/bin/flanneld
--ip-masq
--kube-subnet-mgr
--iface=enp0s9
# flannel cni 바이너리 설치 확인
ls -l /opt/cni/bin/
-rwxr-xr-x. 1 root root 2974540 Jan 17 01:35 flannel
...
# cni 설정 정보 확인
tree /etc/cni/net.d/
cat /etc/cni/net.d/10-flannel.conflist | jq
# cni 설치 후 아래 상태(conditions) 정상 확인
crictl info | jq
"status": {
"conditions": [
{
"message": "",
"reason": "",
"status": true,
"type": "RuntimeReady"
},
{
"message": "",
"reason": "",
"status": true,
"type": "NetworkReady"
},
{
"message": "",
"reason": "",
"status": true,
"type": "ContainerdHasNoDeprecationWarnings"
}
]
}
}
# coredns 파드 정상 기동 확인
kubectl get pod -n kube-system -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-668d6bf9bc-bmdjw 1/1 Running 0 137m 10.244.0.3 k8s-ctr <none> <none>
coredns-668d6bf9bc-cbtn9 1/1 Running 0 137m 10.244.0.2 k8s-ctr <none> <none>
...
# network 정보 확인
ip -c route | grep 10.244.
ip addr # cni0, flannel.1, vethY.. 확인
bridge link
lsns -t net
# iptables 규칙 확인
iptables -t nat -S
iptables -t filter -S
iptables-save
- [k8s-ctr] 노드 정보 확인, 기본 환경 정보 출력 비교, sysctl 변경 확인
- # # kubelet 활성화 확인 : 실제 기동은 kubeadm init 후에 시작됨
systemctl is-active kubelet
systemctl status kubelet --no-pager
# 노드 정보 확인 : 일반 워크로드가 Control Plane에 스케줄 X
kc describe node
Labels: ...
node-role.kubernetes.io/control-plane=
Taints: node-role.kubernetes.io/control-plane:NoSchedule
# 기본 환경 정보 출력 저장
cat /etc/sysconfig/kubelet
tree /etc/kubernetes | tee -a etc_kubernetes-2.txt
tree /var/lib/kubelet | tee -a var_lib_kubelet-2.txt
tree /run/containerd/ -L 3 | tee -a run_containerd-2.txt
pstree -alnp | tee -a pstree-2.txt
systemd-cgls --no-pager | tee -a systemd-cgls-2.txt
lsns | tee -a lsns-2.txt
ip addr | tee -a ip_addr-2.txt
ss -tnlp | tee -a ss-2.txt
df -hT | tee -a df-2.txt
findmnt | tee -a findmnt-2.txt
sysctl -a | tee -a sysctl-2.txt
# 파일 출력 비교 : 빠져나오기 ':q' -> ':q' => 변경된 부분이 어떤 동작과 역할인지 조사해보기!
vi -d etc_kubernetes-1.txt etc_kubernetes-2.txt
vi -d var_lib_kubelet-1.txt var_lib_kubelet-2.txt
vi -d run_containerd-1.txt run_containerd-2.txt
vi -d pstree-1.txt pstree-2.txt
vi -d systemd-cgls-1.txt systemd-cgls-2.txt
vi -d lsns-1.txt lsns-2.txt
vi -d ip_addr-1.txt ip_addr-2.txt
vi -d ss-1.txt ss-2.txt
vi -d df-1.txt df-2.txt
vi -d findmnt-1.txt findmnt-2.txt
# kubelet 에 --protect-kernel-defaults=false 적용되어 관련 코드에 sysctl 커널 파라미터 적용 : 아래 링크 확왼
## 위 설정 시, 커널 튜닝 가능 항목 중 하나라도 kubelet의 기본값과 다르면 오류가 발생합니다
vi -d sysctl-1.txt sysctl-2.txt
kernel.panic = 0 -> 10 변경
kernel.panic_on_oops = 1 기존값 그대로
vm.overcommit_memory = 0 -> 1 변경
vm.panic_on_oom = 0 기존값 그대로
sysctl kernel.keys.root_maxkeys # 1000000 기존값 그대로
sysctl kernel.keys.root_maxbytes # 25000000 # root_maxkeys * 25 기존값 그대로
# kube-proxy 에서도 관련 코드에 sysctl 커널 파라미터 적용 : 아래 링크 확왼
net.nf_conntrack_max = 65536 -> 131072
net.netfilter.nf_conntrack_max = 65536 -> 131072
net.netfilter.nf_conntrack_count = 1 -> 282
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60 -> 3600
net.netfilter.nf_conntrack_tcp_timeout_established = 432000 -> 86400
- # # kubelet 활성화 확인 : 실제 기동은 kubeadm init 후에 시작됨
- [k8s-ctr] 인증서 확인
- [certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-ctr kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 192.168.10.100]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-ctr localhost] and IPs [192.168.10.100 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-ctr localhost] and IPs [192.168.10.100 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
# kubeadm-config 확인
kc describe cm -n kube-system kubeadm-config
...
apiVersion: kubeadhttp://m.k8s.io/v1beta4
caCertificateValidityPeriod: 87600h0m0s # CA인증서: 10년
certificateValidityPeriod: 8760h0m0s # 인증서 : 1년
certificatesDir: /etc/kubernetes/pki # 인증서 위치
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.k8s.io
...
# Checks expiration for the certificates in the local PKI managed by kubeadm.
kubeadm certs check-expiration
[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.
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Jan 16, 2027 14:33 UTC 364d ca no
apiserver Jan 16, 2027 14:33 UTC 364d ca no
apiserver-etcd-client Jan 16, 2027 14:33 UTC 364d etcd-ca no
apiserver-kubelet-client Jan 16, 2027 14:33 UTC 364d ca no
controller-manager.conf Jan 16, 2027 14:33 UTC 364d ca no
etcd-healthcheck-client Jan 16, 2027 14:33 UTC 364d etcd-ca no
etcd-peer Jan 16, 2027 14:33 UTC 364d etcd-ca no
etcd-server Jan 16, 2027 14:33 UTC 364d etcd-ca no
front-proxy-client Jan 16, 2027 14:33 UTC 364d front-proxy-ca no
scheduler.conf Jan 16, 2027 14:33 UTC 364d ca no
super-admin.conf Jan 16, 2027 14:33 UTC 364d ca no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Jan 14, 2036 14:33 UTC 9y no
etcd-ca Jan 14, 2036 14:33 UTC 9y no
front-proxy-ca Jan 14, 2036 14:33 UTC 9y no
#
tree /etc/kubernetes/
tree /etc/kubernetes/pki
# CA 인증서
cat /etc/kubernetes/pki/ca.crt | openssl x509 -text -noout
...
Issuer: CN=kubernetes
Validity
Not Before: Jan 16 14:28:05 2026 GMT
Not After : Jan 14 14:33:05 2036 GMT
Subject: CN=kubernetes
...
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment, Certificate Sign
X509v3 Basic Constraints: critical
CA:TRUE
# apiserver 인증서 : 'TLS Web Server' 키 용도 확인
cat /etc/kubernetes/pki/apiserver.crt | openssl x509 -text -noout
...
Issuer: CN=kubernetes
Validity
Not Before: Jan 16 14:28:05 2026 GMT
Not After : Jan 16 14:33:05 2027 GMT
Subject: CN=kube-apiserver
...
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
40:6A:34:8B:CD:FC:94:4C:19:69:E6:0F:07:E3:4E:7B:29:2F:26:C6
X509v3 Subject Alternative Name:
DNS:k8s-ctr, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:10.96.0.1, IP Address:192.168.10.100
# apiserver-kubelet-client 인증서 : 'TLS Web Client' 키 용도 확인
cat /etc/kubernetes/pki/apiserver-kubelet-client.crt | openssl x509 -text -noout
Subject: O=kubeadm:cluster-admins, CN=kube-apiserver-kubelet-client
...
X509v3 Extended Key Usage:
TLS Web Client Authentication
# 나머지 인증서도 확인해보자!
- [certs] Using certificateDir folder "/etc/kubernetes/pki"
- [k8s-ctr] kubeconfig 확인
- # 관리자 용도
cat /etc/kubernetes/admin.conf
cat /etc/kubernetes/super-admin.conf
# kcm
cat /etc/kubernetes/controller-manager.conf
# scheduler
cat /etc/kubernetes/scheduler.conf
# kubelet
cat /etc/kubernetes/kubelet.conf
...
users:
- name: system:node:k8s-ctr
user:
client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
#
ls -l /var/lib/kubelet/pki
-rw-------. 1 root root 2822 Jan 17 09:34 kubelet-client-2026-01-17-09-34-41.pem
lrwxrwxrwx. 1 root root 59 Jan 17 09:34 kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2026-01-17-09-34-41.pem
-rw-r--r--. 1 root root 2262 Jan 17 09:34 kubelet.crt
-rw-------. 1 root root 1679 Jan 17 09:34 kubelet.key
# kubelet 서버 역할 : Subjec, Key Usage, SAN 확인
cat /var/lib/kubelet/pki/kubelet.crt | openssl x509 -text -noout
Issuer: CN=k8s-ctr-ca@1768610081
Validity
Not Before: Jan 16 23:34:41 2026 GMT
Not After : Jan 16 23:34:41 2027 GMT
Subject: CN=k8s-ctr@1768610081
...
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Authority Key Identifier:
D5:FE:B1:E9:89:9F:46:A8:56:D4:4E:4B:01:7B:2B:49:44:FA:61:85
X509v3 Subject Alternative Name:
DNS:k8s-ctr
# kubelet 클라이언트 역할 : Subjec, Key Usage 확인
cat /var/lib/kubelet/pki/kubelet-client-current.pem | openssl x509 -text -noout
Issuer: CN=kubernetes
Validity
Not Before: Jan 17 00:28:48 2026 GMT
Not After : Jan 17 00:33:48 2027 GMT
Subject: O=system:nodes, CN=system:node:k8s-ctr
...
X509v3 Extended Key Usage:
TLS Web Client Authentication
- # 관리자 용도
- [k8s-ctr] static pod 확인 : etcd, kube-apiserver, kube-scheduler,kube-controller-manager
- # kubeket에 의해 기동되는 static pod 대상 매니페스트 디렉터리 확인
tree /etc/kubernetes/manifests/
/etc/kubernetes/manifests/
├── etcd.yaml
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml
# kubelt 설정 확인
cat /var/lib/kubelet/config.yaml
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
cgroupDriver: systemd
staticPodPath: /etc/kubernetes/manifests
...
cat /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip=192.168.10.100 --pod-infra-container-image=registry.k8s.io/pause:3.10"
# static 파드 확인
kubectl get pod -n kube-system -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
etcd-k8s-ctr 1/1 Running 0 4h9m 192.168.10.100 k8s-ctr <none> <none>
kube-apiserver-k8s-ctr 1/1 Running 0 4h9m 192.168.10.100 k8s-ctr <none> <none>
kube-controller-manager-k8s-ctr 1/1 Running 0 4h9m 192.168.10.100 k8s-ctr <none> <none>
kube-scheduler-k8s-ctr 1/1 Running 0 4h9m 192.168.10.100 k8s-ctr <none> <none>
...
# etcd : etcd client 는 https://192.168.10.100:2379 호출, metrics 은 http://127.0.0.1:2381 확인
tree /var/lib/etcd/
cat /etc/kubernetes/manifests/etcd.yaml
- --advertise-client-urls=https://192.168.10.100:2379
- --listen-client-urls=https://127.0.0.1:2379,https://192.168.10.100:2379
- --listen-metrics-urls=http://127.0.0.1:2381
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd-data
- mountPath: /etc/kubernetes/pki/etcd
name: etcd-certs
hostNetwork: true
priority: 2000001000
priorityClassName: system-node-critical
...
# kube-apiserver
cat /etc/kubernetes/manifests/kube-apiserver.yaml
- command:
- kube-apiserver
# Listen https://<IP>:6443
- --advertise-address=192.168.10.100
- --secure-port=6443
# etcd client -> etcd server(https://127.0.0.1:2379)
- --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
- --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
- --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
- --etcd-servers=https://127.0.0.1:2379
# kubelet-client -> kubelet
- --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
# k8s servicd cid
- --service-cluster-ip-range=10.96.0.0/16
ss -tnlp | grep apiserver
LISTEN 0 4096 *:6443 *:* users:(("kube-apiserver",pid=6400,fd=3))
## k8s 내부에서 api 호출 시 : https://10.96.0.1 혹은 https://kubernetes.default.svc.cluster.local
kubectl get svc,ep
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h26m
NAME ENDPOINTS AGE
endpoints/kubernetes 192.168.10.100:6443 4h26m
# scheduler
cat /etc/kubernetes/manifests/kube-scheduler.yaml
- command:
- kube-scheduler
- --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
- --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
- --bind-address=127.0.0.1
- --kubeconfig=/etc/kubernetes/scheduler.conf
- --leader-elect=true
## tcp 10259 Listen 포트 확인
ss -tnlp | grep scheduler
LISTEN 0 4096 127.0.0.1:10259 0.0.0.0:* users:(("kube-scheduler",pid=6397,fd=3))
## scheduler 파드가 1개 이상일 경우 리더 역할 파드 확인
## Lease는 k8s의 경량 coordination 리소스 : 리더 선출 (Leader Election), 노드/컴포넌트 상태 heartbeat, 저부하(high-scale) 상태 갱신
kubectl get leases.coordination.k8s.io -n kube-system kube-scheduler -o yaml
kubectl get leases.coordination.k8s.io -n kube-system kube-scheduler
NAME HOLDER AGE
kube-scheduler k8s-ctr_7d815157-fdd5-4753-8a64-023d115d3704 4h31m
## Node Heartbeat (Node 상태) : node heartbeat 전용 네임스페이스
kubectl get lease -n kube-node-lease
kubectl get lease -n kube-node-lease -o yaml
# kube-controller-manager
cat /etc/kubernetes/manifests/kube-controller-manager.yaml
- command:
- kube-controller-manager
# kcm bind address
- --bind-address=127.0.0.1
# 노드별 파드 cidr 할당
- --allocate-node-cidrs=true
- --cluster-cidr=10.244.0.0/16
# k8s svc cidr
- --service-cluster-ip-range=10.96.0.0/16
# kcm controller
- --controllers=*,bootstrapsigner,tokencleaner
# lease 사용 : 리더
- --leader-elect=true
# 모든 컨트롤러가 kcm의 단일 권한(identity) 사용하지 않고, 컨트롤러별 개별 ServiceAccount + RBAC 사용
- --use-service-account-credentials=true
## tcp 10257 Listen 포트 확인
ss -tnlp | grep controller
LISTEN 0 4096 127.0.0.1:10257 0.0.0.0:* users:(("kube-controller",pid=6393,fd=3))
## 노드별 파드 CIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
k8s-ctr 10.244.0.0/24
## kcm 파드가 1개 이상일 경우 리더 역할 파드 확인
kubectl get lease -n kube-system kube-controller-manager -o yaml
kubectl get lease -n kube-system kube-controller-manager
## 컨트롤러별 개별 ServiceAccount + RBAC 사용
kubectl get sa -n kube-system | grep controller
attachdetach-controller 0 4h44m
certificate-controller 0 4h44m
clusterrole-aggregation-controller 0 4h44m
...(생략)...
- # kubeket에 의해 기동되는 static pod 대상 매니페스트 디렉터리 확인
- 필수 애드온 설치 (coredns, kube-proxy) 확인
- # coredns 확인
kc describe deploy -n kube-system coredns
kubectl get deploy -n kube-system coredns -owide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
coredns 2/2 2 2 4h46m coredns registry.k8s.io/coredns/coredns:v1.11.3 k8s-app=kube-dns
## label 도 아직 예전 kube-dns 사용
kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-668d6bf9bc-cspcd 1/1 Running 0 4h48m 10.244.0.2 k8s-ctr <none> <none>
coredns-668d6bf9bc-gh225 1/1 Running 0 4h48m 10.244.0.3 k8s-ctr <none> <none>
##
kubectl get svc,ep -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 4h50m
NAME ENDPOINTS AGE
endpoints/kube-dns 10.244.0.2:53,10.244.0.3:53,10.244.0.2:53 + 3 more... 4h49m
## 프로메테우스 메트릭 엔드포인트 확인
curl -s http://10.96.0.10:9153/metrics | head
## configmap 확인
kc describe cm -n kube-system coredns
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}
## forward 정보 확인
cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 168.126.63.1
nameserver 8.8.8.8
# kube-proxy 확인
kubectl get ds -n kube-system -owide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
kube-proxy 1 1 1 1 1 kubernetes.io/os=linux 4h53m kube-proxy registry.k8s.io/kube-proxy:v1.32.11 k8s-app=kube-proxy
kc describe pod -n kube-system -l k8s-app=kube-proxy
kubectl get pod -n kube-system -l k8s-app=kube-proxy -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-proxy-7w44b 1/1 Running 0 4h54m 192.168.10.100 k8s-ctr <none> <none>
kc describe cm -n kube-system kube-proxy
bindAddress: 0.0.0.0
metricsBindAddress: ""
clusterCIDR: 10.244.0.0/16
conntrack:
maxPerCore: null
min: null
mode: "" # 기본 모드 : iptables
nodePortAddresses: null # NodePort 서비스가 '모든 노드 인터페이스(IP)'에 바인딩됨
portRange: "" # kube-proxy 자체가 '포트 범위를 제한하지 않음'
##
ss -tnlp | grep kube-proxy
LISTEN 0 4096 127.0.0.1:10249 0.0.0.0:* users:(("kube-proxy",pid=6631,fd=10)) # 헬스 체크 전용
LISTEN 0 4096 *:10256 *:* users:(("kube-proxy",pid=6631,fd=9)) # 메트릭 노출용
curl 127.0.0.1:10249/healthz ; echo
ok
# 엔드포인트 정보 확인해보자
curl http://127.0.0.1:10256/metrics
curl http://192.168.10.100:10256/metrics
404 page not found
## iptables rule 확인
iptables -t nat -S
iptables -t filter -S
iptables-save
## conntrack 전용 툴 설치
dnf install -y conntrack-tools
conntrack -V
## conntrack 툴 사용
conntrack -L # 전체 conntrack 엔트리 조회
conntrack -L -p tcp # TCP 연결만 보기
conntrack -L -p tcp --state ESTABLISHED # 특정 상태 필터링
conntrack -L | grep dport=443 # 특정 포트 관련 연결
conntrack -E # 실시간 이벤트 추적
## conntrack sysctl 주요 파라미터
## nf_conntrack_max : 최대 엔트리 수
## nf_conntrack_count : 현재 사용 중
## nf_conntrack_tcp_timeout_established : TCP 유지 시간
sysctl -a | grep conntrack
- # coredns 확인
- Customizing components with the kubeadm API - Docs
- [k8s-w1/w2] 설정
- 사전 설정
- # root 권한(로그인 환경) 전환
echo "sudo su -" >> /home/vagrant/.bashrc
sudo su -
# Time, NTP 설정
timedatectl set-local-rtc 0
# 시스템 타임존(Timezone)을 한국(KST, UTC+9) 으로 설정 : 시스템 시간은 UTC 기준 유지, 표시만 KST로 변환
timedatectl set-timezone Asia/Seoul
# SELinux 설정 : Kubernetes는 Permissive 권장
setenforce 0
# 재부팅 시에도 Permissive 적용
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
# firewalld(방화벽) 끄기
systemctl disable --now firewalld
# Swap 비활성화
swapoff -a
# 재부팅 시에도 'Swap 비활성화' 적용되도록 /etc/fstab에서 swap 라인 주석 처리
sed -i '/swap/d' /etc/fstab
# 커널 모듈 로드
modprobe overlay
modprobe br_netfilter
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 커널 파라미터 설정 : 네트워크 설정 - 브릿지 트래픽이 iptables를 거치도록 함
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 설정 적용
sysctl --system >/dev/null 2>&1
# hosts 설정
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
cat << EOF >> /etc/hosts
192.168.10.100 k8s-ctr
192.168.10.101 k8s-w1
192.168.10.102 k8s-w2
EOF
cat /etc/hosts
- # root 권한(로그인 환경) 전환
- CRI 설치 : containerd(runc) v2.1.5
- # Docker 저장소 추가
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# containerd 설치
dnf install -y containerd.io-2.1.5-1.el10
# 기본 설정 생성 및 SystemdCgroup 활성화 (매우 중요)
containerd config default | tee /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
# systemd unit 파일 최신 상태 읽기
systemctl daemon-reload
# containerd start 와 enabled
systemctl enable --now containerd
- # Docker 저장소 추가
- kubeadm, kubelet 및 kubectl 설치 v1.32.11
- # repo 추가
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
# 설치
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
# kubelet 활성화 (실제 기동은 kubeadm init 후에 시작됨)
systemctl enable --now kubelet
# /etc/crictl.yaml 파일 작성
cat << EOF > /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
EOF
- # repo 추가
- kubeadm 으로 k8s join*
-
- Kubernetes API 서버를 신뢰하도록 하는 디스커버리 단계 → Kubernetes API 서버가 노드를 신뢰하도록 하는 TLS 부트스트랩 단계
- Preflight checks : init 과 동일
- Discovery cluster-info 클러스터 정보 발견
- 공개 정보 조회: 사용자가 입력한 토큰과 -discovery-token-ca-cert-hash를 사용하여 API 서버의 kube-public/cluster-info ConfigMap을 익명으로 요청합니다.
- Shared token discovery (--discovery-token) or File/https discovery (--discovery-file)
- 신뢰 검증: 가져온 ConfigMap 내의 CA 인증서가 사용자가 입력한 해시값(--discovery-token-ca-cert-hash sha256:xxxx)과 일치하는지 확인하여, 접속하려는 마스터 노드가 진짜인지 검증합니다.
- TLS Bootstrap (인증서 발급 요청)
- 부트스트랩 인증: 이제 마스터를 신뢰할 수 있으므로, 토큰을 사용해 API 서버에 정식으로 인증을 시도합니다.
- CSR 생성: 워커 노드는 자신만의 개인키를 만들고, API 서버에 "나를 위한 인증서를 서명해달라"는 CSR(Certificate Signing Request)을 보냅니다.
- 자동 승인: 마스터의 Certificate 발급자는 이 요청이 유효한 토큰을 통한 것임을 확인하고 자동으로 승인합니다.
- Kubelet 설정 및 기동
- Kubeconfig 생성: 발급받은 정식 인증서를 사용하여 /etc/kubernetes/kubelet.conf 파일을 생성합니다.
- 노드 등록: kubelet이 이 설정을 가지고 실행되면서 API 서버에 자신을 "Node" 리소스로 등록합니다.
- 최종 합류: 마스터는 이 노드에 kube-proxy 등을 배포하며, 노드 상태가 Ready가 될 준비를 마칩니다.
- # 기본 환경 정보 출력 저장
crictl images
crictl ps
cat /etc/sysconfig/kubelet
tree /etc/kubernetes | tee -a etc_kubernetes-1.txt
tree /var/lib/kubelet | tee -a var_lib_kubelet-1.txt
tree /run/containerd/ -L 3 | tee -a run_containerd-1.txt
pstree -alnp | tee -a pstree-1.txt
systemd-cgls --no-pager | tee -a systemd-cgls-1.txt
lsns | tee -a lsns-1.txt
ip addr | tee -a ip_addr-1.txt
ss -tnlp | tee -a ss-1.txt
df -hT | tee -a df-1.txt
findmnt | tee -a findmnt-1.txt
sysctl -a | tee -a sysctl-1.txt
# kubeadm Configuration 파일 작성
NODEIP=$(ip -4 addr show enp0s9 | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo $NODEIP
cat << EOF > kubeadm-join.yaml
apiVersion: kubeadhttp://m.k8s.io/v1beta4
kind: JoinConfiguration
discovery:
bootstrapToken:
token: "123456.1234567890123456"
apiServerEndpoint: "192.168.10.100:6443"
unsafeSkipCAVerification: true
nodeRegistration:
criSocket: "unix:///run/containerd/containerd.sock"
kubeletExtraArgs:
- name: node-ip
value: "$NODEIP"
EOF
cat kubeadm-join.yaml
# join
kubeadm join --config="kubeadm-join.yaml"
[preflight] Running pre-flight checks
[preflight] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[preflight] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-check] Waiting for a healthy kubelet at http://127.0.0.1:10248/healthz. This can take up to 4m0s
[kubelet-check] The kubelet is healthy after 501.164948ms
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap
# crictl 확인
crictl images
crictl ps
# cluster-info cm 호출 가능 확인
curl -s -k https://192.168.10.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info | jq
-
- [k8s-ctr] k8s-w1/w2 관련 정보 확인
- # join 된 워커 노드 확인
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 6h58m v1.32.11 192.168.10.100 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w1 Ready <none> 2m29s v1.32.11 192.168.10.101 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready <none> 2m29s v1.32.11 192.168.10.102 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
# 노드별 파드 CIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
k8s-ctr 10.244.0.0/24
k8s-w1 10.244.1.0/24
k8s-w2 10.244.2.0/24
# 다른 노드의 파드 CIDR(Per Node Pod CIDR)에 대한 라우팅이 자동으로 커널 라우팅에 추가됨을 확인 : flannel.1 을 통해 VXLAN 통한 라우팅
ip -c route | grep flannel
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
# k8s-ctr 에서 10.244.1.0 IP로 통신 가능(vxlan overlay 사용) 확인
ping -c 1 10.244.1.0
PING 10.244.1.0 (10.244.1.0) 56(84) bytes of data.
64 bytes from 10.244.1.0: icmp_seq=1 ttl=64 time=1.19 ms
# 워커 노드에 Taints 정보 확인
kc describe node k8s-w1
Taints: <none>
# k8s-w1 노드에 배치된 파드 확인
kubectl get pod -A -owide | grep k8s-w2
kubectl get pod -A -owide | grep k8s-w1
kube-flannel kube-flannel-ds-nhfhh 1/1 Running 0 4m3s 192.168.10.101 k8s-w1 <none> <none>
kube-system kube-proxy-7zczb 1/1 Running 0 4m3s 192.168.10.101 k8s-w1 <none> <none>
- # join 된 워커 노드 확인
- [k8s-w1/w2 노드 정보 확인, 기본 환경 정보 출력 비교, sysctl 변경 확인
- # # kubelet 활성화 확인
systemctl status kubelet --no-pager
# 기본 환경 정보 출력 저장
cat /etc/sysconfig/kubelet
tree /etc/kubernetes | tee -a etc_kubernetes-2.txt
/etc/kubernetes
├── kubelet.conf
├── manifests
└── pki
└── ca.crt
cat /etc/kubernetes/kubelet.conf
tree /var/lib/kubelet | tee -a var_lib_kubelet-2.txt
tree /run/containerd/ -L 3 | tee -a run_containerd-2.txt
pstree -alnp | tee -a pstree-2.txt
systemd-cgls --no-pager | tee -a systemd-cgls-2.txt
lsns | tee -a lsns-2.txt
ip addr | tee -a ip_addr-2.txt
ss -tnlp | tee -a ss-2.txt
df -hT | tee -a df-2.txt
findmnt | tee -a findmnt-2.txt
sysctl -a | tee -a sysctl-2.txt
# kubelet 에 --protect-kernel-defaults=false 적용되어 관련 코드에 sysctl 커널 파라미터 적용
vi -d sysctl-1.txt sysctl-2.txt
kernel.panic = 0 -> 10 변경
vm.overcommit_memory = 0 -> 1 변경
# 파일 출력 비교 : 빠져나오기 ':q' -> ':q' => 변경된 부분이 어떤 동작과 역할인지 조사해보기!
vi -d etc_kubernetes-1.txt etc_kubernetes-2.txt
vi -d var_lib_kubelet-1.txt var_lib_kubelet-2.txt
vi -d run_containerd-1.txt run_containerd-2.txt
vi -d pstree-1.txt pstree-2.txt
vi -d systemd-cgls-1.txt systemd-cgls-2.txt
vi -d lsns-1.txt lsns-2.txt
vi -d ip_addr-1.txt ip_addr-2.txt
vi -d ss-1.txt ss-2.txt
vi -d df-1.txt df-2.txt
vi -d findmnt-1.txt findmnt-2.txt
- # # kubelet 활성화 확인
- 사전 설정
- 모니터링 툴 설치 : 프로메테우스-스택 설치 → 인증서 익스포터 설치 → 그라파나 대시보드 확인
- metrics-server 설치
- # metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
# 확인
kubectl top node
kubectl top pod -A --sort-by='cpu'
kubectl top pod -A --sort-by='memory'
- # metrics-server
- kube-prometheus-stack 설치 - Link
- # repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
scrapeInterval: "20s"
evaluationInterval: "20s"
externalLabels:
cluster: "myk8s-cluster"
service:
type: NodePort
nodePort: 30001
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
service:
type: NodePort
nodePort: 30002
alertmanager:
enabled: true
defaultRules:
create: true
kubeProxy:
enabled: false
prometheus-windows-exporter:
prometheus:
monitor:
enabled: false
EOT
cat monitor-values.yaml
# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 80.13.3 \
-f monitor-values.yaml --create-namespace --namespace monitoring
# 확인
helm list -n monitoring
kubectl get pod,svc,ingress,pvc -n monitoring
kubectl get prometheus,servicemonitors,alertmanagers -n monitoring
kubectl get crd | grep monitoring
# 각각 웹 접속 실행 : NodePort 접속
open http://192.168.10.100:30001 # prometheus
open http://192.168.10.100:30002 # grafana : 접속 계정 admin / prom-operator
# 프로메테우스 버전 확인
kubectl exec -it sts/prometheus-kube-prometheus-stack-prometheus -n monitoring -c prometheus -- prometheus --version
prometheus, version 3.9.1
# 그라파나 버전 확인
kubectl exec -it -n monitoring deploy/kube-prometheus-stack-grafana -- grafana --version
grafana version 12.3.1 - [K8S Dashboard 추가] Dashboard → New → Import → 15661, 15757 입력 후 Load ⇒ 데이터소스(Prometheus 선택) 후 Import 클릭
- # repo 추가
- kube-controller-manager, etcd, kube-scheduler 메트릭 수집 설정 → kubeadm init 시 적용해보자!
- # kube-controller-manager bind-address 127.0.0.1 => 0.0.0.0 변경
sed -i 's|--bind-address=127.0.0.1|--bind-address=0.0.0.0|g' /etc/kubernetes/manifests/kube-controller-manager.yaml
cat /etc/kubernetes/manifests/kube-controller-manager.yaml | grep bind-address
- --bind-address=0.0.0.0
# kube-scheduler bind-address 127.0.0.1 => 0.0.0.0 변경
sed -i 's|--bind-address=127.0.0.1|--bind-address=0.0.0.0|g' /etc/kubernetes/manifests/kube-scheduler.yaml
cat /etc/kubernetes/manifests/kube-scheduler.yaml | grep bind-address
- --bind-address=0.0.0.0
# etcd metrics-url(http) 127.0.0.1 에 192.168.10.100 추가
sed -i 's|--listen-metrics-urls=http://127.0.0.1:2381|--listen-metrics-urls=http://127.0.0.1:2381,http://192.168.10.100:2381|g' /etc/kubernetes/manifests/etcd.yaml
cat /etc/kubernetes/manifests/etcd.yaml | grep listen-metrics-urls
- --listen-metrics-urls=http://127.0.0.1:2381,http://192.168.10.100:2381
- # kube-controller-manager bind-address 127.0.0.1 => 0.0.0.0 변경
- k8s 인증서 위치 확인
- # Check certificates expiration for a Kubernetes cluster
kubeadm certs check-expiration
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Jan 17, 2027 00:33 UTC 364d ca no
apiserver Jan 17, 2027 00:33 UTC 364d ca no
apiserver-etcd-client Jan 17, 2027 00:33 UTC 364d etcd-ca no
apiserver-kubelet-client Jan 17, 2027 00:33 UTC 364d ca no
controller-manager.conf Jan 17, 2027 00:33 UTC 364d ca no
etcd-healthcheck-client Jan 17, 2027 00:33 UTC 364d etcd-ca no
etcd-peer Jan 17, 2027 00:33 UTC 364d etcd-ca no
etcd-server Jan 17, 2027 00:33 UTC 364d etcd-ca no
front-proxy-client Jan 17, 2027 00:33 UTC 364d front-proxy-ca no
scheduler.conf Jan 17, 2027 00:33 UTC 364d ca no
super-admin.conf Jan 17, 2027 00:33 UTC 364d ca no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Jan 15, 2036 00:33 UTC 9y no
etcd-ca Jan 15, 2036 00:33 UTC 9y no
front-proxy-ca Jan 15, 2036 00:33 UTC 9y no
#
tree /etc/kubernetes
/etc/kubernetes
|-- admin.conf # kubeconfig file
|-- controller-manager.conf # kubeconfig file
|-- kubelet.conf # kubeconfig file -> 인증서/키 파일은 /var/lib/kubelet/pki/ 에 위치
|-- manifests
| |-- etcd.yaml
| |-- kube-apiserver.yaml
| |-- kube-controller-manager.yaml
| `-- kube-scheduler.yaml
|-- pki # 해당 디렉터리 아래에는 인증서와 키파일 위치 : /etc/kubernetes/pki
| |-- apiserver-etcd-client.crt
| |-- apiserver-etcd-client.key
| |-- apiserver-kubelet-client.crt
| |-- apiserver-kubelet-client.key
| |-- apiserver.crt
| |-- apiserver.key
| |-- ca.crt
| |-- ca.key
| |-- etcd # 해당 디렉터리 아래에는 ETCD 관련 인증서와 키파일 위치 : /etc/kubernetes/pki/etcd
| | |-- ca.crt
| | |-- ca.key
| | |-- healthcheck-client.crt
| | |-- healthcheck-client.key
| | |-- peer.crt
| | |-- peer.key
| | |-- server.crt
| | `-- server.key
| |-- front-proxy-ca.crt
| |-- front-proxy-ca.key
| |-- front-proxy-client.crt
| |-- front-proxy-client.key
| |-- sa.key
| `-- sa.pub
|-- scheduler.conf # kubeconfig file
`-- super-admin.conf # kubeconfig file
# 위 kubelet.conf 에 대한 인증서/키 파일 위치 : worker 노드 동일
tree /var/lib/kubelet/pki/
/var/lib/kubelet/pki/
|-- kubelet-client-2026-01-11-05-06-09.pem
|-- kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2026-01-11-05-06-09.pem
|-- kubelet.crt
`-- kubelet.key
- # Check certificates expiration for a Kubernetes cluster
- x509 certificate exporter 소개 - Github , 다른 툴도 있음 cert-exporter - Github
-
- 인증서 만료 기간 메트릭 노출 A Prometheus exporter for certificates focusing on expiration monitoring
- 만료 전 알람 지원 종류 Get notified before they expire:
- 시크릿(TLS) : TLS Secrets from a Kubernetes cluster
- PEM 인코딩된 파일 : PEM encoded files, by path or scanning directories
- 인증서 및 파일 참조 포함된 kubeconfig : Kubeconfigs with embedded certificates or file references
- 지원 메트릭 종류 The following metrics are available:
- x509_cert_not_before
- x509_cert_not_after
- x509_cert_expired
- x509_cert_expires_in_seconds (optional)
- x509_cert_valid_since_seconds (optional)
- x509_cert_error (optional)
- x509_read_errors
- x509_exporter_build_info
-
- x509 certificate exporter 설치 - Link
-
- values 파일 주요 내용
- 데몬셋 2종류 배포 : 컨트롤 플레인 노드들 수집(cp), 워커 노드들 수집(nodes)
- 수집 종류 : 인증서(crt) 파일, kubeconfig 파일 → 직접 파일 위치 설정
- 프로메테우스 알람 설정 활성화 : warning Days Left(28 일), critical Days Left(14일)
- 그라파나 대시보드 추가 활성화
- values 파일 주요 내용
- # w1/w2 에 node label 설정
kubectl label node k8s-w1 worker="true" --overwrite
kubectl label node k8s-w2 worker="true" --overwrite
kubectl get nodes -l worker=true
# values 파일 작성
cat << EOF > cert-export-values.yaml
# -- hostPaths Exporter
hostPathsExporter:
hostPathVolumeType: Directory
daemonSets:
cp:
nodeSelector:
node-role.kubernetes.io/control-plane: ""
tolerations:
- effect: NoSchedule
key: node-role.kubernetes.io/control-plane
operator: Exists
watchFiles:
- /var/lib/kubelet/pki/kubelet-client-current.pem
- /var/lib/kubelet/pki/kubelet.crt
- /etc/kubernetes/pki/apiserver.crt
- /etc/kubernetes/pki/apiserver-etcd-client.crt
- /etc/kubernetes/pki/apiserver-kubelet-client.crt
- /etc/kubernetes/pki/ca.crt
- /etc/kubernetes/pki/front-proxy-ca.crt
- /etc/kubernetes/pki/front-proxy-client.crt
- /etc/kubernetes/pki/etcd/ca.crt
- /etc/kubernetes/pki/etcd/healthcheck-client.crt
- /etc/kubernetes/pki/etcd/peer.crt
- /etc/kubernetes/pki/etcd/server.crt
watchKubeconfFiles:
- /etc/kubernetes/admin.conf
- /etc/kubernetes/controller-manager.conf
- /etc/kubernetes/scheduler.conf
nodes:
nodeSelector:
worker: "true"
watchFiles:
- /var/lib/kubelet/pki/kubelet-client-current.pem
- /etc/kubernetes/pki/ca.crt
prometheusServiceMonitor:
create: true
scrapeInterval: 15s
scrapeTimeout: 10s
extraLabels:
release: kube-prometheus-stack
prometheusRules:
create: true
warningDaysLeft: 28
criticalDaysLeft: 14
extraLabels:
release: kube-prometheus-stack
grafana:
createDashboard: true
secretsExporter:
enabled: false
EOF
# helm chart 설치
helm repo add enix https://charts.enix.io
helm install x509-certificate-exporter enix/x509-certificate-exporter -n monitoring --values cert-export-values.yaml
# 설치 확인
helm list -n monitoring
## x509 대시보드 추가 : grafana sidecar 컨테이너가 configmap 확인 후 추가
kubectl get cm -n monitoring x509-certificate-exporter-dashboard
kubectl get cm -n monitoring x509-certificate-exporter-dashboard -o yaml
# 데몬셋 확인 : cp, nodes 각각
kubectl get ds -n monitoring -l app.kubernetes.io/instance=x509-certificate-exporter
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
x509-certificate-exporter-cp 1 1 1 1 1 node-role.kubernetes.io/control-plane= 19m
x509-certificate-exporter-nodes 1 1 1 1 1 kubernetes.io/hostname=myk8s-worker 19m
# 파드 정보 확인 : IP 확인
kubectl get pod -n monitoring -l app.kubernetes.io/instance=x509-certificate-exporter -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
x509-certificate-exporter-cp-fzwrl 1/1 Running 0 20m 10.244.0.5 myk8s-control-plane <none> <none>
x509-certificate-exporter-nodes-fckwl 1/1 Running 0 20m 10.244.1.12 myk8s-worker <none> <none>
# 프로메테우스 서비스모니터 수집을 위한 Service(ClusterIP) 정보 확인
kubectl get svc,ep -n monitoring x509-certificate-exporter
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/x509-certificate-exporter ClusterIP 10.96.6.160 <none> 9793/TCP 54s
NAME ENDPOINTS AGE
endpoints/x509-certificate-exporter 10.244.0.5:9793,10.244.1.11:9793 54s
# 컨트롤플레인 노드에 배포된 'x509 익스포터' 파드에 메트릭 호출 확인
curl -s 10.244.0.5:9793/metrics | grep '^x509' | head -n 3
x509_cert_expired{filename="apiserver-etcd-client.crt",filepath="/etc/kubernetes/pki/apiserver-etcd-client.crt",issuer_CN="etcd-ca",serial_number="3639503804793909323",subject_CN="kube-apiserver-etcd-client"} 0
x509_cert_expired{filename="apiserver.crt",filepath="/etc/kubernetes/pki/apiserver.crt",issuer_CN="kubernetes",serial_number="2465930159994320660",subject_CN="kube-apiserver"} 0
x509_cert_expired{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="7083266840940024496",subject_CN="kubernetes"} 0
# 워커 노드에 배포된 'x509 익스포터' 파드에 메트릭 호출 확인
curl -s 10.244.1.11:9793/metrics | grep '^x509' | head -n 3
x509_cert_expired{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="7083266840940024496",subject_CN="kubernetes"} 0
x509_cert_expired{filename="kubelet-client-current.pem",filepath="/var/lib/kubelet/pki/kubelet-client-current.pem",issuer_CN="kubernetes",serial_number="137383486421320059000355181040497730044",subject_CN="system:node:myk8s-worker",subject_O="system:nodes"} 0
x509_cert_not_after{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="7083266840940024496",subject_CN="kubernetes"} 2.083467966e+09
# 프로메테우스 CR 정보 확인 : 서비스모니터와 룰 셀렉터 정보 확인
kubectl get prometheuses.monitoring.coreos.com -n monitoring -o yaml
...
serviceMonitorSelector:
matchLabels:
release: kube-prometheus-stack
ruleSelector:
matchLabels:
release: kube-prometheus-stack
...
# helm 배포 시, label 추가 해둠
kubectl edit servicemonitors -n monitoring x509-certificate-exporter
...
labels:
app.kubernetes.io/instance: x509-certificate-exporter
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: x509-certificate-exporter
app.kubernetes.io/version: 3.19.1
helm.sh/chart: x509-certificate-exporter-3.19.1
release: kube-prometheus-stack
...
# helm 배포 시, label 추가 해둠
kubectl get prometheusrules.monitoring.coreos.com -n monitoring x509-certificate-exporter -o yaml | head -n 20
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
...
labels:
app.kubernetes.io/instance: x509-certificate-exporter
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: x509-certificate-exporter
app.kubernetes.io/version: 3.19.1
helm.sh/chart: x509-certificate-exporter-3.19.1
release: kube-prometheus-stack
name: x509-certificate-exporter
...
-
- 프로메테우스 확인
- 그라파나 확인
- metrics-server 설치
- 샘플 애플리케이션 배포
- 샘플 애플리케이션 배포
- # 샘플 애플리케이션 배포
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
type: ClusterIP
EOF
- # 샘플 애플리케이션 배포
- 애플리케이션 확인 & 반복 호출
- # 배포 확인
kubectl get deploy,svc,ep webpod -owide
# webpod service clusterip 변수 지정
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
echo $SVCIP
# 통신 확인
curl -s $SVCIP
curl -s $SVCIP | grep Hostname
# 반복 호출(신규 터미널)
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
- # 배포 확인
- 샘플 애플리케이션 배포
- kubeadm 인증서 갱신
- 현재 인증서 정보 확인 : 인증서 파일 생성일 → 만료일 확인
- kubeadm 인증서 관리 : kubeadm 으로 생성된 클라이언트 인증서는 1년 후 만료됨.
- # Check certificates expiration for a Kubernetes cluster
kc describe cm -n kube-system kubeadm-config | grep -i cert
caCertificateValidityPeriod: 87600h0m0s
certificateValidityPeriod: 8760h0m0s
certificatesDir: /etc/kubernetes/pki
# 현재 인증서가 UTC 기준 1월 17일 00:34 분 생성되어서, 유효기간 365일(1년) 이후 만료일은 '27년 1월 17일 00:33분.
kubeadm certs check-expiration -v 6
[check-expiration] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
...
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Jan 17, 2027 00:33 UTC 364d ca no
apiserver Jan 17, 2027 00:33 UTC 364d ca no
apiserver-etcd-client Jan 17, 2027 00:33 UTC 364d etcd-ca no
apiserver-kubelet-client Jan 17, 2027 00:33 UTC 364d ca no
controller-manager.conf Jan 17, 2027 00:33 UTC 364d ca no
etcd-healthcheck-client Jan 17, 2027 00:33 UTC 364d etcd-ca no
etcd-peer Jan 17, 2027 00:33 UTC 364d etcd-ca no
etcd-server Jan 17, 2027 00:33 UTC 364d etcd-ca no
front-proxy-client Jan 17, 2027 00:33 UTC 364d front-proxy-ca no
scheduler.conf Jan 17, 2027 00:33 UTC 364d ca no
super-admin.conf Jan 17, 2027 00:33 UTC 364d ca no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Jan 15, 2036 00:33 UTC 9y no
etcd-ca Jan 15, 2036 00:33 UTC 9y no
front-proxy-ca Jan 15, 2036 00:33 UTC 9y no
tree /etc/kubernetes/pki/
ls -l /etc/kubernetes/pki/etcd/
ls -l /etc/kubernetes/pki
-rw-r--r--. 1 root root 1281 Jan 17 09:34 apiserver.crt
-rw-r--r--. 1 root root 1123 Jan 17 09:34 apiserver-etcd-client.crt
-rw-------. 1 root root 1679 Jan 17 09:34 apiserver-etcd-client.key
-rw-------. 1 root root 1675 Jan 17 09:34 apiserver.key
...
# apiserver 인증서(예시) :
cat /etc/kubernetes/pki/apiserver.crt | openssl x509 -text -noout
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 9019049356910942135 (0x7d2a199aea6457b7)
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=kubernetes
Validity
Not Before: Jan 17 00:28:48 2026 GMT
Not After : Jan 17 00:33:48 2027 GMT
Subject: CN=kube-apiserver
...
- 수동 인증서 갱신 소개
-
- kubeadm certs renew : CA는 유지한 채 control-plane 인증서를 재서명하며, kubeconfig 재생성됨. → static pod 재기동이 반드시 필요!
- 갱신 시 영향도
- k8s API 요청 : 수초~수십 초 중단
- kubectl : 일시적 실패
- 워크로드 : 영향 없음
- etcd 영향
- single-node etcd : etcd 재기동 → api server 잠시 불가
- multi-node etcd : 순차 갱신 필요(동시 수행 시, 쿼럼 붕괴 위험)
- worker node 영향
- kubelet 인증서는 자동 갱신 : kubelet client cert
- 수동 인증서 갱신 수행 시 갱신 되는 인증서 목록에는 kubelet.conf 파일이 포함되어 있지 않습니다.
- kubeadm은 순환 가능한 인증서를 사용하여 kubelet의 자동 인증서 갱신을 구성하기 때문입니다.
- kubeadm rejoin 필요 없음
- 워크로드 영향 없음
- HA 컨트롤 플레인을 사용하는 클러스터를 실행하는 경우, 이 명령어는 모든 컨트롤 플레인 노드에서 실행해야 합니다.
- 이 명령은 에 저장된 CA 인증서와 키를 사용하여 갱신을 수행합니다 ⇒ CA(cert/key, 기본 10년 유효 기간)는 재생성하지 않음.
- kubelet 인증서는 자동 갱신 : kubelet client cert
- /etc/kubernetes/pki/
├── ca.crt / ca.key (❌ renew 대상 아님)
├── etcd/
│ ├── ca.crt / ca.key (❌ renew 대상 아님)
- 명령어를 실행한 후에는 컨트롤 플레인 Pod를 재시작해야 합니다.
- 현재 모든 구성 요소 및 인증서에 대해 동적 인증서 갱신이 지원되지 않으므로 이 과정이 필요합니다.
- 정적 Pod는 API 서버가 아닌 로컬 kubelet에 의해 관리되므로 kubectl 명령어를 사용하여 삭제 및 재시작할 수 없습니다.
- 정적 Pod를 재시작하려면 매니페스트 파일을 해당 /etc/kubernetes/manifests/ 디렉터리에서 일시적으로 제거하고 20초 동안 기다립니다( KubeletConfiguration 구조체fileCheckFrequency 의 값 참조).
- Pod가 매니페스트 디렉터리에 없으면 kubelet이 Pod를 종료합니다.
- 그런 다음 파일을 원래 위치로 되돌리고 일정 시간이 지나면 kubelet이 Pod를 다시 생성하고 구성 요소의 인증서 갱신이 완료됩니다.
**touch /etc/kubernetes/manifests/*.yaml watch -d crictl ps** - kubeadm certs renew 특정 인증서를 갱신하거나, all 명령을 사용하여 모든 인증서를 한 번에 갱신할 수 있습니다.
- # 갱신 시 : 기존 cert 삭제 -> CA로 재서명된 새 cert 생성 kubeadm certs renew apiserver kubeadm certs renew etcd-server kubeadm certs renew all
-
- 수동 인증서 갱신 실행
- # 샘플 애플리케이션 반복 호출(신규 터미널)
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
# 사전 백업 : HA Controlplane 모두
cp -r /etc/kubernetes/pki /etc/kubernetes/pki.backup.$(date +%F)
ls -l /etc/kubernetes/pki.backup.$(date +%F)
mkdir /etc/kubernetes/backup-conf.$(date +%F)
cp /etc/kubernetes/*.conf /etc/kubernetes/backup-conf.$(date +%F)
ls -l /etc/kubernetes/backup-conf.$(date +%F)
# 인증서 만료 상태 확인
kubeadm certs check-expiration
# 인증서 전체 갱신 : 기존 cert 삭제 -> CA로 재서명된 새 cert 생성
kubeadm certs renew all
certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself renewed
certificate for serving the Kubernetes API renewed
certificate the apiserver uses to access etcd renewed
certificate for the API server to connect to kubelet renewed
certificate embedded in the kubeconfig file for the controller manager to use renewed
certificate for liveness probes to healthcheck etcd renewed
certificate for etcd nodes to communicate with each other renewed
certificate for serving etcd renewed
certificate for the front proxy client renewed
certificate embedded in the kubeconfig file for the scheduler manager to use renewed
certificate embedded in the kubeconfig file for the super-admin renewed
Done renewing certificates. You must restart the kube-apiserver, kube-controller-manager, kube-scheduler and etcd, so that they can use the new certificates.
# 인증서 만료 상태 확인
kubeadm certs check-expiration
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Jan 17, 2027 12:25 UTC 364d ca no
apiserver Jan 17, 2027 12:25 UTC 364d ca no
apiserver-etcd-client Jan 17, 2027 12:25 UTC 364d etcd-ca no
apiserver-kubelet-client Jan 17, 2027 12:25 UTC 364d ca no
controller-manager.conf Jan 17, 2027 12:25 UTC 364d ca no
etcd-healthcheck-client Jan 17, 2027 12:25 UTC 364d etcd-ca no
etcd-peer Jan 17, 2027 12:25 UTC 364d etcd-ca no
etcd-server Jan 17, 2027 12:25 UTC 364d etcd-ca no
front-proxy-client Jan 17, 2027 12:25 UTC 364d front-proxy-ca no
scheduler.conf Jan 17, 2027 12:25 UTC 364d ca no
super-admin.conf Jan 17, 2027 12:25 UTC 364d ca no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Jan 15, 2036 00:33 UTC 9y no
etcd-ca Jan 15, 2036 00:33 UTC 9y no
front-proxy-ca Jan 15, 2036 00:33 UTC 9y no
# ca 인증서는 그대로, 나머지 인증서는 신규 생성
ls -lt /etc/kubernetes/pki/
-rw-r--r--. 1 root root 1119 Jan 17 21:25 front-proxy-client.crt
-rw-------. 1 root root 1675 Jan 17 21:25 front-proxy-client.key
-rw-r--r--. 1 root root 1176 Jan 17 21:25 apiserver-kubelet-client.crt
-rw-------. 1 root root 1675 Jan 17 21:25 apiserver-kubelet-client.key
-rw-r--r--. 1 root root 1123 Jan 17 21:25 apiserver-etcd-client.crt
-rw-------. 1 root root 1675 Jan 17 21:25 apiserver-etcd-client.key
-rw-r--r--. 1 root root 1281 Jan 17 21:25 apiserver.crt
-rw-------. 1 root root 1675 Jan 17 21:25 apiserver.key
-rw-------. 1 root root 1675 Jan 17 09:34 sa.key
-rw-------. 1 root root 451 Jan 17 09:34 sa.pub
drwxr-xr-x. 2 root root 162 Jan 17 09:34 etcd
-rw-r--r--. 1 root root 1123 Jan 17 09:34 front-proxy-ca.crt
-rw-------. 1 root root 1675 Jan 17 09:34 front-proxy-ca.key
-rw-r--r--. 1 root root 1107 Jan 17 09:34 ca.crt
-rw-------. 1 root root 1679 Jan 17 09:34 ca.key
ls -lt /etc/kubernetes/pki/etcd
-rw-r--r--. 1 root root 1196 Jan 17 21:25 server.crt
-rw-------. 1 root root 1675 Jan 17 21:25 server.key
-rw-r--r--. 1 root root 1196 Jan 17 21:25 peer.crt
-rw-------. 1 root root 1679 Jan 17 21:25 peer.key
-rw-r--r--. 1 root root 1123 Jan 17 21:25 healthcheck-client.crt
-rw-------. 1 root root 1679 Jan 17 21:25 healthcheck-client.key
-rw-r--r--. 1 root root 1094 Jan 17 09:34 ca.crt
-rw-------. 1 root root 1675 Jan 17 09:34 ca.key
# apiserver 인증서
cat /etc/kubernetes/pki/apiserver.crt | openssl x509 -text -noout
Issuer: CN=kubernetes
Validity
Not Before: Jan 17 12:20:10 2026 GMT
Not After : Jan 17 12:25:10 2027 GMT
Subject: CN=kube-apiserver
# control component 의 kubeconfig 신규 생성 확인
ls -lt /etc/kubernetes/*.conf
-rw-------. 1 root root 5682 Jan 17 21:25 /etc/kubernetes/super-admin.conf
-rw-------. 1 root root 5626 Jan 17 21:25 /etc/kubernetes/scheduler.conf
-rw-------. 1 root root 5682 Jan 17 21:25 /etc/kubernetes/controller-manager.conf
-rw-------. 1 root root 5654 Jan 17 21:25 /etc/kubernetes/admin.conf
-rw-------. 1 root root 1974 Jan 17 09:34 /etc/kubernetes/kubelet.conf
# 특히 admin.conf 변경 확인
ls -l /etc/kubernetes/admin.conf
ls -l /etc/kubernetes/backup-conf.$(date +%F)/admin.conf
vi -d /etc/kubernetes/backup-conf.$(date +%F)/admin.conf /etc/kubernetes/admin.conf
- # 샘플 애플리케이션 반복 호출(신규 터미널)
- control-plane static pod 재기동 & admin.conf kubeconfig 재적용
-
# 사전 백업 : static pod 매니페스트 cp -r /etc/kubernetes/manifests /etc/kubernetes/manifests.backup.$(date +%F) ls -l /etc/kubernetes/manifests.backup.$(date +%F) # static pod 모니터링(신규 터미널) watch -d crictl ps # static pod manifest 삭제 rm -rf /etc/kubernetes/manifests/*.yaml # static pod manifest 삭제 확인 crictl ps # static pod manifest 복사 -> 파드 재기동 cp /etc/kubernetes/manifests.backup.$(date +%F)/*.yaml /etc/kubernetes/manifests tree /etc/kubernetes/manifests # 파드 기동 확인 : CA가 바뀌지 않았기 때문에 예전 인증서도 신뢰됨 >> 다만, 예전(?) 인증서의 만료 기간을 놓칠 수 있으니, 같이 갱신 할 것! crictl ps kubectl get pod -n kube-system -owide -v=6 # 특히 admin.conf 변경 확인 ls -l /root/.kube/config ls -l /etc/kubernetes/admin.conf ls -l /etc/kubernetes/backup-conf.$(date +%F)/admin.conf vi -d /root/.kube/config /etc/kubernetes/admin.conf vi -d /etc/kubernetes/backup-conf.$(date +%F)/admin.conf /etc/kubernetes/admin.conf # admin.conf kubeconfig 재적용 yes | cp /etc/kubernetes/admin.conf ~/.kube/config ; echo chown $(id -u):$(id -g) ~/.kube/config kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab" kubens default
-
- 현재 인증서 정보 확인 : 인증서 파일 생성일 → 만료일 확인
K8S Upgrade by kubeadm
- K8S Version Skew Policy - Docs
- 메이저.마이너.패치 버전https://leehosu.tistory.com/entry/AEWS-1-1-Amazon-EKS란

- 1년에 3개의 마이너 버전 출시 → 최근 3개 버전 release branches(패치) 지원- Link‘26.01.18 현재 공식적으로는 1.35, 1.34, 1.33 릴리즈 관리, 1.32는 서비스?
- kube-apiserver 가 HA 일 경우, NEW 버전(1.32) 과 마이너 OLD 1개 아래 버전(1.31) 가능
- kubelet
- kubelet 은 kube-apiserver 보다 NEW 버전을 사용할 수 없다
- kubelet 은 kube-apiserver 보다 3개의 마이너 OLD 버전 가능
- 예시1) kube-apiserver 1.32 일 때 → kubelet 1.32, 1.31, 1.30, 1.29 가능
- 예시2) kube-apiserver (HA) 1.32, 1.31 일때 → kubelet 1.31, 1.30, 1.29 가능 (1.32는 아직 api가 1.31이 있으니 안됨)
- kube-controller-manager(kcm), kube-scheduler, and cloud-controller-manager(ccm)
- 위 3개 구성요소는 kube-apiserver 보다 NEW 버전을 사용할 수 없다
- 위 3개 구성요소는 kube-apiserver 보다 1개의 마이너 OLD 버전 가능
- 예시1) kube-apiserver 1.32 일 때 → 위 3개 구성요소 1.32, 1.31 가능
- 예시2) kube-apiserver (HA) 1.32, 1.31 일때 → 위 3개 구성요소 1.31 만 가능 (1.32는 아직 api가 1.31이 있으니 안됨)
- 즉, kube-apiserver (HA) 환경에서는 kube-apiserver (HA) 먼저 1.31 → 1.32 작업 필요
- kube-proxy
- kube-proxy 는 kube-apiserver 보다 NEW 버전을 사용할 수 없다
- kube-proxy 은 kube-apiserver 보다 3개의 마이너 OLD 버전 가능
- kube-proxy 은 kubelet 보다 3개의 마이너 NEW 혹은 OLD 버전 가능
- 예시1) kube-apiserver 1.32 일 때 → kube-proxy 1.32, 1.31, 1.30, 1.29 가능
- 예시2) kube-apiserver (HA) 1.32, 1.31 일때 → kube-proxy 1.31, 1.30, 1.29 가능 (1.32는 아직 api가 1.31이 있으니 안됨)
- kubectl
- kubectl 은 kube-apiserver 보다 1개의 마이너 NEW/OLD 버전 가능
- 예시1) kube-apiserver 1.32 일 때 → kubectl 1.33, 1.32, 1.31 가능
- 예시2) kube-apiserver (HA) 1.32, 1.31 일때 → kubectl 1.32, 1.31 가능 (1.33는 아직 api가 1.31이 있으니 안됨)
- 메이저.마이너.패치 버전https://leehosu.tistory.com/entry/AEWS-1-1-Amazon-EKS란
- K8S Upgrade 소개
- Upgrade 방식 → 장단점
- In-place : K8S Version Skew 를 이용한 Incremental In-place 업그레이드 전략
- 허용되는 최대 버전 스큐에 도달할 때까지 worker 노드 업그레이드를 연기하면서 control node을 점진적으로 업그레이드
- Blue-Green : old k8s cluster(100대 node) + new k8s cluster(100대 node)
- In-place : K8S Version Skew 를 이용한 Incremental In-place 업그레이드 전략
- 절차
- 사전 준비 → CP 노드 순차 Upgrade → CP 동작 점검 → DP 노드 순차 Upgrade → 전체 동작 점검
- 고려사항
- 기존 Addon(cni, csi 등) 과 App 파드가 신규 K8S 버전의 Control Component(파드 apiserver, kube-proxy 등)들과 호환이 되는지 확인
- 특히 k8s의 api 리소스 버전 사용 시, 변경 여부와 사용 가능 확인
- OS/CRI(containerd) 가 신규 K8S 버전 호환 확인 : OS - CRI - Kubelet(kubectl, kubeadm)
- CI/CD(배포툴) : 기존 배포 파이프라인이 신규 K8S 버전에서도 잘 동작하는지 확인
- 특히 k8s의 api 리소스 버전 사용 시, 변경 여부와 사용 가능 확인
- ETCD 데이터 확인 : 백업!, 업그레이드해도 ETCD 데이터 형식은 하위 호환성을 유지!
- 무중단 서비스 확인 : 주요 Component(kube-proxy, cni 등) 가 업데이트 될때 서비스 트래픽 중단은 없는지?
- 기존 Addon(cni, csi 등) 과 App 파드가 신규 K8S 버전의 Control Component(파드 apiserver, kube-proxy 등)들과 호환이 되는지 확인
- Upgrade 방식 → 장단점
- 가상의 온프레미스 환경 K8S 에서 업그레이드 계획 짜기 In-place Upgrade*
- 환경 소개 : k8s 1.28, CPx3대, DPx3대, HW LB 사용 중저희 팀이 작성한 그림으로 편하게 활용하시기 바랍니다.
- 각 구성요소와 버전저희 팀이 작성한 그림으로 편하게 활용하시기 바랍니다.
- Ubuntu 22.04 (kernel 5.15)
- Containerd 1.7.z
- K8S Version 1.28.2 - kubeadm, kubelet
- CNI : Cilium 1.4.z
- CSI : OpenEBS 3.y.z
- 애플리케이션 및 요구사항 확인 : 현황 조사 후 작성…
- 목표 버전 : K8S 1.28 ⇒ 1.32!
- 버전 호환성 검토
- K8S(kubelet, apiserver..) 1.32 요구 커널 버전 확인 : 예) user namespace 사용 시 커널 6.5 이상 필요 - Docs
- containerd 버전 조사 : 예) user namespace 에 대한 CRI 지원 시 containerd v2.0 및 runc v1.2 이상 필요 - Docs
- K8S 호환 버전 확인 - Docs
- CNI(Cilium) 요구 커널 버전 확인 : 예) BPF-based host routing 기능 필요 시 커널 5.10 이상 필요 - Docs
- CNI 이 지원하는 K8S 버전 확인 - Docs
- CSI : Skip…
- 애플리케이션 요구사항 검토…
- 업그레이드 방법 결정 : in-place vs blue-green
- Dev - Stg - Prd 별 각기 다른 방법 vs 모두 동일 방법
- 결정된 방법으로 업그레이드 계획 수립 : 예시) in-place 결정저희 팀이 작성한 그림으로 편하게 활용하시기 바랍니다.
- 사전 준비
- (옵션) 각 작업 별 상세 명령 실행 및 스크립트 작성, 작업 중단 실패 시 롤백 명령/스크립트 작성
- 모니터링 설정
- (1) ETCD 백업
- (2) CNI(cilium) 업그레이드
- CP 노드 순차 업그레이드 : 1.28 → 1.29 → 1.30
- 노드 1대씩 작업 진행 1.28 → 1.29
- (3) Ubuntu OS(kernel) 업그레이드 → 재부팅
- (4) containerd 업그레이드 → containerd 서비스 재시작
- (5) kubeadm 업그레이드 → kubelet/kubectl 업그레이드 설치 → kubelet 재시작
- 노드 1대씩 작업 진행 1.29 → 1.30
- (5) kubeadm 업그레이드 → kubelet/kubectl 업그레이드 설치 → kubelet 재시작
- 작업 완료 후 CP 노드 상태 확인
- 노드 1대씩 작업 진행 1.28 → 1.29
- DP 노드 순차 업그레이드 : 1.28 → 1.29 → 1.30
- 노드 1대씩 작업 진행 1.28 → 1.29
- (6) 작업 노드 drain 설정 후 파드 Evicted 확인, 서비스 중단 여부 모니터링 확인
- (7) Ubuntu OS(kernel) 업그레이드 → 재부팅
- (8) containerd 업그레이드 → containerd 서비스 재시작
- (9) kubeadm 업그레이드 → kubelet 업그레이드 설치 → kubelet 재시작
- 노드 1대씩 작업 진행 1.29 → 1.30
- (9) kubeadm 업그레이드 → kubelet 업그레이드 설치 → kubelet 재시작
- 작업 완료 후 DP 노드 상태 확인 ⇒ (10) 작업 노드 uncordon 설정 후 다시 상태 확인
- 노드 1대씩 작업 진행 1.28 → 1.29
- K8S 관련 전체적인 동작 1차 점검
- 애플리케이션 파드 동작 확인 등
- CP 노드 순차 업그레이드 : 1.30 → 1.31 → 1.32
- 노드 1대씩 작업 진행 x 버전별 반복
- kubeadm 업그레이드 → kubelet/kubectl 업그레이드 설치 → kubelet 재시작
- 작업 완료 후 CP 노드 상태 확인
- 노드 1대씩 작업 진행 x 버전별 반복
- DP 노드 순차 업그레이드 : 1.30 → 1.31 → 1.32
- 노드 1대씩 작업 진행 x 버전별 반복
- 작업 노드 drain 설정 후 파드 Evicted 확인, 서비스 중단 여부 모니터링 확인
- kubeadm 업그레이드 → kubelet 업그레이드 설치 → kubelet 재시작
- 작업 완료 후 DP 노드 상태 확인 ⇒ 작업 노드 uncordon 설정 후 다시 상태 확인
- 노드 1대씩 작업 진행 x 버전별 반복
- K8S 관련 전체적인 동작 2차 점검
- 애플리케이션 파드 동작 확인 등
- (참고) AWS EKS Upgrade - Link
- Amazon EKS 클러스터 업그레이드 워크숍의 목적은 고객에게 Amazon EKS 클러스터 업그레이드를 계획하고 실행할 수 있는 모범 사례를 제공하는 일련의 실험실을 소개하는 것입니다.
- 우리는 In-Place, Blue/Green 등 다양한 클러스터 업그레이드 전략을 탐구하고 각 전략의 실행 세부 사항을 자세히 살펴볼 것입니다.
- 이 워크숍에서는 EKS 테라폼 청사진, ArgoCD 등을 활용하여 전체 EKS 클러스터 업그레이드 프로세스를 안내합니다.
- Introduction : 소개
- K8S(+AWS EKS) Release Cycles
- Kubernetes 프로젝트는 새로운 기능, 최신 보안 패치 및 버그 수정을 통해 지속적으로 업데이트되고 있습니다. Kubernetes 버전의 의미론을 처음 접하는 경우 Semantic Versioning 용어를 따르며 일반적으로 x.y.z로 표현됩니다. 여기서 x는 주요 버전, y는 마이너 버전, z는 패치 버전입니다. 새로운 Kubernetes 마이너 버전(y)은 대략 4개월마다 출시되며, 모든 버전 >=v1.19는 12개월 동안 표준 지원을 제공하며, 한 번에 최소 3개의 마이너 버전을 지원합니다. Kubernetes 프로젝트는 최신 세 가지 마이너 버전에 대한 릴리스 브랜치를 유지하며, 최신 Kubernetes 릴리스와 관련된 자세한 정보는 여기에서 확인할 수 있습니다.
- Amazon Elastic Kubernetes Service(EKS)는 Kubernetes 프로젝트 릴리스 주기를 따르지만, 버전이 Amazon EKS에서 처음 제공된 후 14개월 동안 한 번에 4개의 마이너 버전에 대한 표준 지원을 제공합니다. 이는 업스트림 Kubernetes가 더 이상 Amazon EKS에서 제공되는 버전을 지원하지 않더라도 마찬가지입니다. Amazon EKS에서 지원되는 Kubernetes 버전에 적용되는 보안 패치를 백포트합니다.
- Amazon EKS Kubernetes 릴리스 캘린더에는 Amazon EKS에서 지원되는 각 Kubernetes 버전에 대한 중요한 릴리스 및 지원 날짜가 있습니다. 최신 EKS 버전의 릴리스가 해당 버전의 Kubernetes보다 몇 주 뒤처지는 이유가 궁금하다면, 이는 Amazon이 새로운 버전의 Kubernetes를 Amazon EKS에서 제공하기 전에 다른 AWS 서비스 및 도구와의 안정성과 호환성을 철저히 테스트하기 때문입니다. 새 버전이 얼마나 빨리 지원될지에 대한 구체적인 날짜나 SLA는 제공하지 않지만, Amazon EKS 팀은 업스트림 릴리스와 EKS 지원 착륙 사이의 격차를 줄이기 위해 노력하고 있습니다.https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html#kubernetes-release-calendar
- [총 26개월 = 14개월 + 12개월] 표준 지원 외에도 Amazon EKS는 최근 확장 지원 기능(출시 발표)을 출시했습니다. 이제 모든 Kubernetes 버전 1.21 이상이 Amazon EKS에서 확장 지원을 받을 수 있습니다. 확장 지원은 표준 지원 종료 후 즉시 자동으로 시작되며 추가로 12개월 동안 계속되어 각 Kubernetes 마이너 버전에 대한 지원이 총 26개월로 늘어납니다. 확장 지원 기간이 끝나기 전에 클러스터가 업데이트되지 않으면 현재 지원되는 가장 오래된 확장 버전으로 자동 업그레이드됩니다. 자세한 내용은 Amazon EKS의 Kubernetes 버전 확장 지원을 참조하세요.https://aws.amazon.com/ko/blogs/containers/amazon-eks-extended-support-for-kubernetes-versions-pricing/
- 확장 지원 기간 동안 Kubernetes 버전을 실행하는 클러스터의 가격은 2024년 4월 1일부터 클러스터당 시간당 총 $0.60의 요금이 부과되며, 비용 표준 지원은 변경되지 않습니다(클러스터당 시간당 $0.10).Support type Duration Price (per cluster per hour)
Standard 14 months starting from the date a version is generally available on Amazon EKS $0.10 Extended 12 months starting from the date a version reaches the end of standard support in Amazon EKS $0.60 - 2024년 7월 23일, Amazon EKS는 Kubernetes 버전 정책을 발표했습니다(출시 발표). Kubernetes 버전 정책에 대한 Amazon EKS 제어를 통해 클러스터 관리자는 EKS 클러스터에 대한 표준 지원 동작 종료를 선택할 수 있습니다. 이러한 제어를 통해 어떤 클러스터가 확장 지원을 받아야 하는지와 Kubernetes 버전에 대한 표준 지원 종료 시 자동으로 업그레이드되어야 하는지 결정할 수 있습니다. 이 제어는 클러스터별로 실행되는 환경이나 애플리케이션에 따라 버전 업그레이드를 비즈니스 요구 사항과 균형 있게 조정할 수 있는 유연성을 제공합니다.
- supportType 속성을 사용하여 새 클러스터와 기존 클러스터 모두에 대한 버전 정책을 설정할 수 있습니다. 버전 지원 정책을 설정하는 데 사용할 수 있는 두 가지 옵션이 있습니다:
- 표준 — 표준 지원이 종료되면 EKS 클러스터가 자동으로 업그레이드될 수 있습니다. 이 설정에서는 연장 지원 비용이 발생하지 않지만, 표준 지원에서 EKS 클러스터는 다음 지원되는 Kubernetes 버전으로 자동으로 업그레이드됩니다.
- 확장 — Kubernetes 버전이 표준 지원 종료에 도달하면 EKS 클러스터가 확장 지원을 시작합니다. 이 설정에서는 확장 지원 요금이 부과됩니다. 클러스터를 표준 지원 Kubernetes 버전으로 업그레이드하여 확장 지원 요금이 발생하지 않도록 할 수 있습니다. 확장 지원에서 실행 중인 클러스터는 확장 지원 종료 시 자동으로 업그레이드할 수 있습니다.
- 이 동작은 EKS 콘솔과 CLI를 통해 쉽게 설정할 수 있습니다. 자세한 내용은 클러스터 업그레이드 정책을 참조하세요.
- Why Upgrade?
- Amazon EKS 버전을 업데이트하는 것은 Kubernetes 클러스터의 보안, 안정성, 성능 및 호환성을 유지하는 데 중요하며, 플랫폼에서 제공하는 최신 기능과 역량을 활용할 수 있도록 보장
- Kubernetes 버전은 제어 평면과 데이터 평면을 모두 포함합니다.
- AWS가 제어 평면을 관리하고 업그레이드하는 동안, 사용자(클러스터 소유자/고객)는 클러스터 제어 평면과 데이터 평면 모두에 대한 업그레이드를 시작할 책임을 집니다.
- 클러스터 업그레이드를 시작할 때, AWS는 제어 평면을 관리하며 데이터 평면의 업그레이드를 시작할 책임이 있습니다.
- 여기에는 자가 관리 노드 그룹, 관리 노드 그룹, Fargate 및 기타 애드온을 통해 프로비저닝된 작업자 노드가 포함됩니다.
- 작업자 노드가 Karpenter Controller를 통해 프로비저닝된 경우, 자동 노드 재활용 및 업그레이드를 위해 드리프트 또는 디스럽션 컨트롤러 기능(spec.expireAfter)을 활용할 수 있습니다.
- 또한 클러스터 업그레이드를 계획할 때 작업 부하의 애플리케이션 가용성을 보장해야 하며, 데이터 평면이 업그레이드되는 동안 작업 부하의 가용성을 보장하기 위해서는 적절한 PodDisruptionBudgets와 topologySpreadConstraint가 필수적입니다.
- Kubernetes 마이너 버전 외에도 Amazon EKS는 새로운 Kubernetes 컨트롤 플레인 설정을 가능하게 하고 보안 수정을 제공하기 위해 주기적으로 새로운 플랫폼 버전을 출시합니다.
- 각 Amazon EKS 마이너 버전은 하나 이상의 관련 플랫폼 버전을 가질 수 있습니다.
- 1.30과 같이 Amazon EKS에서 새로운 Kubernetes 마이너 버전을 사용할 수 있는 경우, 해당 Kubernetes 마이너 버전의 초기 플랫폼 버전은 eks.1부터 시작되며 새로운 버전이 출시될 때마다 플랫폼 버전이 증가합니다(eks.n+1).
- 아래 표는 이를 더 잘 시각화하는 데 도움이 될 수 있으며, Amazon EKS 플랫폼 버전에서 더 자세한 내용을 확인할 수 있습니다. - Docs
- 좋은 소식은 Amazon EKS가 해당 Kubernetes 마이너 버전에 대해 모든 기존 클러스터를 최신 Amazon EKS 플랫폼 버전으로 자동 업그레이드하며, 사용자 측에서 명시적인 조치가 필요하지 않다는 것입니다.
- 따라서 Kubernetes 업데이트 관점에서 볼 때, 안전하고 효율적인 EKS 환경을 위해서는 현재 마이너 버전으로 최신 상태를 유지하는 것이 매우 중요하며, 이는 Amazon EKS의 공유 책임 모델을 반영합니다. 이를 통해 클러스터가 최신 보안 패치 및 버그 수정을 실행하고 있는지 확인하여 보안 취약점의 위험을 줄일 수 있습니다. 또한 성능, 확장성 및 신뢰성을 향상시켜 애플리케이션과 고객에게 더 나은 서비스를 제공합니다.
- Amazon EKS 버전을 업데이트하는 것은 Kubernetes 클러스터의 보안, 안정성, 성능 및 호환성을 유지하는 데 중요하며, 플랫폼에서 제공하는 최신 기능과 역량을 활용할 수 있도록 보장
- Amazon EKS Upgrades
- 이 워크숍을 진행하면서 클러스터 내 업그레이드 및 청록색 업그레이드와 같은 다양한 업그레이드 전략에 대해 배우게 됩니다.
- 또한 업그레이드 전략을 결정하는 기준과 각 단계에 대해 자세히 알아보세요.
- 아래는 인플레이스 클러스터 업그레이드의 고급 워크플로우이며, 다음 모듈에서 이에 대해 자세히 살펴보겠습니다.
- Amazon EKS 클러스터를 in-place upgrade 제자리에서 업그레이드하려면 다음과 같은 조치를 취해야 합니다:
- Kubernetes 및 EKS 릴리스 노트를 검토하세요. 업그레이드하기 전에 확인하세요
- 또한 업그레이드를 시작하기 전에 구현해야 할 중요한 정책, 도구 및 절차를 검토하는 섹션입니다.
- 클러스터 백업을 수행합니다. (선택 사항)
- AWS 콘솔이나 CLI를 사용하여 클러스터 제어 평면을 업그레이드합니다.
- add-on 기능 호환성을 검토하세요.
- 클러스터 데이터 플레인을 업그레이드합니다.
- Kubernetes 및 EKS 릴리스 노트를 검토하세요. 업그레이드하기 전에 확인하세요
- 위의 단계는 높은 수준의 시퀀스만 나타내며, API 사용 중단/수정, 버전 왜곡 검사 등과 같은 다른 검사가 있을 수 있습니다.
- 자세한 내용은 여기에서 확인할 수 있습니다. - Docs
- 또한, 아마존 EKS 클러스터 업그레이드에 대한 모범 사례 및 지침은 EKS 모범 사례 가이드의 클러스터 업그레이드 섹션을 참조하시기 바랍니다.
- 이제 다음 섹션에서 EKS 클러스터 업그레이드를 준비하는 과정을 시작해 보겠습니다.
- K8S(+AWS EKS) Release Cycles
- Preparing for Cluster Upgrades : 업그레이드 전 준비
- 준비
- 클러스터 업그레이드를 시작하기 전에 다음 요구 사항을 확인하세요.
- Amazon EKS에서는 클러스터를 생성할 때 지정한 서브넷에서 사용 가능한 IP 주소를 최대 5개까지 필요로 합니다 .
- 클러스터의 AWS Identity and Access Management(IAM) 역할과 보안 그룹 이 AWS 계정에 있어야 합니다 .
- 비밀 암호화를 활성화하는 경우 클러스터 IAM 역할에 AWS Key Management Service(AWS KMS) 키 권한이 있어야 합니다 .
- 업그레이드 워크플로
- Amazon EKS 및 Kubernetes 버전에 대한 주요 업데이트 식별 Identify
- 사용 중단 정책 이해 및 적절한 매니페스트 리팩토링 Understand , Refactor
- 올바른 업그레이드 전략을 사용하여 EKS 제어 평면 및 데이터 평면 업데이트 Update
- 마지막으로 다운스트림 애드온 종속성 업그레이드
+--------------------------------------+ | Start Upgrade Process | +--------------------------------------+ | | +--------------------------------------+ | **Identify** Major Updates for Amazon | | EKS and Kubernetes Versions | +--------------------------------------+ | | +--------------------------------------+ | **Understand** Deprecation Policy and | | **Refactor** Manifests Accordingly | +--------------------------------------+ | | +--------------------------------------+ | **Update EKS Control Plane** and **Data | | Plane** Using Right Upgrade Strategy | +--------------------------------------+ | | +--------------------------------------+ | **Upgrade Downstream Add-on** | | Dependencies | +--------------------------------------+ | | +--------------------------------------+ | Upgrade Completed | +--------------------------------------+
- 클러스터 업그레이드를 시작하기 전에 다음 요구 사항을 확인하세요.
- EKS Upgrade Insights
- Amazon EKS가 제어 평면 업그레이드를 자동화하는 반면, 업그레이드로 인해 영향을 받을 가능성이 있는 리소스나 애플리케이션을 식별하는 것은 수동 프로세스였습니다.
- 여기에는 릴리스 노트를 검토하여 더 이상 사용되지 않거나 제거된 Kubernetes API를 확인한 다음 해당 API를 사용하는 애플리케이션을 검색하여 수정하는 작업이 포함됩니다.
- Amazon EKS 클러스터 인사이트는 Amazon EKS 및 Kubernetes 모범 사례를 따르는 데 도움이 되는 권장 사항을 제공합니다.
- 모든 Amazon EKS 클러스터는 Amazon EKS가 선별한 인사이트 목록에 대해 자동으로 반복적으로 검사를 받습니다.
- 이러한 인사이트 검사는 Amazon EKS에서 완전히 관리하며 모든 결과를 해결하는 방법에 대한 권장 사항을 제공합니다.
- Cluster 인사이트를 활용하면 최신 Kubernetes 버전으로 업그레이드하는 데 드는 노력을 최소화할 수 있습니다.
- 매일 클러스터 감사 로그를 스캔하여 사용되지 않는 리소스를 찾고 EKS 콘솔에 결과를 표시하거나 API 또는 CLI를 통해 프로그래밍 방식으로 검색할 수 있습니다.
- Amazon EKS는 Kubernetes 버전 업그레이드 준비와 관련된 insight만 출력.
- 클러스터 인사이트는 주기적으로 업데이트됩니다. 클러스터 인사이트를 수동으로 새로 고칠 수 없습니다. 클러스터 문제를 해결하면 클러스터 인사이트가 업데이트되는 데 시간이 걸립니다.
- 권장 사항: 문제를 해결하기 위한 단계.
- 링크: 릴리스 노트, 블로그 게시물과 같은 추가 정보입니다.
- 리소스 목록: 심각도를 반영하는 상태(통과, 경고, 오류, 알 수 없음)가 있는 Kubernetes 리소스 유형(예: CronJobs)입니다.
- 오류: 다음 마이너 버전에서 API에 대한 호출이 제거되었습니다. 업그레이드 후 업그레이드가 실패합니다.
- 경고: 임박한 문제이지만 즉각적인 조치가 필요하지 않습니다(2개 이상 릴리스된 버전에서 지원 중단).
- 알 수 없음: 백엔드 처리 오류.
- 전체 상태: 인사이트의 모든 리소스 중에서 가장 높은 심각도 상태입니다. 이를 통해 업그레이드하기 전에 클러스터에 수정이 필요한지 빠르게 확인할 수 있습니다.
- 대상 버전인 1.26과 관련된 클러스터 인사이트를 가져오는 것으로 시작하겠습니다. EKS 클러스터의 통찰력을 보려면 다음 명령을 실행할 수 있습니다.
# **aws eks list-insights --filter kubernetesVersions=1.26 --cluster-name $CLUSTER_NAME | jq .** { "insights": [ { "id": "**00d5cb0c-45ef-4a1a-9324-d499a143baad**", "name": "Deprecated APIs removed in Kubernetes v1.26", "category": "UPGRADE_READINESS", "kubernetesVersion": "1.26", "lastRefreshTime": "2025-03-29T08:28:13+00:00", "lastTransitionTime": "2025-03-29T08:27:52+00:00", "description": "Checks for usage of deprecated APIs that are scheduled for removal in Kubernetes v1.26. Upgrading your cluster before migrating to the updated APIs supported by v1.26 could cause application impact.", "insightStatus": { "status": "**ERROR**", "reason": "Deprecated API usage detected within last 30 days and your cluster is on Kubernetes v1.25." } }, ... # 상세 설명 확인 aws eks describe-insight --region $AWS_REGION --id **00d5cb0c-45ef-4a1a-9324-d499a143baad** --cluster-name $CLUSTER_NAME | jq { "insight": { "id": "00d5cb0c-45ef-4a1a-9324-d499a143baad", "name": "**Deprecated APIs removed in Kubernetes v1.26**", "category": "UPGRADE_READINESS", "kubernetesVersion": "1.26", "lastRefreshTime": "2025-03-29T08:28:13+00:00", "lastTransitionTime": "2025-03-29T08:27:52+00:00", "description": "Checks for usage of deprecated APIs that are scheduled for removal in Kubernetes v1.26. Upgrading your cluster before migrating to the updated APIs supported by v1.26 could cause application impact.", "insightStatus": { "status": "**ERROR**", "reason": "Deprecated API usage detected within last 30 days and your cluster is on Kubernetes v1.25." }, "recommendation": "Update manifests and API clients to use newer Kubernetes APIs if applicable before upgrading to Kubernetes v1.26.", "additionalInfo": { "EKS update cluster documentation": "<https://docs.aws.amazon.com/eks/latest/userguide/update-cluster.html>", "Kubernetes v1.26 deprecation guide": "<https://kubernetes.io/docs/reference/using-api/deprecation-guide/#v1-26>" }, "resources": [ { "insightStatus": { "status": "ERROR" }, "kubernetesResourceUri": "/apis/autoscaling/**v2beta2**/namespaces/ui/**horizontalpodautoscalers**/ui" } ], "categorySpecificSummary": { "deprecationDetails": [ { "usage": "/apis/flowcontrol.apiserver.k8s.io/v1beta1/flowschemas", "replacedWith": "/apis/flowcontrol.apiserver.k8s.io/v1beta3/flowschemas", "stopServingVersion": "1.26", "startServingReplacementVersion": "1.26", "clientStats": [] }, { "usage": "/apis/flowcontrol.apiserver.k8s.io/v1beta1/prioritylevelconfigurations", "replacedWith": "/apis/flowcontrol.apiserver.k8s.io/v1beta3/prioritylevelconfigurations", "stopServingVersion": "1.26", "startServingReplacementVersion": "1.26", "clientStats": [] }, { "usage": "/apis/autoscaling/v2beta2/horizontalpodautoscalers", "**replacedWith**": "/apis/autoscaling/**v2/horizontalpodautoscalers**", "stopServingVersion": "1.26", "startServingReplacementVersion": "1.23", "clientStats": [ { "userAgent": "argocd-application-controller", "numberOfRequestsLast30Days": 43077, "lastRequestTime": "2025-03-29T08:17:37+00:00" } ] } ], "addonCompatibilityDetails": [] } } }- AWS 관리 콘솔 확인
- 배경
- (Optional) Illustrative Scenario ⇒ API 지원 중단에 대한 정보 소개
- 다음은 업그레이드 인사이트 탭에서 API 상태가 ERROR로 표시되는 예이며, 이는 해당 API가 v1.25에서 지원 중단될 예정임을 나타냅니다.
- 이 API 지원 중단에 대한 자세한 정보를 보려면 인사이트 이름을 클릭하세요.
- 이 이미지에서 우리는 업그레이드 인사이트가 Kubernetes 버전 1.25로 업그레이드할 때 지원 중단으로 인해 API를 활용하는 클러스터에서 실행 중인 리소스 유형을 식별하는 데 도움이 된다는 것을 알 수 있습니다.
- 리소스 유형은 동일하지만 API 버전을 업데이트해야 하는 경우 kubectl-convert 명령을 사용하여 매니페스트 파일을 자동으로 변환할 수 있습니다.
- kubectl-convert는 Kubernetes 매니페스트를 최신 API 버전에서 작동하도록 마이그레이션하는 데 편리한 유틸리티입니다. 이는 API가 최신 Kubernetes 릴리스에서 더 이상 사용되지 않거나 제거될 때 중요해집니다. 도움이 될 수 있습니다.
- API 버전 업데이트: 매니페스트가 더 이상 사용되지 않는 API(예: Ingress의 extensions/v1beta1)를 사용하는 경우 kubectl-convert가 이를 지원되는 버전(예: networking.k8s.io/v1)으로 업데이트할 수 있습니다.
- 호환성 보장: 매니페스트의 API 버전을 업데이트하면 kubectl-convert가 Kubernetes 클러스터 업그레이드 후에도 애플리케이션이 원활하게 작동하도록 보장합니다.
- 메모 : kubectl-convert는 플러그인입니다. 이전 버전의 kubectl에는 kubectl-convert가 내장되어 있었지만, 이제는 별도의 플러그인입니다. 사용하기 전에 설치 - Docs
- kubectl-convert로 매니페스트를 업데이트한 후 kubectl apply를 사용하여 클러스터에 다시 적용해야 합니다.
- kubectl-convert를 사용하기 전에 원본 매니페스트를 백업하는 것이 좋습니다. kubectl-convert를 활용하여 매니페스트를 마이그레이션하면 새로운 Kubernetes 버전으로 원활하게 전환하고 애플리케이션 중단을 피할 수 있습니다.
- # 기본 사용법에는 매니페스트 파일과 원하는 출력 버전을 지정하는 것이 포함됩니다 kubectl convert -f <manifest_file> --output-version <api_group>/<version> # 예시 : 이 명령은 apps/v1 API 버전을 사용하도록 deployment.yaml 파일을 업데이트합니다(이전 버전을 사용하고 있다고 가정). kubectl convert -f deployment.yaml --output-version apps/v1 ~~kubectl get hpa -n ui ui -o yaml | kubectl neat | kubectl convert -f - --output-version /apis/autoscaling/v2~~
- 예시 시나리오
- EKS Upgrade Checklist ⇒ 소개
- EKS 문서를 사용하여 업그레이드 체크리스트를 만듭니다. - **Docs***
- EKS Kubernetes 버전 설명서에는 각 버전에 대한 자세한 변경 사항 목록이 포함되어 있습니다. 각 업그레이드에 대한 체크리스트를 작성하세요.
- EKS 버전 업그레이드에 대한 구체적인 지침은 설명서를 참조하여 각 버전의 주요 변경 사항과 고려 사항을 확인하세요.
- Upgrading K8S Add-ons and Components with the API
- 주요한 요소 미리 확인 : 종종 주요 요소는 *-system 네임스페이스에 설치
- **kubectl get ns | grep -e '-system'** kube-system Active 4h50m # kubectl get-all -n kube-system ...
- 호환성을 위해 설명서를 확인하세요
- 식별된 각 구성 요소에 대해 해당 설명서를 참조하여 대상 Kubernetes 버전과의 호환성을 확인하세요.
- AWS Load Balancer Controller 설명서와 같은 리소스는 버전 호환성 세부 정보를 제공합니다.
- 일부 구성 요소는 진행하기 전에 업그레이드 또는 구성 변경이 필요할 수 있습니다.
- CoreDNS, kube-proxy, Container Network Interface(CNI) 플러그인 및 스토리지 드라이버와 같은 중요한 구성 요소에 세심한 주의를 기울이세요.
- 애드온 및 타사 도구 업그레이드
- 많은 클러스터는 Kubernetes API를 활용하여 유입 제어, 지속적인 전달 및 모니터링과 같은 기능을 제공하는 애드온 및 타사 도구에 의존합니다.
- EKS 클러스터를 업그레이드하려면 호환성을 유지하기 위해 이러한 도구를 업그레이드해야 합니다.
- Amazon VPC CNI: 각 클러스터 버전에 대한 Amazon VPC CNI 추가 기능의 권장 버전은 Kubernetes 자체 관리형 추가 기능에 대한 Amazon VPC CNI 플러그인 업데이트를 참조하세요. Amazon EKS 추가 기능으로 설치된 경우 한 번에 하나의 마이너 버전만 업그레이드할 수 있습니다.
- kube-proxy: Kubernetes kube-proxy 자체 관리형 추가 기능 업데이트를 참조하세요.
- CoreDNS: CoreDNS 자체 관리형 추가 기능 업데이트를 참조하세요.
- AWS Load Balancer 컨트롤러: AWS Load Balancer 컨트롤러는 배포한 EKS 버전과 호환되어야 합니다. 자세한 내용은 설치 안내서를 참조하세요.
- Amazon Elastic Block Store(Amazon EBS) 컨테이너 스토리지 인터페이스(CSI) 드라이버: 설치 및 업그레이드 정보는 Amazon EBS CSI 드라이버를 Amazon EKS 추가 기능으로 관리를 참조하세요.
- Amazon Elastic File System(Amazon EFS) 컨테이너 스토리지 인터페이스(CSI) 드라이버: 설치 및 업그레이드 정보는 Amazon EFS CSI 드라이버를 참조하세요.
- Kubernetes Metrics Server: 자세한 내용은 GitHub의 metrics-server를 참조하세요.
- Kubernetes 클러스터 오토스케일러: Kubernetes 클러스터 오토스케일러의 버전을 업그레이드하려면 배포에서 이미지의 버전을 변경합니다. Cluster Autoscaler는 Kubernetes 스케줄러와 긴밀하게 연결됩니다. 클러스터를 업그레이드할 때는 항상 업그레이드해야 합니다. GitHub 릴리스를 검토하여 Kubernetes 마이너 버전에 해당하는 최신 릴리스의 주소를 찾습니다.
- Karpenter: 설치 및 업그레이드 정보는 Karpenter 설명서를 참조하세요.
- EKS ControlPlane 업그레이드 전 기본 요구 사항 확인사용 가능한 IP 주소 확인
- Amazon EKS 클러스터를 업그레이드하려면 원래 클러스터 생성 시 지정한 서브넷 내에 최소 5개의 여유 IP 주소가 필요합니다. 서브넷에 충분한 여유 IP 주소가 있는지 확인하려면 아래와 같이 특정 명령을 실행할 수 있습니다.
aws ec2 describe-subnets --subnet-ids \\ $(aws eks describe-cluster --name ${CLUSTER_NAME} \\ --query 'cluster.resourcesVpcConfig.subnetIds' \\ --output text) \\ --query 'Subnets[*].[SubnetId,AvailabilityZone,AvailableIpAddressCount]' \\ --output table ---------------------------------------------------- | DescribeSubnets | +---------------------------+--------------+-------+ | subnet-0aeb12f673d69f7c5 | us-west-2c | **4038** | | subnet-047ab61ad85c50486 | us-west-2b | **4069** | | subnet-01bbd11a892aec6ee | us-west-2a | **4030** | +---------------------------+--------------+-------+- 기존 서브넷에 충분한 사용 가능한 IP가 없는 경우 Amazon에서는 UpdateClusterConfiguration을 사용하는 것이 좋습니다.
- 업그레이드를 진행하기 전에 클러스터 구성을 새로운 서브넷으로 업데이트하기 위한 API입니다.
- 가용성 영역(AZ) : 새 서브넷은 클러스터 생성 중에 원래 선택한 동일한 AZ 집합 내에 있어야 합니다.
- VPC 멤버십 : 새 서브넷은 클러스터와 연결된 동일한 VPC에 속해야 합니다.
- 서브넷 업데이트 후에도 충분한 IP가 없다면 추가 CIDR 블록을 VPC에 연결하는 것을 고려하세요. 이렇게 하면 기본적으로 사용 가능한 IP 주소 풀이 늘어납니다. 할 수 있는 일은 다음과 같습니다.
- 개인 CIDR 블록 추가 : RFC 1918을 준수하여 새로운 개인 IP 범위를 도입하여 IP 풀을 확장할 수 있습니다.
- 새로운 CIDR 기반 서브넷 업데이트 : VPC 내에서 새로 구성된 CIDR 블록을 반영하도록 클러스터 서브넷을 업데이트합니다.
- 이러한 지침을 따르면 EKS 클러스터가 원활한 Kubernetes 버전 업그레이드를 수행하는 데 필요한 IP 리소스를 확보할 수 있습니다.
EKS Security Groups: What You Need to Know**ROLE_ARN=$(aws eks describe-cluster --name ${CLUSTER_NAME} \\ --query 'cluster.roleArn' --output text) aws iam get-role --role-name ${ROLE_ARN##*/} \\ --query 'Role.AssumeRolePolicyDocument'** | jq { "Version": "2012-10-17", "Statement": [ { "Sid": "EKSClusterAssumeRole", "Effect": "Allow", "Principal": { "Service": "eks.amazonaws.com" }, "Action": [ "sts:TagSession", "sts:AssumeRole" ] } ] } **aws iam get-role --role-name ${ROLE_ARN##*/} | jq** ...- 클러스터를 만들면 Amazon EKS는 eks-cluster-sg-my-cluster-uniqueID라는 이름의 보안 그룹을 만듭니다.
- 이 보안 그룹에는 다음과 같은 기본 규칙이 있습니다.
- 자동 태그
- : EKS는 클러스터에 대해 만든 보안 그룹에 특정 태그를 주입합니다. 이러한 태그는 작동에 필수적이며 제거하면 다시 추가됩니다.
- 리소스 연결
- 네트워크 인터페이스(ENI): 클러스터를 프로비저닝하면 2-4개의 ENI가 생성됩니다. 이러한 ENI는 EKS에서 생성한 보안 그룹과도 연관됩니다.
- 관리형 노드 그룹 ENI: 사용자가 생성하는 모든 관리형 노드 그룹의 ENI도 이 보안 그룹에 연결됩니다.
- : 이 보안 그룹은 여러 리소스에 자동으로 연결됩니다.
- 기본 보안 규칙
- 인바운드 트래픽: 모든 트래픽은 클러스터 제어 평면과 노드 간에 자유롭게 흐를 수 있습니다.
- 아웃바운드 트래픽: 노드는 모든 목적지로 트래픽을 보낼 수 있습니다.
- : 처음에 보안 그룹은 제한 없는 통신을 허용합니다.
- 사용자 지정 보안 그룹(선택 사항)
- ENI 연결: EKS는 이러한 사용자 정의 그룹을 클러스터의 ENI와 연결합니다.
- 노드 그룹 제한: 그러나 이러한 사용자 지정 그룹은 귀하가 만든 노드 그룹에는 적용되지 않습니다. 이러한 연결을 별도로 관리해야 합니다.
- : 클러스터 생성 중에 선택적으로 자체 보안 그룹을 지정할 수 있습니다. 그렇게 하는 경우:
- 기본 규칙은 개방적입니다. 기본적으로 EKS는 클러스터 구성 요소 간의 무제한 통신을 허용합니다.
- 아웃바운드 트래픽 사용자 지정: 열려 있는 포트를 제한하려면 기본 아웃바운드 규칙을 제거하고 다음 최소 요구 사항을 구현합니다.
- 노드 간 통신: 노드 간 통신에 사용하는 모든 프로토콜과 포트에 대한 규칙을 정의합니다.
- 인바운드 규칙 지속성: EKS는 업데이트 중에 기본 인바운드 규칙을 제거한 후에 해당 규칙을 자동으로 다시 생성합니다.
- 아웃바운드 인터넷 액세스(선택 사항)
- : 노드에 인터넷 액세스가 필요한 경우(예: EKS API 호출 또는 초기 등록) 특정 포트에 대한 아웃바운드 규칙을 구성합니다. 개인 클러스터의 경우 인터넷 액세스가 필요하지 않을 수 있습니다.
- 컨테이너 이미지 액세스
- : 노드는 이미지를 가져오기 위해 컨테이너 레지스트리(예: Amazon ECR 또는 DockerHub)에 액세스해야 합니다. 적절한 레지스트리 엔드포인트에 대한 규칙을 만듭니다.
- IPv4/IPv6에 대한 별도 규칙
- : VPC가 두 주소 패밀리를 모두 사용하는 경우 각각에 대해 별도의 규칙이 필요합니다.
- EKS 클러스터 내에서 트래픽 흐름을 제한하고 싶으신가요? 알아야 할 사항은 다음과 같습니다.
- Amazon EKS가 보안 그룹을 활용하는 방식에 대한 세부 내용은 다음과 같습니다.
- IAM 역할을 사용할 수 있는지, 그리고 계정에 올바른 assume role policy이 있는지 확인하려면 다음 명령을 실행할 수 있습니다.
- AWS는 업그레이드 프로세스를 완료하기 위해 계정의 특정 리소스를 요구합니다. 이러한 리소스가 없으면 클러스터를 업그레이드할 수 없습니다. 제어 평면 업그레이드에는 다음 리소스가 필요합니다.
- EKS 문서를 사용하여 업그레이드 체크리스트를 만듭니다. - **Docs***
- 들어가며
- EKS Cluster Upgrade High Availability (HA) Strategies
- 인플레이스 클러스터 업그레이드 vs 블루/그린 클러스터 업그레이드 비교 ⇒ 소개
- 여러 EKS 버전을 동시에 변경 가능(예: 1.23에서 1.25로)
- 이전 클러스터로 다시 전환 가능
- 최신 시스템(예: Terraform)으로 관리할 수 있는 새 클러스터를 만듭니다.
- 작업 부하를 개별적으로 마이그레이션할 수 있습니다.
- API 엔드포인트 및 OIDC 변경으로 업데이트가 필요. (예: kubectl 및 CI/CD)
- 마이그레이션 중에 2개의 클러스터를 병렬로 실행해야 하므로 비용이 많이 들고 지역 용량이 제한될 수 있습니다.
- 작업 부하가 서로 의존하여 함께 마이그레이션되는 경우 **더 많은 조정(고려)**이 필요합니다.
- 로드 밸런서와 외부 DNS는 여러 클러스터에 쉽게 확장될 수 없습니다.
- 이 전략은 실행 가능하지만, 인플레이스 업그레이드보다 비용이 많이 들고 조정 및 워크로드 마이그레이션에 더 많은 시간이 필요합니다. 어떤 상황에서는 필요할 수 있으므로 신중하게 계획해야 합니다.
- 높은 수준의 자동화와 GitOps와 같은 선언적 시스템을 사용하면 이 접근 방식을 구현하기가 더 쉬울 수 있습니다. 그러나 데이터가 백업되고 새 클러스터로 마이그레이션되도록 하기 위해 상태 저장 워크로드에 대한 추가 예방 조치를 취해야 합니다.
- 장점
- 배포를 위한 애플리케이션 패키징Helm 차트
- Helm은 Kubernetes 애플리케이션을 관리하는 데 도움이 되는 오픈소스 도구입니다. Helm Charts는 빠르고 쉽게 배포할 수 있는 사전 구성된 Kubernetes 리소스 패키지입니다. Helm과 Helm Charts의 도움으로 개발자는 Kubernetes 배포 프로세스를 간소화하고 애플리케이션을 빠르게 가동할 수 있습니다.
- Helm 차트는 Kubernetes 애플리케이션을 표준화된 형식으로 패키징하고 배포하는 방법을 제공하여 다양한 환경에서 애플리케이션을 공유하고 배포하는 것을 더 쉽게 만듭니다. 차트는 공개 또는 비공개 차트 저장소를 통해 공유하거나 내부 사용을 위해 로컬에 저장할 수 있습니다.
- GitOps는 Git과 대상 환경(예: Kubernetes) 사이에서 GitOps 에이전트가 사용되는 최신 지속적 배포(CD) 모델로 작동합니다. GitOps는 또한 Git에서 발생하는 모든 변경 사항을 추적합니다.
- 이 워크숍에서 사용되는 Argo CD는 Git에서 애플리케이션을 배포하고, 애플리케이션의 수명 주기를 관리하고, 환경을 동기화 상태로 유지하는 데 도움이 되는 선언적 GitOps 기반의 지속적인 배포 도구입니다.
- Helm이 차트를 애플리케이션을 관리하고 배포하는 데 중요한 요소로 생각하는 반면, ArgoCD와 같은 GitOps 도구는 Git을 단일 진실 소스로 사용하고 이를 중심으로 애플리케이션 구성과 매니페스트를 저장하고 추적하여 복잡한 배포를 보다 쉽게 만듭니다.
- 참고: 이 워크숍에서는 업그레이드 프로세스 동안 원활한 애플리케이션 출시를 위한 GitOps 도구로 ArgoCD를 활용합니다.
- GitOps
- 데이터 플레인이 업그레이드되는 동안 워크로드의 가용성을 보장하기 위해 PodDisruptionBudgets 및 TopologySpreadConstraints 구성*
- 중요 서비스의 경우 워크로드에 적절한 PodDisruptionBudget(PDB) 가 있는지 확인하세요. 및 TopologySpreadConstraints 데이터 플레인 업그레이드 중에 가용성을 유지합니다. 다양한 워크로드에는 다양한 가용성 요구 사항이 있으므로 각 워크로드의 규모와 요구 사항을 검증하는 것이 중요합니다.
- 토폴로지가 분산된 여러 가용성 영역과 호스트에 워크로드를 분산하면 워크로드가 자동으로 새로운 데이터 평면으로 문제 없이 마이그레이션될 것이라는 확신이 커집니다.
- 아래 PDB는 자발적 중단 중에 배포에서 적어도 하나의 포드가 **orders**사용 가능한 상태로 유지되도록 보장합니다. 더 높은 가용성을 보장해야 하는 경우 minAvailable 값을 적절히 조정할 수 있습니다. 이것을 GitOps 리포에 추가하고 변경 사항을 EKS 클러스터에 동기화해 보겠습니다.
- # **kubectl get node --label-columns=node.kubernetes.io/instance-type,kubernetes.io/arch,kubernetes.io/os,topology.kubernetes.io/zone** **kubectl get node --label-columns=eks.amazonaws.com/capacityType,node.kubernetes.io/lifecycle,karpenter.sh/capacity-type,eks.amazonaws.com/compute-type** **kubectl get node -L eks.amazonaws.com/nodegroup,karpenter.sh/nodepool** **kubectl get nodes -o custom-columns='NODE:.metadata.name,TAINTS:.spec.taints[*].key,VALUES:.spec.taints[*].value,EFFECTS:.spec.taints[*].effect'** **kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, labels: .metadata.labels}'** # kubectl get-all -n orders kubectl get pdb -n orders **kubectl get pod -n orders --show-labels** NAME READY STATUS RESTARTS AGE LABELS **orders**-5b97745747-mxg72 1/1 Running 2 (5h48m ago) 5h48m app.kubernetes.io/component=**service**,app.kubernetes.io/created-by=eks-workshop,app.kubernetes.io/instance=**orders**,app.kubernetes.io/name=**orders**,pod-template-hash=5b97745747 orders-mysql-b9b997d9d-2p8pn 1/1 Running 0 5h48m app.kubernetes.io/component=mysql,app.kubernetes.io/created-by=eks-workshop,app.kubernetes.io/instance=orders,app.kubernetes.io/name=orders,app.kubernetes.io/team=database,pod-template-hash=b9b997d9d # orders 에 pdb.yaml 없음 **tree eks-gitops-repo/apps/orders/** eks-gitops-repo/apps/orders/ ├── configMap.yaml ├── deployment-mysql.yaml ├── deployment.yaml ├── kustomization.yaml ├── namespace.yaml ├── pvc.yaml ├── secrets.yaml ├── service-mysql.yaml ├── service.yaml └── serviceAccount.yaml # orders 에 pdb.yaml 작성 cat << EOF > ~/environment/eks-gitops-repo/apps/orders/pdb.yaml apiVersion**:** policy/v1 kind: **PodDisruptionBudget** metadata: name: **orders-pdb** namespace: **orders** spec: **minAvailable: 1** selector: matchLabels: app.kubernetes.io/component: **service** app.kubernetes.io/instance: **orders** app.kubernetes.io/name: **orders** EOF # kustomization.yaml이 리소스를 추가하고 CodeCommit repo에 변경 사항을 커밋하려면 를 업데이트합니다 cat ~/environment/eks-gitops-repo/apps/orders/kustomization.yaml echo " - pdb.yaml" >> ~/environment/eks-gitops-repo/apps/orders/kustomization.yaml **cat ~/environment/eks-gitops-repo/apps/orders/kustomization.yaml** apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization namespace: orders resources: - namespace.yaml - secrets.yaml - configMap.yaml - serviceAccount.yaml - service.yaml - service-mysql.yaml - deployment.yaml - deployment-mysql.yaml - pvc.yaml **- pdb.yaml** cd ~/environment/eks-gitops-repo/ git add apps/orders/kustomization.yaml git add apps/orders/pdb.yaml git commit -m "Add PDB to orders" **git push** # 마지막으로 ArgoCD UI나 아래 명령을 사용하여 argocd 애플리케이션을 동기화합니다. **argocd app sync orders** ... Sync Revision: c1a355735e446d98f8dc80d74d121a76d82c6f3c Phase: Succeeded Start: 2025-03-25 08:35:11 +0000 UTC Finished: 2025-03-25 08:35:12 +0000 UTC Duration: 1s Message: successfully synced (all tasks run) ...
- PDB 동작 테스트
# PDB의 현재 상태를 확인하여 사용 가능한 Pod 수와 필요한 최소 가용성을 파악합니다. **kubectl get pdb orders-pdb -n orders** NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE orders-pdb 1 N/A 0 3m13s # PDB 선택기에 지정된 레이블을 사용하여 PDB에서 관리하는 Pod를 나열합니다. **kubectl get pods -l app.kubernetes.io/component=service,app.kubernetes.io/instance=orders,app.kubernetes.io/name=orders -n orders** NAME READY STATUS RESTARTS AGE orders-5b97745747-mxg72 1/1 Running 2 (5h57m ago) 5h57m # 이제 Pod가 실행 중인 노드의 이름을 추출해야 합니다. **nodeName=$(kubectl get pods -l app.kubernetes.io/component=service,app.kubernetes.io/instance=orders,app.kubernetes.io/name=orders -n orders -o jsonpath="{range .items[*]}{.spec.nodeName}{'\\n'}{end}") && echo "$nodeName"** ip-10-0-3-199.us-west-2.compute.internal # PDB를 테스트하려면 수동으로 포드 중 하나를 제거하여 중단을 시뮬레이션할 수 있습니다 : **orders-mysql 는 쫓겨나지만 orders는 안나감.** # drain 은 해당 노드에 파드를 Evicted 하고 cordon 설정됨. **kubectl drain "$nodeName" --ignore-daemonsets --force --delete-emptydir-data** ## 노드를 드레이닝하면 PDB에서 관리하는 포드를 포함하여 해당 노드의 모든 포드를 퇴거하려고 시도합니다. 노드를 드레이닝하면 정의된 PDB 요구 사항을 위반하므로 오류가 발생합니다. error when evicting pods/"orders-5b97745747-j2h8d" -n "orders" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget. evicting pod orders/orders-5b97745747-j2h8d **Ctrl + C 명령 실행을 중지** # **kubectl get node "$nodeName"** NAME STATUS ROLES AGE VERSION ip-10-0-3-168.us-west-2.compute.internal Ready,**SchedulingDisabled** <none> 23h v1.25.16-eks-59bf375 **kubectl describe node "$nodeName" | grep -i taint -A2** Taints: dedicated=OrdersApp:NoSchedule **node.kubernetes.io/unschedulable:NoSchedule** Unschedulable: true # PDB 상태를 다시 확인하여 지정된 최소 가용성을 준수하는지 확인하세요. **kubectl get pdb orders-pdb -n orders** NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE orders-pdb 1 N/A 0 5m30s # 나머지 Pod가 계속 실행 중이며 최소 가용성 요구 사항을 충족하는지 확인하려면 Pod와 노드의 상태를 확인하세요. **kubectl get pods -l app.kubernetes.io/component=service,app.kubernetes.io/instance=orders,app.kubernetes.io/name=orders -n orders -o wide** NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES orders-5b97745747-mxg72 1/1 Running 2 (5h59m ago) 6h 10.0.5.219 ip-10-0-3-199.us-west-2.compute.internal <none> <none> # 중단 중에도 애플리케이션이 계속 정상적으로 작동하고 가용성 요구 사항을 충족하는지 확인하기 위해 애플리케이션의 동작을 모니터링합니다. # 테스트 후 노드의 차단을 해제하여 새로운 Pod를 예약할 수 있습니다. **kubectl uncordon "$nodeName"**
- 인플레이스 클러스터 업그레이드 vs 블루/그린 클러스터 업그레이드 비교 ⇒ 소개
- 준비
- Choosing an Upgrade Strategy : 업그레이드 전략 선택 (In-Place vs Blue-Green) ⇒ 소개
- 적절한 업그레이드 전략이 없는 경우의 일반적인 문제
- 명확하게 정의된 EKS 업그레이드 전략이 없는 고객은 다음을 포함한 여러 가지 어려움에 직면할 수 있습니다.
- 계획 되지 않은 가동 중지
- : 적절한 계획 없이 업그레이드하면 예상치 못한 가동 중지가 발생하여 애플리케이션 가용성과 사용자 경험에 영향을 미칠 수 있습니다.
- 호환성 문제
- : 새로운 Kubernetes 버전과 애플리케이션, 애드온 및 도구의 호환성을 철저히 테스트하고 검증하지 못하면 손상 및 중단이 발생할 수 있습니다.
- 롤백의 어려움
- : 업그레이드가 실패하거나 문제가 발생할 경우, 잘 정의된 롤백 계획 없이 이전 버전으로 롤백하는 것은 복잡하고 시간이 많이 걸릴 수 있습니다.
- 보안 취약점
- : 업그레이드를 지연하면 클러스터가 알려진 보안 취약점에 노출되어 애플리케이션과 데이터가 위험에 노출될 수 있습니다.
- 놓친 기회
- : 정기적으로 업그레이드하지 않으면 새로운 기능, 성능 개선 및 최신 Kubernetes 버전에 도입된 최적화를 놓칠 수 있습니다.
- 이러한 과제를 해결하려면 잘 정의된 업그레이드 전략을 수립하는 것이 중요합니다. EKS 업그레이드를 계획할 때 고려해야 할 두 가지 주요 전략이 있습니다. 인플레이스 업그레이드와 블루그린 업그레이드입니다. 각 전략에는 고유한 장점, 단점 및 고려 사항이 있으며, 특정 요구 사항과 제약 조건에 따라 신중하게 평가해야 합니다.
- 이 모듈에서는 두 업그레이드 전략을 자세히 살펴보고 장단점을 논의합니다. 또한 특정 요구 사항과 제약 조건에 따라 가장 적합한 전략을 선택하는 방법에 대한 지침도 제공합니다. 이 모듈을 마치면 In-Place와 Blue-Green 업그레이드의 차이점을 명확하게 이해하고 EKS 클러스터 업그레이드를 계획할 때 정보에 입각한 결정을 내릴 수 있게 됩니다.
- In-place 업그레이드 전략 : 기존 EKS 클러스터를 최신 Kubernetes 버전으로 업그레이드
- EKS 제어 평면을 대상 Kubernetes 버전으로 업그레이드합니다.
- 새로운 Kubernetes 버전과 일치하도록 작업자 노드 AMI를 업데이트합니다.
- 애플리케이션 가동 중지 시간을 최소화하기 위해 작업자 노드를 한 번에 하나씩 또는 소규모로 비우고 교체합니다.
- 새 버전과의 호환성을 보장하기 위해 모든 Kubernetes 매니페스트, 애드온 및 구성을 업데이트합니다.
- 철저한 테스트와 검증을 수행하여 애플리케이션과 서비스가 예상대로 작동하는지 확인합니다.
- VPC, 서브넷, 보안 그룹 등 기존 클러스터 리소스와 구성을 유지합니다.
- 동일한 클러스터 API 엔드포인트를 유지하므로 외부 통합 및 도구를 업데이트할 필요성이 최소화됩니다.
- 업그레이드 프로세스 중에 여러 클러스터를 관리하는 것에 비해 인프라 오버헤드가 덜 필요합니다.
- 클러스터 간에 상태 저장 애플리케이션과 지속형 데이터를 마이그레이션할 필요성을 최소화합니다.
- 업그레이드 과정에서 가동 중지 시간을 최소화하려면 신중한 계획과 조정이 필요합니다.
- 여러 Kubernetes 버전을 건너뛸 경우 연속적인 업그레이드가 여러 번 필요할 수 있으며, 업그레이드 프로세스가 길어질 수 있습니다.
- 업그레이드 중에 문제가 발생하면 롤백이 더 어렵고 시간이 많이 걸릴 수 있습니다. Control Plane이 업그레이드되면 롤백할 수 없습니다.
- 호환성을 보장하기 위해 모든 구성 요소와 종속성에 대한 철저한 테스트와 검증이 필요합니다.
- 내부 업그레이드 프로세스에는 일반적으로 다음 단계가 포함됩니다.
- Blue-Green 업그레이드 전략 : 새 EKS 클러스터(addon, apps)를 만들고 트래픽을 이전 클러스터에서 새 클러스터로 점진적으로 전환
- 원하는 Kubernetes 버전 및 구성으로 새 EKS 클러스터(녹색)를 만듭니다.
- 새 클러스터에 애플리케이션, 애드온 및 구성을 배포합니다.
- 새 클러스터가 예상대로 작동하는지 확인하기 위해 철저한 테스트와 검증을 수행합니다.
- DNS 업데이트, 로드 밸런서 구성 또는 서비스 메시와 같은 기술을 사용하여 점진적으로 이전 클러스터(파란색)에서 새 클러스터(녹색)로 트래픽을 전환합니다.
- 새 클러스터를 면밀히 모니터링하여 트래픽을 처리하고 예상대로 성능이 발휘되는지 확인하세요.
- 모든 트래픽이 새 클러스터로 전환되면 이전 클러스터를 해제합니다.
- 새로운 클러스터를 프로덕션 트래픽으로 전환하기 전에 철저히 테스트할 수 있으므로, 보다 통제되고 안전한 업그레이드 프로세스가 가능합니다.
- 단일 업그레이드에서 여러 Kubernetes 버전을 건너뛸 수 있으므로 전체 업그레이드 시간과 노력이 줄어듭니다.
- 문제가 발생할 경우 트래픽을 이전 클러스터로 다시 전환하여 빠르고 쉬운 롤백 메커니즘을 제공합니다.
- 새 클러스터가 완전히 검증될 때까지 이전 클러스터가 계속 트래픽을 제공하므로 업그레이드 프로세스 동안 가동 중지 시간이 최소화됩니다.
- 두 개의 클러스터를 동시에 유지 관리해야 하므로 업그레이드 프로세스에 추가적인 인프라 리소스와 비용이 필요합니다.
- 클러스터 간 트래픽 이동에 대한 보다 복잡한 조정 및 관리가 필요합니다.
- CI/CD 파이프라인, 모니터링 시스템, 액세스 제어 등의 외부 통합을 업데이트하여 새 클러스터를 가리키도록 해야 합니다.
- 클러스터 간 데이터 마이그레이션이나 동기화가 필요한 상태 저장 애플리케이션의 경우 어려울 수 있습니다.
- 상태 저장 워크로드에 대한 블루-그린 업그레이드를 수행할 때는 데이터 마이그레이션 및 동기화 프로세스를 신중하게 계획하고 실행하세요.
- Velero와 같은 도구를 사용하여 영구 데이터를 마이그레이션하고 클러스터 간에 데이터를 동기화 상태로 유지하며 빠른 롤백을 활성화하세요.
- 새 클러스터에서 영구 볼륨 프로비저닝을 구성하여 이전 클러스터의 스토리지 클래스 또는 프로비저너와 일치시키세요.
- 애플리케이션 설명서를 참조하고 애플리케이션 소유자와 협력하여 데이터 마이그레이션 및 동기화에 대한 특정 요구 사항을 이해하세요.
- Velero와 같은 도구를 적절히 계획하고 활용하는 것은 위험을 최소화하고 상태 저장 애플리케이션에 대한 원활한 업그레이드 프로세스를 보장하는 데 중요합니다.
- 블루-그린 업그레이드 프로세스에는 일반적으로 다음 단계가 포함됩니다.
- In-place 와 Blue-Green 업그레이드 전략 중 선택
- 가동 중지 허용 범위 : 업그레이드 프로세스 중에 애플리케이션과 서비스에 대한 허용 가능한 가동 중지 시간 수준을 고려하세요.
- 복잡성 업그레이드 : 애플리케이션 아키텍처, 종속성 및 상태 구성 요소의 복잡성을 평가합니다.
- Kubernetes 버전 차이 : 현재 Kubernetes 버전과 대상 버전 간의 차이와 애플리케이션 및 애드온의 호환성을 평가합니다.
- 리소스 제약 : 업그레이드 프로세스 중에 여러 클러스터를 유지하기 위한 사용 가능한 인프라 리소스와 예산을 고려하세요. 블루/그린과 유사한 카나리아 전략은 워크로드를 늘리는 동안 이전 클러스터를 확장하는 동안 새 클러스터를 확장하는 것을 제외하고는 이를 최소화합니다.
- 팀 전문성 : 여러 클러스터를 관리하고 트래픽 전환 전략을 구현하는 데 대한 팀의 전문성과 익숙함을 평가합니다.
- 전략을 선택할 때 고려해야 할 요소
- K8S Version Skew 를 이용한 Incremental In-place 업그레이드 전략업그레이드 프로세스:
- EKS 제어 평면을 다음 마이너 버전으로 업그레이드합니다. Kubernetes는 제어 평면이 작업자 노드보다 최대 2개의 마이너 버전(또는 Kubernetes 1.28부터 3개의 마이너 버전) 앞설 수 있는 버전 왜곡을 지원합니다.
- 제어 평면이 최대 허용 스큐를 초과하는 버전으로 업그레이드될 때까지 기존 버전에서 작업자 노드를 유지합니다. 예를 들어, 제어 평면을 1.21에서 시작하고 작업자 노드를 1.21에서 시작하는 경우 작업자 노드를 업그레이드하지 않고도 제어 평면을 1.22, 1.23 및 1.24로 업그레이드할 수 있습니다.
- 제어 평면이 허용되는 최대 스큐를 초과하는 버전(예: 작업자가 여전히 1.21을 사용하고 있는 경우 1.24)에 도달하면 제어 평면을 추가로 업그레이드하기 전에 지원되는 스큐 범위 내의 버전(예: 1.22 또는 1.23)으로 작업자 노드를 업그레이드합니다.
- 제어 평면과 작업자 노드 모두에서 원하는 Kubernetes 버전에 도달할 때까지 1~3단계를 반복합니다.
- 최신 Kubernetes 릴리스보다 여러 버전 뒤쳐져 있으며 점진적으로 따라잡고 싶어합니다.
- 작업자 노드에는 광범위한 테스트가 필요하고 빈번한 업그레이드를 어렵게 만드는 복잡하고 상태 저장형 워크로드가 있습니다.
- 작업자 노드의 중단을 최소화하면서 새로운 기능과 버그 수정에 액세스하기 위해 정기적으로 제어 평면 업그레이드를 수행하려고 합니다.
- 워커 노드가 호환 버전으로 업그레이드될 때까지 일부 새로운 Kubernetes 기능, 성능 개선 및 버그 수정을 완전히 사용할 수 없을 수 있습니다.
- 여러 버전을 건너뛴 후 워커 노드를 업그레이드하는 경우 호환성을 보장하고 문제를 식별하기 위해 철저한 테스트가 중요합니다.
- 이 전략은 유연성을 제공하지만, 비호환성 위험을 최소화하고 클러스터 전체에서 일관된 기능 세트를 보장하기 위해 제어 평면과 작업자 노드 버전을 최대한 가깝게 유지하는 것이 좋습니다.
- 이러한 권장 사항은 일반적인 지침으로 사용되며, 선택하는 구체적인 접근 방식은 고유한 요구 사항, 제약 조건 및 위험 허용 범위에 따라 달라집니다. 업그레이드 전략을 결정할 때 클러스터의 특성, 작업 부하 요구 사항 및 조직의 우선순위를 신중하게 평가하는 것이 필수적입니다. 또한 선택한 업그레이드 접근 방식에 관계없이 잘 정의된 테스트 및 검증 계획을 수립하는 것이 중요합니다. 프로덕션 업그레이드를 진행하기 전에 비프로덕션 환경에서 애플리케이션, 애드온 및 통합을 철저히 테스트하여 잠재적인 문제를 식별하고 해결하세요.
- 허용되는 최대 버전 스큐에 도달할 때까지 작업자 노드 업그레이드를 연기하면서 EKS 제어 평면을 점진적으로 업그레이드
- 결론이 모듈에서는 두 가지 주요 업그레이드 전략인 In-Place 업그레이드와 Blue-Green 업그레이드를 살펴보았습니다. 각 접근 방식의 장단점을 논의하고 다운타임 허용 범위, 업그레이드 복잡성, 리소스 제약, 규제 요구 사항 등 다양한 시나리오와 요인에 따라 권장 사항을 제공했습니다.
- In-Place 업그레이드는 다운타임 허용 범위가 낮은 부 버전 업그레이드에 적합한 반면, Blue-Green 업그레이드는 주요 버전 업그레이드나 상태 복잡도가 높은 애플리케이션에 권장됩니다.
- In-Place upgrades are suitable for minor version upgrades with low downtime tolerance, while Blue-Green upgrades are recommended for major version upgrades or applications with high stateful complexity.
- 성공적인 업그레이드를 위해서는 선택한 전략에 관계없이 신중한 계획, 테스트 및 검증이 필수적입니다.
- Careful planning, testing, and validation are crucial for a successful upgrade, regardless of the chosen strategy.
- 업그레이드 프로세스 중에 문제를 신속하게 식별하고 해결하려면 모니터링, 로깅 및 명확하게 정의된 롤백 계획이 필수적입니다.
- Monitoring, logging, and having a well-defined rollback plan are essential to quickly identify and resolve issues during the upgrade process.
- 습득한 교훈과 진화하는 모범 사례를 기반으로 업그레이드 전략을 지속적으로 평가하고 최적화하는 것은 EKS 클러스터 관리 관행을 개선하는 데 중요합니다.
- Continuously evaluating and optimizing your upgrade strategy based on lessons learned and evolving best practices is key to improving your EKS cluster management practices.
- 클러스터의 특성, 작업 부하 요구 사항, 조직의 우선순위를 철저히 평가하여 가장 적합한 업그레이드 전략을 선택하세요.
- 호환성을 보장하고 문제 위험을 최소화하기 위해 포괄적인 테스트 및 검증 계획을 개발합니다.
- 애플리케이션 소유자, 규정 준수 관리자, 사용자 등 관련 이해 관계자와 협력하여 업그레이드 계획을 해당 요구 사항과 기대치에 맞게 조정합니다.
- 자동화 및 IaC(Infrastructure as Code) 도구를 활용하여 업그레이드 프로세스를 간소화하고 다양한 환경의 일관성을 보장합니다.
- 이전 업그레이드에서 얻은 새로운 모범 사례와 교훈을 통합하여 업그레이드 전략을 정기적으로 검토하고 업데이트하세요.
- 이 모듈의 주요 내용은 다음과 같습니다.
- Amazon EKS 클러스터를 업그레이드하는 것은 최신 Kubernetes 기능, 보안 패치 및 성능 개선을 활용할 수 있도록 하는 중요한 프로세스입니다. 적절한 업그레이드 전략을 선택하는 것은 다운타임을 최소화하고, 위험을 줄이며, 새로운 Kubernetes 버전으로의 원활한 전환을 보장하는 데 필수적입니다.
- 적절한 업그레이드 전략이 없는 경우의 일반적인 문제
- Vagrantfile
- # Base Image <https://portal.cloud.hashicorp.com/vagrant/discover/bento/rockylinux-10.0> BOX_IMAGE = "bento/rockylinux-10.0" # "bento/rockylinux-9" BOX_VERSION = "202510.26.0" N = 2 # max number of Worker Nodes Vagrant.configure("2") do |config| # ControlPlane Nodes config.vm.define "k8s-ctr" do |subconfig| subconfig.vm.box = BOX_IMAGE subconfig.vm.box_version = BOX_VERSION subconfig.vm.provider "virtualbox" do |vb| vb.customize ["modifyvm", :id, "--groups", "/K8S-Upgrade-Lab"] vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] vb.name = "k8s-ctr" vb.cpus = 4 vb.memory = 3072 # 2048 2560 3072 4096 vb.linked_clone = true end subconfig.vm.host_name = "k8s-ctr" subconfig.vm.network "private_network", ip: "192.168.10.100" subconfig.vm.network "forwarded_port", guest: 22, host: "60000", auto_correct: true, id: "ssh" subconfig.vm.synced_folder "./", "/vagrant", disabled: true **subconfig.vm.provision "shell", path: "init_cfg.sh" subconfig.vm.provision "shell", path: "k8s-ctr.sh"** end # Worker Nodes (1..N).each do |i| config.vm.define "k8s-w#{i}" do |subconfig| subconfig.vm.box = BOX_IMAGE subconfig.vm.box_version = BOX_VERSION subconfig.vm.provider "virtualbox" do |vb| vb.customize ["modifyvm", :id, "--groups", "/K8S-Upgrade-Lab"] vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"] vb.name = "k8s-w#{i}" vb.cpus = 2 vb.memory = 2048 vb.linked_clone = true end subconfig.vm.host_name = "k8s-w#{i}" subconfig.vm.network "private_network", ip: "192.168.10.10#{i}" subconfig.vm.network "forwarded_port", guest: 22, host: "6000#{i}", auto_correct: true, id: "ssh" subconfig.vm.synced_folder "./", "/vagrant", disabled: true **subconfig.vm.provision "shell", path: "init_cfg.sh" subconfig.vm.provision "shell", path: "k8s-w.sh"** end end end
- init_cfg.sh : (Update) 가상머신에서 외부 통신(ping 8.8.8.8) 실패 시 해결 설정 추가, swap 파티션 제거 추가
- #!/usr/bin/env bash echo ">>>> Initial Config Start <<<<" echo "[TASK 1] Change Timezone and Enable NTP" timedatectl set-local-rtc 0 timedatectl set-timezone Asia/Seoul echo "[TASK 2] Disable firewalld and selinux" systemctl disable --now firewalld >/dev/null 2>&1 setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config echo "[TASK 3] Disable and turn off SWAP & Delete swap partitions" swapoff -a sed -i '/swap/d' /etc/fstab sfdisk --delete /dev/sda 2 partprobe /dev/sda echo "[TASK 4] Config kernel & module" cat << EOF > /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay >/dev/null 2>&1 modprobe br_netfilter >/dev/null 2>&1 cat << EOF >/etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system >/dev/null 2>&1 echo "[TASK 5] Setting Local DNS Using Hosts file" sed -i '/^127\\.0\\.\\(1\\|2\\)\\.1/d' /etc/hosts cat << EOF >> /etc/hosts 192.168.10.100 k8s-ctr 192.168.10.101 k8s-w1 192.168.10.102 k8s-w2 EOF echo "[TASK 6] Delete default routing - enp0s9 NIC" # setenforce 0 설정 필요 nmcli connection modify enp0s9 ipv4.never-default yes nmcli connection up enp0s9 >/dev/null 2>&1 echo "[TASK 7] Install Containerd" dnf config-manager --add-repo <https://download.docker.com/linux/centos/docker-ce.repo> >/dev/null 2>&1 dnf install -y -q containerd.io-2.1.5-1.el10 containerd config default > /etc/containerd/config.toml sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml systemctl daemon-reload systemctl enable --now containerd >/dev/null 2>&1 echo "[TASK 8] Install kubeadm kubelet kubectl" cat << EOF > /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni EOF dnf install -y -q kubelet kubeadm kubectl --disableexcludes=kubernetes systemctl enable --now kubelet >/dev/null 2>&1 cat << EOF > /etc/crictl.yaml runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock EOF echo ">>>> Initial Config End <<<<"
- k8s-ctr.sh
- #!/usr/bin/env bash echo ">>>> K8S Controlplane config Start <<<<" echo "[TASK 1] Initial Kubernetes" cat << EOF > kubeadm-init.yaml apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration bootstrapTokens: - token: "123456.1234567890123456" ttl: "0s" usages: - signing - authentication nodeRegistration: kubeletExtraArgs: - name: node-ip value: "192.168.10.100" criSocket: "unix:///run/containerd/containerd.sock" localAPIEndpoint: advertiseAddress: "192.168.10.100" --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: "1.32.11" networking: podSubnet: "10.244.0.0/16" serviceSubnet: "10.96.0.0/16" controllerManager: extraArgs: - name: "bind-address" value: "0.0.0.0" scheduler: extraArgs: - name: "bind-address" value: "0.0.0.0" etcd: local: extraArgs: - name: "listen-metrics-urls" value: "," EOF kubeadm init --config="kubeadm-init.yaml" echo "[TASK 2] Setting kube config file" mkdir -p /root/.kube cp -i /etc/kubernetes/admin.conf /root/.kube/config chown $(id -u):$(id -g) /root/.kube/config echo "[TASK 3] Install Helm" curl -fsSL <https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3> | DESIRED_VERSION=v3.18.6 bash >/dev/null 2>&1 echo "[TASK 4] Install kubecolor" dnf install -y -q 'dnf-command(config-manager)' >/dev/null 2>&1 dnf config-manager --add-repo <https://kubecolor.github.io/packages/rpm/kubecolor.repo> >/dev/null 2>&1 dnf install -y -q kubecolor >/dev/null 2>&1 echo "[TASK 5] Install Kubectx & Kubens" dnf install -y -q git >/dev/null 2>&1 git clone <https://github.com/ahmetb/kubectx> /opt/kubectx >/dev/null 2>&1 ln -s /opt/kubectx/kubens /usr/local/bin/kubens ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx echo "[TASK 6] Install Kubeps & Setting PS1" git clone <https://github.com/jonmosco/kube-ps1.git> /root/kube-ps1 >/dev/null 2>&1 cat << "EOT" >> /root/.bash_profile source /root/kube-ps1/kube-ps1.sh KUBE_PS1_SYMBOL_ENABLE=true function get_cluster_short() { echo "$1" | cut -d . -f1 } KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short KUBE_PS1_SUFFIX=') ' PS1='$(kube_ps1)'$PS1 EOT kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab" >/dev/null 2>&1 echo "[TASK 7] Install Flannel CNI" /usr/local/bin/helm repo add flannel <https://flannel-io.github.io/flannel> >/dev/null 2>&1 kubectl create namespace kube-flannel >/dev/null 2>&1 cat << EOF > flannel.yaml podCidr: "10.244.0.0/16" flannel: cniBinDir: "/opt/cni/bin" cniConfDir: "/etc/cni/net.d" args: - "--ip-masq" - "--kube-subnet-mgr" - "--iface=enp0s9" backend: "vxlan" EOF /usr/local/bin/helm install flannel flannel/flannel --namespace kube-flannel --version 0.27.3 -f flannel.yaml >/dev/null 2>&1 echo "[TASK 8] Source the completion" echo 'source <(kubectl completion bash)' >> /etc/profile echo 'source <(kubeadm completion bash)' >> /etc/profile echo "[TASK 9] Alias kubectl to k" echo 'alias k=kubectl' >> /etc/profile echo 'alias kc=kubecolor' >> /etc/profile echo 'complete -o default -F __start_kubectl k' >> /etc/profile echo "[TASK 10] Install Metrics-server" /usr/local/bin/helm repo add metrics-server <https://kubernetes-sigs.github.io/metrics-server/> >/dev/null 2>&1 /usr/local/bin/helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system >/dev/null 2>&1 echo "sudo su -" >> /home/vagrant/.bashrc echo ">>>> K8S Controlplane Config End <<<<"
- k8s-w.sh
- #!/usr/bin/env bash echo ">>>> K8S Node config Start <<<<" echo "[TASK 1] K8S Controlplane Join" NODEIP=$(ip -4 addr show enp0s9 | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}') cat << EOF > kubeadm-join.yaml apiVersion: kubeadm.k8s.io/v1beta4 kind: JoinConfiguration discovery: bootstrapToken: token: "123456.1234567890123456" apiServerEndpoint: "192.168.10.100:6443" unsafeSkipCAVerification: true nodeRegistration: criSocket: "unix:///run/containerd/containerd.sock" kubeletExtraArgs: - name: node-ip value: "$NODEIP" EOF kubeadm join --config="kubeadm-join.yaml" echo "sudo su -" >> /home/vagrant/.bashrc echo ">>>> K8S Node config End <<<<"
- 실습 환경 배포 : 주요 패키지 설치와 kubeadm init/join 작업 실행 출력을 노출되게 설정되어 있음.
- # 실습용 디렉터리 생성 mkdir k8s-upgrade cd k8s-upgrade # 파일 다운로드
- curl -O <https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-upgrade/Vagrantfile>
- curl -O <https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-upgrade/init_cfg.sh>
- curl -O <https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-upgrade/k8s-ctr.sh>
- curl -O <https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-upgrade/k8s-w.sh>
- # 실습 환경 배포
- vagrant up
- vagrant status
업그레이드 절차
- 실습 초기 버전 정보 : k8s 1.32.11
항목 버전 버전 호환성 Rocky Linux 10.0-1.6 version (kernel 6.12 - k8s_kernel) - kubespary containerd v2.1.5 CRI Version(v1), k8s 1.32~1.35 지원 - Link runc v1.3.3 정보 조사 필요 https://github.com/opencontainers/runc kubelet, kubeadm, kubectl v1.32.11 k8s 버전 정책 문서 참고 - Docs helm v3.18.6 k8s 1.30.x ~ 1.33.x 지원 - Docs flannel cni v0.27.3 k8s 1.28~ 이후 - Release - 목표 버전 정보 : k8s 1.34.11 ⇒ 1.35(도전 과제)
| 항목 | 버전 | 버전 호환성 |
| Rocky Linux | 10.1-1.4 | version (kernel 6.12 - k8s_kernel) - kubespary |
| containerd | v.2.1.5 | CRI Version(v1), k8s 1.32~1.35 지원 - Link |
| runc | v1.x.y | 정보 조사 필요 https://github.com/opencontainers/runc |
| kubelet, kubeadm, kubectl | v1.34.x | k8s 버전 정책 문서 참고 - Docs |
| helm | v3.19.x | k8s 1.31.x ~ 1.34.x 지원 - Docs |
| flannel cni | v0.28.0 | k8s 1.28~ 이후 - Release , Upgrade |

- 사전 준비 & etcd 백업
- (TS) Rocky Linux 10 vagrant image 에 SWAP off 해결 : init_cfg.sh 에 반영되어 있음
- echo "[TASK 3] Disable and turn off SWAP & Delete swap partitions"
swapoff -a
sed -i '/swap/d' /etc/fstab
sfdisk --delete /dev/sda 2
partprobe /dev/sda >/dev/null 2>&1 - kubens default
# swap 사용 확인
free -h
lsblk
├─sda2 8:2 0 3.8G 0 part # swap 사용이었으내, 스크립트 명령으로 swapoff 상태
# 이미 swap 삭제되어 있음
cat /etc/fstab
# 기동 중 컨테이너 확인
crictl ps
# 재기동
reboot
# 다시 접속
vagrant ssh k8s-w2
# 파드 기동 실패 반복
crictl ps
crictl ps -a
# 원인 확인
systemctl status kubelet.service --no-pager
journalctl -u kubelet.service -f
free -h
swapon --show
NAME TYPE SIZE USED PRIO
/dev/sda2 partition 3.8G 0B -2
# swap 파티션 즉시 비활성화
swapoff /dev/sda2
swapon --show
# swap 파티션 완전 삭제 : (방식1) fdisk 사용
fdisk /dev/sda
--------------------
p # 파티션 확인
d # delete
2 # sda2 선택
p # 다시 확인 (sda2 없어야 함)
w # 저장 후 종료
--------------------
# swap 파티션 완전 삭제 : (방식2) sfdisk 사용
sfdisk -l
sfdisk --delete /dev/sda 2
lsblk
# 커널에 파티션 테이블 반영
lsblk # 아직도 sda2 남아 있음
partprobe /dev/sda
# 확인
lsblk # sda2 삭제 처리 확인
# reboot 해서 한번 더 확인
reboot
# 다시 접속
vagrant ssh k8s-w2
# 확인
cricrl ps
free -h
swapon --show
lsblk
# (옵션) sda2를 xfs type / 파티션에서 확장 사용 설정 해보기 -
k8s-ctr,k8s-w1도 swap 파티션 완전 삭제 해두기
- # swap 파티션 완전 삭제 : fdisk 사용
fdisk /dev/sda
--------------------
p # 파티션 확인
d # delete
2 # sda2 선택
p # 다시 확인 (sda2 없어야 함)
w # 저장 후 종료
--------------------
# 커널에 파티션 테이블 반영
partprobe /dev/sda - 잔여 작업 : 삭제한 파티션을 활용 - 현재 sda3은 합치는게 불가능(넘버링 순서)하니, 캐시 등 작은 용량 사용 파티션 생성하여 활용.
- echo "[TASK 3] Disable and turn off SWAP & Delete swap partitions"
- (TS) 가상머신에서 외부 통신(ping 8.8.8.8) 실패 시 해결 완료 : init_cfg.sh 에 반영되어 있음
- echo "[TASK 6] Delete default routing - enp0s9 NIC"
nmcli connection modify enp0s9 ipv4.never-default yes
nmcli connection up enp0s9
# 확인
cat /etc/NetworkManager/system-connections/enp0s9.nmconnection
# gateway=192.168.10.1 줄 제거
never-default=true # 추가
- echo "[TASK 6] Delete default routing - enp0s9 NIC"
- kube-prometheus-stack 설치 - Link
- # repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
scrapeInterval: "20s"
evaluationInterval: "20s"
externalLabels:
cluster: "myk8s-cluster"
service:
type: NodePort
nodePort: 30001
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
service:
type: NodePort
nodePort: 30002
alertmanager:
enabled: true
defaultRules:
create: true
kubeProxy:
enabled: false
prometheus-windows-exporter:
prometheus:
monitor:
enabled: false
EOT
cat monitor-values.yaml
# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 80.13.3 \
-f monitor-values.yaml --create-namespace --namespace monitoring
# 확인
helm list -n monitoring
kubectl get pod,svc,ingress,pvc -n monitoring
kubectl get prometheus,servicemonitors,alertmanagers -n monitoring
kubectl get crd | grep monitoring
# 각각 웹 접속 실행 : NodePort 접속
open http://192.168.10.100:30001 # prometheus
open http://192.168.10.100:30002 # grafana : 접속 계정 admin / prom-operator
# 프로메테우스 버전 확인
kubectl exec -it sts/prometheus-kube-prometheus-stack-prometheus -n monitoring -c prometheus -- prometheus --version
prometheus, version 3.9.1
# 그라파나 버전 확인
kubectl exec -it -n monitoring deploy/kube-prometheus-stack-grafana -- grafana --version
grafana version 12.3.1
- # repo 추가
- 샘플 애플리케이션 배포
- # 샘플 애플리케이션 배포
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
type: ClusterIP
EOF - # k8s-ctr 노드에 curl-pod 파드 배포
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: curl-pod
labels:
app: curl
spec:
nodeName: k8s-ctr
containers:
- name: curl
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
- # 샘플 애플리케이션 배포
- 샘플 애플리케이션 확인 & 반복 호출
- # 배포 확인
kubectl get deploy,svc,ep webpod -owide
# 반복 호출
kubectl exec -it curl-pod -- curl webpod | grep Hostname
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
혹은
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
- # 배포 확인
- 동작 확인을 위한 모니터링 걸어두기
- # 터미널1
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
혹은
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
# 터미널2
watch -d kubectl get node
# 터미널3
watch -d kubectl get pod -A -owide
# 터미널4
watch -d kubectl top node
# 터미널5
[k8s-w1] watch -d crictl ps
# 터미널6
[k8s-w2] watch -d crictl ps
- # 터미널1
- ETCD 백업 (snapshot 저장) - Home , Docs
- etcdctl 설치
- # etcd 컨테이너 버전 확인
crictl images | grep etcd
registry.k8s.io/etcd 3.5.24-0 1211402d28f58 21.9MB
kubectl exec -n kube-system etcd-k8s-ctr -- etcdctl version
etcdctl version: 3.5.24
API version: 3.5
# 버전 변수 지정
ETCD_VER=3.5.24
# cpu arch 변수 지정
ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then ARCH=arm64; fi
echo $ARCH
# etcd 바이너리 다운로드
curl -L https://github.com/etcd-io/etcd/releases/download/v${ETCD_VER}/etcd-v${ETCD_VER}-linux-${ARCH}.tar.gz -o /tmp/etcd-v${ETCD_VER}.tar.gz
ls /tmp
# 압축 해제
mkdir -p /tmp/etcd-download
tar xzvf /tmp/etcd-v${ETCD_VER}.tar.gz -C /tmp/etcd-download --strip-components=1
# 실행 파일 이동 (etcdctl, etcdutl)
mv /tmp/etcd-download/etcdctl /usr/local/bin/
mv /tmp/etcd-download/etcdutl /usr/local/bin/
chown root:root /usr/local/bin/etcdctl
chown root:root /usr/local/bin/etcdutl
# 설치 확인
etcdctl version
etcdctl version: 3.5.24
API version: 3.5 - etcdctl 기본 사용
- # listen-client-urls 정보 확인
kubectl describe pod -n kube-system etcd-k8s-ctr | grep isten-client-urls
--listen-client-urls=https://127.0.0.1:2379,https://192.168.10.100:2379
# etcdctl 환경변수 설정
export ETCDCTL_API=3 # etcd v3 API를 사용
export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt
export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key
export ETCDCTL_ENDPOINTS=https://127.0.0.1:2379 # listen-client-urls
# 확인
etcdctl endpoint status -w table
etcdctl member list -w table
+------------------+---------+---------+-----------------------------+-----------------------------+------------+
| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER |
+------------------+---------+---------+-----------------------------+-----------------------------+------------+
| f330bec74ce6cc42 | started | k8s-ctr | https://192.168.10.100:2380 | https://192.168.10.100:2379 | false |
+------------------+---------+---------+-----------------------------+-----------------------------+------------+ - etcd 백업 (snapshot 저장)

- # 백업 수행
mkdir /backup
etcdctl snapshot save /backup/etcd-snapshot-$(date +%F).db
# 백업 파일 확인
tree /backup/
/backup/
└── etcd-snapshot-2026-01-20.db
# 백업 결과 확인
etcdutl snapshot status /backup/etcd-snapshot-2026-01-20.db
a63bd37d, 2132, 865, 1.9 MB
- (TS) Rocky Linux 10 vagrant image 에 SWAP off 해결 : init_cfg.sh 에 반영되어 있음
- CNI Upgrade
- Flannel CNI 업그레이드 : v0.27.3 → v0.27.4
- 고려 사항 - Helm
- 데몬셋으로 삭제 후 재기동 되는 시간 단축을 위해 노드에 미리 이미지 다운로드 해두기
- 재기동 시 배포된 app 파드 영향도 파악
- 노드에 미리 이미지 다운로드 해두기
- #
crictl images | grep flannel
ghcr.io/flannel-io/flannel-cni-plugin v1.7.1-flannel1 127562bd9047f 5.14MB
ghcr.io/flannel-io/flannel v0.27.3 d84558c0144bc 33.1MB
# cni-pligin 버전 정보도 확인 후 pull 해두자!
crictl pull ghcr.io/flannel-io/flannel:v0.27.4
crictl pull ghcr.io/flannel-io/flannel:v1.8.0-flannel1
crictl images | grep flannel
ghcr.io/flannel-io/flannel-cni-plugin v1.7.1-flannel1 127562bd9047f 5.14MB
ghcr.io/flannel-io/flannel v0.27.3 d84558c0144bc 33.1MB
ghcr.io/flannel-io/flannel v0.27.4 24d577aa4188d 33.2MB
# [w1/w2]
crictl pull ghcr.io/flannel-io/flannel:v0.27.4
crictl pull ghcr.io/flannel-io/flannel:v1.8.0-flannel1
crictl images | grep flannel - Flannel CNI 업그레이드 by helm
- # 모니터링
## 터미널1 : 아래 호출 중단 발생 확인!
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
혹은
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
## 터미널3 : kube-flannel 파드 재기동 후 정상 기동까지 시간 확인
watch -d kubectl get pod -n kube-flannel
# 작업 전 정보 확인
helm list -n kube-flannel
helm get values -n kube-flannel flannel
kubectl get pod -n kube-flannel -o yaml | grep -i image: | sort | uniq
kubectl get ds -n kube-flannel -owide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
kube-flannel-ds 3 3 3 3 3 <none> 126m kube-flannel ghcr.io/flannel-io/flannel:v0.27.3 app=flannel
# 신규 버전 명시 values 파일 작성
cat << EOF > flannel.yaml
podCidr: "10.244.0.0/16"
flannel:
cniBinDir: "/opt/cni/bin"
cniConfDir: "/etc/cni/net.d"
args:
- "--ip-masq"
- "--kube-subnet-mgr"
- "--iface=enp0s9"
backend: "vxlan"
image:
tag: v0.27.4
EOF
# helm 업그레이드 수행 : Flannel은 DaemonSet 이므로 노드별 순차 업데이트
helm upgrade flannel flannel/flannel -n kube-flannel -f flannel.yaml --version 0.27.4
kubectl -n kube-flannel rollout status ds/kube-flannel-ds
# 확인
helm list -n kube-flannel
helm get values -n kube-flannel flannel
# 확인
crictl ps
kubectl get ds -n kube-flannel -owide
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE CONTAINERS IMAGES SELECTOR
kube-flannel-ds 3 3 3 3 3 <none> 127m kube-flannel ghcr.io/flannel-io/flannel:v0.27.3 app=flannel
kubectl get pod -n kube-flannel -o yaml | grep -i image: | sort | uniq
image: ghcr.io/flannel-io/flannel-cni-plugin:v1.8.0-flannel1
image: ghcr.io/flannel-io/flannel:v0.27.4
- 고려 사항 - Helm
- Flannel CNI 업그레이드 : v0.27.3 → v0.27.4
- OS Upgrade
- [k8s-ctr] Rocky Linux OS 마이너 버전 Upgrade : v10.0 → v10.1 ⇒ 노드 재기동 - Link
- 고려 사항
- ControlPlane 노드의 경우 HA(3대 이상) 되어 있을 경우, 1대 노드가 장애 시에도 문제 없는 환경인지 한번 더 확인
- 단일 파드가 배포된 노드 reboot 시 영향도?
- 현재 pv/pvc 를 사용하고 있지 않은, 예시(프로메테우스/그라파나)가 배포된 노드 reboot 시 영향도?
- 고려 사항
- [k8s-ctr] 작업 전 정보 확인 : reboot 시 영향도 파악
- # Rocky Linux Upgrade 후 reboot 해야되니, reboot 시 영향도 파악
kubectl get pod -A -owide |grep k8s-ctr
default curl-pod 1/1 Running 0 49m 10.244.0.4 k8s-ctr <none> <none>
kube-flannel kube-flannel-ds-f2572 1/1 Running 0 3m33s 192.168.10.100 k8s-ctr <none> <none>
kube-system coredns-668d6bf9bc-ctkmb 1/1 Running 0 130m 10.244.0.2 k8s-ctr <none> <none>
kube-system coredns-668d6bf9bc-vff46 1/1 Running 0 130m 10.244.0.3 k8s-ctr <none> <none>
kube-system etcd-k8s-ctr 1/1 Running 0 130m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-apiserver-k8s-ctr 1/1 Running 0 130m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-controller-manager-k8s-ctr 1/1 Running 0 130m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-proxy-wwd9c 1/1 Running 0 130m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-scheduler-k8s-ctr 1/1 Running 0 130m 192.168.10.100 k8s-ctr <none> <none>
monitoring kube-prometheus-stack-prometheus-node-exporter-6k5rk 1/1 Running 0 24m 192.168.10.100 k8s-ctr <none> <none>
# coredns 노드 분산 배포 : 만약 아래 조치 없이 k8s-ctr reboot 시 영향도를 생각해보자!
k scale deployment -n kube-system coredns --replicas 1
kubectl get pod -A -owide |grep k8s-ctr
kubectl scale deployment -n kube-system coredns --replicas 2
kubectl get pod -n kube-system -owide | grep coredns
- # Rocky Linux Upgrade 후 reboot 해야되니, reboot 시 영향도 파악
- [k8s-ctr] Rocky Linux 마이너 버전 Upgrade : v10.0 → v10.1 ⇒ 노드 재기동 - Link
- # 모니터링
## 터미널1 : 아래 호출 중단 발생 확인!
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
[k8s-w1] SVCIP=<IP 직접 입력>
[k8s-w1] while true; do curl -s $SVCIP | grep Hostname; sleep 1; done # 이유는 k8s-ctr reboot 시에도 app 워크로드 통신 확인을 위함
혹은
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
# Rocky Linux 버전 확인
rpm -aq | grep release # rocky-release-10.0-1.6.el10.noarch
uname -r # 6.12.0-55.39.1.el10_0.aarch64
-------------------------------------------------------------
# (옵션) containerd.io 버전 업그레이드 되지 않게, 현재 설치 버전 고정 설정
rpm -q containerd.io # 현재 설치 버전 정보 확인
containerd.io-2.1.5-1.el10.aarch64
## 버전 잠금 플러그인 설치
dnf install -y 'dnf-command(versionlock)'
## containerd.io 버전 잠금
dnf versionlock add containerd.io
Adding versionlock on: containerd.io-0:2.1.5-1.el10.*
## 버전 잠금 목록 확인
dnf versionlock list
containerd.io-0:2.1.5-1.el10.*
-------------------------------------------------------------
# update 수행 : 해당 과정에서 containerd 가 업그레이드 된것으로 보임. 정확한 버전 관리가 필요 시, containerd/runc 예외 설정 할 것
dnf -y update # 6.12.0-124.21.1.el10_1.aarch64
## 커널 업데이트 완료 : 새로운 리눅스 커널 버전인 6.12.0-124.27.1.el10_1.aarch64이 시스템에 성공적으로 설치됨.
## 부트로더 갱신 : 다음 재부팅 시 시스템은 가장 최신 버전인 6.12.0-124 커널로 부팅됨.
Running scriptlet: kernel-modules-core-6.12.0-124.27.1.el10_1.aarch64 584/584
Running scriptlet: kernel-core-6.12.0-124.27.1.el10_1.aarch64 584/584
Generating grub configuration file ...
Adding boot menu entry for UEFI Firmware Settings ...
done
kdump: For kernel=/boot/vmlinuz-6.12.0-124.27.1.el10_1.aarch64, crashkernel=2G-4G:256M,4G-64G:320M,64G-:576M now.
Please reboot the system for the change to take effect.
Note if you don't want kdump-utils to manage the crashkernel kernel parameter, please set auto_reset_crashkernel=no in /etc/kdump.conf.
Running scriptlet: kernel-modules-6.12.0-124.27.1.el10_1.aarch64 584/584
Running scriptlet: kdump-utils-1.0.54-7.el10.aarch64 584/584
kdump: For kernel=/boot/vmlinuz-6.12.0-55.39.1.el10_0.aarch64, crashkernel=2G-4G:256M,4G-64G:320M,64G-:576M now.
Please reboot the system for the change to take effect.
Note if you don't want kdump-utils to manage the crashkernel kernel parameter, please set auto_reset_crashkernel=no in /etc/kdump.conf.
# 재기동 : 단일 Controlplane node 환경이므로, reboot 후 부팅 정상화 완료 기간에는 k8s api 통신 불가!
reboot
ping 192.168.10.100
64 bytes from 192.168.10.100: icmp_seq=47 ttl=64 time=0.452 ms
64 bytes from 192.168.10.100: icmp_seq=48 ttl=64 time=0.406 ms
Request timeout for icmp_seq 49 # 재부팅 시점에 ping 통신 불가
Request timeout for icmp_seq 50
Request timeout for icmp_seq 51
Request timeout for icmp_seq 52
Request timeout for icmp_seq 53
Request timeout for icmp_seq 54
Request timeout for icmp_seq 55
Request timeout for icmp_seq 56
Request timeout for icmp_seq 57
Request timeout for icmp_seq 58
Request timeout for icmp_seq 59
Request timeout for icmp_seq 60
Request timeout for icmp_seq 61
Request timeout for icmp_seq 62
Request timeout for icmp_seq 63
Request timeout for icmp_seq 64
Request timeout for icmp_seq 65
Request timeout for icmp_seq 66
64 bytes from 192.168.10.100: icmp_seq=67 ttl=64 time=0.364 ms # 부팅 과정에서 nic 처리 정상 시점~
64 bytes from 192.168.10.100: icmp_seq=68 ttl=64 time=0.291 ms
...
# vagrant ssh k8s-ctr
rpm -aq | grep release # rocky-release-10.1-1.4.el10.noarch
uname -r # 6.12.0-124.27.1.el10_1.aarch64
# 파드 기동 확인
kubectl get pod -A -owide |grep k8s-ctr
default curl-pod 1/1 Running 1 (2m25s ago) 61m 10.244.0.3 k8s-ctr <none> <none>
kube-flannel kube-flannel-ds-f2572 1/1 Running 1 (2m25s ago) 15m 192.168.10.100 k8s-ctr <none> <none>
kube-system coredns-668d6bf9bc-ctkmb 1/1 Running 1 (2m25s ago) 142m 10.244.0.2 k8s-ctr <none> <none>
kube-system etcd-k8s-ctr 1/1 Running 1 (2m25s ago) 142m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-apiserver-k8s-ctr 1/1 Running 1 (2m25s ago) 142m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-controller-manager-k8s-ctr 1/1 Running 1 (2m25s ago) 142m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-proxy-wwd9c 1/1 Running 1 (2m25s ago) 142m 192.168.10.100 k8s-ctr <none> <none>
kube-system kube-scheduler-k8s-ctr 1/1 Running 1 (2m25s ago) 142m 192.168.10.100 k8s-ctr <none> <none>
monitoring kube-prometheus-stack-prometheus-node-exporter-6k5rk 1/1 Running 1 (2m25s ago) 36m 192.168.10.100 k8s-ctr <none> <none>
- # 모니터링
- [k8s-ctr] Rocky Linux OS 마이너 버전 Upgrade : v10.0 → v10.1 ⇒ 노드 재기동 - Link
- Containerd Upgrade
- containerd.io-2.1.5-y ⇒ containerd.io-2.2.1-y 업그레이드 방법과 통신 모니터링 가이드 추가
- Kubeadm , Kubelet, Kubectl Upgrade → 목표 버전까지 단계별 반복 작업
- [k8s-ctr] Kubeadm , Kubelet, Kubectl Upgrade : v1.32.11 → v1.33.11
- 고려 사항
- kubeadm 업그레이드 → kubelet/kubectl 업그레이드 설치 → kubelet 재시작
- containerd를 재시작 X : kubeadm 업그레이드 후에도 ‘CRI API 버전 동일, 소켓 동일, 런타임 상태 유지’
- 다만, pause 컨테이너 이미지 변경 할 경우에는 containerd 재시작 필요 → 이후 부터 기동되는 파드들에 적용됨.
- containerd를 재시작 X : kubeadm 업그레이드 후에도 ‘CRI API 버전 동일, 소켓 동일, 런타임 상태 유지’
- 미리 노드에 신규 버전 이미지 다운로드 해둘것!
- kubeadm 업그레이드 → kubelet/kubectl 업그레이드 설치 → kubelet 재시작
- 고려 사항
- kubeadm 신규 버전 설치 & kubeadm upgrade plan : kubelet/kubectl은 아직 업그레이드하지 않음
- kubeadm upgrade plan
- kubem 업그레이드 적용을 실행하기 전에 kubem 업그레이드 계획을 실행할 수 있습니다. 계획 하위 명령은 어떤 버전으로 업그레이드할 수 있는지 확인하고 현재 클러스터가 업그레이드 가능한지 확인합니다.
- # repo 수정 : 기본 1.32 -> 1.33
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
# 전체 설치 가능 버전 확인 : --disableexcludes=... kubernetes repo에 설정된 exclude 규칙을 이번 설치에서만 무시(1회성 옵션 처럼 사용)
dnf list --showduplicates kubeadm --disableexcludes=kubernetes
Installed Packages
kubeadm.aarch64 1.32.11-150500.1.1 @kubernetes
Available Packages
..
kubeadm.aarch64 1.33.7-150500.1.1 kubernetes
kubeadm.ppc64le 1.33.7-150500.1.1 kubernetes
kubeadm.s390x 1.33.7-150500.1.1 kubernetes
kubeadm.src 1.33.7-150500.1.1 kubernetes
kubeadm.x86_64 1.33.7-150500.1.1 kubernetes
# 설치
dnf install -y --disableexcludes=kubernetes kubeadm-1.33.7-150500.1.1
Upgrading:
kubeadm aarch64 1.33.7-150500.1.1 kubernetes 11 M
# 설치 파일들 확인
which kubeadm && kubeadm version -o yaml
# 업그레이드 계획 확인 : 현재 버전 , 대상 버전 , 컴포넌트 변경 사항
kubeadm upgrade plan
[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 k8s-ctr v1.32.11 v1.33.7
kubelet k8s-w1 v1.32.11 v1.33.7
kubelet k8s-w2 v1.32.11 v1.33.7
Upgrade to the latest stable version:
COMPONENT NODE CURRENT TARGET
kube-apiserver k8s-ctr v1.32.11 v1.33.7
kube-controller-manager k8s-ctr v1.32.11 v1.33.7
kube-scheduler k8s-ctr v1.32.11 v1.33.7
kube-proxy 1.32.11 v1.33.7 # kube-proxy 데몬셋으로 모든 노드에 영향
CoreDNS v1.11.3 v1.12.0 # coredns 도메인 처리로 영향
etcd k8s-ctr 3.5.24-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
_____________________________________________________________________
- kubeadm upgrade plan
- kubeadm upgrade apply 실행
-
- etcd schema 확인
- kube-apiserver / controller-manager / scheduler static pod 교체
- CoreDNS 업그레이드
- kube-proxy 업그레이드
- kubeadm init 및 kubeadm join과 유사하게 비행 전 검사를 실행하여 컨테이너 이미지를 다운로드하고 클러스터를 업그레이드하기 좋은 상태로 유지합니다.
- /etc/kubernetes/manifests에서 디스크의 컨트롤 플레인 매니페스트 파일을 업그레이드하고 파일이 변경된 경우 큐브렛이 구성 요소를 재시작할 때까지 기다립니다.
- 업데이트된 kubeadm 및 kublet 구성을 kubeadm-config와 kublet-config ConfigMaps의 클러스터에 업로드합니다(둘 다 kube-system 네임스페이스에 있습니다).
- 이 노드에 대한 업데이트된 큐브렛 구성을 /var/lib/kubelet/config.yaml에 작성하고, 노드의 /var/lib/kubelet/instance-config.yaml 파일과 containerRuntimeEndpoint와 같은 패치 필드를 이 인스턴스 구성에서 /var/lib/kubelet/config.yaml로 읽습니다.
- RBAC 규칙을 위해 부트스트랩 토큰과 클러스터-인포 ConfigMap을 구성합니다. 이는 kubeadm init 단계와 동일하며 클러스터가 부트스트랩 토큰에 참여하는 노드를 계속 지원하도록 보장합니다.
- 클러스터에 있는 모든 기존 kube-apiserver가 이미 대상 버전으로 업그레이드된 경우 조건부로 kube-proxy 및 CoreDNS 애드온을 업그레이드합니다.
- 업그레이드 후 작업을 수행합니다. 예를 들어, 릴리스별로 사용되지 않는 기능을 정리하는 등의 작업을 수행합니다.
- # 모니터링
## 터미널1 : 아래 호출 중단 발생 확인!
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
[k8s-w1] SVCIP=<IP 직접 입력>
[k8s-w1] while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
혹은
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
# 터미널2
watch -d kubectl get node
## 터미널3
watch -d kubectl get pod -n kube-system
## 터미널4
watch -d etcdctl member list -w table
# 컨테이너 이미지 미리 다운로드 : 특히 업그레이드 작업 시, 작업 시간 단축을 위해서 수행할 것
kubeadm config images pull
[config/images] Pulled registry.k8s.io/kube-apiserver:v1.33.7
[config/images] Pulled registry.k8s.io/kube-controller-manager:v1.33.7
[config/images] Pulled registry.k8s.io/kube-scheduler:v1.33.7
[config/images] Pulled registry.k8s.io/kube-proxy:v1.33.7
[config/images] Pulled registry.k8s.io/coredns/coredns:v1.12.0
[config/images] Pulled registry.k8s.io/pause:3.10
[config/images] Pulled registry.k8s.io/etcd:3.5.24-0
crictl images
...
# [k8s-w1/w2] kube-proxy 데몬셋과 coredns 신규 버전은 all node 에 미리 다운로드 해두자!
crictl pull registry.k8s.io/kube-proxy:v1.33.7
crictl pull registry.k8s.io/coredns/coredns:v1.12.0
# 실행 후 y 입력
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.11
[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
...
[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.
-
- k8s 상태 확인
- # control component(apiserver, kcm 등)의 컨테이너 이미지는 1.33.7 업그레이드 되었고,
# kubelet 은 아직 업그레이드 되지 않은 상태. node 출력에 버전은 kubelet 버전을 기준 출력으로 보임.
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 68m v1.32.11 192.168.10.100 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.1.5
k8s-w1 Ready <none> 63m v1.32.11 192.168.10.101 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready <none> 62m v1.32.11 192.168.10.102 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
kubectl describe node k8s-ctr | grep 'Kubelet Version:'
Kubelet Version: v1.32.11
kubectl get pod -A
...
# 이미지 업데이트 확인 : etcd, pause 기존 버전만 local 존재 확인.
crictl images
IMAGE TAG IMAGE ID SIZE
registry.k8s.io/coredns/coredns v1.11.3 2f6c962e7b831 16.9MB
registry.k8s.io/coredns/coredns v1.12.0 f72407be9e08c 19.1MB
registry.k8s.io/etcd 3.5.24-0 1211402d28f58 21.9MB
registry.k8s.io/kube-apiserver v1.32.11 58951ea1a0b5d 26.4MB
registry.k8s.io/kube-apiserver v1.33.7 6d7bc8e445519 27.4MB
registry.k8s.io/kube-controller-manager v1.32.11 82766e5f2d560 24.2MB
registry.k8s.io/kube-controller-manager v1.33.7 a94595d0240bc 25.1MB
registry.k8s.io/kube-proxy v1.32.11 dcdb790dc2bfe 27.6MB
registry.k8s.io/kube-proxy v1.33.7 78ccb937011a5 28.3MB
registry.k8s.io/kube-scheduler v1.32.11 cfa17ff3d6634 19.2MB
registry.k8s.io/kube-scheduler v1.33.7 94005b6be50f0 19.9MB
registry.k8s.io/pause 3.10 afb61768ce381 268kB
# static pod yaml 파일 내용 업데이트 확인 : yaml 파일 내에 images 부분 업데이트
ls -l /etc/kubernetes/manifests/
cat /etc/kubernetes/manifests/*.yaml | grep -i image:
image: registry.k8s.io/etcd:3.5.24-0
image: registry.k8s.io/kube-apiserver:v1.33.7
image: registry.k8s.io/kube-controller-manager:v1.33.7
image: registry.k8s.io/kube-scheduler:v1.33.7
# kube-system 네임스페이스 내에 동작 중인 파드 내에 컨테이너 이미지 정보 출력
kubectl get pods -n kube-system -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{range .spec.containers[*]} - {.name}: {.image}{"\n"}{end}{"\n"}{end}'
coredns-674b8bbfcf-8qhd9
- coredns: registry.k8s.io/coredns/coredns:v1.12.0
etcd-k8s-ctr
- etcd: registry.k8s.io/etcd:3.5.24-0 # 기존 버전 동일(유지)
kube-apiserver-k8s-ctr
- kube-apiserver: registry.k8s.io/kube-apiserver:v1.33.7
kube-controller-manager-k8s-ctr
- kube-controller-manager: registry.k8s.io/kube-controller-manager:v1.33.7
kube-proxy-2c6d6
- kube-proxy: registry.k8s.io/kube-proxy:v1.33.7
kube-scheduler-k8s-ctr
- kube-scheduler: registry.k8s.io/kube-scheduler:v1.33.7
- # control component(apiserver, kcm 등)의 컨테이너 이미지는 1.33.7 업그레이드 되었고,
- kubelet/kubectl 업그레이드
- # 전체 설치 가능 버전 확인
dnf list --showduplicates kubelet --disableexcludes=kubernetes
dnf list --showduplicates kubectl --disableexcludes=kubernetes
# Upgrade 설치
dnf install -y --disableexcludes=kubernetes kubelet-1.33.7-150500.1.1 kubectl-1.33.7-150500.1.1
Upgrading:
kubectl aarch64 1.33.7-150500.1.1 kubernetes 9.7 M
kubelet aarch64 1.33.7-150500.1.1 kubernetes 13 M
# 설치 파일들 확인
which kubectl && kubectl version --client=true
which kubelet && kubelet --version
# 재시작
systemctl daemon-reload
systemctl restart kubelet # k8s api 10초 정도 단절
# 확인
kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 75m v1.33.7 192.168.10.100 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.1.5
k8s-w1 Ready <none> 70m v1.32.11 192.168.10.101 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready <none> 69m v1.32.11 192.168.10.102 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
kc describe node k8s-ctr
kubectl describe node k8s-ctr | grep 'Kubelet Version:'
Kubelet Version: v1.33.7
- # 전체 설치 가능 버전 확인
- 프로메테우스 알람 : 버전 관련 KubeVersionMismatch
- # 프로메테우스 룰 확인 : kubernetes-system
kc describe prometheusrules.monitoring.coreos.com -n monitoring kube-prometheus-stack-kubernetes-system
...
Spec:
Groups:
Name: kubernetes-system
Rules:
Alert: KubeVersionMismatch
Annotations:
Description: There are {{ $value }} different semantic versions of Kubernetes components running on cluster {{ $labels.cluster }}.
runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubeversionmismatch
Summary: Different semantic versions of Kubernetes components running.
Expr: count by (cluster) (count by (git_version, cluster) (label_replace(kubernetes_build_info{job!~"kube-dns|coredns"},"git_version","$1","git_version","(v[0-9]*.[0-9]*).*"))) > 1
For: 15m
Labels:
Severity: warning
Alert: KubeClientErrors
Annotations:
Description: Kubernetes API server client '{{ $labels.job }}/{{ $labels.instance }}' is experiencing {{ $value | humanizePercentage }} errors on cluster {{ $labels.cluster }}.
runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubeclienterrors
Summary: Kubernetes API server client is experiencing errors.
Expr: (sum(rate(rest_client_requests_total{job="apiserver",code=~"5.."}[5m])) by (cluster, instance, job, namespace)
/ sum(rate(rest_client_requests_total{job="apiserver"}[5m])) by (cluster, instance, job, namespace)) > 0.01
For: 15m
Labels:
Severity: warning
Events: <none>
# KubeVersionMismatch PromQL 쿼리문
count by (cluster) (count by (git_version, cluster) (label_replace(kubernetes_build_info{job!~"kube-dns|coredns"},"git_version","$1","git_version","(v[0-9]*.[0-9]*).*"))) > 1
# kubernetes_build_info : Kubernetes 컴포넌트의 빌드 정보 메트릭
## 포함정보 : git_version (예: v1.29.3) , git_commit , go_version - apiserver / controller / scheduler / kubelet에서 노출
# kubernetes_build_info{job!~"kube-dns|coredns"} : 필터링 조건
## CoreDNS / kube-dns 제외 : 클러스터 버전 판단에 불필요한 워크로드 제거 ->
# label_replace — 버전 정규화 : patch 버전 차이는 무시하고 minor 버전만 비교 -> v1.29.3, v1.29.4 → v1.29 로 통일
## (v[0-9]*.[0-9]*) v : major.minor
## .* : patch 및 그 이후 문자열 제거
label_replace(
...,
"git_version",
"$1",
"git_version",
"(v[0-9]*.[0-9]*).*"
)
# count by (git_version, cluster) (...) : 내부 count (버전 종류 세기)
## 같은 cluster 내에서 서로 다른 git_version(v1.28, v1.29 등) 이 몇 개 있는지 집계
# count by (cluster) (...) : 외부 count (클러스터별 버전 개수)
## 클러스터마다 서로 다른 minor 버전의 개수
# 최종 조건 > 1 : 하나의 클러스터 안에 서로 다른 Kubernetes minor 버전이 2개 이상 존재
## ❌ 클러스터 버전 불일치 상태 : (업그레이드 중이거나, 노드 드리프트 발생) - PromQL 확인

- 아래 처럼, controlplane node 1대에 kubeadm/kubelet 으로 upgrade 하여, 기존 워커노드(kublet)과 버전 차이가 발생한 상태
- # 프로메테우스 룰 확인 : kubernetes-system
- [k8s-ctr] Kubeadm , Kubelet, Kubectl Upgrade : v1.32.11 → v1.33.11
- admin 용 kubeconfig 업데이트
- [k8s-ctr] Kubeadm , Kubelet, Kubectl Upgrade : v1.33.11 → v1.34.3
- kubeadm 신규 버전 설치 & kubeadm upgrade plan : kubelet/kubectl은 아직 업그레이드하지 않음
- # repo 수정 : 기본 1.33 -> 1.34
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
# 전체 설치 가능 버전 확인 : --disableexcludes=... kubernetes repo에 설정된 exclude 규칙을 이번 설치에서만 무시(1회성 옵션 처럼 사용)
dnf list --showduplicates kubeadm --disableexcludes=kubernetes
# 설치
dnf install -y --disableexcludes=kubernetes kubeadm-1.34.3-150500.1.1
# 설치 파일들 확인
which kubeadm && kubeadm version -o yaml
# 업그레이드 계획 확인 : 현재 버전 , 대상 버전 , 컴포넌트 변경 사항
kubeadm upgrade plan
[upgrade/versions] Target version: v1.34.3
[upgrade/versions] Latest version in the v1.33 series: v1.33.7
Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply':
COMPONENT NODE CURRENT TARGET
kubelet k8s-w1 v1.32.11 v1.34.3
kubelet k8s-w2 v1.32.11 v1.34.3
kubelet k8s-ctr v1.33.7 v1.34.3
Upgrade to the latest stable version:
COMPONENT NODE CURRENT TARGET
kube-apiserver k8s-ctr v1.33.7 v1.34.3
kube-controller-manager k8s-ctr v1.33.7 v1.34.3
kube-scheduler k8s-ctr v1.33.7 v1.34.3
kube-proxy 1.33.7 v1.34.3
CoreDNS v1.12.0 v1.12.1
etcd k8s-ctr 3.5.24-0 3.6.5-0 # 1.32->1.33 과 다르게, 1.33->1.34시 etcd 업그레이드 필요
You can now apply the upgrade by executing the following command:
kubeadm upgrade apply v1.34.3
_____________________________________________________________________
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
_____________________________________________________________________
- # repo 수정 : 기본 1.33 -> 1.34
- kubeadm upgrade apply 실행 : etcd 신규 버전 기동으로 이전 1.32 → 1.33 때보다 약간 소요 시간 추가
- # 모니터링
## 터미널1 : 아래 호출 중단 발생 확인!
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
[k8s-w1] SVCIP=<IP 직접 입력>
[k8s-w1] while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
혹은
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
# 터미널2
watch -d kubectl get node
## 터미널3
watch -d kubectl get pod -n kube-system
## 터미널4
watch -d etcdctl member list -w table
# 컨테이너 이미지 미리 다운로드 : 특히 업그레이드 작업 시, 작업 시간 단축을 위해서 수행할 것
kubeadm config images pull
[config/images] Pulled registry.k8s.io/kube-apiserver:v1.34.3
[config/images] Pulled registry.k8s.io/kube-controller-manager:v1.34.3
[config/images] Pulled registry.k8s.io/kube-scheduler:v1.34.3
[config/images] Pulled registry.k8s.io/kube-proxy:v1.34.3
[config/images] Pulled registry.k8s.io/coredns/coredns:v1.12.1
[config/images] Pulled registry.k8s.io/pause:3.10.1
[config/images] Pulled registry.k8s.io/etcd:3.6.5-0
crictl images
...
# [k8s-w1/w2] kube-proxy 데몬셋과 coredns 신규 버전, pause 은 all node 에 미리 다운로드 해두자!
crictl pull registry.k8s.io/kube-proxy:v1.34.3
crictl pull registry.k8s.io/coredns/coredns:v1.12.1
crictl pull registry.k8s.io/pause:3.10.1
crictl images
# 실행 : etcd 신규 버전 기동으로 이전 1.32 → 1.33 때보다 약간 소요 시간 추가
kubeadm upgrade apply v1.34.3 --yes
[upgrade] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
[upgrade] Use 'kubeadm init phase upload-config kubeadm --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.34.3"
[upgrade/versions] Cluster version: v1.33.7
[upgrade/versions] kubeadm version: v1.34.3
[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.34.3" (timeout: 5m0s)...
[upgrade/staticpods] Writing new Static Pod manifests to "/etc/kubernetes/tmp/kubeadm-upgraded-manifests4082174448"
[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-18-17-06-48/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-18-17-06-48/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-18-17-06-48/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-18-17-06-48/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!
W0118 17:09:20.581163 36197 postupgrade.go:116] Using temporary directory /etc/kubernetes/tmp/kubeadm-kubelet-config2787812587 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-config2787812587/config.yaml
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/instance-config.yaml"
[patches] Applied patch of type "application/strategic-merge-patch+json" to target "kubeletconfiguration"
[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.34.3".
[upgrade] Now please proceed with upgrading the rest of the nodes by following the right order.
- # 모니터링
- K8S 상태 확인
- # control component(apiserver, kcm 등)의 컨테이너 이미지는 1.34.3 업그레이드 되었고,
# kubelet 은 아직 업그레이드 되지 않은 상태. node 출력에 버전은 kubelet 버전을 기준 출력으로 보임.
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 125m v1.33.7 192.168.10.100 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.1.5
k8s-w1 Ready <none> 120m v1.32.11 192.168.10.101 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready <none> 119m v1.32.11 192.168.10.102 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
kubectl describe node k8s-ctr | grep 'Kubelet Version:'
Kubelet Version: v1.33.7
kubectl get pod -A
...
# 이미지 업데이트 확인 : etcd, pause 신규 버전 pull 확인.
crictl images
IMAGE TAG IMAGE ID SIZE
registry.k8s.io/coredns/coredns v1.11.3 2f6c962e7b831 16.9MB
registry.k8s.io/coredns/coredns v1.12.0 f72407be9e08c 19.1MB
registry.k8s.io/coredns/coredns v1.12.1 138784d87c9c5 20.4MB
registry.k8s.io/etcd 3.5.24-0 1211402d28f58 21.9MB
registry.k8s.io/etcd 3.6.5-0 2c5f0dedd21c2 21.1MB
registry.k8s.io/kube-apiserver v1.32.11 58951ea1a0b5d 26.4MB
registry.k8s.io/kube-apiserver v1.33.7 6d7bc8e445519 27.4MB
registry.k8s.io/kube-apiserver v1.34.3 cf65ae6c8f700 24.6MB
registry.k8s.io/kube-controller-manager v1.32.11 82766e5f2d560 24.2MB
registry.k8s.io/kube-controller-manager v1.33.7 a94595d0240bc 25.1MB
registry.k8s.io/kube-controller-manager v1.34.3 7ada8ff13e54b 20.7MB
registry.k8s.io/kube-proxy v1.32.11 dcdb790dc2bfe 27.6MB
registry.k8s.io/kube-proxy v1.33.7 78ccb937011a5 28.3MB
registry.k8s.io/kube-proxy v1.34.3 4461daf6b6af8 22.8MB
registry.k8s.io/kube-scheduler v1.32.11 cfa17ff3d6634 19.2MB
registry.k8s.io/kube-scheduler v1.33.7 94005b6be50f0 19.9MB
registry.k8s.io/kube-scheduler v1.34.3 2f2aa21d34d2d 15.8MB
registry.k8s.io/pause 3.10 afb61768ce381 268kB
registry.k8s.io/pause 3.10.1 d7b100cd9a77b 268kB
# static pod yaml 파일 내용 업데이트 확인 : yaml 파일 내에 images 부분 업데이트
ls -l /etc/kubernetes/manifests/
cat /etc/kubernetes/manifests/*.yaml | grep -i image:
image: registry.k8s.io/etcd:3.6.5-0
image: registry.k8s.io/kube-apiserver:v1.34.3
image: registry.k8s.io/kube-controller-manager:v1.34.3
image: registry.k8s.io/kube-scheduler:v1.34.3
kubectl get pods -n kube-system -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{range .spec.containers[*]} - {.name}: {.image}{"\n"}{end}{"\n"}{end}'
coredns-66bc5c9577-v99kw
- coredns: registry.k8s.io/coredns/coredns:v1.12.1
etcd-k8s-ctr
- etcd: registry.k8s.io/etcd:3.6.5-0
kube-apiserver-k8s-ctr
- kube-apiserver: registry.k8s.io/kube-apiserver:v1.34.3
kube-controller-manager-k8s-ctr
- kube-controller-manager: registry.k8s.io/kube-controller-manager:v1.34.3
kube-proxy-9p996
- kube-proxy: registry.k8s.io/kube-proxy:v1.34.3
kube-scheduler-k8s-ctr
- kube-scheduler: registry.k8s.io/kube-scheduler:v1.34.3
- # control component(apiserver, kcm 등)의 컨테이너 이미지는 1.34.3 업그레이드 되었고,
- kubelet/kubectl 업그레이드
- # 전체 설치 가능 버전 확인
dnf list --showduplicates kubelet --disableexcludes=kubernetes
dnf list --showduplicates kubectl --disableexcludes=kubernetes
# Upgrade 설치
dnf install -y --disableexcludes=kubernetes kubelet-1.34.3-150500.1.1 kubectl-1.34.3-150500.1.1
Upgrading:
kubectl aarch64 1.33.7-150500.1.1 kubernetes 9.7 M
kubelet aarch64 1.33.7-150500.1.1 kubernetes 13 M
# 설치 파일들 확인
which kubectl && kubectl version --client=true
which kubelet && kubelet --version
# 재시작
systemctl daemon-reload # systemctl daemon-reexec 와 차이점은?
systemctl restart kubelet # k8s api 10초 정도 단절
# 확인
kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 128m v1.34.3 192.168.10.100 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.1.5
k8s-w1 Ready <none> 123m v1.32.11 192.168.10.101 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready <none> 122m v1.32.11 192.168.10.102 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
kc describe node k8s-ctr
kubectl describe node k8s-ctr | grep 'Kubelet Version:'
Kubelet Version: v1.34.3
- # 전체 설치 가능 버전 확인
- admin 용 kubeconfig 업데이트
- #
ls -l ~/.kube/config
-rw-------. 1 root root 5618 Jan 18 13:58 /root/.kube/config
#
ls -l /etc/kubernetes/admin.conf
-rw-------. 1 root root 5642 Jan 18 17:09 /etc/kubernetes/admin.conf
# admin 용 kubeconfig 업데이트
yes | cp /etc/kubernetes/admin.conf ~/.kube/config ; echo
chown $(id -u):$(id -g) ~/.kube/config
kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab"
kubens default
- #
- (TS) pause 컨테이너 신규 이미지 적용
- # kubelet이 사용하는 pause 이미지 확인
kubeadm config images list | grep pause
I0118 17:15:57.171957 46038 version.go:260] remote version is much newer: v1.35.0; falling back to: stable-1.34
registry.k8s.io/pause:3.10.1
# 로컬에 다운로드된 pause 컨테이너 이미지(버전) 확인
crictl images | grep pause
registry.k8s.io/pause 3.10 afb61768ce381 268kB
registry.k8s.io/pause 3.10.1 d7b100cd9a77b 268kB
# 현재 기동 프로세스에 pause 컨테이너 버전 정보 확인
ps -ef | grep -i pause | grep kubelet | grep pause
root 43246 1 3 17:13 ? 00:00:02 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --node-ip=192.168.10.100 --pod-infra-container-image=registry.k8s.io/pause:3.10
# containde 데몬 설정에 pause 컨테이너 버전 정보 확인
cat /etc/containerd/config.toml | grep -i pause:
sandbox = 'registry.k8s.io/pause:3.10'
# ctr 툴 사용하여 컨테이너 정보 확인
ctr -n k8s.io containers list
ctr -n k8s.io containers list | grep pause
0df5d6dd8682e41293cc58b402a85f4262c134957459b1b7f95c09ee7a9c8b44 registry.k8s.io/pause:3.10 io.containerd.runc.v2
0e7bf5a60b47f65bd760a074194e76794e0e63d7dcdd7e295555b7fa251fee9c registry.k8s.io/pause:3.10 io.containerd.runc.v2
# containde 데몬 설정에 pause 컨테이너 버전 정보 변경 설정
sed -i 's/pause:3.10/pause:3.10.1/g' /etc/containerd/config.toml
cat /etc/containerd/config.toml | grep -i pause:
sandbox = 'registry.k8s.io/pause:3.10.1'
# flags.env 파일 내용 확인
cat /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--node-ip=192.168.10.100 --pod-infra-container-image=registry.k8s.io/pause:3.10"
# flags.env 파일 수정 : 3.10.1 변경 : $ 이동
vi /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--node-ip=192.168.10.100 --pod-infra-container-image=registry.k8s.io/pause:3.10.1"
# containde 데몬 설정에 pause 컨테이너 버전 정보 변경 적용을 위한 containerd restart
systemctl restart kubelet.service
systemctl status kubelet.service --no-pager
# 파드 재생성 확인
kubectl delete pod curl-pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: curl-pod
labels:
app: curl
spec:
nodeName: k8s-ctr
containers:
- name: curl
image: nicolaka/netshoot
command: ["tail"]
args: ["-f", "/dev/null"]
terminationGracePeriodSeconds: 0
EOF
# 확인
ctr -n k8s.io containers list
ctr -n k8s.io containers list | grep pause
720875ad9758efaa9afa17dbf5a912c8d9033b216aafe405c0f52f5fcdc11efa registry.k8s.io/pause:3.10.1 io.containerd.runc.v2
- # kubelet이 사용하는 pause 이미지 확인
- worker node Upgrdade → 목표 버전까지 단계별 반복 작업
- worker node 작업 : v1.32.11 → v1.33.11 ⇒ v1.34.3
- 고려 사항
- 특정 워커 노드에 종속된 파드나 pv/pvc 있는지 확인 → 필요 시 조치
- 워커 노드에 쿼럼 기반 동작 app 파드 존재 시, 워커 노드 다수 작업 시 주의
- 고려 사항
- 사전 정보 확인 & pdb 설정 추가
- # 워커 1번에 '프로메테우스, 그라파나' 파드 기동 확인 -> 8번에서 워커 노드 작업 시 워커 2번 부터 하자.
kubectl get pod -A -owide | grep k8s-w1
default webpod-697b545f57-kh44p 1/1 Running 1 (30m ago) 143m 10.244.1.5 k8s-w1 <none> <none>
kube-flannel kube-flannel-ds-4f2s2 1/1 Running 1 (30m ago) 96m 192.168.10.101 k8s-w1 <none> <none>
kube-system kube-proxy-spq7f 1/1 Running 1 (30m ago) 32m 192.168.10.101 k8s-w1 <none> <none>
kube-system metrics-server-5dd7b49d79-fvx7p 1/1 Running 1 (30m ago) 3h43m 10.244.1.4 k8s-w1 <none> <none>
monitoring kube-prometheus-stack-grafana-5cb7c586f9-jfnhp 3/3 Running 3 (30m ago) 117m 10.244.1.2 k8s-w1 <none> <none>
monitoring kube-prometheus-stack-prometheus-node-exporter-hxmk7 1/1 Running 1 (30m ago) 117m 192.168.10.101 k8s-w1 <none> <none>
monitoring prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 2 (30m ago) 116m 10.244.1.3 k8s-w1 <none> <none>
kubectl get pod -A -owide | grep k8s-w2
default webpod-697b545f57-cmwsk 1/1 Running 1 (28m ago) 143m 10.244.2.6 k8s-w2 <none> <none>
kube-flannel kube-flannel-ds-88r76 1/1 Running 1 (28m ago) 96m 192.168.10.102 k8s-w2 <none> <none>
kube-system coredns-66bc5c9577-v99kw 1/1 Running 1 (28m ago) 32m 10.244.2.4 k8s-w2 <none> <none>
kube-system kube-proxy-9p996 1/1 Running 1 (28m ago) 32m 192.168.10.102 k8s-w2 <none> <none>
monitoring alertmanager-kube-prometheus-stack-alertmanager-0 2/2 Running 2 (28m ago) 117m 10.244.2.3 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-kube-state-metrics-7846957b5b-hz9vf 1/1 Running 18 (28m ago) 117m 10.244.2.5 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-operator-584f446c98-p5nms 1/1 Running 1 (28m ago) 117m 10.244.2.2 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-prometheus-node-exporter-gkcfw 1/1 Running 1 (28m ago) 117m 192.168.10.102 k8s-w2 <none> <none>
# sts 확인
kubectl get sts -A
NAMESPACE NAME READY AGE
monitoring alertmanager-kube-prometheus-stack-alertmanager 1/1 118m
monitoring prometheus-kube-prometheus-stack-prometheus 1/1 118m
# pv,pvc 확인
kubectl get pv,pvc -A
No resources found
# webpod deployment 에 pdb 설정 : 해당 정책은 항상 최소 2개의 Pod가 Ready 상태여야 함 , drain / eviction 시 단 하나의 Pod도 축출 불가
cat <<EOF | kubectl apply -f -
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: webpod
namespace: default
spec:
maxUnavailable: 0
selector:
matchLabels:
app: webpod
EOF
# 확인
kubectl get pdb
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
webpod N/A 0 0 6s
- # 워커 1번에 '프로메테우스, 그라파나' 파드 기동 확인 -> 8번에서 워커 노드 작업 시 워커 2번 부터 하자.
- 작업 대상 워커 노드 1대 drain 후 상태 확인 : 파드 Evicted, 서비스 중단 모니터링
- # 모니터링
## 터미널1 : 아래 drain 시 호출 상태 확인
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done
혹은
kubectl exec -it curl-pod -- sh -c 'while true; do curl -s --connect-timeout 1 webpod | grep Hostname; echo "---" ; sleep 1; done'
## 터미널2
watch -d kubectl get node
## 터미널3
watch -d kubectl get pod
# 워커 1번에 '프로메테우스, 그라파나' 파드 기동 확인 -> 워커 노드 작업 시 워커 2번 부터 진행
# 작업 대상 워커 노드 1대 drain : cordened 만 됨.
kubectl drain k8s-w2
node/k8s-w2 cordoned
error: unable to drain node "k8s-w2" due to error: [cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-flannel/kube-flannel-ds-88r76, kube-system/kube-proxy-9p996, monitoring/kube-prometheus-stack-prometheus-node-exporter-gkcfw, cannot delete Pods with local storage (use --delete-emptydir-data to override): monitoring/alertmanager-kube-prometheus-stack-alertmanager-0], continuing command...
There are pending nodes to be drained:
k8s-w2
cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-flannel/kube-flannel-ds-88r76, kube-system/kube-proxy-9p996, monitoring/kube-prometheus-stack-prometheus-node-exporter-gkcfw
cannot delete Pods with local storage (use --delete-emptydir-data to override): monitoring/alertmanager-kube-prometheus-stack-alertmanager-0
# 노드 및 파드 상태 확인
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 3h51m v1.34.3
k8s-w1 Ready <none> 3h50m v1.32.11
k8s-w2 Ready,SchedulingDisabled <none> 3h49m v1.32.11
kubectl get pod -A -owide |grep k8s-w2
default webpod-697b545f57-cmwsk 1/1 Running 1 (37m ago) 152m 10.244.2.6 k8s-w2 <none> <none>
kube-flannel kube-flannel-ds-88r76 1/1 Running 1 (37m ago) 105m 192.168.10.102 k8s-w2 <none> <none>
kube-system coredns-66bc5c9577-v99kw 1/1 Running 1 (37m ago) 41m 10.244.2.4 k8s-w2 <none> <none>
kube-system kube-proxy-9p996 1/1 Running 1 (37m ago) 41m 192.168.10.102 k8s-w2 <none> <none>
monitoring alertmanager-kube-prometheus-stack-alertmanager-0 2/2 Running 2 (37m ago) 126m 10.244.2.3 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-kube-state-metrics-7846957b5b-hz9vf 1/1 Running 18 (37m ago) 126m 10.244.2.5 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-operator-584f446c98-p5nms 1/1 Running 1 (37m ago) 126m 10.244.2.2 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-prometheus-node-exporter-gkcfw 1/1 Running 1 (37m ago) 126m 192.168.10.102 k8s-w2 <none> <none>
# 다시 시도 -> 취소
kubectl drain k8s-w2 --ignore-daemonsets --delete-emptydir-data
node/k8s-w2 already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-flannel/kube-flannel-ds-88r76, kube-system/kube-proxy-9p996, monitoring/kube-prometheus-stack-prometheus-node-exporter-gkcfw
evicting pod monitoring/kube-prometheus-stack-operator-584f446c98-p5nms
evicting pod kube-system/coredns-66bc5c9577-v99kw
evicting pod default/webpod-697b545f57-cmwsk
evicting pod monitoring/alertmanager-kube-prometheus-stack-alertmanager-0
evicting pod monitoring/kube-prometheus-stack-kube-state-metrics-7846957b5b-hz9vf
error when evicting pods/"webpod-697b545f57-cmwsk" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/alertmanager-kube-prometheus-stack-alertmanager-0 evicted
pod/kube-prometheus-stack-operator-584f446c98-p5nms evicted
pod/kube-prometheus-stack-kube-state-metrics-7846957b5b-hz9vf evicted
evicting pod default/webpod-697b545f57-cmwsk
CTRL+C로 취소
# 노드 및 파드 상태 확인
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 3h54m v1.34.3
k8s-w1 Ready <none> 3h53m v1.32.11
k8s-w2 Ready,SchedulingDisabled <none> 3h52m v1.32.11
kubectl get pod -A -owide |grep k8s-w2
default webpod-697b545f57-cmwsk 1/1 Running 1 (39m ago) 154m 10.244.2.6 k8s-w2 <none> <none>
kube-flannel kube-flannel-ds-88r76 1/1 Running 1 (39m ago) 107m 192.168.10.102 k8s-w2 <none> <none>
kube-system kube-proxy-9p996 1/1 Running 1 (39m ago) 43m 192.168.10.102 k8s-w2 <none> <none>
monitoring kube-prometheus-stack-prometheus-node-exporter-gkcfw 1/1 Running 1 (39m ago) 128m 192.168.10.102 k8s-w2 <none> <none>
# pdb 제거 후 다시 drain 시도
kubectl delete pdb webpod
kubectl drain k8s-w2 --ignore-daemonsets --delete-emptydir-data
node/k8s-w2 already cordoned
Warning: ignoring DaemonSet-managed Pods: kube-flannel/kube-flannel-ds-88r76, kube-system/kube-proxy-9p996, monitoring/kube-prometheus-stack-prometheus-node-exporter-gkcfw
evicting pod default/webpod-697b545f57-cmwsk
pod/webpod-697b545f57-cmwsk evicted
node/k8s-w2 drained
# 노드 상태 확인
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 3h55m v1.34.3
k8s-w1 Ready <none> 3h54m v1.32.11
k8s-w2 Ready,SchedulingDisabled <none> 3h53m v1.32.11
kc describe node k8s-w2
...
Taints: node.kubernetes.io/unschedulable:NoSchedule
- # 모니터링
- 작업 대상 워커 노드 1대 kubeadm v.1.33 업그레이드 :vagrant ssh k8s-w1/w2
-
- kubeadm 업그레이드 노드는 클러스터 업그레이드가 시작된 후 단일 제어 평면 또는 작업자 노드를 업그레이드합니다(kubeadm 업그레이드 적용 실행).
- 명령어는 /etc/kubernetes/manifests/kube-apiserver.yaml 파일이 존재하는지 확인하여 노드가 제어 평면 노드인지 여부를 감지합니다.
- 해당 파일을 찾은 kubeadm 도구는 이 노드에 실행 중인 kube-apiserver Pod가 있다고 추론합니다.
- Kubeadm 업그레이드가 적용되는 것과 유사하게 비행 전 점검을 실행합니다.
- 제어 평면 노드의 경우, /etc/kubernetes/manifests에서 디스크의 제어 평면 매니페스트 파일을 업그레이드하고 파일이 변경된 경우 큐브렛이 구성 요소를 재시작할 때까지 기다립니다.
- 이 노드에 대한 업데이트된 큐브렛 구성을 /var/lib/kubelet/config.yaml에 작성하고, 노드의 /var/lib/kubelet/instance-config.yaml 파일과 containerRuntimeEndpoint와 같은 패치 필드를 이 인스턴스 구성에서 /var/lib/kubelet/config.yaml로 읽습니다.
- (제어 평면 노드의 경우) 클러스터의 모든 기존 API 서버가 이미 대상 버전으로 업그레이드된 경우, kube-proxy 및 CoreDNS 애드온을 조건부로 업그레이드합니다.
- 사용되지 않는 기능을 정리하는 등 업그레이드 후 작업을 수행합니다.
- # repo 수정 : 기본 1.32 -> 1.33
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
# 설치
dnf install -y --disableexcludes=kubernetes kubeadm-1.33.7-150500.1.1
# 설치 파일들 확인
which kubeadm && kubeadm version -o yaml
# 노드 업그레이드 수행 : Upgrade commands for a node in the cluster
kubeadm upgrade node
[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 pre-flight checks
[upgrade/preflight] Skipping prepull. Not a control plane node.
[upgrade/control-plane] Skipping phase. Not a control plane node.
[upgrade/kubeconfig] Skipping phase. Not a control plane node.
W0118 18:03:11.809998 20192 postupgrade.go:117] Using temporary directory /etc/kubernetes/tmp/kubeadm-kubelet-config1774036046 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-config1774036046/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/addon] Skipping the addon/coredns phase. Not a control plane node.
[upgrade/addon] Skipping the addon/kube-proxy phase. Not a control plane node.
-
- 작업 대상 워커 노드 1대 kubelet v.1.33 업그레이드 설치 → kubelet 재시작
- # 전체 설치 가능 버전 확인
dnf list --showduplicates kubelet --disableexcludes=kubernetes
dnf list --showduplicates kubectl --disableexcludes=kubernetes
# Upgrade 설치
dnf install -y --disableexcludes=kubernetes kubelet-1.33.7-150500.1.1 kubectl-1.33.7-150500.1.1
# 설치 파일들 확인
which kubectl && kubectl version --client=true
which kubelet && kubelet --version
# 재시작
systemctl daemon-reload
systemctl restart kubelet
systemctl status kubelet --no-pager
# 데몬셋 파드 정상 기동 상태 확인
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
26dcd175dbdce d84558c0144bc 58 minutes ago Running kube-flannel 1 7f590b37d6d89 kube-flannel-ds-88r76 kube-flannel
381e37256545e 4461daf6b6af8 58 minutes ago Running kube-proxy 1 01f149693ad57 kube-proxy-9p996 kube-system
fffdbdfe2dc47 6b5bc413b280c 58 minutes ago Running node-exporter 1 7850bc5648f48 kube-prometheus-stack-prometheus-node-exporter-gkcfw monitoring
# 확인 : 아직 k8s-k2 는 SchedulingDisabled 상태
kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 57m v1.34.3
k8s-w1 Ready <none> 56m v1.32.11
k8s-w2 Ready,SchedulingDisabled <none> 55m v1.33.7
- # 전체 설치 가능 버전 확인
- 작업 대상 워커 노드 1대 v.1.34 업그레이드
- # repo 수정 : 기본 1.33 -> 1.34
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
# 설치
dnf install -y --disableexcludes=kubernetes kubeadm-1.34.3-150500.1.1
# 설치 파일들 확인
which kubeadm && kubeadm version -o yaml
# 노드 업그레이드 수행 : Upgrade commands for a node in the cluster
kubeadm upgrade node
# Upgrade 설치
dnf install -y --disableexcludes=kubernetes kubelet-1.34.3-150500.1.1 kubectl-1.34.3-150500.1.1
# 설치 파일들 확인
which kubectl && kubectl version --client=true
which kubelet && kubelet --version
# 재시작
systemctl daemon-reload
systemctl restart kubelet
systemctl status kubelet --no-pager
# 데몬셋 파드 정상 기동 상태 확인
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE
26dcd175dbdce d84558c0144bc 58 minutes ago Running kube-flannel 1 7f590b37d6d89 kube-flannel-ds-88r76 kube-flannel
381e37256545e 4461daf6b6af8 58 minutes ago Running kube-proxy 1 01f149693ad57 kube-proxy-9p996 kube-system
fffdbdfe2dc47 6b5bc413b280c 58 minutes ago Running node-exporter 1 7850bc5648f48 kube-prometheus-stack-prometheus-node-exporter-gkcfw monitoring
# 확인
kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 59m v1.34.3 192.168.10.100 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.2.1
k8s-w1 Ready <none> 58m v1.32.11 192.168.10.101 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready,SchedulingDisabled <none> 57m v1.34.3 192.168.10.102 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
- # repo 수정 : 기본 1.33 -> 1.34
- 작업 대상 워커 노드 1대 uncordon 으로 정상화 → 파드 배치 확인
- # 워커 노드 업그레이드 완료 후 정상화 상태면, uncordon 으로 정상화
kubectl uncordon k8s-w2
node/k8s-w2 uncordoned
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 175m v1.34.3 192.168.10.100 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.1.5
k8s-w1 Ready <none> 171m v1.32.11 192.168.10.101 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
k8s-w2 Ready <none> 170m v1.34.3 192.168.10.102 <none> Rocky Linux 10.1 (Red Quartz) 6.12.0-124.27.1.el10_1.aarch64 containerd://2.1.5
# 파드 배치 확인
kubectl scale deployment webpod --replicas 1
kubectl get pod -owide
kubectl scale deployment webpod --replicas 2
kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl-pod 1/1 Running 0 59m 10.244.0.10 k8s-ctr <none> <none>
webpod-697b545f57-9lfzd 1/1 Running 0 36s 10.244.2.7 k8s-w2 <none> <none>
webpod-697b545f57-kh44p 1/1 Running 1 (71m ago) 3h4m 10.244.1.5 k8s-w1 <none> <none>
- # 워커 노드 업그레이드 완료 후 정상화 상태면, uncordon 으로 정상화
- 나머지 워커 노드 1대도 동일하게 작업 진행
- # drain
kubectl drain k8s-w1 --ignore-daemonsets --delete-emptydir-data
# 프로메테우스, 그라파나 신규 재기동 확인
kubectl get pod -A -owide
# vagrant ssh k8s-w1
# repo 수정 : 기본 1.32 -> 1.33
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
# 설치
dnf install -y --disableexcludes=kubernetes kubeadm-1.33.7-150500.1.1
# 노드 업그레이드 수행 : Upgrade commands for a node in the cluster
kubeadm upgrade node
# Upgrade 설치
dnf install -y --disableexcludes=kubernetes kubelet-1.33.7-150500.1.1 kubectl-1.33.7-150500.1.1
# 재시작
systemctl daemon-reload
systemctl restart kubelet
systemctl status kubelet --no-pager
# 데몬셋 파드 정상 기동 상태 확인
crictl ps
# repo 수정 : 기본 1.33 -> 1.34
cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.34/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
dnf makecache
# 설치
dnf install -y --disableexcludes=kubernetes kubeadm-1.34.3-150500.1.1
# 노드 업그레이드 수행 : Upgrade commands for a node in the cluster
kubeadm upgrade node
# Upgrade 설치
dnf install -y --disableexcludes=kubernetes kubelet-1.34.3-150500.1.1 kubectl-1.34.3-150500.1.1
# 재시작
systemctl daemon-reload
systemctl restart kubelet
systemctl status kubelet --no-pager
# 데몬셋 파드 정상 기동 상태 확인
crictl ps
- # drain
- worker node 작업 : v1.32.11 → v1.33.11 ⇒ v1.34.3
- 워커 노드 1개 제거(reset) → 다시 join 해보기
- kubeadm reset workflow internal design - Docs
-
- kubeadm 명령어가 이전에 실행된 노드에서 kubeadm reest 하위 명령어를 사용할 수 있습니다.
- 이 하위 명령은 노드에 대한 최선의 정리를 수행합니다. 특정 작업에 실패하면 개입하여 수동 정리를 수행해야 합니다.
- 이 명령어는 phases 단계를 지원합니다. 자세한 내용은 kubeadm 리셋 단계를 참조하세요 - Docs
- 명령어는 구성 파일을 지원합니다.
- Additionally:
- IPVS, iptables and nftables rules are not cleaned up.
- CNI (network plugin) configuration is not cleaned up.
- .kube/ in the user's home directory is not cleaned up.
- containerd 이미지
- 명령에는 다음 단계가 있습니다:
- 노드가 정상인지 확인하기 위해 preflight checks을 실행합니다.
- 제어 평면 노드의 경우 로컬 etcd 멤버 데이터를 제거합니다. For control plane nodes, removes any local etcd member data.
- Stops the kubelet.
- Stops running containers.
- Unmounts any mounted directories in /var/lib/kubelet.
- /var/lib/kubelet 및 /etc/kubernetes에서 kubem이 관리하는 모든 파일과 디렉터리를 삭제합니다.
-
- [k8s-ctr] control 에서 노드 제거
- # node drain
kubectl drain k8s-w1 --ignore-daemonsets --delete-emptydir-data
# node delete : API 서버에서 노드 객체 제거
kubectl delete node k8s-w1
# 노드 상태 확인
kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 4h48m v1.34.3
k8s-w2 Ready <none> 4h46m v1.34.3
- # node drain
- 제거 대상 노드에서 kubeadm reset 실행 :vagrant ssh k8s-w1/w2
- # kubeadm reset 실행
kubeadm reset -f
[preflight] Running pre-flight checks
W0118 18:47:05.774616 33957 removeetcdmember.go:105] [reset] No kubeadm config, using etcd pod spec to get data directory
[reset] Deleted contents of the etcd data directory: /var/lib/etcd
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Deleting contents of directories: [/etc/kubernetes/manifests /var/lib/kubelet /etc/kubernetes/pki]
[reset] Deleting files: [/etc/kubernetes/admin.conf /etc/kubernetes/super-admin.conf /etc/kubernetes/kubelet.conf /etc/kubernetes/bootstrap-kubelet.conf /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf]
The reset process does not perform cleanup of CNI plugin configuration,
network filtering rules and kubeconfig files.
For information on how to perform this cleanup manually, please see:
https://k8s.io/docs/reference/setup-tools/kubeadm/kubeadm-reset/
# 관련 디렉터리 확인
tree /etc/kubernetes/
/etc/kubernetes/
├── manifests
├── pki
└── tmp
├── kubeadm-kubelet-config2965303515
│ └── config.yaml
└── kubeadm-kubelet-config350067924
└── config.yaml
tree /var/lib/kubelet/
/var/lib/kubelet/
tree /etc/cni
/etc/cni
└── net.d
└── 10-flannel.conflist
# 디렉터리/파일 삭제
rm -rf /etc/cni/net.d
rm -rf /etc/kubernetes/
rm -rf /var/lib/kubelet
rm -rf /var/lib/etcd # 컨트롤 플레인 노드일 경우만
# iptables 정리
iptables -t nat -S
iptables -t filter -S
iptables -F
iptables -t nat -F
iptables -t mangle -F
iptables -X
# containerd 는 삭제하지 않음 (재사용 가능)
systemctl status containerd --no-pager
systemctl status kubelet --no-pager
# kubelet 서비스 중지
systemctl stop kubelet && systemctl disable kubelet
# (옵션) contrainrd 서비스 중지
systemctl stop containerd && systemctl disable containerd
# reboot
reboot
- # kubeadm reset 실행
- kubeadm reset workflow internal design - Docs