티스토리 뷰

기시다님의 3주차 스터디 내용을 정리한 내용입니다.

kubeadm을 사용하여 쿠버네티스 클러스터를 구축하고 업그레이드하는 방법

kubeadm의 기본 개념부터 클러스터 구축, CNI 설치, 모니터링 툴 설치, 그리고 클러스터 업그레이드까지 유용한 스터디

EKS 버전 업그레이드 등 쿠버네티스 업데이트에 대한 절차를 배울수 있는 시간이었습니다.

금주도 건강 이슈로 실습보다는 정리에 초점을 둔 포스팅이라 향후에 실습이 필요할것 같다.

 

 

 

  • kubeadm 으로 k8s 구성 절차
    1. [공통] 사전 설정
    2. [공통] CRI 설치 : containerd
    3. [공통] kubeadm, kubelet 및 kubectl 설치
    4. [Controlplane node] kubeadm 으로 k8s 클러스터 구성 → Flannel CNI 설치 → 편의성 설치 및 확인
    5. [Worker nodes] kubeadm 으로 k8s 클러스터 join → 확인
    6. 모니터링 툴 설치 : 프로메테우스-스택 설치 → 인증서 익스포터 설치 → 그라파나 대시보드 확인
    7. 샘플 애플리케이션 배포
    8. kubeadm 인증서 갱신
  1. 주요 설치 버전 정보
    : 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
  2. [공통] 사전 설정 - 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에게 넘겨버리는 행위이므로 클러스터의 안정성을 위해 반드시 비활성화해야 합니다.
        1. 리소스/성능 예측 불가능
          • Pod가 메모리를 초과 사용해도 죽지 않고 디스크를 쓰면서 버팀
          • Kubernetes는 정상 Pod로 인식하지만 실제로는 클러스터 성능을 낮추는 중
        2. resources: requests: memory: 1Gi # 최소 필요 메모리 limits: memory: 2Gi # 최대 사용 가능 메모리
        3. Kubernetes의 리소스 관리 철학
          • 기본 관리 철학 : 메모리가 부족하면, Pod를 즉시 종료(OOMKilled) 시키고 다른 노드에서 재시작
          • Swap 켜져 있으면 죽지 않고 느린 상태로 계속 실행 → K8s 입장에서는 정상 Pod 인건지 판단 불가
          • 스케줄링 판단 오류 -> 노드 병목 지점 -> 전체 클러스터 성능 저하
    • # 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가 필요한 이유
        1. 컨테이너 이미지 생성
          • 컨테이너 이미지는 처음부터 레이어 단위로 만들어지고, 각 명령어가 하나의 레이어가 됨 (모든 레이어는 읽기 전용)
          • 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)
          
        2. 디스크 공간 절약
          • 같은 이미지를 쓰는 컨테이너가 많아질수록 디스크 효율 차이가 커집니다.
          • # 같은 Base Image 사용하는 컨테이너 100개 ubuntu:20.04 (100MB) ← 이건 한번만 저장 ↓ Container 1 (변경사항만 1MB) Container 2 (변경사항만 2MB) Container 3 (변경사항만 1MB) ...
          • OverlayFS 없는 경우,
            • 100MB × 컨테이너 수
          • OverlayFS 있는 경우,
            • 100MB + 각 컨테이너의 변경분만 추가
        3. 컨테이너 시작 속도
          • 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
      containerd의 Snapshotter를 확인 및 설정
      • 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는 언제 필요할까?
        1. 이미지 Pull 할 때
          • Layer = 스냅샷 체인
          • Layer가 3개인 nginx 이미지를 pull 하는 경우, Snopshotter은 다음과 같은 일을 수행합니다.
          # nginx 이미지 pull
          docker pull nginx
          
          # Snapshotter가 하는 일
          1. Layer 1 다운로드 → 스냅샷 생성
          2. Layer 2 다운로드 → 이전 스냅샷 위에 추가
          3. Layer 3 다운로드 → 이전 스냅샷 위에 추가
          
        2. 컨테이너 실행할 때
          • OverlayFS - Layer를 겹쳐서 보여주는 기술
          • Snapshotter - 어떤 Layer를 어떻게 쌓을지 결정하는 관리자
          docker run nginx
          
          # Snapshotter가 하는 일:
          1. nginx 이미지의 모든 읽기 전용 스냅샷 가져오기
          2. 스냅샷 순서대로 쌓기
          3. 맨 위에 쓰기 가능한 컨테이너 레이어(스냅샷) 추가
          4. 이 구조를 컨테이너 rootfs로 마운트
          
      • Containerd의 Snapshotter 종류
        • containerd는 여러 Snapshotter 구현체를 제공합니다.
        • 대부분 기본값으로 'overlayfs'가 선택되지만, 운영 환경에서는 어떤 기술이 사용되는지 직접 확인하고 명시하는 것이 좋습니다.
        • (주의) 커널에 overlayfs 모듈이 없거나 특정 제약에 걸려 'native'로 fallback 하는 경우, 컨테이너 생성이 느려지거나 디스크 사용량이 급증할 수 있습니다.
        # containerd 설정 확인
        cat /etc/containerd/config.toml | grep snapshotter
        
        Snapshotter 특징 사용 환경
        overlayfs 빠르고 효율적 (사실상 표준) 리눅스 대부분
        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
          
    • # 커널 모듈 확인 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
  3. [공통] CRI 설치 : containerd(runc) v2.1.5 - Docs , K8S_1.32_CRI_Docs
    1. containerd is a member of CNCF with 'graduated' status. : An open and reliable container runtim
    2. containerd(runc) 설치 v2.1.5 - Getting-started dssds
  4. [공통] kubeadm, kubelet 및 kubectl 설치 v1.32.11 - Docs
    1. kubeadm, kubelet 및 kubectl 설치 v1.32.11
      1. # 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
  5. [k8s-ctr] kubeadm 으로 k8s 클러스터 구성 & Flannel CNI 설치 v0.27.3 & 편의성 설정 등
    1. Customizing components with the kubeadm API - Docs
      1. kubeadm Configuration (v1beta4)
      2. 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
    2. kubeadm Configuration (v1beta4) - Docs
      1. 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
      2. # 기본 환경 정보 출력 저장
        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)를 얻기 위해 필요
    3. [k8s-ctr] kubeadm init 수행
        1. 사전 검사 (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) ← 버전 확인
        2. 인증서 및 키 생성 (Certificates) : Control Plane이 안전하게 통신할 수 있도록 인증서를 /etc/kubernetes/pki 생성
          • CA 인증서 (클러스터 전체 기본 신뢰체계)
          • API Server용 서버 인증서
          • API Server ↔ kubelet 통신용 인증서
          • Front-Proxy CA / Front-Proxy 클라이언트 인증서
          • etcd 관련 인증서 및 키 (local etcd 사용 시)
        3. 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
        4. 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 사용하는 경우)
        5. kubelet 시작 및 대기
          • kubeadm은 manifest 적용 후 kubelet을 재시작하거나 설정을 적용하고 , Control Plane이 성공적으로 시작될 때까지 기다립니다.
          • API Server가 정상적으로 건강 상태(healthz)를 반환할 때까지 대기합니다 : /healthz or /livez endpoints.
        6. Save the kubeadm ClusterConfiguration in a ConfigMap for later reference
          • kubectl get cm -n kube-system **kubeadm-config**
        7. 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
        8. 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 …(생략)…
          • 부트스트랩 토큰은 기본적으로 24시간 유효하며, kubeadm join 시 사용할 수 있습니다.
        9. 필수 애드온 설치 (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.
    4.  [k8s-ctr] k8s 관련 작업 편의성 설정 : 자동 완성, kubecolor, kubectx, kubens, kube-ps, helm, k9s 설치
      1. #
        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
    5. [k8s-ctr] Flannel CNI 설치 v0.27.3 
      1. cni0 브리지에 veth 와 flannel.1 연동
      2. # 현재 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
    6. [k8s-ctr] 노드 정보 확인, 기본 환경 정보 출력 비교, sysctl 변경 확인
      1. # # 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
    7. [k8s-ctr] 인증서 확인
      1. [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

        # 나머지 인증서도 확인해보자!
    8. [k8s-ctr] kubeconfig 확인
      1. # 관리자 용도
        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
    9. [k8s-ctr] static pod 확인 : etcd, kube-apiserver, kube-scheduler,kube-controller-manager
      1. # 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
        ...(생략)...
    10. 필수 애드온 설치 (coredns, kube-proxy) 확인
      1. # 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
  6. [k8s-w1/w2] 설정
    1. 사전 설정
      1. # 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
    2. CRI 설치 : containerd(runc) v2.1.5
      1. # 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
    3. kubeadm, kubelet 및 kubectl 설치 v1.32.11
      1. # 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
    4. kubeadm 으로 k8s join*
        • Kubernetes API 서버를 신뢰하도록 하는 디스커버리 단계 → Kubernetes API 서버가 노드를 신뢰하도록 하는 TLS 부트스트랩 단계
        1. Preflight checks : init 과 동일
        2. Discovery cluster-info 클러스터 정보 발견
          • 공개 정보 조회: 사용자가 입력한 토큰과 -discovery-token-ca-cert-hash를 사용하여 API 서버의 kube-public/cluster-info ConfigMap익명으로 요청합니다.
        3. Shared token discovery (--discovery-token) or File/https discovery (--discovery-file)
          • 신뢰 검증: 가져온 ConfigMap 내의 CA 인증서가 사용자가 입력한 해시값(--discovery-token-ca-cert-hash sha256:xxxx)과 일치하는지 확인하여, 접속하려는 마스터 노드가 진짜인지 검증합니다.
        4. TLS Bootstrap (인증서 발급 요청)
          • 부트스트랩 인증: 이제 마스터를 신뢰할 수 있으므로, 토큰을 사용해 API 서버에 정식으로 인증을 시도합니다.
          • CSR 생성: 워커 노드는 자신만의 개인키를 만들고, API 서버에 "나를 위한 인증서를 서명해달라"는 CSR(Certificate Signing Request)을 보냅니다.
          • 자동 승인: 마스터의 Certificate 발급자는 이 요청이 유효한 토큰을 통한 것임을 확인하고 자동으로 승인합니다.
        5. Kubelet 설정 및 기동
          • Kubeconfig 생성: 발급받은 정식 인증서를 사용하여 /etc/kubernetes/kubelet.conf 파일을 생성합니다.
          • 노드 등록: kubelet이 이 설정을 가지고 실행되면서 API 서버에 자신을 "Node" 리소스로 등록합니다.
          • 최종 합류: 마스터는 이 노드에 kube-proxy 등을 배포하며, 노드 상태가 Ready가 될 준비를 마칩니다.
        6. # 기본 환경 정보 출력 저장
          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
    5. [k8s-ctr] k8s-w1/w2 관련 정보 확인
      1. # 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>
    6. [k8s-w1/w2 노드 정보 확인, 기본 환경 정보 출력 비교, sysctl 변경 확인
      1. # # 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
  7. 모니터링 툴 설치 : 프로메테우스-스택 설치 → 인증서 익스포터 설치 → 그라파나 대시보드 확인
    1. metrics-server 설치
      1. # 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'
    2. kube-prometheus-stack 설치 - Link
      1. # 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
      2. [K8S Dashboard 추가] Dashboard → New → Import → 15661, 15757 입력 후 Load ⇒ 데이터소스(Prometheus 선택) 후 Import 클릭
    3. kube-controller-manager, etcd, kube-scheduler 메트릭 수집 설정 → kubeadm init 시 적용해보자!
      1. # 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
    4. k8s 인증서 위치 확인
      1. # 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
    5. 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
    6. x509 certificate exporter 설치 - Link
        • values 파일 주요 내용
          • 데몬셋 2종류 배포 : 컨트롤 플레인 노드들 수집(cp), 워커 노드들 수집(nodes)
          • 수집 종류 : 인증서(crt) 파일, kubeconfig 파일 → 직접 파일 위치 설정
          • 프로메테우스 알람 설정 활성화 : warning Days Left(28 일), critical Days Left(14일)
          • 그라파나 대시보드 추가 활성화
      1. # 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
        ...
    7. 프로메테우스 확인
    8. 그라파나 확인
  8. 샘플 애플리케이션 배포
    1. 샘플 애플리케이션 배포
      1. # 샘플 애플리케이션 배포
        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
    2. 애플리케이션 확인 & 반복 호출
      1. # 배포 확인
        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
  9. kubeadm 인증서 갱신
    1. 현재 인증서 정보 확인 : 인증서 파일 생성일 → 만료일 확인
      1. kubeadm 인증서 관리 : kubeadm 으로 생성된 클라이언트 인증서는 1년 후 만료됨.
      2. # 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
        ...
    2. 수동 인증서 갱신 소개
        • 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년 유효 기간)는 재생성하지 않음.
          • /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
    3. 수동 인증서 갱신 실행
      1. # 샘플 애플리케이션 반복 호출(신규 터미널)
        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
    4. control-plane static pod 재기동 & admin.conf kubeconfig 재적용
      1. # 사전 백업 : 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이 있으니 안됨)
  • 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)
    • 절차
      • 사전 준비 → 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 등) 가 업데이트 될때 서비스 트래픽 중단은 없는지?
  • 가상의 온프레미스 환경 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!
    1. 버전 호환성 검토
      1. K8S(kubelet, apiserver..) 1.32 요구 커널 버전 확인 : 예) user namespace 사용 시 커널 6.5 이상 필요 - Docs
      2. containerd 버전 조사 : 예) user namespace 에 대한 CRI 지원 시 containerd v2.0 및 runc v1.2 이상 필요 - Docs
        1. K8S 호환 버전 확인 - Docs
      3. CNI(Cilium) 요구 커널 버전 확인 : 예) BPF-based host routing 기능 필요 시 커널 5.10 이상 필요 - Docs
        • CNI 이 지원하는 K8S 버전 확인 - Docs
      4. CSI : Skip…
      5. 애플리케이션 요구사항 검토…
    2. 업그레이드 방법 결정 : in-place vs blue-green
      • Dev - Stg - Prd 별 각기 다른 방법 vs 모두 동일 방법
    3. 결정된 방법으로 업그레이드 계획 수립 : 예시) in-place 결정저희 팀이 작성한 그림으로 편하게 활용하시기 바랍니다.
    4. 사전 준비
      1. (옵션) 각 작업 별 상세 명령 실행 및 스크립트 작성, 작업 중단 실패 시 롤백 명령/스크립트 작성
      2. 모니터링 설정
      3. (1) ETCD 백업
      4. (2) CNI(cilium) 업그레이드
    5. 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 노드 상태 확인
    6. 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 설정 후 다시 상태 확인
    7. K8S 관련 전체적인 동작 1차 점검
      • 애플리케이션 파드 동작 확인 등
    8. CP 노드 순차 업그레이드 : 1.30 → 1.31 → 1.32
      • 노드 1대씩 작업 진행 x 버전별 반복
        • kubeadm 업그레이드 → kubelet/kubectl 업그레이드 설치 → kubelet 재시작
      • 작업 완료 후 CP 노드 상태 확인
    9. DP 노드 순차 업그레이드 : 1.30 → 1.31 → 1.32
      • 노드 1대씩 작업 진행 x 버전별 반복
        • 작업 노드 drain 설정 후 파드 Evicted 확인, 서비스 중단 여부 모니터링 확인
        • kubeadm 업그레이드 → kubelet 업그레이드 설치 → kubelet 재시작
      • 작업 완료 후 DP 노드 상태 확인 ⇒ 작업 노드 uncordon 설정 후 다시 상태 확인
    10. 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)을 활용할 수 있습니다.
        • 또한 클러스터 업그레이드를 계획할 때 작업 부하의 애플리케이션 가용성을 보장해야 하며, 데이터 평면이 업그레이드되는 동안 작업 부하의 가용성을 보장하기 위해서는 적절한 PodDisruptionBudgetstopologySpreadConstraint가 필수적입니다.
        • 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 Upgrades
    • 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를 사용하는 애플리케이션을 검색하여 수정하는 작업이 포함됩니다.
        EKS Upgrade Insights
        • Amazon EKS 클러스터 인사이트는 Amazon EKS 및 Kubernetes 모범 사례를 따르는 데 도움이 되는 권장 사항을 제공합니다.
        • 모든 Amazon EKS 클러스터는 Amazon EKS가 선별한 인사이트 목록에 대해 자동으로 반복적으로 검사를 받습니다.
        • 이러한 인사이트 검사는 Amazon EKS에서 완전히 관리하며 모든 결과를 해결하는 방법에 대한 권장 사항을 제공합니다.
        • Cluster 인사이트를 활용하면 최신 Kubernetes 버전으로 업그레이드하는 데 드는 노력을 최소화할 수 있습니다.
        • 매일 클러스터 감사 로그를 스캔하여 사용되지 않는 리소스를 찾고 EKS 콘솔에 결과를 표시하거나 API 또는 CLI를 통해 프로그래밍 방식으로 검색할 수 있습니다.
        • Amazon EKS는 Kubernetes 버전 업그레이드 준비와 관련된 insight만 출력.
        • 클러스터 인사이트는 주기적으로 업데이트됩니다. 클러스터 인사이트를 수동으로 새로 고칠 수 없습니다. 클러스터 문제를 해결하면 클러스터 인사이트가 업데이트되는 데 시간이 걸립니다.
        Each insight includes
        1. 권장 사항: 문제를 해결하기 위한 단계.
        2. 링크: 릴리스 노트, 블로그 게시물과 같은 추가 정보입니다.
        3. 리소스 목록: 심각도를 반영하는 상태(통과, 경고, 오류, 알 수 없음)가 있는 Kubernetes 리소스 유형(예: CronJobs)입니다.
          • 오류: 다음 마이너 버전에서 API에 대한 호출이 제거되었습니다. 업그레이드 후 업그레이드가 실패합니다.
          • 경고: 임박한 문제이지만 즉각적인 조치가 필요하지 않습니다(2개 이상 릴리스된 버전에서 지원 중단).
          • 알 수 없음: 백엔드 처리 오류.
        4. 전체 상태: 인사이트의 모든 리소스 중에서 가장 높은 심각도 상태입니다. 이를 통해 업그레이드하기 전에 클러스터에 수정이 필요한지 빠르게 확인할 수 있습니다.
        Amazon EKS 클러스터를 버전 1.25에서 1.26으로 업그레이드하는 방법을 알아보겠습니다.
        • 대상 버전인 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 : 매니페스트 마이그레이션 도구
        • 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
          1. 주요한 요소 미리 확인 : 종종 주요 요소는 *-system 네임스페이스에 설치
          2. **kubectl get ns | grep -e '-system'** kube-system Active 4h50m # kubectl get-all -n kube-system ...
          3. 호환성을 위해 설명서를 확인하세요
            • 식별된 각 구성 요소에 대해 해당 설명서를 참조하여 대상 Kubernetes 버전과의 호환성을 확인하세요.
            • AWS Load Balancer Controller 설명서와 같은 리소스는 버전 호환성 세부 정보를 제공합니다.
            • 일부 구성 요소는 진행하기 전에 업그레이드 또는 구성 변경이 필요할 수 있습니다.
            • CoreDNS, kube-proxy, Container Network Interface(CNI) 플러그인 및 스토리지 드라이버와 같은 중요한 구성 요소에 세심한 주의를 기울이세요.
          4. 애드온 및 타사 도구 업그레이드
            • 많은 클러스터는 Kubernetes API를 활용하여 유입 제어, 지속적인 전달 및 모니터링과 같은 기능을 제공하는 애드온 및 타사 도구에 의존합니다.
            • EKS 클러스터를 업그레이드하려면 호환성을 유지하기 위해 이러한 도구를 업그레이드해야 합니다.
          일반적인 추가 기능의 다음 예제와 관련 업그레이드 설명서를 참조하세요. 추가 팁 : Velero 와 같은 백업 툴로 클러스터의 주요 데이터를 미리 백업 해두기, 테스트 환경에서 충분히 사전 테스트 해보기
        • 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** |
          +---------------------------+--------------+-------+
          
          새로운 서브넷 요구 사항
          • 가용성 영역(AZ) : 새 서브넷은 클러스터 생성 중에 원래 선택한 동일한 AZ 집합 내에 있어야 합니다.
          • VPC 멤버십 : 새 서브넷은 클러스터와 연결된 동일한 VPC에 속해야 합니다.
          IP 풀 확장(선택 사항)
          • 서브넷 업데이트 후에도 충분한 IP가 없다면 추가 CIDR 블록을 VPC에 연결하는 것을 고려하세요. 이렇게 하면 기본적으로 사용 가능한 IP 주소 풀이 늘어납니다. 할 수 있는 일은 다음과 같습니다.
            • 개인 CIDR 블록 추가 : RFC 1918을 준수하여 새로운 개인 IP 범위를 도입하여 IP 풀을 확장할 수 있습니다.
            • 새로운 CIDR 기반 서브넷 업데이트 : VPC 내에서 새로 구성된 CIDR 블록을 반영하도록 클러스터 서브넷을 업데이트합니다.
          • 이러한 지침을 따르면 EKS 클러스터가 원활한 Kubernetes 버전 업그레이드를 수행하는 데 필요한 IP 리소스를 확보할 수 있습니다.
          EKS IAM Role 확인
          **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**
          ...
          
          EKS Security Groups: What You Need to Know
          • 클러스터를 만들면 Amazon EKS는 eks-cluster-sg-my-cluster-uniqueID라는 이름의 보안 그룹을 만듭니다.
          • 이 보안 그룹에는 다음과 같은 기본 규칙이 있습니다.
          Amazon EKS 보안 그룹: 연결 및 규칙
          1. 자동 태그
          2. : EKS는 클러스터에 대해 만든 보안 그룹에 특정 태그를 주입합니다. 이러한 태그는 작동에 필수적이며 제거하면 다시 추가됩니다.
          3. 리소스 연결
            • 네트워크 인터페이스(ENI): 클러스터를 프로비저닝하면 2-4개의 ENI가 생성됩니다. 이러한 ENI는 EKS에서 생성한 보안 그룹과도 연관됩니다.
            • 관리형 노드 그룹 ENI: 사용자가 생성하는 모든 관리형 노드 그룹의 ENI도 이 보안 그룹에 연결됩니다.
          4. : 이 보안 그룹은 여러 리소스에 자동으로 연결됩니다.
          5. 기본 보안 규칙
            • 인바운드 트래픽: 모든 트래픽은 클러스터 제어 평면과 노드 간에 자유롭게 흐를 수 있습니다.
            • 아웃바운드 트래픽: 노드는 모든 목적지로 트래픽을 보낼 수 있습니다.
          6. : 처음에 보안 그룹은 제한 없는 통신을 허용합니다.
          7. 사용자 지정 보안 그룹(선택 사항)
            • ENI 연결: EKS는 이러한 사용자 정의 그룹을 클러스터의 ENI와 연결합니다.
            • 노드 그룹 제한: 그러나 이러한 사용자 지정 그룹은 귀하가 만든 노드 그룹에는 적용되지 않습니다. 이러한 연결을 별도로 관리해야 합니다.
          8. : 클러스터 생성 중에 선택적으로 자체 보안 그룹을 지정할 수 있습니다. 그렇게 하는 경우:
          기본적으로 EKS는 광범위한 권한을 가진 기본 보안 그룹을 제공하지만, 사용자가 사용자 정의 그룹을 사용하여 더 엄격한 보안 정책을 구현할 수 있도록 합니다.EKS 클러스터의 보안 강화
          1. 기본 규칙은 개방적입니다. 기본적으로 EKS는 클러스터 구성 요소 간의 무제한 통신을 허용합니다.
          2. 아웃바운드 트래픽 사용자 지정: 열려 있는 포트를 제한하려면 기본 아웃바운드 규칙을 제거하고 다음 최소 요구 사항을 구현합니다.
            • 노드 간 통신: 노드 간 통신에 사용하는 모든 프로토콜과 포트에 대한 규칙을 정의합니다.
          3. 인바운드 규칙 지속성: EKS는 업데이트 중에 기본 인바운드 규칙을 제거한 후에 해당 규칙을 자동으로 다시 생성합니다.
          추가 고려 사항
          1. 아웃바운드 인터넷 액세스(선택 사항)
          2. : 노드에 인터넷 액세스가 필요한 경우(예: EKS API 호출 또는 초기 등록) 특정 포트에 대한 아웃바운드 규칙을 구성합니다. 개인 클러스터의 경우 인터넷 액세스가 필요하지 않을 수 있습니다.
          3. 컨테이너 이미지 액세스
          4. : 노드는 이미지를 가져오기 위해 컨테이너 레지스트리(예: Amazon ECR 또는 DockerHub)에 액세스해야 합니다. 적절한 레지스트리 엔드포인트에 대한 규칙을 만듭니다.
          5. IPv4/IPv6에 대한 별도 규칙
          6. : VPC가 두 주소 패밀리를 모두 사용하는 경우 각각에 대해 별도의 규칙이 필요합니다.
          테스트는 매우 중요합니다. 프로덕션 클러스터에 변경 사항을 배포하기 전에 모든 Pod를 철저히 테스트하여 새로운 보안 규칙에 따라 올바르게 작동하는지 확인하세요.
        • EKS 클러스터 내에서 트래픽 흐름을 제한하고 싶으신가요? 알아야 할 사항은 다음과 같습니다.
        • Amazon EKS가 보안 그룹을 활용하는 방식에 대한 세부 내용은 다음과 같습니다.
        • IAM 역할을 사용할 수 있는지, 그리고 계정에 올바른 assume role policy이 있는지 확인하려면 다음 명령을 실행할 수 있습니다.
        • AWS는 업그레이드 프로세스를 완료하기 위해 계정의 특정 리소스를 요구합니다. 이러한 리소스가 없으면 클러스터를 업그레이드할 수 없습니다. 제어 평면 업그레이드에는 다음 리소스가 필요합니다.
      • 들어가며
      • 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"**
            
            
    • Choosing an Upgrade Strategy : 업그레이드 전략 선택 (In-Place vs Blue-Green) ⇒ 소개
      • 적절한 업그레이드 전략이 없는 경우의 일반적인 문제
        • 명확하게 정의된 EKS 업그레이드 전략이 없는 고객은 다음을 포함한 여러 가지 어려움에 직면할 수 있습니다.
        • 계획 되지 않은 가동 중지
        • : 적절한 계획 없이 업그레이드하면 예상치 못한 가동 중지가 발생하여 애플리케이션 가용성과 사용자 경험에 영향을 미칠 수 있습니다.
        • 호환성 문제
        • : 새로운 Kubernetes 버전과 애플리케이션, 애드온 및 도구의 호환성을 철저히 테스트하고 검증하지 못하면 손상 및 중단이 발생할 수 있습니다.
        • 롤백의 어려움
        • : 업그레이드가 실패하거나 문제가 발생할 경우, 잘 정의된 롤백 계획 없이 이전 버전으로 롤백하는 것은 복잡하고 시간이 많이 걸릴 수 있습니다.
        • 보안 취약점
        • : 업그레이드를 지연하면 클러스터가 알려진 보안 취약점에 노출되어 애플리케이션과 데이터가 위험에 노출될 수 있습니다.
        • 놓친 기회
        • : 정기적으로 업그레이드하지 않으면 새로운 기능, 성능 개선 및 최신 Kubernetes 버전에 도입된 최적화를 놓칠 수 있습니다.
        • 이러한 과제를 해결하려면 잘 정의된 업그레이드 전략을 수립하는 것이 중요합니다. EKS 업그레이드를 계획할 때 고려해야 할 두 가지 주요 전략이 있습니다. 인플레이스 업그레이드와 블루그린 업그레이드입니다. 각 전략에는 고유한 장점, 단점 및 고려 사항이 있으며, 특정 요구 사항과 제약 조건에 따라 신중하게 평가해야 합니다.
        • 이 모듈에서는 두 업그레이드 전략을 자세히 살펴보고 장단점을 논의합니다. 또한 특정 요구 사항과 제약 조건에 따라 가장 적합한 전략을 선택하는 방법에 대한 지침도 제공합니다. 이 모듈을 마치면 In-Place와 Blue-Green 업그레이드의 차이점을 명확하게 이해하고 EKS 클러스터 업그레이드를 계획할 때 정보에 입각한 결정을 내릴 수 있게 됩니다.
      • In-place 업그레이드 전략 : 기존 EKS 클러스터를 최신 Kubernetes 버전으로 업그레이드
        1. EKS 제어 평면을 대상 Kubernetes 버전으로 업그레이드합니다.
        2. 새로운 Kubernetes 버전과 일치하도록 작업자 노드 AMI를 업데이트합니다.
        3. 애플리케이션 가동 중지 시간을 최소화하기 위해 작업자 노드를 한 번에 하나씩 또는 소규모로 비우고 교체합니다.
        4. 새 버전과의 호환성을 보장하기 위해 모든 Kubernetes 매니페스트, 애드온 및 구성을 업데이트합니다.
        5. 철저한 테스트와 검증을 수행하여 애플리케이션과 서비스가 예상대로 작동하는지 확인합니다.
        인플레이스 업그레이드의 장점
        1. VPC, 서브넷, 보안 그룹 등 기존 클러스터 리소스와 구성을 유지합니다.
        2. 동일한 클러스터 API 엔드포인트를 유지하므로 외부 통합 및 도구를 업데이트할 필요성이 최소화됩니다.
        3. 업그레이드 프로세스 중에 여러 클러스터를 관리하는 것에 비해 인프라 오버헤드가 덜 필요합니다.
        4. 클러스터 간에 상태 저장 애플리케이션과 지속형 데이터를 마이그레이션할 필요성을 최소화합니다.
        인플레이스 업그레이드의 단점
        1. 업그레이드 과정에서 가동 중지 시간을 최소화하려면 신중한 계획과 조정이 필요합니다.
        2. 여러 Kubernetes 버전을 건너뛸 경우 연속적인 업그레이드가 여러 번 필요할 수 있으며, 업그레이드 프로세스가 길어질 수 있습니다.
        3. 업그레이드 중에 문제가 발생하면 롤백이 더 어렵고 시간이 많이 걸릴 수 있습니다. Control Plane이 업그레이드되면 롤백할 수 없습니다.
        4. 호환성을 보장하기 위해 모든 구성 요소와 종속성에 대한 철저한 테스트와 검증이 필요합니다.
      • 내부 업그레이드 프로세스에는 일반적으로 다음 단계가 포함됩니다.
      • Blue-Green 업그레이드 전략 : 새 EKS 클러스터(addon, apps)를 만들고 트래픽을 이전 클러스터에서 새 클러스터로 점진적으로 전환
        1. 원하는 Kubernetes 버전 및 구성으로 새 EKS 클러스터(녹색)를 만듭니다.
        2. 새 클러스터에 애플리케이션, 애드온 및 구성을 배포합니다.
        3. 새 클러스터가 예상대로 작동하는지 확인하기 위해 철저한 테스트와 검증을 수행합니다.
        4. DNS 업데이트, 로드 밸런서 구성 또는 서비스 메시와 같은 기술을 사용하여 점진적으로 이전 클러스터(파란색)에서 새 클러스터(녹색)로 트래픽을 전환합니다.
        5. 새 클러스터를 면밀히 모니터링하여 트래픽을 처리하고 예상대로 성능이 발휘되는지 확인하세요.
        6. 모든 트래픽이 새 클러스터로 전환되면 이전 클러스터를 해제합니다.
        블루-그린 업그레이드의 장점
        1. 새로운 클러스터를 프로덕션 트래픽으로 전환하기 전에 철저히 테스트할 수 있으므로, 보다 통제되고 안전한 업그레이드 프로세스가 가능합니다.
        2. 단일 업그레이드에서 여러 Kubernetes 버전을 건너뛸 수 있으므로 전체 업그레이드 시간과 노력이 줄어듭니다.
        3. 문제가 발생할 경우 트래픽을 이전 클러스터로 다시 전환하여 빠르고 쉬운 롤백 메커니즘을 제공합니다.
        4. 새 클러스터가 완전히 검증될 때까지 이전 클러스터가 계속 트래픽을 제공하므로 업그레이드 프로세스 동안 가동 중지 시간이 최소화됩니다.
        블루-그린 업그레이드의 단점
        1. 두 개의 클러스터를 동시에 유지 관리해야 하므로 업그레이드 프로세스에 추가적인 인프라 리소스와 비용이 필요합니다.
        2. 클러스터 간 트래픽 이동에 대한 보다 복잡한 조정 및 관리가 필요합니다.
        3. CI/CD 파이프라인, 모니터링 시스템, 액세스 제어 등의 외부 통합을 업데이트하여 새 클러스터를 가리키도록 해야 합니다.
        4. 클러스터 간 데이터 마이그레이션이나 동기화가 필요한 상태 저장 애플리케이션의 경우 어려울 수 있습니다.
        상태 저장 워크로드에 대한 고려 사항
        • 상태 저장 워크로드에 대한 블루-그린 업그레이드를 수행할 때는 데이터 마이그레이션 및 동기화 프로세스를 신중하게 계획하고 실행하세요.
        • Velero와 같은 도구를 사용하여 영구 데이터를 마이그레이션하고 클러스터 간에 데이터를 동기화 상태로 유지하며 빠른 롤백을 활성화하세요.
        • 새 클러스터에서 영구 볼륨 프로비저닝을 구성하여 이전 클러스터의 스토리지 클래스 또는 프로비저너와 일치시키세요.
        • 애플리케이션 설명서를 참조하고 애플리케이션 소유자와 협력하여 데이터 마이그레이션 및 동기화에 대한 특정 요구 사항을 이해하세요.
        • Velero와 같은 도구를 적절히 계획하고 활용하는 것은 위험을 최소화하고 상태 저장 애플리케이션에 대한 원활한 업그레이드 프로세스를 보장하는 데 중요합니다.
      • 블루-그린 업그레이드 프로세스에는 일반적으로 다음 단계가 포함됩니다.
      • In-place 와 Blue-Green 업그레이드 전략 중 선택
        1. 가동 중지 허용 범위 : 업그레이드 프로세스 중에 애플리케이션과 서비스에 대한 허용 가능한 가동 중지 시간 수준을 고려하세요.
        2. 복잡성 업그레이드 : 애플리케이션 아키텍처, 종속성 및 상태 구성 요소의 복잡성을 평가합니다.
        3. Kubernetes 버전 차이 : 현재 Kubernetes 버전과 대상 버전 간의 차이와 애플리케이션 및 애드온의 호환성을 평가합니다.
        4. 리소스 제약 : 업그레이드 프로세스 중에 여러 클러스터를 유지하기 위한 사용 가능한 인프라 리소스와 예산을 고려하세요. 블루/그린과 유사한 카나리아 전략은 워크로드를 늘리는 동안 이전 클러스터를 확장하는 동안 새 클러스터를 확장하는 것을 제외하고는 이를 최소화합니다.
        5. 팀 전문성 : 여러 클러스터를 관리하고 트래픽 전환 전략을 구현하는 데 대한 팀의 전문성과 익숙함을 평가합니다.
        의사 결정 차트 (예시)
      • 전략을 선택할 때 고려해야 할 요소
      • K8S Version Skew 를 이용한 Incremental In-place 업그레이드 전략업그레이드 프로세스:
        1. EKS 제어 평면을 다음 마이너 버전으로 업그레이드합니다. Kubernetes는 제어 평면이 작업자 노드보다 최대 2개의 마이너 버전(또는 Kubernetes 1.28부터 3개의 마이너 버전) 앞설 수 있는 버전 왜곡을 지원합니다.
        2. 제어 평면이 최대 허용 스큐를 초과하는 버전으로 업그레이드될 때까지 기존 버전에서 작업자 노드를 유지합니다. 예를 들어, 제어 평면을 1.21에서 시작하고 작업자 노드를 1.21에서 시작하는 경우 작업자 노드를 업그레이드하지 않고도 제어 평면을 1.22, 1.23 및 1.24로 업그레이드할 수 있습니다.
        3. 제어 평면이 허용되는 최대 스큐를 초과하는 버전(예: 작업자가 여전히 1.21을 사용하고 있는 경우 1.24)에 도달하면 제어 평면을 추가로 업그레이드하기 전에 지원되는 스큐 범위 내의 버전(예: 1.22 또는 1.23)으로 작업자 노드를 업그레이드합니다.
        4. 제어 평면과 작업자 노드 모두에서 원하는 Kubernetes 버전에 도달할 때까지 1~3단계를 반복합니다.
        이상적인 사용 사례:
        • 최신 Kubernetes 릴리스보다 여러 버전 뒤쳐져 있으며 점진적으로 따라잡고 싶어합니다.
        • 작업자 노드에는 광범위한 테스트가 필요하고 빈번한 업그레이드를 어렵게 만드는 복잡하고 상태 저장형 워크로드가 있습니다.
        • 작업자 노드의 중단을 최소화하면서 새로운 기능과 버그 수정에 액세스하기 위해 정기적으로 제어 평면 업그레이드를 수행하려고 합니다.
        고려 사항:
        • 워커 노드가 호환 버전으로 업그레이드될 때까지 일부 새로운 Kubernetes 기능, 성능 개선 및 버그 수정을 완전히 사용할 수 없을 수 있습니다.
        • 여러 버전을 건너뛴 후 워커 노드를 업그레이드하는 경우 호환성을 보장하고 문제를 식별하기 위해 철저한 테스트가 중요합니다.
        • 이 전략은 유연성을 제공하지만, 비호환성 위험을 최소화하고 클러스터 전체에서 일관된 기능 세트를 보장하기 위해 제어 평면과 작업자 노드 버전을 최대한 가깝게 유지하는 것이 좋습니다.
        Kubernetes의 버전 스큐 지원을 활용하면 이전 버전에서 시작하더라도 EKS 클러스터의 증분적 인플레이스 업그레이드를 수행할 수 있습니다. 이 접근 방식을 사용하면 워크로드 중단을 최소화하면서 Kubernetes 버전을 점진적으로 따라잡을 수 있습니다.마지막으로, 업그레이드 프로세스 중 및 이후에 발생할 수 있는 문제를 감지하고 문제를 해결하기 위한 포괄적인 모니터링 및 로깅 메커니즘이 있는지 확인하십시오. 습득한 교훈과 진화하는 모범 사례를 기반으로 업그레이드 전략을 정기적으로 검토하고 업데이트하여 EKS 클러스터 관리 관행을 지속적으로 개선하십시오.
      • 이러한 권장 사항은 일반적인 지침으로 사용되며, 선택하는 구체적인 접근 방식은 고유한 요구 사항, 제약 조건 및 위험 허용 범위에 따라 달라집니다. 업그레이드 전략을 결정할 때 클러스터의 특성, 작업 부하 요구 사항 및 조직의 우선순위를 신중하게 평가하는 것이 필수적입니다. 또한 선택한 업그레이드 접근 방식에 관계없이 잘 정의된 테스트 및 검증 계획을 수립하는 것이 중요합니다. 프로덕션 업그레이드를 진행하기 전에 비프로덕션 환경에서 애플리케이션, 애드온 및 통합을 철저히 테스트하여 잠재적인 문제를 식별하고 해결하세요.
      • 허용되는 최대 버전 스큐에 도달할 때까지 작업자 노드 업그레이드를 연기하면서 EKS 제어 평면을 점진적으로 업그레이드
      • 결론이 모듈에서는 두 가지 주요 업그레이드 전략인 In-Place 업그레이드와 Blue-Green 업그레이드를 살펴보았습니다. 각 접근 방식의 장단점을 논의하고 다운타임 허용 범위, 업그레이드 복잡성, 리소스 제약, 규제 요구 사항 등 다양한 시나리오와 요인에 따라 권장 사항을 제공했습니다.
        1. In-Place 업그레이드는 다운타임 허용 범위가 낮은 부 버전 업그레이드에 적합한 반면, Blue-Green 업그레이드는 주요 버전 업그레이드나 상태 복잡도가 높은 애플리케이션에 권장됩니다.
        2. 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.
        3. 성공적인 업그레이드를 위해서는 선택한 전략에 관계없이 신중한 계획, 테스트 및 검증이 필수적입니다.
        4. Careful planning, testing, and validation are crucial for a successful upgrade, regardless of the chosen strategy.
        5. 업그레이드 프로세스 중에 문제를 신속하게 식별하고 해결하려면 모니터링, 로깅 및 명확하게 정의된 롤백 계획이 필수적입니다.
        6. Monitoring, logging, and having a well-defined rollback plan are essential to quickly identify and resolve issues during the upgrade process.
        7. 습득한 교훈과 진화하는 모범 사례를 기반으로 업그레이드 전략을 지속적으로 평가하고 최적화하는 것은 EKS 클러스터 관리 관행을 개선하는 데 중요합니다.
        8. Continuously evaluating and optimizing your upgrade strategy based on lessons learned and evolving best practices is key to improving your EKS cluster management practices.
        EKS 클러스터 업그레이드를 계획할 때 다음 모범 사례를 고려하세요.
        1. 클러스터의 특성, 작업 부하 요구 사항, 조직의 우선순위를 철저히 평가하여 가장 적합한 업그레이드 전략을 선택하세요.
        2. 호환성을 보장하고 문제 위험을 최소화하기 위해 포괄적인 테스트 및 검증 계획을 개발합니다.
        3. 애플리케이션 소유자, 규정 준수 관리자, 사용자 등 관련 이해 관계자와 협력하여 업그레이드 계획을 해당 요구 사항과 기대치에 맞게 조정합니다.
        4. 자동화 및 IaC(Infrastructure as Code) 도구를 활용하여 업그레이드 프로세스를 간소화하고 다양한 환경의 일관성을 보장합니다.
        5. 이전 업그레이드에서 얻은 새로운 모범 사례와 교훈을 통합하여 업그레이드 전략을 정기적으로 검토하고 업데이트하세요.
        추가 자료
      • 이 모듈의 주요 내용은 다음과 같습니다.
      • 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

 

  1. 사전 준비 & etcd 백업
    1. (TS) Rocky Linux 10 vagrant image 에 SWAP off 해결 : init_cfg.sh 에 반영되어 있음
      1. 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
      2. 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 / 파티션에서 확장 사용 설정 해보기
      3. k8s-ctr
        ,
        k8s-w1
        도 swap 파티션 완전 삭제 해두기
      4. # swap 파티션 완전 삭제 : fdisk 사용
        fdisk /dev/sda
        --------------------
        p        # 파티션 확인
        d        # delete
        2        # sda2 선택
        p        # 다시 확인 (sda2 없어야 함)
        w        # 저장 후 종료
        --------------------

        # 커널에 파티션 테이블 반영
        partprobe /dev/sda
      5. 잔여 작업 : 삭제한 파티션을 활용 - 현재 sda3은 합치는게 불가능(넘버링 순서)하니, 캐시 등 작은 용량 사용 파티션 생성하여 활용.
    2. (TS) 가상머신에서 외부 통신(ping 8.8.8.8) 실패 시 해결 완료 : init_cfg.sh 에 반영되어 있음
      1. 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 # 추가
    3. kube-prometheus-stack 설치 - Link
      1. # 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
    4. 샘플 애플리케이션 배포
      1. # 샘플 애플리케이션 배포
        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
      2. # 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
    5. 샘플 애플리케이션 확인 & 반복 호출
      1. # 배포 확인
        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
    6. 동작 확인을 위한 모니터링 걸어두기
      1. # 터미널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
    7. ETCD 백업 (snapshot 저장) - Home , Docs
      1. etcdctl 설치
      2. # 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
      3. etcdctl 기본 사용
      4. # 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 |
        +------------------+---------+---------+-----------------------------+-----------------------------+------------+
      5. etcd 백업 (snapshot 저장)

      6. # 백업 수행
        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
  2. CNI Upgrade
    1. Flannel CNI 업그레이드 : v0.27.3v0.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
  3. OS Upgrade
    1. [k8s-ctr] Rocky Linux OS 마이너 버전 Upgrade : v10.0v10.1 ⇒ 노드 재기동 - Link
      • 고려 사항
        • ControlPlane 노드의 경우 HA(3대 이상) 되어 있을 경우, 1대 노드가 장애 시에도 문제 없는 환경인지 한번 더 확인
        • 단일 파드가 배포된 노드 reboot 시 영향도?
        • 현재 pv/pvc 를 사용하고 있지 않은, 예시(프로메테우스/그라파나)가 배포된 노드 reboot 시 영향도?
    2. [k8s-ctr] 작업 전 정보 확인 : reboot 시 영향도 파악
      1. # 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
    3. [k8s-ctr] Rocky Linux 마이너 버전 Upgrade : v10.0v10.1 ⇒ 노드 재기동 - Link
      1. # 모니터링
        ## 터미널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>
  4. Containerd Upgrade
    1. containerd.io-2.1.5-y ⇒ containerd.io-2.2.1-y 업그레이드 방법과 통신 모니터링 가이드 추가
  5. Kubeadm , Kubelet, Kubectl Upgrade → 목표 버전까지 단계별 반복 작업
    1. [k8s-ctr] Kubeadm , Kubelet, Kubectl Upgrade : v1.32.11 → v1.33.11
      • 고려 사항
        • kubeadm 업그레이드kubelet/kubectl 업그레이드 설치 → kubelet 재시작
          • containerd를 재시작 X : kubeadm 업그레이드 후에도 ‘CRI API 버전 동일, 소켓 동일, 런타임 상태 유지’
            • 다만, pause 컨테이너 이미지 변경 할 경우에는 containerd 재시작 필요 → 이후 부터 기동되는 파드들에 적용됨.
        • 미리 노드에 신규 버전 이미지 다운로드 해둘것!
    2. kubeadm 신규 버전 설치 & kubeadm upgrade plan : kubelet/kubectl은 아직 업그레이드하지 않음
      1. 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
          _____________________________________________________________________
        •  
    3. 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. # 모니터링
        ## 터미널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.
    4. k8s 상태 확인
      1. # 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
    5. kubelet/kubectl 업그레이드
      1. # 전체 설치 가능 버전 확인 
        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
    6. 프로메테우스 알람 : 버전 관련 KubeVersionMismatch
      1. # 프로메테우스 룰 확인 : 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개 이상 존재
        ## ❌ 클러스터 버전 불일치 상태 : (업그레이드 중이거나, 노드 드리프트 발생)
      2. PromQL 확인
      3. 아래 처럼, controlplane node 1대에 kubeadm/kubelet 으로 upgrade 하여, 기존 워커노드(kublet)과 버전 차이가 발생한 상태
  6. admin 용 kubeconfig 업데이트
    1. [k8s-ctr] Kubeadm , Kubelet, Kubectl Upgrade : v1.33.11v1.34.3
    2. kubeadm 신규 버전 설치 & kubeadm upgrade plan : kubelet/kubectl은 아직 업그레이드하지 않음
      1. # 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
        _____________________________________________________________________
    3. kubeadm upgrade apply 실행 : etcd 신규 버전 기동으로 이전 1.32 → 1.33 때보다 약간 소요 시간 추가
      1. # 모니터링
        ## 터미널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.
    4. K8S 상태 확인
      1. # 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
    5. kubelet/kubectl 업그레이드
      1. # 전체 설치 가능 버전 확인 
        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
    6. admin 용 kubeconfig 업데이트
      1. #
        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
    7. (TS) pause 컨테이너 신규 이미지 적용
      1. # 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  
  7. worker node Upgrdade → 목표 버전까지 단계별 반복 작업
    1. worker node 작업 : v1.32.11v1.33.11 ⇒ v1.34.3
      • 고려 사항
        • 특정 워커 노드에 종속된 파드나 pv/pvc 있는지 확인 → 필요 시 조치
        • 워커 노드에 쿼럼 기반 동작 app 파드 존재 시, 워커 노드 다수 작업 시 주의
    2. 사전 정보 확인 & pdb 설정 추가
      1. # 워커 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
    3. 작업 대상 워커 노드 1대 drain 후 상태 확인 : 파드 Evicted, 서비스 중단 모니터링
      1. # 모니터링
        ## 터미널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
    4. 작업 대상 워커 노드 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 애드온을 조건부로 업그레이드합니다.
        • 사용되지 않는 기능을 정리하는 등 업그레이드 후 작업을 수행합니다.
      1. # 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.
    5. 작업 대상 워커 노드 1대 kubelet v.1.33 업그레이드 설치 → kubelet 재시작
      1. # 전체 설치 가능 버전 확인 
        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
    6. 작업 대상 워커 노드 1대 v.1.34 업그레이드
      1. # 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
    7. 작업 대상 워커 노드 1대 uncordon 으로 정상화 → 파드 배치 확인
      1. # 워커 노드 업그레이드 완료 후 정상화 상태면, 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>
    8. 나머지 워커 노드 1대도 동일하게 작업 진행
      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
  8. 워커 노드 1개 제거(reset) → 다시 join 해보기
    1. 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이 관리하는 모든 파일과 디렉터리를 삭제합니다.
    2. [k8s-ctr] control 에서 노드 제거
      1. # 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
    3. 제거 대상 노드에서 kubeadm reset 실행 :vagrant ssh k8s-w1/w2
      1. # 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

 

 

 

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함