devlos
Devlos Archive
devlos
전체 방문자
오늘
어제
01-25 19:12

최근 글

  • 분류 전체보기 (111) N
    • 프로젝트 (1)
    • MSA 설계 & 도메인주도 설계 (9)
    • 클라우드 컴퓨팅 & NoSQL (91) N
      • [K8S Deploy] K8S 디플로이 스터디 (4) N
      • [Cilium Study] 실리움 스터디 (8)
      • [KANS] 쿠버네티스 네트워크 심화 스터디 (12)
      • [T101] 테라폼 4기 스터디 (8)
      • [CICD] CICD 맛보기 스터디 (3)
      • [T101] 테라폼 기초 입문 스터디 (6)
      • [AEWS] Amazon EKS 워크숍 스터디 (7)
      • [PKOS] 쿠버네티스 실무 실습 스터디 (7)
      • Kubernetes (13)
      • Docker (7)
      • Redis (1)
      • Jenkins (3)
      • Terraform (1)
      • Ansible (4)
      • Kafka (1)
    • 프로그래밍 (7)
      • Spring Boot (5)
      • Broker (1)
    • 성능과 튜닝 (1)
    • ALM (0)
    • 기타 (2)

인기 글

태그

  • docker
  • CloudNet@
  • DevOps
  • t101 4기
  • 도커
  • 테라폼
  • MSA
  • PKOS
  • kOps
  • 쿠버네티스 스터디
  • terraform
  • 쿠버네티스
  • 데브옵스
  • cilium
  • Kubernetes

티스토리

최근 댓글

hELLO · Designed By 정상우.
devlos

Devlos Archive

[3주차 - K8S Deploy] Kubeadm & K8S Upgrade 1/2 (26.01.23)
클라우드 컴퓨팅 & NoSQL/[K8S Deploy] K8S 디플로이 스터디

[3주차 - K8S Deploy] Kubeadm & K8S Upgrade 1/2 (26.01.23)

2026. 1. 23. 01:14
반응형

 

들어가며

안녕하세요! Devlos입니다.

이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 K8S Deploy 3주 차 주제인 "Kubeadm & K8S Upgrade"중 kubeadm 기반 kubernetes cluster 설치에 대해서 정리한 내용입니다.

1주차에서는 Kubernetes The Hard Way를 통해 쿠버네티스 클러스터를 수동으로 구축하는 과정을 배웠고,
2주차에서는 Ansible을 사용하여 인프라 자동화의 기초를 배웠다면,
이번 3주차에서는 kubeadm을 사용하여 쿠버네티스 클러스터를 구축하고 업그레이드하는 방법을 배워봅니다.

kubeadm의 기본 개념부터 클러스터 구축, CNI 설치, 모니터링 툴 설치, 그리고 클러스터 업그레이드까지 전반적인 내용을 다뤄봅니다.


Kubeadm이란?

 

kubeadm은 쿠버네티스 클러스터를 빠르고 쉽게 구축할 수 있도록 도와주는 공식 도구입니다.

 

최소한의 안전한 클러스터를 신속하게 구축할 수 있게 해주며, 사용자 친화적인 방식으로 필요한 작업(kubeadm init, kubeadm join 등)을 수행합니다.
kubeadm의 주요 역할은 로컬 노드의 파일 시스템 및 Kubernetes API에 한정되어 있으며, 다른 고수준 도구의 빌딩 블록으로 사용하기 적합합니다.

주요 명령어는 다음과 같습니다.

  • kubeadm init: 초기 컨트롤 플레인 노드를 부트스트랩할 때 사용합니다.
  • kubeadm join: 워커 노드 또는 추가 컨트롤 플레인 노드를 클러스터에 조인할 때 사용합니다.
  • kubeadm upgrade: 클러스터를 더 최신 버전으로 업그레이드할 때 사용합니다.
  • kubeadm reset: kubeadm init/join을 통해 변경된 모든 설정을 원상 복구할 때 사용합니다.

설치 워크플로우는 다음과 같습니다.

 

 

업그레이드 워크플로우는 다음과 같습니다.

 

실습환경 구성

# 가시다님께서 실습을 위해 작성한 vagrant file
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-kubeadm/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
    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
    end
  end

end

# Vagrant up
vagrant up

설치 절차 및 정보

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 인증서 갱신

주요 설치 버전 정보는 다음과 같습니다.
k8s 1.32.11 버전을 설치할 것이고, 해당 버전에 호환되는 구성 요소들을 사용합니다.

 

항목 버전 역할 k8s 버전 호환성
Rocky Linux 10.0-1.6 OS(모든 노드) 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 에이전트(모든 노드) k8s 버전 정책 문서 참고 - Docs
kubeadm v1.32.11 클러스터 초기화/관리(모든 노드) 상동
kubectl v1.32.11 클러스터 관리 CLI(컨트롤 플레인, 운영PC) 상동
helm v3.18.6 패키지 매니저(컨트롤 플레인, 운영PC) k8s 1.30.x ~ 1.33.x 지원 - Docs
flannel cni v0.27.3 네트워크 CNI(모든 노드) k8s 1.28~ 이후 - Release

 

1. [공통] 사전 설정

기본 정보 확인 : vagrant ssh k8s-ctr

# User 정보
whoami
# vagrant

id
# uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant) ...

pwd
# /home/vagrant

# cpu, mem
lscpu
# Architecture:             aarch64
# CPU(s):                   4
# Vendor ID:                Apple
# ...

free -h
#               total        used        free      shared  buff/cache   available
# Mem:           2.8Gi       289Mi       2.4Gi        18Mi       147Mi       2.5Gi
# Swap:          3.8Gi          0B       3.8Gi
# ...

# Disk
lsblk
# NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
# sda      8:0    0   64G  0 disk 
# ├─sda1   8:1    0  600M  0 part /boot/efi
# ├─sda2   8:2    0  3.8G  0 part [SWAP]
# └─sda3   8:3    0 59.6G  0 part /
# ...

df -hT
# Filesystem     Type      Size  Used Avail Use% Mounted on
# /dev/sda3      xfs        60G  2.5G   58G   5% /
# /dev/sda1      vfat      599M   13M  587M   3% /boot/efi
# ...

# Network
ip -br -c -4 addr
# lo               UNKNOWN        127.0.0.1/8 
# enp0s8           UP             10.0.2.15/24 
# enp0s9           UP             192.168.10.100/24 
# ...

ip -c route
# default via 10.0.2.2 dev enp0s8 proto dhcp src 10.0.2.15 metric 100 
# default via 192.168.10.1 dev enp0s9 proto static metric 101 
# 10.0.2.0/24 dev enp0s8 proto kernel scope link src 10.0.2.15 metric 100 
# 192.168.10.0/24 dev enp0s9 proto kernel scope link src 192.168.10.100 metric 101 
# ...

ip addr
# enp0s8 / enp0s9 상세 IPv4/IPv6, MAC 주소 등 상세 정보 출력 ...

# Host Info, Kernel
hostnamectl
#     Static hostname: k8s-ctr
#     Operating System: Rocky Linux 10.0 (Red Quartz)
#     OS Support End: Thu 2035-05-31
#     Kernel: Linux 6.12.0-55.39.1.el10_0.aarch64
#     Architecture: arm64
# ...
uname -r

# 6.12.0-55.39.1.el10_0.aarch64
rpm -aq | grep release
# rocky-release-10.0-1.6.el10.noarch ...

# cgroup 버전 확인
stat -fc %T /sys/fs/cgroup
# cgroup2fs (cgroup v2 사용)

findmnt
# TARGET            SOURCE   FSTYPE   OPTIONS
# /                 /dev/sda3
# ├─/boot/efi       /dev/sda1        vfat ...
# ...
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
# CGroup /:
# -.slice
# ├─user.slice
# ├─init.scope
# └─system.slice
#   ├─NetworkManager.service
#   ├─sshd.service
#   └─...

# Process
pstree
# systemd─┬─NetworkManager───3*[{NetworkManager}]
#         ├─VBoxService───8*[{VBoxService}]
#         ├─sshd───sshd-session───sshd-session───bash───pstree
#         └─...

lsns # 현재 세션의 time/cgroup/pid/user/net 등 namespace ID 확인 
#         NS TYPE   NPROCS   PID USER    COMMAND
# 4026531834 time        2  4519 vagrant -bash
# 4026531835 cgroup      2  4519 vagrant -bash
# 4026531836 pid         2  4519 vagrant -bash
# 4026531837 user        2  4519 vagrant -bash
# 4026531838 uts         2  4519 vagrant -bash
# 4026531839 ipc         2  4519 vagrant -bash
# 4026531840 net         2  4519 vagrant -bash
# 4026531841 mnt         2  4519 vagrant -bash

다음으로 root 권한으로 전환합니다.

sudo su -

Time, NTP 설정

인증서 만료 시간, 로그 타임스탬프 등 모든 노드에 동기화된 시간 필요

# timedatectl 정보 확인
timedatectl status
# Local time: Tue 2026-01-20 15:21:15 UTC
# Universal time: Tue 2026-01-20 15:21:15 UTC
# Time zone: UTC (UTC, +0000)
# System clock synchronized: yes, NTP service: active, RTC in local TZ: yes
# Warning: RTC 를 local time 으로 사용 중이라 문제를 유발할 수 있다는 경고 출력
timedatectl set-local-rtc 0
# RTC in local TZ: no 로 변경, 이후 경고 메시지 사라짐
timedatectl status
# Local time / Universal time / RTC time 값 유지, RTC in local TZ: no 로 표시됨

# 시스템 타임존(Timezone)을 한국(KST, UTC+9) 으로 설정 : 시스템 시간은 UTC 기준 유지, 표시만 KST로 변환
date
# Tue Jan 20 03:21:27 PM UTC 2026
timedatectl set-timezone Asia/Seoul
date
# Wed Jan 21 12:21:34 AM KST 2026 (UTC+9 로 변경된 것 확인)

# systemd가 시간 동기화 서비스(chronyd) 를 관리하도록 설정되어 있음 : ntpd 대신 chrony 사용 (Rocky 9/10 기본)
timedatectl status
# Local time: Wed 2026-01-21 00:21:38 KST
# Universal time: Tue 2026-01-20 15:21:38 UTC
# Time zone: Asia/Seoul (KST, +0900)
# System clock synchronized: yes, NTP service: active, RTC in local TZ: no
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
# ^- ec2-3-39-176-65.ap-north>     2   9   377    25   -784us[ -784us] +/- 9261us
# ^- 175.210.18.47                 2   8   377   343   -558us[ -558us] +/-   21ms
# ^- 203.32.26.46                  2   9   377   353    +15ms[  +15ms] +/-  226ms
# ^* 211.108.117.211               2   9   377   479   -382us[ -146us] +/- 6780us

# 현재 시스템 시간이 얼마나 정확한지 종합 성적표
chronyc tracking
# Reference ID    : D36C75D3 (211.108.117.211)
# Stratum         : 3
# System time     : 0.000211809 seconds slow of NTP time
# Last offset     : +0.000236014 seconds
# RMS offset      : 0.009700730 seconds
# Frequency       : 6.022 ppm slow
# Root delay      : 0.013224309 seconds, Root dispersion : 0.004029782 seconds
# ...

SELinux 설정, firewalld(방화벽) 끄기

SELinux(Security Enhanced Linux)는 리눅스 커널에 적용되는 보안 기능으로, 시스템의 파일, 프로세스, 네트워크 등에서 발생할 수 있는 악의적이거나 비정상적인 접근을 세밀하게 제어하는 역할을 합니다. 기본적인 리눅스 권한(파일 소유자, 그룹, 퍼미션)만으로는 부족할 수 있는 보안을, 정책 기반의 다양한 규칙들로 한층 더 강화해줍니다.

즉, SELinux는 시스템에 접근하는 모든 행위에 대해 "정책에 의해 허용된 것인지"를 검사하고, 허용되지 않은 동작은 차단하거나 경고합니다.

Enforcing: SELinux가 활성화된 상태로, 보안 정책에 어긋나는 동작을 탐지하면 실제로 실행을 차단합니다. 즉, 허용되지 않은 접근 시도가 발생하면 해당 작업이 막히고, 로그에도 기록됩니다.

Permissive: SELinux는 적용되어 있지만, 정책을 어긴 동작이 발생해도 차단하지 않고 허용하며, 단지 로그로만 남깁니다. 디버깅이나 정책 검증에 주로 사용됩니다.

Kubernetes 공식 가이드에서는 예측하지 못한 차단으로 인한 문제를 줄이기 위해 "Permissive" 모드를 권장합니다. 실제 서비스 운영 환경에서는 보안 정책에 따라 "Enforcing"을 쓸 수도 있지만, 실습 및 문제 원인 파악에는 Permissive가 더 편리합니다.

# SELinux 설정 : Kubernetes는 Permissive 권장
getenforce
# Enforcing
sestatus
# SELinux status:                 enabled
# Current mode:                   enforcing
# Loaded policy name:             targeted
# ...
setenforce 0
getenforce
# Permissive
sestatus
# SELinux status:                 enabled
# Current mode:                   permissive
# Loaded policy name:             targeted
# ...

# 재부팅 시에도 Permissive 적용
cat /etc/selinux/config | grep ^SELINUX
# SELINUX=enforcing
# SELINUXTYPE=targeted
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
cat /etc/selinux/config | grep ^SELINUX
# SELINUX=permissive
# SELINUXTYPE=targeted

# firewalld(방화벽) 끄기
systemctl status firewalld
# ● firewalld.service - firewalld - dynamic firewall daemon
#      Loaded: loaded (...; enabled; ...)
#      Active: active (running) since Tue 2026-01-20 23:18:36 KST; 1h ...
#      Main PID: 644 (firewalld)
#      Tasks: 2 (limit: 18742)
#      Memory: 49.9M (peak: 70.9M)
#      ...
systemctl disable --now firewalld
# Removed '/etc/systemd/system/multi-user.target.wants/firewalld.service'.
# Removed '/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service'.
systemctl status firewalld
# ○ firewalld.service - firewalld - dynamic firewall daemon
#      Loaded: loaded (...; disabled; ...)
#      Active: inactive (dead)
#      ...

Swap 비활성화

Kubernetes는 안정적인 노드 자원 관리를 위해 swap이 꺼진 상태(비활성화)를 요구합니다. swap이 활성화되어 있으면 kubelet이 정상적으로 동작하지 않거나 클러스터 조인이 실패할 수 있으므로 반드시 swap을 비활성화해야 합니다.

Swap은 시스템의 물리적 메모리(RAM)가 부족할 때, 하드디스크의 일부 공간을 마치 메모리처럼 사용하는 기능입니다. 즉, 사용하지 않는 메모리 페이지를 디스크로 옮겨 일시적으로 여유 메모리를 확보하지만, 디스크 I/O 성능 저하로 인해 시스템 반응 속도가 느려질 수 있습니다.

# Swap 비활성화
lsblk
# NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
# sda      8:0    0   64G  0 disk 
# ├─sda1   8:1    0  600M  0 part /boot/efi
# ├─sda2   8:2    0  3.8G  0 part [SWAP]
# └─sda3   8:3    0 59.6G  0 part /
free -h | grep Swap
# Swap:          3.8Gi          0B       3.8Gi
swapoff -a
lsblk
# NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
# sda      8:0    0   64G  0 disk 
# ├─sda1   8:1    0  600M  0 part /boot/efi
# ├─sda2   8:2    0  3.8G  0 part 
# └─sda3   8:3    0 59.6G  0 part /
free -h | grep Swap
# Swap:             0B          0B          0B

# 재부팅 시에도 'Swap 비활성화' 적용되도록 /etc/fstab에서 swap 라인 삭제
cat /etc/fstab | grep swap
# UUID=2270fed4-fef4-43c1-909f-b08a96bb14e9 none                    swap    defaults        0 0
sed -i '/swap/d' /etc/fstab
cat /etc/fstab | grep swap
# (출력 없음: swap 항목 제거됨)

커널 모듈 및 커널 파라미터 설정 (네트워크 설정)

브릿지 트래픽이란, 리눅스 커널에서 네트워크 브릿지(bridge) 기능을 사용할 때, 두 네트워크 인터페이스를 연결하여 패킷이 서로를 통과하도록 하는 네트워크 트래픽을 의미합니다. 예를 들어, 가상머신(VM)이나 컨테이너 환경에서 여러 네트워크 인터페이스를 하나의 브릿지에 연결하면, 한 인터페이스로 들어온 데이터가 브릿지를 통해 다른 인터페이스로 전달될 수 있습니다.

 

기본적으로 네트워크 브릿지를 통과하는 트래픽은 브릿지 레이어에서만 처리되고, 리눅스의 iptables(방화벽) 규칙에는 적용되지 않습니다. 하지만 Kubernetes 같은 컨테이너 오케스트레이션 환경에서는 pod 간 통신, 서비스 접근 제어 등 다양한 네트워크 보안 정책을 구현할 때, 이 브릿지 트래픽도 iptables 규칙의 검사를 받게 해야 합니다.

 

즉, net.bridge.bridge-nf-call-iptables=1 과 같이 sysctl 파라미터를 설정하면 브릿지를 넘나드는 패킷도 iptables의 filter/NAT 룰을 적용받아서 보안 정책, 트래픽 제어, 네트워크 격리 등이 가능합니다.

# 커널 모듈 확인
lsmod
# Module                  Size  Used by
# rfkill                 36864  1
# nft_fib_inet           12288  0
# ...
lsmod | grep -iE 'overlay|br_netfilter'
# (처음에는 overlay / br_netfilter 모듈이 로드되어 있지 않음)

# 커널 모듈 로드
modprobe overlay
modprobe br_netfilter
lsmod | grep -iE 'overlay|br_netfilter'
# br_netfilter           32768  0
# bridge                327680  1 br_netfilter
# overlay               200704  0

# 부팅 시 자동 로드를 위해 설정 파일 생성
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
overlay
br_netfilter
tree /etc/modules-load.d/
# /etc/modules-load.d/
# └── k8s.conf
# 
# 1 directory, 1 file

# 커널 파라미터 설정 : 네트워크 설정 - 브릿지 트래픽이 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
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
tree /etc/sysctl.d/
# /etc/sysctl.d/
# ├── 99-sysctl.conf -> ../sysctl.conf
# └── k8s.conf
# 
# 1 directory, 2 files

# 설정 적용
sysctl --system
# * Applying /usr/lib/sysctl.d/10-default-yama-scope.conf ...
# * Applying /usr/lib/sysctl.d/10-map-count.conf ...
# ...
# * Applying /etc/sysctl.d/k8s.conf ...
# net.bridge.bridge-nf-call-iptables = 1
# net.bridge.bridge-nf-call-ip6tables = 1
# net.ipv4.ip_forward = 1
# ...

# 적용 확인
sysctl net.bridge.bridge-nf-call-iptables
# net.bridge.bridge-nf-call-iptables = 1
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1

hosts 설정

# hosts 설정
cat /etc/hosts
# # Loopback entries; do not change.
# # For historical reasons, localhost precedes localhost.localdomain:
# 127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
# ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
# # See hosts(5) for proper format and other examples:
# # 192.168.1.10 foo.example.org foo
# # 192.168.1.13 bar.example.org bar
# 127.0.1.1 k8s-ctr k8s-ctr
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
# # Loopback entries; do not change.
# # For historical reasons, localhost precedes localhost.localdomain:
# 127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
# ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
# # See hosts(5) for proper format and other examples:
# # 192.168.1.10 foo.example.org foo
# # 192.168.1.13 bar.example.org bar
# 192.168.10.100 k8s-ctr
# 192.168.10.101 k8s-w1
# 192.168.10.102 k8s-w2

# 확인
ping -c 1 k8s-ctr
 # PING k8s-ctr (192.168.10.100) 56(84) bytes of data.
 # 64 bytes from k8s-ctr (192.168.10.100): icmp_seq=1 ttl=64 time=0.024 ms
 # --- k8s-ctr ping statistics ---
 # 1 packets transmitted, 1 received, 0% packet loss, time 0ms
ping -c 1 k8s-w1
 # PING k8s-w1 (192.168.10.101) 56(84) bytes of data.
 # 64 bytes from k8s-w1 (192.168.10.101): icmp_seq=1 ttl=64 time=0.789 ms
 # --- k8s-w1 ping statistics ---
 # 1 packets transmitted, 1 received, 0% packet loss, time 0ms
ping -c 1 k8s-w2
 # PING k8s-w2 (192.168.10.102) 56(84) bytes of data.
 # 64 bytes from k8s-w2 (192.168.10.102): icmp_seq=1 ttl=64 time=1.05 ms
 # --- k8s-w2 ping statistics ---
 # 1 packets transmitted, 1 received, 0% packet loss, time 0ms

2. containerd(runc) v2.1.5 설치

containerd는 CNCF 'Graduated' 단계의 공식 컨테이너 런타임으로, 신뢰성과 범용성을 갖춘 오픈소스입니다.

주요 특징:

  • 단순성, 견고함, 이식성에 중점을 둔 표준 컨테이너 런타임
  • Linux/Windows에서 데몬으로 동작하며 컨테이너의 생애주기(이미지 관리, 실행, 네트워크/저장소 연결 등) 전체를 담당
  • 개발자 또는 최종 사용자가 직접 사용하기보다는, Kubernetes 등 상위 시스템에 내장되어 사용되는 용도에 최적화됨

Containerd 와 K8S(1.32~1.34) 호환 버전 확인(2.1.0+, 2.0.1+, 1.7.24+, 1.6.36+…) - Docs

Kubernetes와 containerd 버전 호환성 표는 각 Kubernetes 버전에서 사용 가능한 containerd 버전을 보여줍니다. 예를 들어, Kubernetes 1.32는 containerd 2.1.0 이상, 2.0.1 이상, 1.7.24 이상, 1.6.36 이상 버전과 호환됩니다. 여러 버전이 나열된 경우, 그 중 하나를 선택하여 사용할 수 있습니다. CRI Version은 Container Runtime Interface 버전으로, Kubernetes와 컨테이너 런타임 간 통신 프로토콜 버전을 의미합니다. 현재 모든 버전이 CRI v1을 사용합니다.

Kubernetes Version containerd Version CRI Version
1.32* 2.1.0+, 2.0.1+, 1.7.24+, 1.6.36+ v1
1.33 2.1.0+, 2.0.4+, 1.7.24+, 1.6.36+ v1
1.34 2.1.3+, 2.0.6+, 1.7.28+, 1.6.39+ v1
1.35 2.2.0+, 2.1.5+, 1.7.28+ v1

 

containerd 설정 파일 버전 호환성 표는 /etc/containerd/config.toml 파일에서 사용할 수 있는 설정 형식 버전과 해당 버전을 지원하는 최소 containerd 버전을 나타냅니다. Configuration Version 3은 containerd v2.0.0 이상에서만 사용 가능하며, 더 많은 기능과 향상된 설정 옵션을 제공합니다. 실습에서는 containerd v2.1.5를 사용하므로 Configuration Version 3을 사용할 수 있습니다.

 

Daemon Configuration : /etc/containerd/config.toml

Configuration Version Minimum containerd version
1 v1.0.0
2 v1.3.0
3 v2.0.0

 

설치

# dnf == yum, 버전 정보 확인
dnf
yum
dnf --version
yum --version

# Docker 저장소 추가 : dockerd 설치 X, containerd 설치 OK
dnf repolist
tree /etc/yum.repos.d/
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf repolist
tree /etc/yum.repos.d/
cat /etc/yum.repos.d/docker-ce.repo
dnf makecache

# 설치 가능한 모든 containerd.io 버전 확인
dnf list --showduplicates containerd.io
*Available Packages
containerd.io.aarch64                                   1.7.23-3.1.el10                                   docker-ce-stable
containerd.io.aarch64                                   1.7.24-3.1.el10                                   docker-ce-stable
containerd.io.aarch64                                   1.7.25-3.1.el10                                   docker-ce-stable
containerd.io.aarch64                                   1.7.26-3.1.el10                                   docker-ce-stable
containerd.io.aarch64                                   1.7.27-3.1.el10                                   docker-ce-stable
containerd.io.aarch64                                   1.7.28-1.el10                                     docker-ce-stable
containerd.io.aarch64                                   1.7.28-2.el10                                     docker-ce-stable
containerd.io.aarch64                                   1.7.29-1.el10                                     docker-ce-stable
containerd.io.aarch64                                   2.1.5-1.el10                                      docker-ce-stable
containerd.io.aarch64                                   2.2.0-2.el10                                      docker-ce-stable
containerd.io.aarch64                                   2.2.1-1.el10                                      docker-ce-stable*

# containerd 설치
dnf install -y containerd.io-2.1.5-1.el10
# Package         Arch      Version          Repository          Size
# Installing:
#  containerd.io   aarch64   2.1.5-1.el10     docker-ce-stable    31 M
# Transaction Summary
# Install  1 Package
# Total download size: 31 M
# Installed size: 110 M
# Downloading Packages:
# containerd.io-2.1.5-1.el10.aarch64.rpm  11 MB/s |  31 MB     00:02
# Importing GPG key 0x621E9F35:
#  Userid     : "Docker Release (CE rpm) <docker@docker.com>"
#  Fingerprint: 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35
# Installed:
#   containerd.io-2.1.5-1.el10.aarch64
# Complete!

# 설치된 파일 확인
which runc && runc --version
# /usr/bin/runc
# runc version 1.3.3
# commit: v1.3.3-0-gd842d771
# spec: 1.2.1
# go: go1.24.9
# libseccomp: 2.5.3

which containerd && containerd --version
# /usr/bin/containerd
# containerd containerd.io v2.1.5 fcd43222d6b07379a4be9786bda52438f0dd16a1

which containerd-shim-runc-v2 && containerd-shim-runc-v2 -v
# /usr/bin/containerd-shim-runc-v2
# containerd-shim-runc-v2:
#   Version:  v2.1.5
#   Revision: fcd43222d6b07379a4be9786bda52438f0dd16a1
#   Go version: go1.24.9

which ctr && ctr --version
# /usr/bin/ctr
# ctr containerd.io v2.1.5

cat /etc/containerd/config.toml
# disabled_plugins = ["cri"]
# (기본 설정 파일은 CRI 플러그인이 비활성화된 상태로 생성됨)

tree /usr/lib/systemd/system | grep containerd
# ├── containerd.service

cat /usr/lib/systemd/system/containerd.service
# [Unit]
# Description=containerd container runtime
# Documentation=https://containerd.io
# After=network.target dbus.service
# [Service]
# ExecStartPre=-/sbin/modprobe overlay
# ExecStart=/usr/bin/containerd
# Type=notify
# Delegate=yes
# KillMode=process
# Restart=always
# RestartSec=5
# LimitNPROC=infinity
# LimitCORE=infinity
# TasksMax=infinity
# OOMScoreAdjust=-999
# [Install]
# WantedBy=multi-user.target

# 기본 설정 생성 및 SystemdCgroup 활성화 (매우 중요)
containerd config default | tee /etc/containerd/config.toml
# version = 3
# root = '/var/lib/containerd'
# state = '/run/containerd'
# temp = ''
# disabled_plugins = []
# required_plugins = []
# oom_score = 0
# imports = []
# [grpc]
#   address = '/run/containerd/containerd.sock'
#   tcp_address = ''
#   uid = 0
#   gid = 0
# [plugins]
#   [plugins.'io.containerd.cri.v1.images']
#     snapshotter = 'overlayfs'
#   [plugins.'io.containerd.cri.v1.runtime']
#     [plugins.'io.containerd.cri.v1.runtime'.containerd]
#       default_runtime_name = 'runc'
#       [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
#         runtime_type = 'io.containerd.runc.v2'
#         [plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
#           SystemdCgroup = false
# (기본 설정 파일이 생성됨, SystemdCgroup = false 상태)

# https://v1-32.docs.kubernetes.io/ko/docs/setup/production-environment/container-runtimes/#cgroupfs-cgroup-driver
# cgroupfs 드라이버는 kubelet의 기본 cgroup 드라이버이다. 
# cgroupfs 드라이버가 사용될 때, kubelet과 컨테이너 런타임은 직접적으로 cgroup 파일시스템과 상호작용하여 cgroup들을 설정한다.
# cgroupfs 드라이버가 권장되지 않는 때가 있는데, systemd가 init 시스템인 경우이다. 
# 이것은 systemd가 시스템에 단 하나의 cgroup 관리자만 있을 것으로 기대하기 때문이다. 
# 또한, cgroup v2를 사용할 경우에도 cgroupfs 대신 systemd cgroup 드라이버를 사용한다.
# -----------------------------------------------------------
# https://github.com/containerd/containerd/blob/main/docs/cri/config.md
*## In containerd 2.x
version = 3
[plugins.'io.containerd.cri.v1.images']
  snapshotter = "overlayfs"
## In containerd 1.x  
version = 2
[plugins."io.containerd.grpc.v1.cri".containerd]
  snapshotter = "overlayfs"*
# -----------------------------------------------------------
cat /etc/containerd/config.toml | grep -i systemdcgroup
#             SystemdCgroup = false

sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

cat /etc/containerd/config.toml | grep -i systemdcgroup
#             SystemdCgroup = true

# systemd unit 파일 최신 상태 읽기
systemctl daemon-reload

# containerd start 와 enabled
systemctl enable --now containerd
# Created symlink '/etc/systemd/system/multi-user.target.wants/containerd.service' → '/usr/lib/systemd/system/containerd.service'.

# 
systemctl status containerd --no-pager
# ● containerd.service - containerd container runtime
#      Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled; preset: disabled)
#      Active: active (running) since Wed 2026-01-21 01:49:11 KST; 4s ago
#       Docs: https://containerd.io
#    Process: 5337 ExecStartPre=/sbin/modprobe overlay (code=exited, status=0/SUCCESS)
#   Main PID: 5338 (containerd)
#      Tasks: 9
#     Memory: 15.1M (peak: 23.9M)
#        CPU: 43ms
#     CGroup: /system.slice/containerd.service
#             └─5338 /usr/bin/containerd

journalctl -u containerd.service --no-pager
# Jan 21 01:49:11 k8s-ctr systemd[1]: Starting containerd.service - containerd container runtime...
# Jan 21 01:49:11 k8s-ctr containerd[5338]: time="2026-01-21T01:49:11.389012040+09:00" level=info msg="starting containerd" revision=fcd43222d6b07379a4be9786bda52438f0dd16a1 version=v2.1.5
# Jan 21 01:49:11 k8s-ctr containerd[5338]: time="2026-01-21T01:49:11.398493722+09:00" level=info msg="Connect containerd service"
# Jan 21 01:49:11 k8s-ctr containerd[5338]: time="2026-01-21T01:49:11.404242840+09:00" level=info msg=serving... address=/run/containerd/containerd.sock
# Jan 21 01:49:11 k8s-ctr containerd[5338]: time="2026-01-21T01:49:11.404430841+09:00" level=info msg="containerd successfully booted in 0.016186s"
# Jan 21 01:49:11 k8s-ctr systemd[1]: Started containerd.service - containerd container runtime.

pstree -alnp
# systemd,1
#   └─containerd,5338
#       ├─{containerd},5340
#       ├─{containerd},5341
#       └─... (containerd 프로세스 트리 확인)

systemd-cgls --no-pager
# CGroup /:
# └─system.slice
#   └─containerd.service
#     └─5338 /usr/bin/containerd

# containerd의 유닉스 도메인 소켓 확인 : kubelet에서 사용 , containerd client 3종(ctr, nerdctr, crictl)도 사용
containerd config dump | grep -n containerd.sock
# 11:  address = '/run/containerd/containerd.sock'

ls -l /run/containerd/containerd.sock
# srw-rw----. 1 root root 0 Jan 21 01:49 /run/containerd/containerd.sock

ss -xl | grep containerd
# u_str LISTEN 0      4096        /run/containerd/containerd.sock.ttrpc 26779            * 0
# u_str LISTEN 0      4096              /run/containerd/containerd.sock 24836            * 0

ss -xnp | grep containerd
# u_str ESTAB 0      0                                                 * 24831            * 26766 users:(("containerd",pid=5338,fd=2),("containerd",pid=5338,fd=1))

# 플러그인 확인
ctr --address /run/containerd/containerd.sock version
# Client:
#   Version:  v2.1.5
#   Revision: fcd43222d6b07379a4be9786bda52438f0dd16a1
#   Go version: go1.24.9
# Server:
#   Version:  v2.1.5
#   Revision: fcd43222d6b07379a4be9786bda52438f0dd16a1
#   UUID: 5c9ff538-c469-424c-9027-d5b9904302e9

ctr plugins ls
# TYPE                                      ID                       PLATFORMS         STATUS
# io.containerd.content.v1                  content                  -                 ok        # 이미지 레이어 저장
# io.containerd.snapshotter.v1              native                   linux/arm64/v8    ok        # 기본 스냅샷터, 단순 파일시스템 스냅샷 생성/복원
# io.containerd.snapshotter.v1              overlayfs                linux/arm64/v8    ok        # Kubernetes 기본 snapshotter, Overlay 파일시스템 사용
# io.containerd.snapshotter.v1              zfs                      linux/arm64/v8    skip      # ZFS 볼륨 스냅샷 지원, 현재 사용되지 않음(skip)
# io.containerd.metadata.v1                 bolt                     -                 ok        # 메타데이터 DB (bolt), 컨테이너/이미지 정보 저장
# io.containerd.cri.v1                      images                   -                 ok        # Kubernetes CRI 이미지 기능 인터페이스
# io.containerd.cri.v1                      runtime                  linux/arm64/v8    ok        # 컨테이너 런타임 기능 (Kubernetes CRI용)
# io.containerd.grpc.v1                     cri                      -                 ok        # Kubernetes CRI와 gRPC 통신 지원

 

 

위 그림은 command-line containerd clients(ctr, nerdctl, crictl)가 containerd 데몬과 어떻게 통신하는지를 보여줍니다.

 

ctr와 nerdctl는 /run/containerd/containerd.sock 유닉스 도메인 소켓을 통해 직접 gRPC로 containerd에 요청을 보내고, crictl는 Kubernetes CRI API를 통해 간접적으로 containerd에 접근합니다. containerd는 containerd-shim을 거쳐 OCI 런타임(runc)을 호출하여 실제 컨테이너 프로세스를 생성·관리합니다.

3. [공통] kubeadm, kubelet 및 kubectl 설치 v1.32.11

kubeadm, kubelet, kubectl은 Kubernetes 클러스터 구축 및 관리에 필수적인 세 가지 핵심 도구입니다. 이 세 도구는 반드시 동일한 버전으로 설치되어야 하며, 버전 불일치는 클러스터 오작동을 유발할 수 있습니다.

  • kubeadm: 클러스터를 부트스트랩하고 초기화하는 도구로, 컨트롤 플레인 노드와 워커 노드를 클러스터에 조인시키는 역할을 합니다.
  • kubelet: 각 노드에서 실행되는 에이전트로, 컨테이너 런타임(containerd)과 통신하여 Pod를 생성·관리합니다.
  • kubectl: 클러스터와 상호작용하기 위한 명령줄 도구로, 리소스 생성, 조회, 삭제 등의 작업을 수행합니다.

중요 사항:

  • Kubernetes 저장소 설정 시 exclude 옵션을 사용하여 실수로 dnf update 시 자동 업그레이드되는 것을 방지합니다.
  • --disableexcludes=kubernetes 옵션을 사용하면 exclude 규칙을 일시적으로 무시하고 설치할 수 있습니다.
  • kubelet은 kubeadm init 실행 전에는 /var/lib/kubelet/config.yaml 파일이 없어서 시작에 실패하는 것이 정상입니다.
# repo 추가
## exclude=... : 실수로 dnf update 시 kubelet 자동 업그레이드 방지
dnf repolist
# repo id                       repo name
# appstream                     Rocky Linux 10 - AppStream
# baseos                        Rocky Linux 10 - BaseOS
# docker-ce-stable              Docker CE Stable - aarch64
# extras                        Rocky Linux 10 - Extras

tree /etc/yum.repos.d/
# /etc/yum.repos.d/
# ├── docker-ce.repo
# ├── rocky-addons.repo
# ├── rocky-devel.repo
# ├── rocky-extras.repo
# └── rocky.repo

# 해당 k8s 버전으로 설치
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
# [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

dnf makecache
# Docker CE Stable - aarch64            13 kB/s | 2.0 kB     00:00
# Kubernetes                            11 kB/s |  19 kB     00:01
# Rocky Linux 10 - BaseOS              744  B/s | 4.3 kB     00:05
# Rocky Linux 10 - AppStream           6.9 kB/s | 4.3 kB     00:00
# Rocky Linux 10 - Extras              3.5 kB/s | 3.1 kB     00:00
# Metadata cache created.

# 설치
## --disableexcludes=... kubernetes repo에 설정된 exclude 규칙을 이번 설치에서만 무시(1회성 옵션 처럼 사용)
## 설치 가능 버전 확인
dnf list --showduplicates kubelet # '--disableexcludes=kubernetes' 아래 처럼 있는 경우와 없는 경우 비교해보기
# Error: No matching Packages to list (exclude 규칙으로 인해 목록이 비어있음)

dnf list --showduplicates kubelet --disableexcludes=kubernetes
# Available Packages
# kubelet.aarch64             1.32.0-150500.1.1              kubernetes
# kubelet.aarch64             1.32.1-150500.1.1              kubernetes
# ...
# kubelet.aarch64             1.32.11-150500.1.1             kubernetes

dnf list --showduplicates kubeadm --disableexcludes=kubernetes
# Available Packages
# kubeadm.aarch64             1.32.0-150500.1.1              kubernetes
# kubeadm.aarch64             1.32.1-150500.1.1              kubernetes
# ...
# kubeadm.aarch64             1.32.11-150500.1.1             kubernetes

dnf list --showduplicates kubectl --disableexcludes=kubernetes
# Available Packages
# kubectl.aarch64             1.32.0-150500.1.1              kubernetes
# kubectl.aarch64             1.32.1-150500.1.1              kubernetes
# ...
# kubectl.aarch64             1.32.11-150500.1.1             kubernetes

## 버전 정보 미지정 시, 제공 가능 최신 버전 설치됨.
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
# Dependencies resolved.
# =====================================================================
#  Package          Arch      Version               Repository    Size
# =====================================================================
# 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
# Transaction Summary
# Install  5 Packages
# Total download size: 46 M
# Installed size: 280 M
# Importing GPG key 0x9A296436:
#  Userid     : "isv:kubernetes OBS Project <isv:kubernetes@build.opensuse.org>"
#  Fingerprint: DE15 B144 86CD 377B 9E87 6E1A 2346 54DA 9A29 6436
# Installed:
#   cri-tools-1.32.0-150500.1.1.aarch64
#   kubeadm-1.32.11-150500.1.1.aarch64
#   kubectl-1.32.11-150500.1.1.aarch64
#   kubelet-1.32.11-150500.1.1.aarch64
#   kubernetes-cni-1.6.0-150500.1.1.aarch64
# Complete!

# kubelet 활성화 (실제 기동은 kubeadm init 후에 시작됨)
systemctl enable --now kubelet
# Created symlink '/etc/systemd/system/multi-user.target.wants/kubelet.service' → '/usr/lib/systemd/system/kubelet.service'.

ps -ef |grep kubelet
# (kubelet 프로세스는 아직 실행되지 않음 - kubeadm init 전에는 config.yaml이 없어서 실패)

# 설치 파일들 확인
which kubeadm && kubeadm version -o yaml
# /usr/bin/kubeadm
# clientVersion:
#   buildDate: "2025-12-16T18:06:36Z"
#   compiler: gc
#   gitCommit: 2195eae9e91f2e72114365d9bb9c670d0c08de12
#   gitTreeState: clean
#   gitVersion: v1.32.11
#   goVersion: go1.24.11
#   major: "1"
#   minor: "32"
#   platform: linux/arm64

which kubectl && kubectl version --client=true
# /usr/bin/kubectl
# Client Version: v1.32.11
# Kustomize Version: v5.5.0

which kubelet && kubelet --version
# /usr/bin/kubelet
# Kubernetes v1.32.11

# cri-tools
which crictl && crictl version
# /usr/bin/crictl
# WARN[0000] Config "/etc/crictl.yaml" does not exist, trying next: "/usr/bin/crictl.yaml"
# WARN[0000] runtime connect using default endpoints: [unix:///run/containerd/containerd.sock unix:///run/crio/crio.sock unix:///var/run/cri-dockerd.sock]. As the default settings are now deprecated, you should set the endpoint instead.
# Version:  0.1.0
# RuntimeName:  containerd
# RuntimeVersion:  v2.1.5
# RuntimeApiVersion:  v1

# /etc/crictl.yaml 파일 작성
cat << EOF > /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
EOF

# crictl을 통한 containerd 정보 확인 (설명: crictl은 Kubernetes CRI API를 통해 containerd와 통신)
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
# total 63200
# drwxr-xr-x. 2 root root    4096 Jan 21 02:09 .
# drwxr-xr-x. 3 root root      17 Jan 21 02:09 ..
# -rwxr-xr-x. 1 root root 3239200 Dec 12  2024 bandwidth
# -rwxr-xr-x. 1 root root 3731632 Dec 12  2024 bridge
# -rwxr-xr-x. 1 root root 9123544 Dec 12  2024 dhcp
# -rwxr-xr-x. 1 root root 3379872 Dec 12  2024 dummy
# -rwxr-xr-x. 1 root root 3742888 Dec 12  2024 firewall
# -rwxr-xr-x. 1 root root 3383408 Dec 12  2024 host-device
# -rwxr-xr-x. 1 root root 2812400 Dec 12  2024 host-local
# -rwxr-xr-x. 1 root root 3380928 Dec 12  2024 ipvlan
# -rwxr-xr-x. 1 root root 2953200 Dec 12  2024 loopback
# -rwxr-xr-x. 1 root root 3448024 Dec 12  2024 macvlan
# -rwxr-xr-x. 1 root root 3312488 Dec 12  2024 portmap
# -rwxr-xr-x. 1 root root 3524072 Dec 12  2024 ptp
# -rwxr-xr-x. 1 root root 3091976 Dec 12  2024 sbr
# -rwxr-xr-x. 1 root root 2526944 Dec 12  2024 static
# -rwxr-xr-x. 1 root root 3516272 Dec 12  2024 tap
# -rwxr-xr-x. 1 root root 2956032 Dec 12  2024 tuning
# -rwxr-xr-x. 1 root root 3380544 Dec 12  2024 vlan
# -rwxr-xr-x. 1 root root 3160560 Dec 12  2024 vrf

tree /opt/cni
# /opt/cni
# └── bin
#     ├── bandwidth
#     ├── bridge
#     ├── dhcp
#     ├── dummy
#     ├── firewall
#     ├── host-device
#     ├── host-local
#     ├── ipvlan
#     ├── LICENSE
#     ├── loopback
#     ├── macvlan
#     ├── portmap
#     ├── ptp
#     ├── README.md
#     ├── sbr
#     ├── static
#     ├── tap
#     ├── tuning
#     ├── vlan
#     └── vrf
# 2 directories, 20 files

tree /etc/cni/
# /etc/cni/
# └── net.d
# 2 directories, 0 files

# kubelet 상태 확인 (kubeadm init 전에는 config.yaml이 없어서 실패하는 것이 정상)
systemctl is-active kubelet
# activating

systemctl status kubelet --no-pager
# ● kubelet.service - kubelet: The Kubernetes Node Agent
#      Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
#     Drop-In: /usr/lib/systemd/system/kubelet.service.d
#              └─10-kubeadm.conf
#      Active: activating (auto-restart) (Result: exit-code) since Wed 2026-01-21 02:11:14 KST; 71ms ago
#    Process: 5752 ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS (code=exited, status=1/FAILURE)
#   Main PID: 5752 (code=exited, status=1/FAILURE)

journalctl -u kubelet --no-pager
# E0121 02:09:42.541728    5622 run.go:72] "command failed" err="failed to load kubelet config file, path: /var/lib/kubelet/config.yaml, error: failed to load Kubelet config file /var/lib/kubelet/config.yaml, error failed to read kubelet config file \"/var/lib/kubelet/config.yaml\", error: open /var/lib/kubelet/config.yaml: no such file or directory"
# (kubeadm init 전에는 /var/lib/kubelet/config.yaml이 없어서 kubelet이 시작되지 않음 - 정상적인 동작)

tree /usr/lib/systemd/system | grep kubelet -A1
# ├── kubelet.service
# ├── kubelet.service.d
# │   └── 10-kubeadm.conf

cat /usr/lib/systemd/system/kubelet.service
# [Unit]
# Description=kubelet: The Kubernetes Node Agent
# Documentation=https://kubernetes.io/docs/
# Wants=network-online.target
# After=network-online.target
# [Service]
# ExecStart=/usr/bin/kubelet
# Restart=always
# StartLimitInterval=0
# RestartSec=10
# [Install]
# WantedBy=multi-user.target

cat /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
# # Note: This dropin only works with kubeadm and kubelet v1.11+
# [Service]
# Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
# Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# EnvironmentFile=-/etc/sysconfig/kubelet
# ExecStart=
# ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

tree /etc/kubernetes
# /etc/kubernetes
# └── manifests
# 2 directories, 0 files

tree /var/lib/kubelet
# /var/lib/kubelet
# 0 directories, 0 files

cat /etc/sysconfig/kubelet
# KUBELET_EXTRA_ARGS=

# cgroup , namespace 정보 확인
systemd-cgls --no-pager
# CGroup /:
# └─system.slice
#   ├─containerd.service
#   │ └─5338 /usr/bin/containerd
#   └─... (기타 서비스들)

lsns
#         NS TYPE   NPROCS   PID USER    COMMAND
# 4026531834 time      134     1 root    /usr/lib/systemd/systemd
# 4026531835 cgroup    134     1 root    /usr/lib/systemd/systemd
# 4026531836 pid       134     1 root    /usr/lib/systemd/systemd
# 4026531837 user      133     1 root    /usr/lib/systemd/systemd
# 4026531838 uts       125     1 root    /usr/lib/systemd/systemd
# 4026531839 ipc       134     1 root    /usr/lib/systemd/systemd
# 4026531840 net       132     1 root    /usr/lib/systemd/systemd
# 4026531841 mnt       119     1 root    /usr/lib/systemd/systemd

# containerd의 유닉스 도메인 소켓 확인 : kubelet에서 사용 , containerd client 3종(ctr, nerdctr, crictl)도 사용
ls -l /run/containerd/containerd.sock
# srw-rw----. 1 root root 0 Jan 21 01:49 /run/containerd/containerd.sock

ss -xl | grep containerd
# u_str LISTEN 0      4096        /run/containerd/containerd.sock.ttrpc 26779            * 0
# u_str LISTEN 0      4096              /run/containerd/containerd.sock 24836            * 0

ss -xnp | grep containerd
# u_str ESTAB 0      0                                                 * 24831            * 26766 users:(("containerd",pid=5338,fd=2),("containerd",pid=5338,fd=1))

4. [k8s-ctr] kubeadm 으로 k8s 클러스터 구성 & Flannel CNI 설치 v0.27.3 & 편의성 설정 등

kubeadm init 명령은 Kubernetes 클러스터의 컨트롤 플레인을 초기화하는 핵심 단계입니다.

이 과정은 다음과 같은 순서로 진행됩니다.

 

1. 사전 검사 (Preflight Checks)

kubeadm은 클러스터 초기화 전에 시스템 상태를 검사합니다. CRI(Container Runtime Interface) 엔드포인트 연결 확인, 루트 권한 확인, kubelet 버전이 kubeadm이 지원하는 최소 버전(current minor -1) 이상인지 확인합니다. 이러한 검사에서 오류가 발생하면 초기화가 중단됩니다.

 

2. 인증서 및 키 생성 (Certificates)

Control Plane 구성 요소들이 안전하게 통신할 수 있도록 /etc/kubernetes/pki 디렉토리에 인증서와 키를 생성합니다. 생성되는 인증서는 CA 인증서(클러스터 전체 기본 신뢰체계), API Server용 서버 인증서, API Server와 kubelet 간 통신용 인증서, Front-Proxy CA 및 Front-Proxy 클라이언트 인증서, 그리고 local etcd를 사용하는 경우 etcd 관련 인증서 및 키입니다.

 

3. kubeconfig 파일 생성

control plane components를 위한 kubeconfig 파일을 /etc/kubernetes 디렉토리에 생성합니다. 생성되는 파일은 kubelet이 TLS bootstrap 중 사용하는 /etc/kubernetes/bootstrap-kubelet.conf, controller-manager용 /etc/kubernetes/controller-manager.conf, scheduler용 /etc/kubernetes/scheduler.conf, 그리고 클러스터 관리용 /etc/kubernetes/admin.conf와 /etc/kubernetes/super-admin.conf입니다.

 

4. Control Plane 구성 요소 생성 (Static Pods)

kubeadm은 /etc/kubernetes/manifests 디렉토리에 Static Pod 매니페스트를 생성하여 kubelet이 자동으로 Control Plane 구성 요소를 실행하도록 합니다. 모든 Static Pod는 kube-system 네임스페이스에 배포되며, tier:control-plane과 component:{component-name} 레이블을 가집니다. 또한 system-node-critical 우선순위 클래스를 사용하고, Control Plane 시작을 위해 hostNetwork: true가 설정됩니다. 생성되는 구성 요소는 kube-apiserver, kube-controller-manager, kube-scheduler, 그리고 local etcd를 사용하는 경우 etcd입니다.

 

5. kubelet 시작 및 대기

kubeadm은 manifest 적용 후 kubelet을 재시작하거나 설정을 적용하고, Control Plane이 성공적으로 시작될 때까지 대기합니다. 특히 API Server가 정상적으로 건강 상태를 반환할 때까지 /healthz 또는 /livez 엔드포인트를 확인하며 대기합니다.

 

6. ClusterConfiguration을 configMap에 저장

kubeadm은 나중에 참조할 수 있도록 ClusterConfiguration을 kube-system 네임스페이스의 kubeadm-config ConfigMap에 저장합니다. 이 ConfigMap은 kubectl get cm -n kube-system kubeadm-config 명령으로 확인할 수 있습니다.

 

7. 노드 라벨링 및 태인트

컨트롤 플레인 노드에 node-role.kubernetes.io/control-plane="" 레이블을 추가하고, node-role.kubernetes.io/control-plane:NoSchedule 태인트를 적용합니다. 이 태인트는 일반 워크로드가 Control Plane 노드에 스케줄되지 않도록 방지합니다.

 

8. Bootstrap 토큰 생성 및 클러스터 부트스트랩 설정

Control Plane에 부트스트랩 토큰을 생성하고, 각 노드가 클러스터에 합류할 수 있도록 ConfigMap, RBAC 규칙, CSR 접근 권한 등을 설정합니다. 특히 kube-public 네임스페이스에 공개 cluster-info ConfigMap을 생성하며, 이는 '신원 확인 전, 최소한의 신뢰 부트스트랩 데이터'로 사용됩니다. 또한 인증되지 않은 사용자(RBAC 그룹 system:unauthenticated)가 이 ConfigMap에 접근할 수 있도록 Role과 RoleBinding을 생성합니다. 부트스트랩 토큰은 기본적으로 24시간 유효하며, kubeadm join 명령 시 사용할 수 있습니다.

9. 필수 애드온 설치

kube-proxy와 CoreDNS를 설치합니다. kube-proxy의 경우 kube-system 네임스페이스에 ServiceAccount를 생성한 후 DaemonSet으로 배포됩니다.

CoreDNS의 경우 레거시 kube-dns 애드온과의 호환성을 위해 서비스 이름이 kube-dns로 지정됩니다. kube-system 네임스페이스에 CoreDNS용 ServiceAccount를 생성하고, coredns ServiceAccount를 system:coredns ClusterRole의 권한에 바인딩합니다. 참고로 Kubernetes 버전 1.21부터는 kubeadm에서 kube-dns 사용 지원이 제거되었습니다.

 

# 기본 환경 정보 출력 저장
crictl images
# (kubeadm init 전에는 이미지가 없음)

crictl ps
# (kubeadm init 전에는 컨테이너가 없음)

cat /etc/sysconfig/kubelet
# KUBELET_EXTRA_ARGS=

tree /etc/kubernetes  | tee -a etc_kubernetes-1.txt
# /etc/kubernetes
# └── manifests
# 2 directories, 0 files

tree /var/lib/kubelet | tee -a var_lib_kubelet-1.txt
# /var/lib/kubelet
# 0 directories, 0 files

tree /run/containerd/ -L 3 | tee -a run_containerd-1.txt
# /run/containerd/
# ├── containerd.sock
# ├── containerd.sock.ttrpc
# ├── io.containerd.grpc.v1.cri
# ├── io.containerd.runtime.v2.task
# └── io.containerd.sandbox.controller.v1.shim
# 4 directories, 2 files

pstree -alnp | tee -a pstree-1.txt
# systemd,1 --switched-root --system --deserialize=46 no_timer_check
#   |-systemd-journal,457
#   |-systemd-userdbd,495
#   |   |-systemd-userwor,6500
#   |   |-systemd-userwor,6501
#   |   `-systemd-userwor,6759
#   |-systemd-udevd,505
#   |-rpcbind,591 -w -f
#   |-auditd,593
#   |   |-{auditd},594
#   |   |-sedispatch,595
#   |   `-{auditd},596
#   |-dbus-broker-lau,633 --scope system --audit
#   |   `-dbus-broker,641 --log 4 --controller 9 --machine-id df4a7ea6da70431281c0868cf44eae5a --max-bytes 536870912 --max-fds 4096 --max-matches 131072 --audit
#   |-irqbalance,645
#   |   `-{irqbalance},663
#   |-lsmd,646 -d
#   |-systemd-logind,652
#   |-chronyd,665 -F 2
#   |-VBoxService,763 --pidfile /var/run/vboxadd-service.sh
#   |   |-{VBoxService},768
#   |   |-{VBoxService},769
#   |   |-{VBoxService},771
#   |   |-{VBoxService},772
#   |   |-{VBoxService},774
#   |   |-{VBoxService},776
#   |   |-{VBoxService},777
#   |   `-{VBoxService},779
#   |-polkitd,812 --no-debug --log-level=err
#   |   |-{polkitd},814
#   |   |-{polkitd},815
#   |   `-{polkitd},816
#   |-gssproxy,839 -i
#   |   |-{gssproxy},866
#   |   |-{gssproxy},867
#   |   |-{gssproxy},868
#   |   |-{gssproxy},869
#   |   `-{gssproxy},870
#   |-sshd,849
#   |   `-sshd-session,4496
#   |       `-sshd-session,4518
#   |           `-bash,4519
#   |               `-sudo,4584 su -
#   |                   `-sudo,4586 su -
#   |                       `-su,4587 -
#   |                           `-bash,4588
#   |                               |-pstree,6883 -alnp
#   |                               `-tee,6884 -a pstree-1.txt
#   |-tuned,851 -Es /usr/sbin/tuned -l -P
#   |   |-{tuned},1111
#   |   |-{tuned},1112
#   |   `-{tuned},1113
#   |-atd,877 -f
#   |-crond,882 -n
#   |-agetty,885 -o -- \\u --noreset --noclear - linux
#   |-rsyslogd,1133 -n
#   |   |-{rsyslogd},1148
#   |   `-{rsyslogd},1151
#   |-NetworkManager,3044 --no-daemon
#   |   |-{NetworkManager},3045
#   |   |-{NetworkManager},3046
#   |   `-{NetworkManager},3047
#   |-systemd,4501 --user
#   |   `-(sd-pam),4503
#   |-udisksd,4709
#   |   |-{udisksd},4710
#   |   |-{udisksd},4711
#   |   |-{udisksd},4713
#   |   |-{udisksd},4714
#   |   |-{udisksd},4715
#   |   `-{udisksd},4716
#   `-containerd,5338
#       |-{containerd},5340
#       |-{containerd},5341
#       |-{containerd},5342
#       |-{containerd},5343
#       |-{containerd},5344
#       |-{containerd},5345
#       |-{containerd},5346
#       |-{containerd},5347
#       `-{containerd},6430

systemd-cgls --no-pager | tee -a systemd-cgls-1.txt
# CGroup /:
# -.slice
# ├─user.slice
# │ └─user-1000.slice
# │   ├─user@1000.service …
# │   │ └─init.scope
# │   │   ├─4501 /usr/lib/systemd/systemd --user
# │   │   └─4503 (sd-pam)
# │   └─session-4.scope
# │     ├─4496 sshd-session: vagrant [priv]
# │     ├─4518 sshd-session: vagrant@pts/0
# │     ├─4519 -bash
# │     ├─4584 sudo su -
# │     ├─4586 sudo su -
# │     ├─4587 su -
# │     ├─4588 -bash
# │     ├─6893 systemd-cgls --no-pager
# │     └─6894 tee -a systemd-cgls-1.txt
# ├─init.scope
# │ └─1 /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_tim…
# └─system.slice
#   ├─irqbalance.service
#   │ └─645 /usr/sbin/irqbalance
#   ├─libstoragemgmt.service
#   │ └─646 /usr/bin/lsmd -d
#   ├─vboxadd-service.service
#   │ └─763 /usr/sbin/VBoxService --pidfile /var/run/vboxadd-service.sh
#   ├─containerd.service …
#   │ └─5338 /usr/bin/containerd
#   ├─systemd-udevd.service …
#   │ └─udev
#   │   └─505 /usr/lib/systemd/systemd-udevd
#   ├─dbus-broker.service
#   │ ├─633 /usr/bin/dbus-broker-launch --scope system --audit
#   │ └─641 dbus-broker --log 4 --controller 9 --machine-id df4a7ea6da70431281c08…
#   ├─polkit.service
#   │ └─812 /usr/lib/polkit-1/polkitd --no-debug --log-level=err
#   ├─chronyd.service
#   │ └─665 /usr/sbin/chronyd -F 2
#   ├─auditd.service
#   │ ├─593 /usr/sbin/auditd
#   │ └─595 /usr/sbin/sedispatch
#   ├─tuned.service
#   │ └─851 /usr/bin/python3 -Es /usr/sbin/tuned -l -P
#   ├─kubelet.service
#   │ └─6885 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-ku…
#   ├─systemd-journald.service
#   │ └─457 /usr/lib/systemd/systemd-journald
#   ├─atd.service
#   │ └─877 /usr/sbin/atd -f
#   ├─sshd.service
#   │ └─849 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
#   ├─crond.service
#   │ └─882 /usr/sbin/crond -n
#   ├─NetworkManager.service
#   │ └─3044 /usr/sbin/NetworkManager --no-daemon
#   ├─gssproxy.service
#   │ └─839 /usr/sbin/gssproxy -i
#   ├─rsyslog.service
#   │ └─1133 /usr/sbin/rsyslogd -n
#   ├─systemd-userdbd.service
#   │ ├─ 495 /usr/lib/systemd/systemd-userdbd
#   │ ├─6500 systemd-userwork: waiting...
#   │ ├─6501 systemd-userwork: waiting...
#   │ └─6759 systemd-userwork: waiting...
#   ├─rpcbind.service
#   │ └─591 /usr/bin/rpcbind -w -f
#   ├─udisks2.service
#   │ └─4709 /usr/libexec/udisks2/udisksd
#   ├─system-getty.slice
#   │ └─getty@tty1.service
#   │   └─885 /sbin/agetty -o -- \u --noreset --noclear - linux
#   └─systemd-logind.service
#     └─652 /usr/lib/systemd/systemd-logind

lsns | tee -a lsns-1.txt
#         NS TYPE   NPROCS   PID USER    COMMAND
# 4026531834 time      192     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531835 cgroup    192     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531836 pid       192     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531837 user      191     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531838 uts       183     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531839 ipc       192     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531840 net       190     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531841 mnt       177     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026532080 mnt         1   505 root    ├─/usr/lib/systemd/systemd-udevd
# 4026532081 mnt         4   495 root    ├─/usr/lib/systemd/systemd-userdbd
# 4026532103 uts         4   495 root    ├─/usr/lib/systemd/systemd-userdbd
# 4026532104 uts         1   505 root    ├─/usr/lib/systemd/systemd-udevd
# 4026532124 mnt         2   633 dbus    ├─/usr/bin/dbus-broker-launch --scope system --audit
# 4026532125 mnt         1   665 chrony  ├─/usr/sbin/chronyd -F 2
# 4026532127 mnt         1  3044 root    ├─/usr/sbin/NetworkManager --no-daemon
# 4026532128 net         1   645 root    ├─/usr/sbin/irqbalance
# 4026532188 mnt         1   645 root    ├─/usr/sbin/irqbalance
# 4026532190 uts         1   665 chrony  ├─/usr/sbin/chronyd -F 2
# 4026532194 uts         1   645 root    ├─/usr/sbin/irqbalance
# 4026532196 user        1   645 root    ├─/usr/sbin/irqbalance
# 4026532199 mnt         1   652 root    ├─/usr/lib/systemd/systemd-logind
# 4026532200 uts         1   652 root    ├─/usr/lib/systemd/systemd-logind
# 4026532202 net         1   812 polkitd ├─/usr/lib/polkit-1/polkitd --no-debug --log-level=err
# 4026532265 mnt         1   812 polkitd ├─/usr/lib/polkit-1/polkitd --no-debug --log-level=err
# 4026532266 uts         1   812 polkitd ├─/usr/lib/polkit-1/polkitd --no-debug --log-level=err
# 4026532340 mnt         1  1133 root    ├─/usr/sbin/rsyslogd -n
# 4026532342 mnt         1   839 root    └─/usr/sbin/gssproxy -i
# 4026531862 mnt         1    38 root    kdevtmpfs

ip addr | tee -a ip_addr-1.txt
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#     inet 127.0.0.1/8 scope host lo
#        valid_lft forever preferred_lft forever
#     inet6 ::1/128 scope host noprefixroute 
#        valid_lft forever preferred_lft forever
# 2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:90:ea:eb brd ff:ff:ff:ff:ff:ff
#     altname enx08002790eaeb
#     inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s8
#        valid_lft 75042sec preferred_lft 75042sec
#     inet6 fd17:625c:f037:2:a00:27ff:fe90:eaeb/64 scope global dynamic mngtmpaddr proto kernel_ra 
#        valid_lft 85858sec preferred_lft 13858sec
#     inet6 fe80::a00:27ff:fe90:eaeb/64 scope link proto kernel_ll 
#        valid_lft forever preferred_lft forever
# 3: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:55:33:36 brd ff:ff:ff:ff:ff:ff
#     altname enx080027553336
#     inet 192.168.10.100/24 brd 192.168.10.255 scope global noprefixroute enp0s9
#        valid_lft forever preferred_lft forever
#     inet6 fd2a:b6fc:560f:26a3:92ed:39f:41b:9d55/64 scope global dynamic noprefixroute 
#        valid_lft 2591887sec preferred_lft 604687sec
#     inet6 fe80::5112:5b2:39d3:68c5/64 scope link noprefixroute 
#        valid_lft forever preferred_lft forever

ss -tnlp | tee -a ss-1.txt
# State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess                                                   
# LISTEN 0      128          0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=849,fd=7))                            
# LISTEN 0      4096       127.0.0.1:41597      0.0.0.0:*    users:(("containerd",pid=5338,fd=11))                    
# LISTEN 0      4096         0.0.0.0:111        0.0.0.0:*    users:(("rpcbind",pid=591,fd=5),("systemd",pid=1,fd=164))
# LISTEN 0      128             [::]:22            [::]:*    users:(("sshd",pid=849,fd=8))                            
# LISTEN 0      4096            [::]:111           [::]:*    users:(("rpcbind",pid=591,fd=7),("systemd",pid=1,fd=166))
# LISTEN 0      4096               *:9090             *:*    users:(("systemd",pid=1,fd=182))

df -hT | tee -a df-1.txt
# Filesystem     Type      Size  Used Avail Use% Mounted on
# /dev/sda3      xfs        60G  3.5G   57G   6% /
# devtmpfs       devtmpfs  4.0M     0  4.0M   0% /dev
# tmpfs          tmpfs     1.4G     0  1.4G   0% /dev/shm
# efivarfs       efivarfs  256K  6.3K  250K   3% /sys/firmware/efi/efivars
# tmpfs          tmpfs     566M   15M  551M   3% /run
# tmpfs          tmpfs     1.0M     0  1.0M   0% /run/credentials/systemd-journald.service
# /dev/sda1      vfat      599M   13M  587M   3% /boot/efi
# tmpfs          tmpfs     1.0M     0  1.0M   0% /run/credentials/getty@tty1.service
# tmpfs          tmpfs     283M  8.0K  283M   1% /run/user/1000

findmnt | tee -a findmnt-1.txt
# TARGET                                        SOURCE      FSTYPE      OPTIONS
# /                                             /dev/sda3   xfs         rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota
# ├─/var/lib/nfs/rpc_pipefs                     sunrpc      rpc_pipefs  rw,relatime
# ├─/boot/efi                                   /dev/sda1   vfat        rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro
# ├─/dev                                        devtmpfs    devtmpfs    rw,nosuid,seclabel,size=4096k,nr_inodes=357738,mode=755,inode64
# │ ├─/dev/hugepages                            hugetlbfs   hugetlbfs   rw,nosuid,nodev,relatime,seclabel,pagesize=2M
# │ ├─/dev/mqueue                               mqueue      mqueue      rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/dev/shm                                  tmpfs       tmpfs       rw,nosuid,nodev,seclabel,inode64
# │ └─/dev/pts                                  devpts      devpts      rw,nosuid,noexec,relatime,seclabel,gid=5,mode=620,ptmxmode=000
# ├─/sys                                        sysfs       sysfs       rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/sys/fs/selinux                           selinuxfs   selinuxfs   rw,nosuid,noexec,relatime
# │ ├─/sys/kernel/debug                         debugfs     debugfs     rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/sys/kernel/tracing                       tracefs     tracefs     rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/sys/fs/fuse/connections                  fusectl     fusectl     rw,nosuid,nodev,noexec,relatime
# │ ├─/sys/kernel/security                      securityfs  securityfs  rw,nosuid,nodev,noexec,relatime
# │ ├─/sys/fs/cgroup                            cgroup2     cgroup2     rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot
# │ ├─/sys/fs/pstore                            pstore      pstore      rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/sys/firmware/efi/efivars                 efivarfs    efivarfs    rw,nosuid,nodev,noexec,relatime
# │ ├─/sys/fs/bpf                               bpf         bpf         rw,nosuid,nodev,noexec,relatime,mode=700
# │ └─/sys/kernel/config                        configfs    configfs    rw,nosuid,nodev,noexec,relatime
# ├─/proc                                       proc        proc        rw,nosuid,nodev,noexec,relatime
# │ └─/proc/sys/fs/binfmt_misc                  systemd-1   autofs      rw,relatime,fd=35,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=3613
# │   └─/proc/sys/fs/binfmt_misc                binfmt_misc binfmt_misc rw,nosuid,nodev,noexec,relatime
# └─/run                                        tmpfs       tmpfs       rw,nosuid,nodev,seclabel,size=578796k,nr_inodes=819200,mode=755,inode64
#   ├─/run/credentials/systemd-journald.service tmpfs       tmpfs       ro,nosuid,nodev,noexec,relatime,nosymfollow,seclabel,size=1024k,nr_inodes=1024,mode=700,inode64,noswap
#   ├─/run/user/1000                            tmpfs       tmpfs       rw,nosuid,nodev,relatime,seclabel,size=289396k,nr_inodes=72349,mode=700,uid=1000,gid=1000,inode64
#   └─/run/credentials/getty@tty1.service       tmpfs       tmpfs       ro,nosuid,nodev,noexec,relatime,nosymfollow,seclabel,size=1024k,nr_inodes=1024,mode=700,inode64,noswap

sysctl -a | tee -a sysctl-1.txt
# kernel.watchdog = 1
# kernel.watchdog_cpumask = 0-3
# net.bridge.bridge-nf-call-iptables = 1
# net.bridge.bridge-nf-call-ip6tables = 1
# net.ipv4.ip_forward = 1
# net.ipv4.conf.all.forwarding = 1
# net.ipv4.conf.default.forwarding = 1
# net.ipv4.conf.enp0s8.forwarding = 1
# net.ipv4.conf.enp0s9.forwarding = 1
# net.ipv4.conf.lo.forwarding = 1
# (기타 수많은 커널 파라미터들...)

# kubeadm Configuration 파일 작성
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"  # 미설정 시 10.0.2.15 맵핑
  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"
EOF

cat 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"

# (옵션) 컨테이너 이미지 미리 다운로드 : 특히 업그레이드 작업 시, 작업 시간 단축을 위해서 수행할 것
kubeadm config images pull
# I0121 02:23:06.171073    6423 version.go:261] remote version is much newer: v1.35.0; falling back to: stable-1.32
# [config/images] Pulled registry.k8s.io/kube-apiserver:v1.32.11
# [config/images] Pulled registry.k8s.io/kube-controller-manager:v1.32.11
# [config/images] Pulled registry.k8s.io/kube-scheduler:v1.32.11
# [config/images] Pulled registry.k8s.io/kube-proxy:v1.32.11
# [config/images] Pulled registry.k8s.io/coredns/coredns:v1.11.3
# [config/images] Pulled registry.k8s.io/pause:3.10
# [config/images] Pulled registry.k8s.io/etcd:3.5.24-0

# k8s controlplane 초기화 설정 수행
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

# Your Kubernetes control-plane has initialized successfully!

# To start using your cluster, you need to run the following as a regular user:

#   mkdir -p $HOME/.kube
#   sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
#   sudo chown $(id -u):$(id -g) $HOME/.kube/config

# Alternatively, if you are the root user, you can run:

#   export KUBECONFIG=/etc/kubernetes/admin.conf

# You should now deploy a pod network to the cluster.
# Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
#   https://kubernetes.io/docs/concepts/cluster-administration/addons/

# Then you can join any number of worker nodes by running the following on each as root:

# kubeadm join 192.168.10.100:6443 --token 123456.1234567890123456 \
#         --discovery-token-ca-cert-hash sha256:6c9fa5b6cd99aaa8eea57a1f9716e15ae8c4142c748421e3f459b6be0131cf70 


# 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
# kubeadm init 실행 후 모든 Control Plane 구성 요소와 필수 애드온 이미지가 다운로드되었습니다.

crictl ps
# CONTAINER           IMAGE               CREATED             STATE               NAME                      ATTEMPT             POD ID              POD                               NAMESPACE
# 3362d1643ecff       dcdb790dc2bfe       33 seconds ago      Running             kube-proxy                0                   e9df7b0d85975       kube-proxy-qhqpz                  kube-system
# 392f49d53a9d4       1211402d28f58       42 seconds ago      Running             etcd                      0                   b990beb24852c       etcd-k8s-ctr                      kube-system
# 32dca52b75ce8       cfa17ff3d6634       43 seconds ago      Running             kube-scheduler            0                   d469c96375645       kube-scheduler-k8s-ctr            kube-system
# c176ce83ecb68       82766e5f2d560       43 seconds ago      Running             kube-controller-manager   0                   d8fa016e22810       kube-controller-manager-k8s-ctr   kube-system
# 2820f726fb702       58951ea1a0b5d       43 seconds ago      Running             kube-apiserver            0                   b8406c854bf46       kube-apiserver-k8s-ctr            kube-system
# Control Plane 구성 요소들이 Static Pod로 실행 중입니다. kube-proxy는 DaemonSet으로 실행됩니다.

# kubeconfig 작성
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config
chown $(id -u):$(id -g) /root/.kube/config
# kubectl이 클러스터와 통신하기 위해 kubeconfig 파일을 설정합니다.
# /etc/kubernetes/admin.conf는 클러스터 관리자 권한을 가진 kubeconfig 파일입니다.

# 확인
kubectl cluster-info
# Kubernetes control plane is running at https://192.168.10.100:6443
# CoreDNS is running at https://192.168.10.100:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
# 
# To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
# Control Plane이 정상적으로 실행 중이며, API Server 엔드포인트와 CoreDNS 서비스 정보를 확인할 수 있습니다.

kubectl get node -owide
# NAME      STATUS     ROLES           AGE   VERSION    INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                        KERNEL-VERSION                  CONTAINER-RUNTIME
# k8s-ctr   NotReady   control-plane   69s   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
# 노드가 control-plane 역할로 표시되었지만 NotReady 상태입니다. 이는 CNI(Container Network Interface)가 아직 설치되지 않아서입니다.

kubectl get nodes -o json | jq ".items[] | {name:.metadata.name} + .status.capacity"
# {
#   "name": "k8s-ctr",
#   "cpu": "4",
#   "ephemeral-storage": "60970Mi",
#   "hugepages-1Gi": "0",
#   "hugepages-2Mi": "0",
#   "hugepages-32Mi": "0",
#   "hugepages-64Ki": "0",
#   "memory": "2893976Ki",
#   "pods": "110"
# }
# 노드의 리소스 용량 정보를 확인할 수 있습니다. 최대 110개의 Pod를 스케줄할 수 있습니다.

kubectl get pod -n kube-system -owide
# NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
# coredns-668d6bf9bc-47zx5          0/1     Pending   0          75s   <none>           <none>    <none>           <none>
# coredns-668d6bf9bc-77pjv          0/1     Pending   0          75s   <none>           <none>    <none>           <none>
# etcd-k8s-ctr                      1/1     Running   0          81s   192.168.10.100   k8s-ctr   <none>           <none>
# kube-apiserver-k8s-ctr            1/1     Running   0          82s   192.168.10.100   k8s-ctr   <none>           <none>
# kube-controller-manager-k8s-ctr   1/1     Running   0          82s   192.168.10.100   k8s-ctr   <none>           <none>
# kube-proxy-qhqpz                  1/1     Running   0          76s   192.168.10.100   k8s-ctr   <none>           <none>
# kube-scheduler-k8s-ctr            1/1     Running   0          82s   192.168.10.100   k8s-ctr   <none>           <none>
# Control Plane 구성 요소들은 모두 Running 상태입니다. CoreDNS Pod는 CNI가 설치되지 않아 Pending 상태입니다.

# 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   85s
# CoreDNS 서비스는 레거시 호환성을 위해 kube-dns라는 이름으로 생성됩니다. ClusterIP는 10.96.0.10입니다.

# cluster-info ConfigMap 공개 : cluster-info는 '신원 확인 전, 최소한의 신뢰 부트스트랩 데이터'
kubectl -n kube-public get configmap cluster-info
# NAME           DATA   AGE
# cluster-info   2      90s
# cluster-info ConfigMap이 kube-public 네임스페이스에 생성되었습니다. 이는 인증되지 않은 사용자도 접근할 수 있는 공개 정보입니다.

kubectl -n kube-public get configmap cluster-info -o yaml
# apiVersion: v1
# data:
#   jws-kubeconfig-123456: eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQ1NiJ9..cu7Nc8yJNTLPRZOiaIUD7y6aANtYy6YKFwNZUL5LpMs
#   kubeconfig: |
#     apiVersion: v1
#     clusters:
#     - cluster:
#         certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJUVFnd2ViNU1Fd0l3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBeE1qQXhOek0xTlROYUZ3MHpOakF4TVRneE56UXdOVE5hTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUMzanZiYjFhNHVwU2dxSGdIWWZ1cnR0QVJ3akZyWCtOV0JvYTUwdFpNUGhHRWZoQVBJRHFURm00OTYKZ0FDRXk5ZVhBZldDSlVPS0pjaXlvWWdFcHk5M2E2MnVnN2VlbFRXQ0haM3ZhRUFZNEt6UVRpWTdkekZIVGYxLwpXdEZRSU1qOElTYk9oN1ZaTlQyUVRTQTJjZkhjUUNBcWtvMkRLdWxaNHhVbzhxNHVCQ0hwbStLMEhiK3FEb0JECk13c0tLNWJNakt5anZJRURHUzVQNE5KQ0lmeVhHL0ljM3pEMWJpdFFMbW9FWGpJeklyTGZvNzc4VXRwVWdOeFcKaW5YNWc1ZUJvc2FpeHNpcm9sc3ZkMVRxMDMwRHFPNTFaSGlGVmxsblQvVk9xZnh1WmlKU0d3RWx0cVZlcTRMWQp5dldpOHg5TG16aDRHcUh4TmJUaU5HVSt4ajk5QWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRa2JqWStYbHQ1eExHeUlXQ3E1aFR5UDlGZVh6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQVVEdXpURkdKRgpjU2RmYm5RL3BZUndSOG9LZ1diMHg3SGNvVEZBT2tDcERhUm9LVVFrOGhmYTJDRnpsS0dYZDdLTHRKOU9JMVBaCkdiUnFKNXJyRWR3dVFhUnB4elNJN1BtWlpmdHMwekoyNHkwYlJyV0orUEFtSlNBdFVyUUM5Y3JORVNVMkRQaHYKYUVVTjVCZGJBYjRPOWk5TFNmdkdaQWV3bW5QNDVTOFAyS1JCZTdBZit0NjU2UE5oMysxdW1jVkdSZldFdi9ZNQpJUVR2MVRIVlJ6NjRlTE1RMnVEQUFHQ0t1cGp0RlFXZEhwSjA1dzNObjdZMFhxeGovTEdaSTRXMHBnVnl4QUJkCnpwN0lZWXJFS21RZDlNUzJrZXRxZVB6WDFOa1VmcXZQckNJbWZtZ1BPVEYrTnZGS1NSM2xUY3dVVHRQVHJDUEoKUzJ6eU1rVlIraVRxCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
#         server: https://192.168.10.100:6443
#       name: ""
#     contexts: null
#     current-context: ""
#     kind: Config
#     preferences: {}
#     users: null
# kind: ConfigMap
# metadata:
#   creationTimestamp: "2026-01-20T17:40:59Z"
#   name: cluster-info
#   namespace: kube-public
#   resourceVersion: "316"
#   uid: dc9196fb-1749-48cc-9ea5-bf788fb48f21
# cluster-info ConfigMap에는 API Server 엔드포인트와 CA 인증서가 포함된 kubeconfig가 저장되어 있습니다.
# jws-kubeconfig-123456는 JWT(JSON Web Token) 서명된 kubeconfig로, kubeadm join 시 사용됩니다.

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
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 4686048711720833794 (0x41083079be4c1302)
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: CN=kubernetes
#         Validity
#             Not Before: Jan 20 17:35:53 2026 GMT
#             Not After : Jan 18 17:40:53 2036 GMT
#         Subject: CN=kubernetes
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:b7:8e:f6:db:d5:ae:2e:a5:28:2a:1e:01:d8:7e:
#                     ...
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Key Encipherment, Certificate Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier: 
#                 24:6E:36:3E:5E:5B:79:C4:B1:B2:21:60:AA:E6:14:F2:3F:D1:5E:5F
#             X509v3 Subject Alternative Name: 
#                 DNS:kubernetes
#     Signature Algorithm: sha256WithRSAEncryption
#     Signature Value:
#         14:0e:ec:d3:14:62:45:71:27:5f:6e:74:3f:a5:84:70:47:ca:
#         ...
# CA 인증서의 상세 정보를 확인할 수 있습니다. 이 인증서는 10년간 유효하며(Not After: Jan 18 17:40:53 2036 GMT), 
# 클러스터의 루트 CA 역할을 합니다.

curl -s -k https://192.168.10.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info | jq
# {
#   "kind": "ConfigMap",
#   "apiVersion": "v1",
#   "metadata": {
#     "name": "cluster-info",
#     "namespace": "kube-public",
#     "uid": "dc9196fb-1749-48cc-9ea5-bf788fb48f21",
#     "resourceVersion": "316",
#     "creationTimestamp": "2026-01-20T17:40:59Z",
#     "managedFields": [
#       {
#         "manager": "kubeadm",
#         "operation": "Update",
#         "apiVersion": "v1",
#         "time": "2026-01-20T17:40:59Z",
#         "fieldsType": "FieldsV1",
#         "fieldsV1": {
#           "f:data": {
#             ".": {},
#             "f:kubeconfig": {}
#           }
#         }
#       },
#       {
#         "manager": "kube-controller-manager",
#         "operation": "Update",
#         "apiVersion": "v1",
#         "time": "2026-01-20T17:41:05Z",
#         "fieldsType": "FieldsV1",
#         "fieldsV1": {
#           "f:data": {
#             "f:jws-kubeconfig-123456": {}
#           }
#         }
#       }
#     ]
#   },
#   "data": {
#     "jws-kubeconfig-123456": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQ1NiJ9..cu7Nc8yJNTLPRZOiaIUD7y6aANtYy6YKFwNZUL5LpMs",
#     "kubeconfig": "apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJUVFnd2ViNU1Fd0l3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBeE1qQXhOek0xTlROYUZ3MHpOakF4TVRneE56UXdOVE5hTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUMzanZiYjFhNHVwU2dxSGdIWWZ1cnR0QVJ3akZyWCtOV0JvYTUwdFpNUGhHRWZoQVBJRHFURm00OTYKZ0FDRXk5ZVhBZldDSlVPS0pjaXlvWWdFcHk5M2E2MnVnN2VlbFRXQ0haM3ZhRUFZNEt6UVRpWTdkekZIVGYxLwpXdEZRSU1qOElTYk9oN1ZaTlQyUVRTQTJjZkhjUUNBcWtvMkRLdWxaNHhVbzhxNHVCQ0hwbStLMEhiK3FEb0JECk13c0tLNWJNakt5anZJRURHUzVQNE5KQ0lmeVhHL0ljM3pEMWJpdFFMbW9FWGpJeklyTGZvNzc4VXRwVWdOeFcKaW5YNWc1ZUJvc2FpeHNpcm9sc3ZkMVRxMDMwRHFPNTFaSGlGVmxsblQvVk9xZnh1WmlKU0d3RWx0cVZlcTRMWQp5dldpOHg5TG16aDRHcUh4TmJUaU5HVSt4ajk5QWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRa2JqWStYbHQ1eExHeUlXQ3E1aFR5UDlGZVh6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQVVEdXpURkdKRgpjU2RmYm5RL3BZUndSOG9LZ1diMHg3SGNvVEZBT2tDcERhUm9LVVFrOGhmYTJDRnpsS0dYZDdLTHRKOU9JMVBaCkdiUnFKNXJyRWR3dVFhUnB4elNJN1BtWlpmdHMwekoyNHkwYlJyV0orUEFtSlNBdFVyUUM5Y3JORVNVMkRQaHYKYUVVTjVCZGJBYjRPOWk5TFNmdkdaQWV3bW5QNDVTOFAyS1JCZTdBZit0NjU2UE5oMysxdW1jVkdSZldFdi9ZNQpJUVR2MVRIVlJ6NjRlTE1RMnVEQUFHQ0t1cGp0RlFXZEhwSjA1dzNObjdZMFhxeGovTEdaSTRXMHBnVnl4QUJkCnpwN0lZWXJFS21RZDlNUzJrZXRxZVB6WDFOa1VmcXZQckNJbWZtZ1BPVEYrTnZGS1NSM2xUY3dVVHRQVHJDUEoKUzJ6eU1rVlIraVRxCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\n    server: https://192.168.10.100:6443\n  name: \"\"\ncontexts: null\ncurrent-context: \"\"\nkind: Config\npreferences: {}\nusers: null\n"
#   }
# }
# 인증 없이도 cluster-info ConfigMap에 접근할 수 있습니다. 이는 kubeadm join 시 워커 노드가 클러스터 정보를 얻기 위해 필요합니다.
# kube-controller-manager가 bootstrap-signer 컨트롤러를 통해 jws-kubeconfig-123456를 추가했습니다.

curl -s -k https://192.168.10.100:6443/api/v1/namespaces/default/pods # X
# {
#   "kind": "Status",
#   "apiVersion": "v1",
#   "metadata": {},
#   "status": "Failure",
#   "message": "pods is forbidden: User \"system:anonymous\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
#   "reason": "Forbidden",
#   "details": {
#     "kind": "pods"
#   },
#   "code": 403
# }
# 인증 없이는 다른 리소스에 접근할 수 없습니다. cluster-info ConfigMap만 예외적으로 인증 없이 접근 가능합니다.

kubectl -n kube-public get role
# NAME                                   CREATED AT
# kubeadm:bootstrap-signer-clusterinfo   2026-01-20T17:40:59Z
# system:controller:bootstrap-signer     2026-01-20T17:40:59Z
# cluster-info ConfigMap에 대한 접근 권한을 부여하는 Role이 생성되었습니다.

kubectl -n kube-public get rolebinding
# NAME                                   ROLE                                        AGE
# kubeadm:bootstrap-signer-clusterinfo   Role/kubeadm:bootstrap-signer-clusterinfo   2m17s
# system:controller:bootstrap-signer     Role/system:controller:bootstrap-signer     2m17s
# RoleBinding을 통해 system:unauthenticated 그룹에 cluster-info ConfigMap 접근 권한이 부여되었습니다.

 

kubeadm init 실행 시 클러스터 부트스트랩을 위해 다음과 같은 객체들이 자동으로 생성됩니다.

 

1. Namespace: kube-public

kube-public 네임스페이스는 클러스터 전체에 공개된 정보를 저장하기 위한 특별한 네임스페이스입니다. 이 네임스페이스의 리소스는 인증되지 않은 사용자도 접근할 수 있도록 설계되었습니다.

 

2. ConfigMap: cluster-info

cluster-info ConfigMap은 클러스터의 최소한의 부트스트랩 정보를 담고 있습니다. 이 ConfigMap에는 다음 정보가 포함됩니다:

  • API Server 엔드포인트: https://192.168.10.100:6443
  • CA 인증서: 클러스터의 루트 인증서 (certificate-authority-data)
  • JWT 서명된 kubeconfig: 부트스트랩 토큰과 함께 사용되는 서명된 설정 파일

이 정보는 아직 클러스터에 조인하지 않은 워커 노드가 클러스터에 합류하기 위해 필요한 최소한의 신뢰 데이터입니다.

 

3. Role + RoleBinding

kubeadm은 kube-public 네임스페이스에 특별한 Role과 RoleBinding을 생성합니다:

  • Role: kubeadm:bootstrap-signer-clusterinfo - cluster-info ConfigMap에 대한 get 권한을 정의
  • RoleBinding: system:unauthenticated 그룹에 위 Role을 바인딩

일반적으로 Kubernetes는 모든 API 요청에 인증을 요구합니다. 그러나 cluster-info ConfigMap은 예외적으로 인증 없이 접근할 수 있습니다. 이는 닭과 달걀 문제(chicken-and-egg problem)를 해결하기 위한 설계입니다:

  1. 워커 노드는 클러스터에 조인하기 전에는 클러스터 인증서를 가지고 있지 않습니다.
  2. 하지만 클러스터에 조인하려면 API Server의 엔드포인트와 CA 인증서가 필요합니다.
  3. 따라서 인증 없이도 접근 가능한 공개 엔드포인트를 통해 최소한의 정보를 제공해야 합니다.

동작 흐름:

  1. 워커 노드에서 kubeadm join 명령 실행
  2. kubeadm은 부트스트랩 토큰을 사용하여 kube-public 네임스페이스의 cluster-info ConfigMap에 접근
  3. ConfigMap에서 API Server 엔드포인트와 CA 인증서를 획득
  4. 획득한 정보를 사용하여 API Server와 통신하고 CSR(Certificate Signing Request)을 제출
  5. CSR이 승인되면 노드가 클러스터에 정식으로 조인

보안 고려사항:

cluster-info ConfigMap은 인증 없이 접근 가능하지만, 다음 정보만 포함합니다:

  • API Server 엔드포인트 (이미 공개되어야 하는 정보)
  • CA 인증서 (공개 키이므로 노출되어도 안전)
  • 부트스트랩 토큰 ID (토큰 자체는 포함되지 않음)

실제 인증은 부트스트랩 토큰을 통해 이루어지며, 이 토큰은 제한된 권한만 가지고 있어 노드 조인에 필요한 최소한의 작업만 수행할 수 있습니다.

[k8s-ctr] k8s 관련 작업 편의성 설정 : 자동 완성, kubecolor, kubectx, kubens, kube-ps, helm, k9s 설치

echo "sudo su -" >> /home/vagrant/.bashrc

# Source the completion
source <(kubectl completion bash)
source <(kubeadm completion bash)
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'source <(kubeadm completion bash)' >> /etc/profile
kubectl get <tab 2번>

# Alias kubectl to k
alias k=kubectl
complete -o default -F __start_kubectl k
echo 'alias k=kubectl' >> /etc/profile
echo 'complete -o default -F __start_kubectl k' >> /etc/profile
k get node

# kubecolor 설치 : https://kubecolor.github.io/setup/install/
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --add-repo https://kubecolor.github.io/packages/rpm/kubecolor.repo
dnf repolist
dnf install -y kubecolor
kubecolor get node

alias kc=kubecolor
echo 'alias kc=kubecolor' >> /etc/profile
kc get node
kc describe node

# Install Kubectx & Kubens"
dnf install -y git
git clone https://github.com/ahmetb/kubectx /opt/kubectx
ln -s /opt/kubectx/kubens /usr/local/bin/kubens
ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx


# Install Kubeps & Setting PS1
git clone https://github.com/jonmosco/kube-ps1.git /root/kube-ps1
cat << "EOT" >> /root/.bash_profile
source /root/kube-ps1/kube-ps1.sh
KUBE_PS1_SYMBOL_ENABLE=true
function get_cluster_short() {
  echo "$1" | cut -d . -f1
}
KUBE_PS1_CLUSTER_FUNCTION=get_cluster_short
KUBE_PS1_SUFFIX=') '
PS1='$(kube_ps1)'$PS1
EOT

# 빠져나오기
exit
exit

# 다시 접속
vagrant ssh k8s-ctr
whoami
pwd
kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab"
kubens default

# helm 3 설치 : https://helm.sh/docs/intro/install
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | DESIRED_VERSION=v3.18.6 bash
helm version

# k9s 설치 : https://github.com/derailed/k9s
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
wget https://github.com/derailed/k9s/releases/latest/download/k9s_linux_${CLI_ARCH}.tar.gz
tar -xzf k9s_linux_*.tar.gz
ls -al k9s
chown root:root k9s
mv k9s /usr/local/bin/
chmod +x /usr/local/bin/k9s
k9s

[k8s-ctr] Flannel CNI 설치 v0.27.3

Flannel은 Kubernetes에서 가장 널리 사용되는 CNI(Container Network Interface) 플러그인 중 하나입니다. VXLAN 백엔드를 사용하여 Pod 간 통신을 제공하며, 각 노드에 DaemonSet으로 배포됩니다.

# 현재 k8s 클러스터에 파드 전체 CIDR 확인
kc describe pod -n kube-system kube-controller-manager-k8s-ctr
# Name:                 kube-controller-manager-k8s-ctr
# Namespace:            kube-system
# Priority:             2000001000
# Priority Class Name:  system-node-critical
# Node:                 k8s-ctr/192.168.10.100
# ...
#     Command:
#       kube-controller-manager
#       --allocate-node-cidrs=true
#       --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
#       --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
#       --bind-address=127.0.0.1
#       --client-ca-file=/etc/kubernetes/pki/ca.crt
#       --cluster-cidr=10.244.0.0/16
#       --cluster-name=kubernetes
#       --cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
#       --cluster-signing-key-file=/etc/kubernetes/pki/ca.key
#       --controllers=*,bootstrapsigner,tokencleaner
#       --kubeconfig=/etc/kubernetes/controller-manager.conf
#       --leader-elect=true
#       --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
#       --root-ca-file=/etc/kubernetes/pki/ca.crt
#       --service-account-private-key-file=/etc/kubernetes/pki/sa.key
#       --service-cluster-ip-range=10.96.0.0/16
#       --use-service-account-credentials=true
# kube-controller-manager의 --cluster-cidr=10.244.0.0/16 옵션을 통해 클러스터 전체 Pod CIDR을 확인할 수 있습니다.
# --allocate-node-cidrs=true는 각 노드에 Pod CIDR을 할당하도록 설정합니다.
# --service-cluster-ip-range=10.96.0.0/16는 Service의 ClusterIP 범위입니다.

# 노드별 파드 CIDR 확인 
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
# k8s-ctr 10.244.0.0/24
# 각 노드는 클러스터 CIDR(10.244.0.0/16)에서 서브넷을 할당받습니다. k8s-ctr 노드는 10.244.0.0/24를 할당받았습니다.
# 이는 최대 254개의 Pod IP를 제공할 수 있습니다 (10.244.0.1 ~ 10.244.0.254).

# 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
# "flannel" has been added to your repositories
# Flannel 공식 Helm 저장소를 추가합니다.

helm repo update
# Hang tight while we grab the latest from your chart repositories...
# ...Successfully got an update from the "flannel" chart repository
# Update Complete. ⎈Happy Helming!⎈
# Helm 저장소를 업데이트하여 최신 차트 정보를 가져옵니다.

kubectl create namespace kube-flannel
# namespace/kube-flannel created
# 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
# Flannel 설정 파일을 생성합니다.
# podCidr: 클러스터 전체 Pod CIDR (kube-controller-manager의 --cluster-cidr와 일치해야 함)
# cniBinDir: CNI 바이너리 파일이 설치될 디렉토리
# cniConfDir: CNI 설정 파일이 생성될 디렉토리
# --ip-masq: 외부 네트워크로 나가는 트래픽에 IP 마스커레이딩 적용
# --kube-subnet-mgr: Kubernetes API를 통해 서브넷 정보를 관리
# --iface=enp0s9: Flannel이 사용할 네트워크 인터페이스 지정 (192.168.10.100이 할당된 인터페이스)
# backend: vxlan - VXLAN 터널링을 사용하여 Pod 간 통신 제공

helm install flannel flannel/flannel --namespace kube-flannel --version 0.27.3 -f flannel.yaml
# NAME: flannel
# LAST DEPLOYED: Wed Jan 21 02:54:57 2026
# NAMESPACE: kube-flannel
# STATUS: deployed
# REVISION: 1
# TEST SUITE: None
# Flannel v0.27.3을 Helm을 통해 설치합니다. DaemonSet이 각 노드에 배포됩니다.

# 확인
helm list -A
# NAME    NAMESPACE       REVISION        UPDATED                                      STATUS          CHART           APP VERSION
# flannel kube-flannel    1               2026-01-21 02:54:57.773349078 +0900 KST      deployed        flannel-v0.27.3 v0.27.3
# 설치된 Helm 릴리스를 확인합니다.

helm get values -n kube-flannel flannel
# USER-SUPPLIED VALUES:
# flannel:
#   args:
#   - --ip-masq
#   - --kube-subnet-mgr
#   - --iface=enp0s9
#   backend: vxlan
#   cniBinDir: /opt/cni/bin
#   cniConfDir: /etc/cni/net.d
# podCidr: 10.244.0.0/16
# 적용된 설정 값을 확인합니다.

kubectl get ds,pod,cm -n kube-flannel -owide
# NAME                             DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS     IMAGES                               SELECTOR
# daemonset.apps/kube-flannel-ds   1         1         0       1            0           <none>          12s   kube-flannel   ghcr.io/flannel-io/flannel:v0.27.3   app=flannel
# 
# NAME                        READY   STATUS     RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
# pod/kube-flannel-ds-w9s2j   0/1     Init:1/2   0          12s   192.168.10.100   k8s-ctr   <none>           <none>
# 
# NAME                         DATA   AGE
# configmap/kube-flannel-cfg   2      12s
# configmap/kube-root-ca.crt   1      24s
# DaemonSet이 배포되었지만 아직 초기화 중입니다. Init 컨테이너가 CNI 바이너리와 설정 파일을 설치하고 있습니다.

kc describe cm -n kube-flannel kube-flannel-cfg
# Name:         kube-flannel-cfg
# Namespace:    kube-flannel
# Labels:       app=flannel
#               app.kubernetes.io/managed-by=Helm
#               tier=node
# Annotations:  meta.helm.sh/release-name: flannel
#               meta.helm.sh/release-namespace: kube-flannel
# 
# Data
# ====
# cni-conf.json:
# ----
# {
#   "name": "cbr0",
#   "cniVersion": "0.3.1",
#   "plugins": [
#     {
#       "type": "flannel",
#       "delegate": {
#         "hairpinMode": true,
#         "isDefaultGateway": true
#       }
#     },
#     {
#       "type": "portmap",
#       "capabilities": {
#         "portMappings": true
#       }
#     }
#   ]
# }
# 
# net-conf.json:
# ----
# {
#   "Network": "10.244.0.0/16",
#   "Backend": {
#     "Type": "vxlan"
#   }
# }
# ConfigMap에는 CNI 설정(cni-conf.json)과 Flannel 네트워크 설정(net-conf.json)이 포함되어 있습니다.
# cni-conf.json은 containerd가 사용할 CNI 플러그인 설정이고, net-conf.json은 Flannel의 네트워크 구성입니다.

kc describe ds -n kube-flannel
# ...
#     Command:
#       /opt/bin/flanneld
#       --ip-masq
#       --kube-subnet-mgr
#       --iface=enp0s9
# Flannel DaemonSet의 컨테이너가 실행하는 명령어를 확인할 수 있습니다.
# flanneld는 각 노드에서 실행되며, Kubernetes API를 통해 서브넷 정보를 관리하고 VXLAN 인터페이스를 구성합니다.

# flannel cni 바이너리 설치 확인
ls -l /opt/cni/bin/
# total 66032
# -rwxr-xr-x. 1 root root 3239200 Dec 12  2024 bandwidth
# -rwxr-xr-x. 1 root root 3731632 Dec 12  2024 bridge
# -rwxr-xr-x. 1 root root 9123544 Dec 12  2024 dhcp
# -rwxr-xr-x. 1 root root 3379872 Dec 12  2024 dummy
# -rwxr-xr-x. 1 root root 3742888 Dec 12  2024 firewall
# -rwxr-xr-x. 1 root root 2903098 Jan 21 02:55 flannel
# -rwxr-xr-x. 1 root root 3383408 Dec 12  2024 host-device
# -rwxr-xr-x. 1 root root 2812400 Dec 12  2024 host-local
# -rwxr-xr-x. 1 root root 3380928 Dec 12  2024 ipvlan
# -rw-r--r--. 1 root root   11357 Dec 12  2024 LICENSE
# -rwxr-xr-x. 1 root root 2953200 Dec 12  2024 loopback
# -rwxr-xr-x. 1 root root 3448024 Dec 12  2024 macvlan
# -rwxr-xr-x. 1 root root 3312488 Dec 12  2024 portmap
# -rwxr-xr-x. 1 root root 3524072 Dec 12  2024 ptp
# -rw-r--r--. 1 root root    2343 Dec 12  2024 README.md
# -rwxr-xr-x. 1 root root 3091976 Dec 12  2024 sbr
# -rwxr-xr-x. 1 root root 2526944 Dec 12  2024 static
# -rwxr-xr-x. 1 root root 3516272 Dec 12  2024 tap
# -rwxr-xr-x. 1 root root 2956032 Dec 12  2024 tuning
# -rwxr-xr-x. 1 root root 3380544 Dec 12  2024 vlan
# -rwxr-xr-x. 1 root root 3160560 Dec 12  2024 vrf
# Flannel Init 컨테이너가 flannel CNI 바이너리를 /opt/cni/bin/ 디렉토리에 설치했습니다.
# 이 디렉토리는 containerd가 CNI 플러그인을 찾는 기본 경로입니다.

# cni 설정 정보 확인
tree /etc/cni/net.d/
# /etc/cni/net.d/
# └── 10-flannel.conflist
# 
# 1 directory, 1 file
# CNI 설정 파일이 /etc/cni/net.d/ 디렉토리에 생성되었습니다.
# 파일 이름의 숫자(10)는 정렬 순서를 의미하며, containerd는 이 디렉토리의 .conflist 파일을 읽어 CNI 설정을 로드합니다.

cat /etc/cni/net.d/10-flannel.conflist | jq
# {
#   "name": "cbr0",
#   "cniVersion": "0.3.1",
#   "plugins": [
#     {
#       "type": "flannel",
#       "delegate": {
#         "hairpinMode": true,
#         "isDefaultGateway": true
#       }
#     },
#     {
#       "type": "portmap",
#       "capabilities": {
#         "portMappings": true
#       }
#     }
#   ]
# }
# CNI 설정 파일의 내용입니다. flannel 플러그인과 portmap 플러그인(포트 포워딩용)이 구성되어 있습니다.
# cbr0은 컨테이너 브리지 이름이며, 각 Pod는 이 브리지를 통해 통신합니다.

# cni 설치 후 아래 상태(conditions) 정상 확인
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"
#       },
#       {
#         "Config": {
#           "CNIVersion": "0.3.1",
#           "Name": "cbr0",
#           "Plugins": [
#             {
#               "Network": {
#                 "ipam": {},
#                 "type": "flannel"
#               },
#               "Source": "{\"delegate\":{\"hairpinMode\":true,\"isDefaultGateway\":true},\"type\":\"flannel\"}"
#             },
#             {
#               "Network": {
#                 "capabilities": {
#                   "portMappings": true
#                 },
#                 "ipam": {},
#                 "type": "portmap"
#               },
#               "Source": "{\"capabilities\":{\"portMappings\":true},\"type\":\"portmap\"}"
#             }
#           ],
#           "Source": "{\n  \"name\": \"cbr0\",\n  \"cniVersion\": \"0.3.1\",\n  \"plugins\": [\n    {\n      \"type\": \"flannel\",\n      \"delegate\": {\n        \"hairpinMode\": true,\n        \"isDefaultGateway\": true\n      }\n    },\n    {\n      \"type\": \"portmap\",\n      \"capabilities\": {\n        \"portMappings\": true\n      }\n    }\n  ]\n}\n"
#         },
#         "IFName": "eth0"
#       }
#     ],
#     "PluginConfDir": "/etc/cni/net.d",
#     "PluginDirs": [
#       "/opt/cni/bin"
#     ],
#     "PluginMaxConfNum": 1,
#     "Prefix": "eth"
#   },
#   ...
#   "status": {
#     "conditions": [
#       {
#         "message": "",
#         "reason": "",
#         "status": true,
#         "type": "RuntimeReady"
#       },
#       {
#         "message": "",
#         "reason": "",
#         "status": true,
#         "type": "NetworkReady"
#       },
#       {
#         "message": "",
#         "reason": "",
#         "status": true,
#         "type": "ContainerdHasNoDeprecationWarnings"
#       }
#     ]
#   }
# }
# containerd가 CNI 설정을 정상적으로 로드했으며, NetworkReady 상태가 true입니다.
# cbr0 네트워크가 eth0 인터페이스로 매핑되어 Pod 네트워크가 구성되었습니다.

# coredns 파드 정상 기동 확인
kubectl get pod -n kube-system -owide
# NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
# coredns-668d6bf9bc-47zx5          1/1     Running   0          14m   10.244.0.3       k8s-ctr   <none>           <none>
# coredns-668d6bf9bc-77pjv          1/1     Running   0          14m   10.244.0.2       k8s-ctr   <none>           <none>
# etcd-k8s-ctr                      1/1     Running   0          14m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-apiserver-k8s-ctr            1/1     Running   0          14m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-controller-manager-k8s-ctr   1/1     Running   0          14m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-proxy-qhqpz                  1/1     Running   0          14m   192.168.10.100   k8s-ctr   <none>           <none>
# kube-scheduler-k8s-ctr            1/1     Running   0          14m   192.168.10.100   k8s-ctr   <none>           <none>
# CNI 설치 전에는 Pending 상태였던 CoreDNS Pod들이 이제 Running 상태가 되었고, Pod IP(10.244.0.2, 10.244.0.3)가 할당되었습니다.
# 이는 Flannel이 정상적으로 작동하여 Pod 네트워크를 구성했음을 의미합니다.

# network 정보 확인
ip -c route | grep 10.244.
# 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
# 노드의 Pod CIDR(10.244.0.0/24)에 대한 라우팅 정보입니다. cni0 브리지를 통해 해당 서브넷으로 트래픽이 전달됩니다.

ip addr
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#     inet 127.0.0.1/8 scope host lo
#        valid_lft forever preferred_lft forever
#     inet6 ::1/128 scope host noprefixroute 
#        valid_lft forever preferred_lft forever
# 2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:90:ea:eb brd ff:ff:ff:ff:ff:ff
#     altname enx08002790eaeb
#     inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s8
#        valid_lft 73368sec preferred_lft 73368sec
# 3: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:55:33:36 brd ff:ff:ff:ff:ff:ff
#     altname enx080027553336
#     inet 192.168.10.100/24 brd 192.168.10.255 scope global noprefixroute enp0s9
#        valid_lft forever preferred_lft forever
# 4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
#     link/ether ca:69:44:50:93:9e brd ff:ff:ff:ff:ff:ff
#     inet 10.244.0.0/32 scope global flannel.1
#        valid_lft forever preferred_lft forever
#     inet6 fe80::c869:44ff:fe50:939e/64 scope link proto kernel_ll 
#        valid_lft forever preferred_lft forever
# 5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
#     link/ether 1a:7c:2c:59:31:f9 brd ff:ff:ff:ff:ff:ff
#     inet 10.244.0.1/24 brd 10.244.0.255 scope global cni0
#        valid_lft forever preferred_lft forever
#     inet6 fe80::187c:2cff:fe59:31f9/64 scope link proto kernel_ll 
#        valid_lft forever preferred_lft forever
# 6: vethf88c96d1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default qlen 1000
#     link/ether e6:5d:04:6c:d2:4b brd ff:ff:ff:ff:ff:ff link-netns cni-e5110b94-cd63-c3c5-cbe9-2cae2adf6077
#     inet6 fe80::e45d:4ff:fe6c:d24b/64 scope link proto kernel_ll 
#        valid_lft forever preferred_lft forever
# 7: vethf09464e2@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default qlen 1000
#     link/ether b6:37:ad:b1:90:89 brd ff:ff:ff:ff:ff:ff link-netns cni-5db9e363-6377-1f95-6d86-ad565ecce772
#     inet6 fe80::b437:adff:feb1:9089/64 scope link proto kernel_ll 
#        valid_lft forever preferred_lft forever
# 네트워크 인터페이스 정보를 확인할 수 있습니다:
# - flannel.1: VXLAN 터널 인터페이스, 노드 간 Pod 통신을 위한 오버레이 네트워크 (10.244.0.0/32)
# - cni0: 컨테이너 브리지, 같은 노드 내 Pod 간 통신을 위한 L2 브리지 (10.244.0.1/24)
# - veth*: 가상 이더넷 쌍, 각 Pod의 네트워크 네임스페이스를 호스트 네임스페이스와 연결
# MTU가 1450인 것은 VXLAN 헤더(50바이트)를 고려한 값입니다.

bridge link
# 6: vethf88c96d1@enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 master cni0 state forwarding priority 32 cost 2 
# 7: vethf09464e2@enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 master cni0 state forwarding priority 32 cost 2 
# veth 인터페이스들이 cni0 브리지에 연결되어 있음을 확인할 수 있습니다.
# 각 veth는 하나의 Pod를 나타내며, 브리지를 통해 같은 노드의 다른 Pod와 통신할 수 있습니다.

lsns -t net
#         NS TYPE NPROCS   PID USER       NETNSID NSFS                                                COMMAND
# 4026531840 net     169     1 root    unassigned                                                     /usr/lib/systemd/systemd
# 4026532128 net       1   645 root    unassigned                                                     ├─/usr/sbin/irqbalance
# 4026532202 net       1   812 polkitd unassigned                                                     └─/usr/lib/polkit-1/polkitd
# 4026532300 net       2 10163 65535            0 /run/netns/cni-e5110b94-cd63-c3c5-cbe9-2cae2adf6077 /pause
# 4026532375 net       2 10155 65535            0 /run/netns/cni-5db9e363-6377-1f95-6d86-ad565ecce772 /pause
# 각 Pod는 독립적인 네트워크 네임스페이스를 가지고 있습니다.
# /run/netns/cni-* 경로는 Pod의 네트워크 네임스페이스를 나타내며, pause 컨테이너가 이 네임스페이스를 유지합니다.

# iptables 규칙 확인
iptables -t nat -S
iptables -t filter -S

iptables-save
# (모든 iptables 규칙을 저장 형식으로 출력합니다. 백업 및 복원에 사용할 수 있습니다.)
# iptables 규칙은 kube-proxy와 Flannel에 의해 자동으로 관리됩니다.
# NAT 테이블은 Service의 ClusterIP를 Pod IP로 변환하고, FILTER 테이블은 네트워크 정책을 적용합니다.

[k8s-ctr] 노드 정보 확인, 기본 환경 정보 출력 비교, sysctl 변경 확인

# # kubelet 활성화 확인 : 실제 기동은 kubeadm init 후에 시작됨
systemctl is-active kubelet
# active
# kubelet 서비스가 활성화되어 실행 중입니다.

systemctl status kubelet --no-pager
# ● kubelet.service - kubelet: The Kubernetes Node Agent
#      Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
#     Drop-In: /usr/lib/systemd/system/kubelet.service.d
#              └─10-kubeadm.conf
#      Active: active (running) since Wed 2026-01-21 02:41:00 KST; 38min ago
#  Invocation: c34c2c29a1c744cb87e847e22c9df203
#        Docs: https://kubernetes.io/docs/
#    Main PID: 8074 (kubelet)
#       Tasks: 13 (limit: 18742)
#      Memory: 41.7M (peak: 44M)
#         CPU: 1min 15.492s
#      CGroup: /system.slice/kubelet.service
#              └─8074 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kub…
# kubelet이 systemd 서비스로 실행 중이며, 10-kubeadm.conf 드롭인 파일을 통해 kubeadm 설정을 사용합니다.
# kubeadm init 실행 후 kubelet이 시작되어 Control Plane Pod들을 관리하고 있습니다.

# 노드 정보 확인 : 일반 워크로드가 Control Plane에 스케줄 X
kc describe node
# Name:               k8s-ctr
# Roles:              control-plane
# Labels:             beta.kubernetes.io/arch=arm64
#                     beta.kubernetes.io/os=linux
#                     kubernetes.io/arch=arm64
#                     kubernetes.io/hostname=k8s-ctr
#                     kubernetes.io/os=linux
#                     node-role.kubernetes.io/control-plane=
#                     node.kubernetes.io/exclude-from-external-load-balancers=
# Annotations:        flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"ca:69:44:50:93:9e"}
#                     flannel.alpha.coreos.com/backend-type: vxlan
#                     flannel.alpha.coreos.com/kube-subnet-manager: true
#                     flannel.alpha.coreos.com/public-ip: 192.168.10.100
#                     kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock
#                     node.alpha.kubernetes.io/ttl: 0
#                     volumes.kubernetes.io/controller-managed-attach-detach: true
# CreationTimestamp:  Wed, 21 Jan 2026 02:40:58 +0900
# Taints:             node-role.kubernetes.io/control-plane:NoSchedule
# Unschedulable:      false
# 노드에 control-plane 레이블과 NoSchedule 태인트가 설정되어 있어 일반 워크로드가 스케줄되지 않습니다.
# Flannel 관련 어노테이션을 통해 VXLAN 백엔드 정보를 확인할 수 있습니다.

# Conditions:
#   Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
#   ----                 ------  -----------------                 ------------------                ------                       -------
#   NetworkUnavailable   False   Wed, 21 Jan 2026 02:55:12 +0900   Wed, 21 Jan 2026 02:55:12 +0900   FlannelIsUp                  Flannel is running on this node
#   MemoryPressure       False   Wed, 21 Jan 2026 03:16:00 +0900   Wed, 21 Jan 2026 02:40:57 +0900   KubeletHasSufficientMemory   kubelet has sufficient memory available
#   DiskPressure         False   Wed, 21 Jan 2026 03:16:00 +0900   Wed, 21 Jan 2026 02:40:57 +0900   KubeletHasNoDiskPressure     kubelet has no disk pressure
#   PIDPressure          False   Wed, 21 Jan 2026 03:16:00 +0900   Wed, 21 Jan 2026 02:40:57 +0900   KubeletHasSufficientPID      kubelet has sufficient PID available
#   Ready                True    Wed, 21 Jan 2026 03:16:00 +0900   Wed, 21 Jan 2026 02:55:17 +0900   KubeletReady                 kubelet is posting ready status
# 노드의 상태 조건을 확인할 수 있습니다. NetworkUnavailable이 False이고 Ready가 True이므로 노드가 정상적으로 작동 중입니다.
# Flannel이 실행되어 네트워크가 구성되었고, 리소스 압박 없이 정상 상태입니다.

# Addresses:
#   InternalIP:  192.168.10.100
#   Hostname:    k8s-ctr
# Capacity:
#   cpu:                4
#   ephemeral-storage:  60970Mi
#   hugepages-1Gi:      0
#   hugepages-2Mi:      0
#   hugepages-32Mi:     0
#   hugepages-64Ki:     0
#   memory:             2893976Ki
#   pods:               110
# Allocatable:
#   cpu:                4
#   ephemeral-storage:  57538510753
#   hugepages-1Gi:      0
#   hugepages-2Mi:      0
#   hugepages-32Mi:     0
#   hugepages-64Ki:     0
#   memory:             2791576Ki
#   pods:               110
# 노드의 리소스 용량과 할당 가능한 리소스를 확인할 수 있습니다. 최대 110개의 Pod를 스케줄할 수 있습니다.

# PodCIDR:                      10.244.0.0/24
# PodCIDRs:                     10.244.0.0/24
# 노드에 할당된 Pod CIDR입니다. kube-controller-manager의 --allocate-node-cidrs=true 옵션에 의해 자동 할당되었습니다.

# Non-terminated Pods:          (8 in total)
#   Namespace                   Name                               CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
#   ---------                   ----                               ------------  ----------  ---------------  -------------  ---
#   kube-flannel                kube-flannel-ds-w9s2j              100m (2%)     0 (0%)      50Mi (1%)        0 (0%)         24m
#   kube-system                 coredns-668d6bf9bc-47zx5           100m (2%)     0 (0%)      70Mi (2%)        170Mi (6%)     38m
#   kube-system                 coredns-668d6bf9bc-77pjv          100m (2%)     0 (0%)      70Mi (2%)        170Mi (6%)     38m
#   kube-system                 etcd-k8s-ctr                       100m (2%)     0 (0%)      100Mi (3%)       0 (0%)         38m
#   kube-system                 kube-apiserver-k8s-ctr              250m (6%)     0 (0%)      0 (0%)           0 (0%)         38m
#   kube-system                 kube-controller-manager-k8s-ctr    200m (5%)     0 (0%)      0 (0%)           0 (0%)         38m
#   kube-system                 kube-proxy-qhqpz                   0 (0%)        0 (0%)      0 (0%)           0 (0%)         38m
#   kube-system                 kube-scheduler-k8s-ctr               100m (2%)     0 (0%)      0 (0%)           0 (0%)         38m
# Allocated resources:
#   (Total limits may be over 100 percent, i.e., overcommitted.)
#   Resource           Requests     Limits
#   --------           --------     ------
#   cpu                950m (23%)   0 (0%)
#   memory             290Mi (10%)  340Mi (12%)
# 노드에서 실행 중인 Pod들의 리소스 사용량을 확인할 수 있습니다. 총 8개의 Pod가 실행 중이며, CPU는 23%, 메모리는 10% 사용 중입니다.

# 기본 환경 정보 출력 저장
cat /etc/sysconfig/kubelet
# KUBELET_EXTRA_ARGS=

tree /etc/kubernetes  | tee -a etc_kubernetes-2.txt
# /etc/kubernetes
# ├── admin.conf
# ├── controller-manager.conf
# ├── kubelet.conf
# ├── manifests
# │   ├── etcd.yaml
# │   ├── kube-apiserver.yaml
# │   ├── kube-controller-manager.yaml
# │   └── kube-scheduler.yaml
# ├── pki
# │   ├── apiserver.crt
# │   ├── apiserver-etcd-client.crt
# │   ├── apiserver-etcd-client.key
# │   ├── apiserver.key
# │   ├── apiserver-kubelet-client.crt
# │   ├── apiserver-kubelet-client.key
# │   ├── ca.crt
# │   ├── ca.key
# │   ├── 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
# └── super-admin.conf
# 
# 4 directories, 31 files
# kubeadm init 실행 후 생성된 파일들을 확인할 수 있습니다:
# - kubeconfig 파일들: admin.conf, controller-manager.conf, kubelet.conf, scheduler.conf, super-admin.conf
# - manifests: Static Pod 매니페스트 파일들 (etcd, kube-apiserver, kube-controller-manager, kube-scheduler)
# - pki: 인증서 및 키 파일들 (CA, API Server, etcd, front-proxy 등)

tree /var/lib/kubelet | tee -a var_lib_kubelet-2.txt
# /var/lib/kubelet
# ├── checkpoints
# ├── config.yaml
# ├── cpu_manager_state
# ├── device-plugins
# │   └── kubelet.sock
# ├── kubeadm-flags.env
# ├── memory_manager_state
# ├── pki
# │   ├── kubelet-client-2026-01-21-02-40-56.pem
# │   ├── kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2026-01-21-02-40-56.pem
# │   ├── kubelet.crt
# │   └── kubelet.key
# ├── plugins
# ├── plugins_registry
# ├── pod-resources
# │   └── kubelet.sock
# └── pods
#     ├── 1fac1c35f1565d90c54f12e6a7f31eac
#     │   ├── containers
#     │   │   └── etcd
#     │   │       └── 18d31be2
#     │   ├── etc-hosts
#     │   ├── plugins
#     │   └── volumes
#     ├── 20414908-ba02-4c61-a957-4a1c2f992f35
#     │   ├── containers
#     │   │   └── coredns
#     │   │       └── 3614549f
#     ├── 4c9e7ba9c576bda9272d29d2be8688bd
#     │   ├── containers
#     │   │   └── kube-scheduler
#     ├── 50c5cf354a58e388996b0acd73e093c6
#     │   ├── containers
#     │   │   └── kube-apiserver
#     ├── 7314ab3f0ec6401c196ca943fad44a05
#     │   ├── containers
#     │   │   └── kube-controller-manager
#     ├── b39e0b94-5da7-44d5-a766-f8ca9cb29201
#     │   ├── containers
#     │   │   └── coredns
#     ├── b8bf8da8-eff5-4c54-81fb-95841b60b327
#     │   ├── containers
#     │   │   └── kube-proxy
#     └── bda4ec80-fa17-444a-a8da-b89a2a3661da
#         ├── containers
#         │   ├── install-cni
#         │   ├── install-cni-plugin
#         │   └── kube-flannel
#         ├── etc-hosts
#         ├── plugins
#         │   └── kubernetes.io~empty-dir
#         │       ├── wrapped_flannel-cfg
#         │       └── wrapped_kube-api-access-mn5s4
# kubelet의 작업 디렉토리 구조입니다:
# - config.yaml: kubelet 설정 파일
# - kubeadm-flags.env: kubeadm이 설정한 kubelet 플래그
# - pki: kubelet 클라이언트 인증서 및 키
# - pods: 각 Pod의 런타임 데이터 (컨테이너, 볼륨, 플러그인 등)
# - device-plugins, pod-resources: 소켓 파일들

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

 

다음 표는 kubeadm init 및 Flannel CNI 설치 전후의 시스템 상태를 비교한 것입니다.

 

주요 디렉토리 및 파일 구조 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
/etc/kubernetes manifests/ (빈 디렉토리) admin.conf, controller-manager.conf, kubelet.conf, scheduler.conf, super-admin.conf, manifests/ (Static Pod YAML), pki/ (인증서 및 키) kubeadm init 실행 시 kubeconfig 파일, Static Pod 매니페스트, 인증서가 생성됨
/var/lib/kubelet 빈 디렉토리 config.yaml, kubeadm-flags.env, pki/ (kubelet 인증서), pods/ (Pod 런타임 데이터), checkpoints/, device-plugins/, pod-resources/ kubelet이 시작되면서 설정 파일과 Pod 데이터가 생성됨
/run/containerd/ containerd.sock, io.containerd.grpc.v1.cri, io.containerd.runtime.v2.task, io.containerd.sandbox.controller.v1.shim 동일 + io.containerd.grpc.v1.cri/containers/ (10개 컨테이너), io.containerd.grpc.v1.cri/sandboxes/ (8개 샌드박스) Control Plane Pod와 CoreDNS Pod가 실행되면서 컨테이너와 샌드박스가 생성됨

 

프로세스 및 cgroup 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
pstree containerd만 실행 중 containerd +
kubelet +여러 containerd-shim 프로세스
kubelet이 시작되고, 각 Pod마다 containerd-shim이 생성됨
systemd-cgls system.slice만 존재 system.slice + kubepods.slice
(Pod cgroup 계층)
kubelet이 Pod를 cgroup으로 관리하기 시작. kubepods-burstable.slice, kubepods-besteffort.slice 생성

 

네트워크 네임스페이스 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
lsns 기본 네임스페이스만 존재 (time, cgroup, pid, user, uts, ipc, net, mnt) 기본 네임스페이스 +
Pod별 네임스페이스(각 Pod마다 mnt, ipc, pid, cgroup, net 네임스페이스 생성)
각 Pod가 독립적인 네임스페이스를 가지게 됨. Control Plane Pod는 hostNetwork를 사용하므로 net 네임스페이스를 공유

 

네트워크 인터페이스 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
ip addr lo, enp0s8, enp0s9 lo, enp0s8, enp0s9 +
flannel.1 (VXLAN 터널),
cni0 (컨테이너 브리지),
veth* (Pod별 가상 이더넷)
Flannel CNI 설치 후 VXLAN 터널 인터페이스와 컨테이너 브리지가 생성됨. 각 Pod마다 veth 쌍이 생성됨

 

소켓 상태 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
ss -tnlp sshd(22), containerd(41597), rpcbind(111), systemd(9090) 동일 +
kube-apiserver(6443),
kubelet(10250, 10248),
kube-controller-manager(10257),
kube-scheduler(10259),
etcd(2379, 2380)
Control Plane 구성 요소들이 각각의 포트에서 리스닝 시작

 

파일시스템 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
df -hT 기본 파일시스템만 기본 파일시스템 +
overlay (각 Pod의 컨테이너 레이어), shm (각 Pod의 공유 메모리)
각 Pod의 컨테이너마다 overlay 파일시스템과 shm이 마운트됨. /dev/sda3 사용량이 2% → 4%로 증가

마운트 포인트 비교

항목 kubeadm init 전 (1) kubeadm init 후 (2) 설명
findmnt 기본 마운트 포인트만 기본 마운트 포인트 +
/run/containerd/io.containerd.grpc.v1.cri/ (각 Pod의 런타임 디렉토리),
/run/netns/cni-* (Pod 네트워크 네임스페이스),
/var/lib/kubelet/pods/* (Pod 볼륨 마운트)
Pod 실행에 필요한 런타임 디렉토리, 네트워크 네임스페이스, 볼륨이 마운트됨

 

커널 파라미터 (sysctl) 변경사항 (kubelet에 의해 변경된 파라미터)

파라미터 kubeadm init 전 (1) kubeadm init 후 (2) 설명
kernel.panic 0 10 커널 패닉 시 자동 재부팅 시간(초) 설정
kernel.panic_on_oops 1 1 (변경 없음) Oops 발생 시 패닉 유발 여부
vm.overcommit_memory 0 1 메모리 오버커밋 허용 (항상 성공)
vm.panic_on_oom 0 0 (변경 없음) OOM 발생 시 패닉 유발 여부
kernel.keys.root_maxkeys 1000000 1000000 (변경 없음) root 사용자 최대 키 수
kernel.keys.root_maxbytes 25000000 25000000 (변경 없음) root 사용자 최대 키 바이트 수

 

 

커널 파라미터 (sysctl) 변경사항 (kube-proxy에 의해 변경된 파라미터)

파라미터 kubeadm init 전 (1) kubeadm init 후 (2) 설명
net.nf_conntrack_max 65536 131072 최대 연결 추적 항목 수 (2배 증가)
net.netfilter.nf_conntrack_max 65536 131072 netfilter 연결 추적 최대값
net.netfilter.nf_conntrack_count 1 282 현재 추적 중인 연결 수 (Pod 통신으로 증가)
net.netfilter.nf_conntrack_tcp_timeout_close_wait 60 3600 TCP CLOSE_WAIT 상태 타임아웃 (1시간)
net.netfilter.nf_conntrack_tcp_timeout_established 432000 86400 TCP ESTABLISHED 상태 타임아웃 (5일 → 1일)

 

파일 시스템 통계 변경

파라미터 kubeadm init 전 (1) kubeadm init 후 (2) 설명
fs.file-nr 1312 1888 열린 파일 수 증가 (Pod 실행으로 인한 파일 핸들 증가)
fs.inode-nr 23971 43665 사용 중인 inode 수 증가 (Pod 및 컨테이너 파일 생성)
fs.dentry-state 27045 19047 48944 38417 디렉토리 엔트리 캐시 상태 변화
vi -d etc_kubernetes-1.txt etc_kubernetes-2.txt
# /etc/kubernetes                 |  /etc/kubernetes
#   └── manifests                   |  ├── admin.conf                  
#   --------------------------------|  ├── controller-manager.conf     
#   --------------------------------|  ├── kubelet.conf                
#   --------------------------------|  ├── manifests                

vi -d var_lib_kubelet-1.txt var_lib_kubelet-2.txt
# 비교 결과: kubeadm init 전에는 /var/lib/kubelet이 비어있었지만, 이후에는 kubelet 설정 파일, 인증서, Pod 런타임 데이터가 생성됨

vi -d run_containerd-1.txt run_containerd-2.txt
# 비교 결과: kubeadm init 전에는 containerd 소켓만 존재했지만, 이후에는 10개의 컨테이너와 8개의 샌드박스가 생성됨
# - containers: Control Plane Pod (etcd, kube-apiserver, kube-controller-manager, kube-scheduler), CoreDNS Pod, kube-proxy Pod, Flannel Pod
# - sandboxes: 각 Pod의 샌드박스 (pause 컨테이너)

vi -d pstree-1.txt pstree-2.txt
# 비교 결과: kubeadm init 전에는 containerd만 실행 중이었지만, 이후에는 kubelet과 여러 containerd-shim 프로세스가 추가됨
# - kubelet: Kubernetes 노드 에이전트
# - containerd-shim: 각 Pod의 컨테이너를 관리하는 프로세스

vi -d systemd-cgls-1.txt systemd-cgls-2.txt
# 비교 결과: kubeadm init 전에는 system.slice만 존재했지만, 이후에는 kubepods.slice가 추가됨
# - kubepods-burstable.slice: Burstable QoS 클래스 Pod들 (Control Plane, CoreDNS, Flannel)
# - kubepods-besteffort.slice: BestEffort QoS 클래스 Pod들 (kube-proxy)
# 각 Pod는 cri-containerd-* 형식의 cgroup으로 관리됨

vi -d lsns-1.txt lsns-2.txt
# 비교 결과: kubeadm init 전에는 기본 네임스페이스만 존재했지만, 이후에는 각 Pod마다 독립적인 네임스페이스가 생성됨
# - 각 Pod는 mnt, ipc, pid, cgroup 네임스페이스를 가짐
# - CoreDNS Pod는 net 네임스페이스도 별도로 가짐 (hostNetwork=false)
# - Control Plane Pod는 hostNetwork=true이므로 net 네임스페이스를 공유

vi -d ip_addr-1.txt ip_addr-2.txt
# 비교 결과: kubeadm init 전에는 lo, enp0s8, enp0s9만 존재했지만, Flannel CNI 설치 후 네트워크 인터페이스가 추가됨
# - flannel.1: VXLAN 터널 인터페이스 (10.244.0.0/32)
# - cni0: 컨테이너 브리지 (10.244.0.1/24)
# - veth*: 각 Pod의 가상 이더넷 인터페이스


vi -d ss-1.txt ss-2.txt
# 비교 결과: kubeadm init 전에는 기본 서비스 포트만 열려있었지만, 이후에는 Control Plane 구성 요소들의 포트가 추가됨
# - kube-apiserver: 6443 (HTTPS), 127.0.0.1:6443, 192.168.10.100:6443
# - kubelet: 10250 (read-only API), 10248 (health check)
# - kube-controller-manager: 10257 (secure port)
# - kube-scheduler: 10259 (secure port)
# - etcd: 2379 (client), 2380 (peer)

vi -d df-1.txt df-2.txt
# 비교 결과: kubeadm init 전에는 기본 파일시스템만 존재했지만, 이후에는 각 Pod의 컨테이너마다 overlay와 shm이 마운트됨
# - overlay: 각 컨테이너의 레이어드 파일시스템
# - shm: 각 Pod의 공유 메모리 (64MB)
# - /dev/sda3 사용량이 2% → 4%로 증가 (Pod 이미지 및 데이터 저장)

vi -d findmnt-1.txt findmnt-2.txt
# 비교 결과: kubeadm init 전에는 기본 마운트 포인트만 존재했지만, 이후에는 Pod 실행에 필요한 마운트가 추가됨
# - /run/containerd/io.containerd.grpc.v1.cri/*: 각 Pod의 런타임 디렉토리
# - /run/netns/cni-*: Pod 네트워크 네임스페이스
# - /var/lib/kubelet/pods/*: Pod 볼륨 마운트 포인트

# kubelet 에 --protect-kernel-defaults=false 적용되어 관련 코드에 sysctl 커널 파라미터 적용 : 아래 링크 확왼
## 위 설정 시, 커널 튜닝 가능 항목 중 하나라도 kubelet의 기본값과 다르면 오류가 발생합니다
vi -d sysctl-1.txt sysctl-2.txt
# 비교 결과: kubeadm init 및 kube-proxy 실행 후 커널 파라미터가 변경됨
# 주요 변경사항은 위의 "커널 파라미터 (sysctl) 변경사항" 표에 정리되어 있음


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

[k8s-ctr] 인증서 확인

# kubeadm-config 확인
kc describe cm -n kube-system kubeadm-config
# Name:         kubeadm-config
# Namespace:    kube-system
# Labels:       <none>
# Annotations:  <none>
# 
# Data
# ====
# ClusterConfiguration:
# ----
# apiServer: {}
# apiVersion: kubeadm.k8s.io/v1beta4
# caCertificateValidityPeriod: 87600h0m0s
# certificateValidityPeriod: 8760h0m0s
# certificatesDir: /etc/kubernetes/pki
# clusterName: kubernetes
# controllerManager: {}
# dns: {}
# encryptionAlgorithm: RSA-2048
# etcd:
#   local:
#     dataDir: /var/lib/etcd
# imageRepository: registry.k8s.io
# kind: ClusterConfiguration
# kubernetesVersion: v1.32.11
# networking:
#   dnsDomain: cluster.local
#   podSubnet: 10.244.0.0/16
#   serviceSubnet: 10.96.0.0/16
# proxy: {}
# scheduler: {}
# kubeadm-config ConfigMap에는 클러스터 설정 정보가 저장되어 있습니다.
# - caCertificateValidityPeriod: 87600h0m0s (10년) - CA 인증서 유효 기간
# - certificateValidityPeriod: 8760h0m0s (1년) - 일반 인증서 유효 기간
# - certificatesDir: /etc/kubernetes/pki - 인증서 저장 디렉토리
# - encryptionAlgorithm: RSA-2048 - 암호화 알고리즘

# 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 20, 2027 17:40 UTC   364d            ca                      no      
# apiserver                  Jan 20, 2027 17:40 UTC   364d            ca                      no      
# apiserver-etcd-client      Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# apiserver-kubelet-client   Jan 20, 2027 17:40 UTC   364d            ca                      no      
# controller-manager.conf    Jan 20, 2027 17:40 UTC   364d            ca                      no      
# etcd-healthcheck-client    Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# etcd-peer                  Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# etcd-server                Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# front-proxy-client         Jan 20, 2027 17:40 UTC   364d            front-proxy-ca          no      
# scheduler.conf             Jan 20, 2027 17:40 UTC   364d            ca                      no      
# super-admin.conf           Jan 20, 2027 17:40 UTC   364d            ca                      no      
# 
# CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
# ca                      Jan 18, 2036 17:40 UTC   9y              no      
# etcd-ca                 Jan 18, 2036 17:40 UTC   9y              no      
# front-proxy-ca          Jan 18, 2036 17:40 UTC   9y              no      
# kubeadm이 관리하는 인증서들의 만료 정보를 확인합니다.
# - 일반 인증서: 1년 유효 (364일 남음)
# - CA 인증서: 10년 유효 (9년 남음)
# - EXTERNALLY MANAGED: no - kubeadm이 관리하는 인증서 (외부 관리 아님)
# 인증서는 만료 전에 `kubeadm certs renew` 명령으로 갱신할 수 있습니다.

tree /etc/kubernetes/
# /etc/kubernetes/
# ├── admin.conf
# ├── controller-manager.conf
# ├── kubelet.conf
# ├── manifests
# │   ├── etcd.yaml
# │   ├── kube-apiserver.yaml
# │   ├── kube-controller-manager.yaml
# │   └── kube-scheduler.yaml
# ├── pki
# │   ├── apiserver.crt
# │   ├── apiserver-etcd-client.crt
# │   ├── apiserver-etcd-client.key
# │   ├── apiserver.key
# │   ├── apiserver-kubelet-client.crt
# │   ├── apiserver-kubelet-client.key
# │   ├── ca.crt
# │   ├── ca.key
# │   ├── 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
# └── super-admin.conf
# 
# 4 directories, 31 files
# /etc/kubernetes/ 디렉토리 구조:
# - kubeconfig 파일들: admin.conf, controller-manager.conf, kubelet.conf, scheduler.conf, super-admin.conf
# - manifests/: Static Pod 매니페스트 파일들
# - pki/: 인증서 및 키 파일들

tree /etc/kubernetes/pki
# /etc/kubernetes/pki
# ├── apiserver.crt
# ├── apiserver-etcd-client.crt
# ├── apiserver-etcd-client.key
# ├── apiserver.key
# ├── apiserver-kubelet-client.crt
# ├── apiserver-kubelet-client.key
# ├── ca.crt
# ├── ca.key
# ├── 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
# 
# 2 directories, 22 files
# 인증서 디렉토리 구조:
# - ca.crt/ca.key: Kubernetes CA 인증서 및 키
# - apiserver.crt/apiserver.key: API Server 인증서 및 키
# - apiserver-kubelet-client.crt/key: API Server가 kubelet과 통신할 때 사용하는 클라이언트 인증서
# - apiserver-etcd-client.crt/key: API Server가 etcd와 통신할 때 사용하는 클라이언트 인증서
# - etcd/: etcd 관련 인증서 (CA, peer, server, healthcheck-client)
# - front-proxy-ca.crt/key: 프론트 프록시 CA 인증서 (aggregation layer용)
# - front-proxy-client.crt/key: 프론트 프록시 클라이언트 인증서
# - sa.key/sa.pub: Service Account 서명 키 쌍

# CA 인증서
cat /etc/kubernetes/pki/ca.crt | openssl x509 -text -noout
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 4686048711720833794 (0x41083079be4c1302)
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: CN=kubernetes
#         Validity
#             Not Before: Jan 20 17:35:53 2026 GMT
#             Not After : Jan 18 17:40:53 2036 GMT
#         Subject: CN=kubernetes
#         ...
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Key Encipherment, Certificate Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier: 
#                 24:6E:36:3E:5E:5B:79:C4:B1:B2:21:60:AA:E6:14:F2:3F:D1:5E:5F
#             X509v3 Subject Alternative Name: 
#                 DNS:kubernetes
# Kubernetes CA 인증서는 클러스터의 모든 인증서를 서명하는 루트 인증서입니다.
# - Issuer/Subject: CN=kubernetes (자기 서명 인증서)
# - 유효 기간: 10년 (2026-01-20 ~ 2036-01-18)
# - Key Usage: Digital Signature, Key Encipherment, Certificate Sign (인증서 서명 권한)
# - Basic Constraints: CA:TRUE (인증 기관임을 명시)
# - 이 CA 인증서로 모든 클러스터 구성 요소의 인증서를 서명합니다.

# apiserver 인증서 : 'TLS Web Server' 키 용도 확인
cat /etc/kubernetes/pki/apiserver.crt | openssl x509 -text -noout
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 4408778139016981249 (0x3d2f205bd59d6b01)
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: CN=kubernetes
#         Validity
#             Not Before: Jan 20 17:35:53 2026 GMT
#             Not After : Jan 20 17:40:53 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: 
#                 24:6E:36:3E:5E:5B:79:C4:B1:B2:21:60:AA:E6:14:F2:3F:D1:5E:5F
#             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
# API Server 인증서는 HTTPS 서버 인증에 사용됩니다.
# - Issuer: CN=kubernetes (Kubernetes CA가 서명)
# - Subject: CN=kube-apiserver
# - 유효 기간: 1년 (2026-01-20 ~ 2027-01-20)
# - Extended Key Usage: TLS Web Server Authentication (TLS 서버 인증용)
# - Subject Alternative Name (SAN): API Server에 접근할 수 있는 모든 DNS 이름과 IP 주소
#   - DNS: k8s-ctr (노드 호스트명), kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local
#   - IP: 10.96.0.1 (Service CIDR의 첫 번째 IP, kubernetes Service), 192.168.10.100 (노드 IP)
# 이 인증서는 API Server의 HTTPS 엔드포인트(6443)에서 사용됩니다.

# apiserver-kubelet-client 인증서 : 'TLS Web Client' 키 용도 확인
cat /etc/kubernetes/pki/apiserver-kubelet-client.crt | openssl x509 -text -noout
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 8822992219099166046 (0x7a7190ad5370f55e)
#         Signature Algorithm: sha256WithRSAEncryption
#         Issuer: CN=kubernetes
#         Validity
#             Not Before: Jan 20 17:35:53 2026 GMT
#             Not After : Jan 20 17:40:53 2027 GMT
#         Subject: O=kubeadm:cluster-admins, CN=kube-apiserver-kubelet-client
#         ...
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Key Encipherment
#             X509v3 Extended Key Usage: 
#                 TLS Web Client Authentication
#             X509v3 Basic Constraints: critical
#                 CA:FALSE
#             X509v3 Authority Key Identifier: 
#                 24:6E:36:3E:5E:5B:79:C4:B1:B2:21:60:AA:E6:14:F2:3F:D1:5E:5F
# API Server가 kubelet과 통신할 때 사용하는 클라이언트 인증서입니다.
# - Issuer: CN=kubernetes (Kubernetes CA가 서명)
# - Subject: O=kubeadm:cluster-admins, CN=kube-apiserver-kubelet-client
#   - O=kubeadm:cluster-admins: 클러스터 관리자 그룹에 속함
# - 유효 기간: 1년 (2026-01-20 ~ 2027-01-20)
# - Extended Key Usage: TLS Web Client Authentication (TLS 클라이언트 인증용)
# - API Server가 kubelet의 10250 포트(읽기 전용 API)에 접근할 때 이 인증서를 사용합니다.
# - kubelet은 이 인증서를 신뢰하여 API Server의 요청을 인증합니다.

[k8s-ctr] kubeconfig 확인

# 관리자 용도
cat /etc/kubernetes/admin.conf 
# apiVersion: v1
# clusters:
# - cluster:
#     server: https://192.168.10.100:6443
#     certificate-authority-data: ... (base64, CA 인증서)
#   name: kubernetes
# contexts:
# - context:
#     cluster: kubernetes
#     user: kubernetes-admin
#   name: kubernetes-admin@kubernetes
# current-context: kubernetes-admin@kubernetes
# users:
# - name: kubernetes-admin
#   user:
#     client-certificate-data: ... (base64, admin 클라이언트 인증서)
#     client-key-data: ... (base64, admin 클라이언트 키)
# admin.conf 는 kubectl 에서 사용하는 기본 관리자 kubeconfig 입니다.

cat /etc/kubernetes/super-admin.conf
# apiVersion: v1
# clusters:
# - cluster:
#     server: https://192.168.10.100:6443
#     certificate-authority-data: ... (base64, CA 인증서)
#   name: kubernetes
# contexts:
# - context:
#     cluster: kubernetes
#     user: kubernetes-super-admin
#   name: kubernetes-super-admin@kubernetes
# current-context: kubernetes-super-admin@kubernetes
# users:
# - name: kubernetes-super-admin
#   user:
#     client-certificate-data: ... (base64, super-admin 인증서)
#     client-key-data: ... (base64, super-admin 키)
# super-admin.conf 는 별도의 super-admin 권한을 가진 kubeconfig 입니다.

# kcm
cat /etc/kubernetes/controller-manager.conf 
# apiVersion: v1
# clusters:
# - cluster:
#     server: https://192.168.10.100:6443
#     certificate-authority-data: ... (base64, CA 인증서)
#   name: kubernetes
# contexts:
# - context:
#     cluster: kubernetes
#     user: system:kube-controller-manager
#   name: system:kube-controller-manager@kubernetes
# current-context: system:kube-controller-manager@kubernetes
# users:
# - name: system:kube-controller-manager
#   user:
#     client-certificate-data: ... (base64, 컨트롤러 매니저 클라이언트 인증서)
#     client-key-data: ... (base64, 클라이언트 키)
# controller-manager.conf 는 kube-controller-manager Static Pod에서 사용하는 kubeconfig 입니다.

# scheduler
cat /etc/kubernetes/scheduler.conf 
# apiVersion: v1
# clusters:
# - cluster:
#     server: https://192.168.10.100:6443
#     certificate-authority-data: ... (base64, CA 인증서)
#   name: kubernetes
# contexts:
# - context:
#     cluster: kubernetes
#     user: system:kube-scheduler
#   name: system:kube-scheduler@kubernetes
# current-context: system:kube-scheduler@kubernetes
# users:
# - name: system:kube-scheduler
#   user:
#     client-certificate-data: ... (base64, 스케줄러 클라이언트 인증서)
#     client-key-data: ... (base64, 클라이언트 키)
# scheduler.conf 는 kube-scheduler Static Pod에서 사용하는 kubeconfig 입니다.

# kubelet
cat /etc/kubernetes/kubelet.conf
# apiVersion: v1
# clusters:
# - cluster:
#     server: https://192.168.10.100:6443
#     certificate-authority-data: ... (base64, CA 인증서)
#   name: kubernetes
# contexts:
# - context:
#     cluster: kubernetes
#     user: system:node:k8s-ctr
#   name: system:node:k8s-ctr@kubernetes
# current-context: system:node:k8s-ctr@kubernetes
# 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
# kubelet.conf 는 kubelet 프로세스가 API Server 와 통신할 때 사용하는 kubeconfig 입니다.

# kubelet 클라이언트 인증서 파일 확인
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 디렉토리에는 서버용 인증서(kubelet.crt), 키(kubelet.key), 클라이언트 인증서/키(kubelet-client-*.pem)가 저장됩니다.
# kubelet-client-current.pem 심볼릭 링크를 통해 현재 사용 중인 클라이언트 인증서를 가리킵니다.

# kubelet 서버 역할 : Subject, Key Usage, SAN 확인
cat /var/lib/kubelet/pki/kubelet.crt | openssl x509 -text -noout
# Certificate:
#     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.crt 는 노드에서 동작하는 kubelet 서버(10250 포트 등)에 대한 서버 인증서입니다.
# - Issuer: CN=k8s-ctr-ca@... (노드 로컬 CA가 서명한 인증서)
# - Subject: CN=k8s-ctr@... (노드 식별자)
# - Extended Key Usage: TLS Web Server Authentication (서버 인증 용도)
# - SAN: DNS:k8s-ctr (노드 호스트명으로 접근 시 인증서 검증 가능)

# kubelet 클라이언트 역할 : Subject, Key Usage 확인
cat /var/lib/kubelet/pki/kubelet-client-current.pem | openssl x509 -text -noout
# Certificate:
#     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
# kubelet-client-current.pem 은 kubelet 이 API Server 에 접속할 때 사용하는 클라이언트 인증서입니다.
# - Issuer: CN=kubernetes (클러스터 CA가 서명한 클라이언트 인증서)
# - Subject: O=system:nodes, CN=system:node:k8s-ctr (노드 인증용 계정)
# - Extended Key Usage: TLS Web Client Authentication (클라이언트 인증 용도)
# 이 인증서를 통해 kubelet 은 system:node:k8s-ctr 사용자로서 API Server 에 인증됩니다.

[k8s-ctr] static pod 확인 : etcd, kube-apiserver, kube-scheduler,kube-controller-manager

Static Pod는 kubelet이 관리하고 직접 실행하는 Pod로, 일반적으로 Kubernetes API Server를 거치지 않고 노드의 로컬(manifests) 디렉토리에 있는 YAML 파일을 기반으로 생성됩니다.

 

즉, master(혹은 control-plane) 구성요소 등 시스템 핵심 역할을 하는 Pod는 관리자에 의해 노드의 /etc/kubernetes/manifests/ 디렉토리에 YAML 파일을 배치해두면, kubelet이 이를 감지하여 자동으로 실행‧관리합니다.

  • kubelet은 static pod manifest 파일이 수정/삭제되면 이를 즉시 감지하여 Pod를 자동 반영(생성/종료/변경)합니다.
  • static pod는 API Server를 통하지 않고 직접 kubelet이 관리하기 때문에, control-plane이 정상동작하지 않는 상황에서도 동작을 보장할 수 있습니다.

etcd, kube-apiserver, kube-scheduler, kube-controller-manager와 같은 control-plane 구성요소는 Kubernetes 클러스터 전체의 기반이자 핵심 역할을 담당합니다. 이 구성요소들은 Kubernetes 리소스(Pod, Deployment 등) 자체를 관리/조정하는 주체이기 때문에, 클러스터 내부 오브젝트(Pod 등)로 스케줄링하거나 API Server 등 일반적인 제어흐름에 의존할 수 없습니다.

 

만약 이러한 control-plane 컴포넌트가 일반적인 Deployment/Pod처럼 동작한다면, API Server와 같은 핵심 서비스가 중단될 시 클러스터 전체가 스스로 자가복구하지 못하게 됩니다. 이를 방지하고, API Server가 중단된 상황에서도 control-plane이 즉시 복구될 수 있도록 하기 위해 static pod 형태로 운영합니다.

 

kubelet이 직접 pod manifest를 읽고 실행함으로써, API Server의 장애와 무관하게 control-plane이 항상 유지됩니다.

 

# kubelet에 의해 기동되는 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
# coredns-668d6bf9bc-47zx5          1/1     Running   0          8h    10.244.0.3       k8s-ctr   <none>           <none>
# coredns-668d6bf9bc-77pjv          1/1     Running   0          8h    10.244.0.2       k8s-ctr   <none>           <none>
# etcd-k8s-ctr                      1/1     Running   0          8h    192.168.10.100   k8s-ctr   <none>           <none>
# kube-apiserver-k8s-ctr            1/1     Running   0          8h    192.168.10.100   k8s-ctr   <none>           <none>
# kube-controller-manager-k8s-ctr   1/1     Running   0          8h    192.168.10.100   k8s-ctr   <none>           <none>
# kube-proxy-qhqpz                  1/1     Running   0          8h    192.168.10.100   k8s-ctr   <none>           <none>
# kube-scheduler-k8s-ctr            1/1     Running   0          8h    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/
# /var/lib/etcd/
# └── member
#     ├── snap
#     │   ├── 0000000000000002-0000000000002711.snap
#     │   ├── 0000000000000002-0000000000004e22.snap
#     │   └── db
#     └── wal
#         ├── 0000000000000000-0000000000000000.wal
#         └── 0.tmp

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
#...
# spec:
#   containers:
#   - 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
# apiVersion: coordination.k8s.io/v1
# kind: Lease
# metadata:
#   creationTimestamp: "2026-01-20T17:41:02Z"
#   name: kube-scheduler
#   namespace: kube-system
#   resourceVersion: "25685"
#   uid: 86ff7a16-4648-472b-b356-86ae8007c909
# spec:
#   acquireTime: "2026-01-20T17:41:02.043509Z"
#   holderIdentity: k8s-ctr_52bdcb3e-29b8-4988-a4f2-d2d3221753b0
#   leaseDurationSeconds: 15
#   leaseTransitions: 0
#   renewTime: "2026-01-21T01:46:36.126156Z"

kubectl get leases.coordination.k8s.io -n kube-system kube-scheduler
# NAME             HOLDER                                         AGE
# kube-scheduler   k8s-ctr_52bdcb3e-29b8-4988-a4f2-d2d3221753b0   8h

## Node Heartbeat (Node 상태) : node heartbeat 전용 네임스페이스
kubectl get lease -n kube-node-lease
# NAME      HOLDER    AGE
# k8s-ctr   k8s-ctr   8h

kubectl get lease -n kube-node-lease -o yaml
# apiVersion: v1
# items:
# - apiVersion: coordination.k8s.io/v1
#   kind: Lease
#   metadata:
#     creationTimestamp: "2026-01-20T17:41:00Z"
#     name: k8s-ctr
#     namespace: kube-node-lease
#     ownerReferences:
#     - apiVersion: v1
#       kind: Node
#       name: k8s-ctr
#       uid: f9c71ccf-bb13-4bdf-825f-059f3d061c18
#     resourceVersion: "25693"
#     uid: 5c196278-17d9-4fe2-bea4-675ea6be5713
#   spec:
#     holderIdentity: k8s-ctr
#     leaseDurationSeconds: 40
#     renewTime: "2026-01-21T01:46:43.187273Z"
# kind: List
# metadata:
#   resourceVersion: ""

# 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=7858,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
# apiVersion: coordination.k8s.io/v1
# kind: Lease
# metadata:
#   creationTimestamp: "2026-01-20T17:41:00Z"
#   name: kube-controller-manager
#   namespace: kube-system
#   resourceVersion: "25833"
#   uid: f69dd611-06c1-47e7-91ca-f5e46e0747fb
# spec:
#   acquireTime: "2026-01-20T17:41:00.318171Z"
#   holderIdentity: k8s-ctr_b9a18658-8bdc-4ab4-9862-0a6b91701610
#   leaseDurationSeconds: 15
#   leaseTransitions: 0
#   renewTime: "2026-01-21T01:48:30.587000Z"

kubectl get lease -n kube-system kube-controller-manager
# NAME                      HOLDER                                         AGE
# kube-controller-manager   k8s-ctr_b9a18658-8bdc-4ab4-9862-0a6b91701610   8h
## 컨트롤러별 개별 ServiceAccount + RBAC 사용

kubectl get sa -n kube-system | grep controller
# attachdetach-controller                       0         8h
# certificate-controller                        0         8h
# clusterrole-aggregation-controller            0         8h
# cronjob-controller                            0         8h
...(생략)...

필수 애드온 설치 (coredns, kube-proxy) 확인

# coredns 확인
kc describe deploy -n kube-system coredns
# Name:                   coredns
# Namespace:              kube-system
# CreationTimestamp:      Wed, 21 Jan 2026 02:41:00 +0900
# Labels:                 k8s-app=kube-dns
# Selector:               k8s-app=kube-dns
# Replicas:               2 desired | 2 updated | 2 total | 2 available | 0 unavailable
# Containers:
#  coredns:
#   Image:       registry.k8s.io/coredns/coredns:v1.11.3
#   Ports:       53/UDP, 53/TCP, 9153/TCP
#   Args:
#     -conf
#     /etc/coredns/Corefile
#   Limits:
#     memory:  170Mi
#   Requests:
#     cpu:     100m
#     memory:  70Mi
#   Liveness:   http-get http://:8080/health delay=60s timeout=5s period=10s #success=1 #failure=5
#   Readiness:  http-get http://:8181/ready  delay=0s  timeout=1s period=10s #success=1 #failure=3
# Volumes:
#  config-volume:
#   Type: ConfigMap
#   Name: coredns
# Events:  <none>
# CoreDNS는 클러스터 DNS(서비스 디스커버리) 역할을 담당합니다.
# - 53/UDP,53/TCP: DNS 질의 포트
# - 9153/TCP: Prometheus 메트릭 포트
kubectl get deploy -n kube-system coredns -owide
# NAME      READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                                    SELECTOR
# coredns   2/2     2            2           8h    coredns      registry.k8s.io/coredns/coredns:v1.11.3   k8s-app=kube-dns
# READY=2/2 → CoreDNS 파드 2개가 모두 Ready 상태입니다.

## 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-47zx5   1/1     Running   0          8h    10.244.0.3   k8s-ctr   <none>           <none>
# coredns-668d6bf9bc-77pjv   1/1     Running   0          8h    10.244.0.2   k8s-ctr   <none>           <none>
# CoreDNS 파드는 Pod CIDR(10.244.0.0/16)에서 IP를 할당받아 동작합니다.

##
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   8h
#
# NAME                 ENDPOINTS                                               AGE
# endpoints/kube-dns   10.244.0.2:53,10.244.0.3:53,10.244.0.2:53 + 3 more...   8h
# kube-dns Service(10.96.0.10)는 CoreDNS Pod 엔드포인트로 트래픽을 분산합니다.

## 프로메테우스 메트릭 엔드포인트 확인
curl -s http://10.96.0.10:9153/metrics | head
# # HELP coredns_build_info A metric with a constant '1' value labeled by version, revision, and goversion from which CoreDNS was built.
# # TYPE coredns_build_info gauge
# coredns_build_info{goversion="go1.21.11",revision="a6338e9",version="1.11.3"} 1
# # HELP coredns_cache_entries The number of elements in the cache.
# # TYPE coredns_cache_entries gauge
# coredns_cache_entries{server="dns://:53",type="denial",view="",zones="."} 1
# coredns_cache_entries{server="dns://:53",type="success",view="",zones="."} 0
# # HELP coredns_cache_misses_total The count of cache misses. Deprecated, derive misses from cache hits/requests counters.
# # TYPE coredns_cache_misses_total counter
# coredns_cache_misses_total{server="dns://:53",view="",zones="."} 1
# Service IP로 메트릭(9153) 접근이 되면 kube-proxy(Service 라우팅)와 CoreDNS가 정상 동작 중임을 간접 확인할 수 있습니다.

## configmap 확인
kc describe cm -n kube-system coredns
# Name:         coredns
# Namespace:    kube-system
# Data
# ====
# Corefile:
# ----
# .: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
# search Davolink
# nameserver 1.214.68.2
# nameserver 61.41.153.2
# CoreDNS의 forward 플러그인은 기본적으로 여기(/etc/resolv.conf)에 설정된 nameserver로 외부 질의를 포워딩합니다.


# 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   8h    kube-proxy   registry.k8s.io/kube-proxy:v1.32.11   k8s-app=kube-proxy
# kube-proxy는 노드마다 1개씩 동작하는 DaemonSet이며, Service 라우팅/로드밸런싱 규칙을 적용합니다.

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-qhqpz   1/1     Running   0          8h    192.168.10.100   k8s-ctr   <none>           <none>
# kube-proxy는 hostNetwork로 실행되어 IP가 노드 IP(192.168.10.100)로 보입니다.

kc describe cm -n kube-system kube-proxy
# Name:         kube-proxy
# Namespace:    kube-system
# Labels:       app=kube-proxy
# Annotations:  kubeadm.kubernetes.io/component-config.hash: sha256:... (생략)
#
# Data
# ====
# config.conf:
# ----
# apiVersion: kubeproxy.config.k8s.io/v1alpha1
# bindAddress: 0.0.0.0
# clusterCIDR: 10.244.0.0/16
# mode: ""
# conntrack:
#   maxPerCore: null
#   min: null
# ...
#
# kubeconfig.conf:
# ----
# apiVersion: v1
# kind: Config
# clusters:
# - cluster:
#     certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
#     server: https://192.168.10.100:6443
#   name: default
# users:
# - name: default
#   user:
#     tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
# ...
# kube-proxy 설정 포인트:
# - clusterCIDR: Pod CIDR(10.244.0.0/16) 인지 → masquerade/conntrack 등에 사용
# - mode: "" (기본값/자동) → 보통 iptables 모드로 동작(이후 iptables 규칙이 생성됨)
# - kubeconfig.conf는 ServiceAccount 토큰으로 API Server에 접근합니다.

##
ss -tnlp | grep kube-proxy
# LISTEN 0      4096        127.0.0.1:10249      0.0.0.0:*    users:(("kube-proxy",pid=8168,fd=12))  # 헬스 체크 전용
# LISTEN 0      4096                *:10256            *:*    users:(("kube-proxy",pid=8168,fd=11))  # 메트릭 노출용
# 10249: healthz(로컬), 10256: metrics(전체 인터페이스)

curl 127.0.0.1:10249/healthz ; echo
# ok

## iptables rule 확인
iptables -t nat -S
iptables -t filter -S
iptables-save

## conntrack 전용 툴 설치

# conntrack-tools 패키지는 커널의 netfilter(conntrack) 연결 추적 표(table)를 조회/조작/모니터링할 수 있는 유틸리티입니다.
# 이 툴을 사용하면 실제로 살아있는 커넥션 목록을 보고, 특정 커넥션만 삭제하거나 실시간으로 세션 변화를 추적할 수 있습니다.
# (네트워크/방화벽 문제, Kubernetes kube-proxy 동작 등 트러블슈팅 때 매우 유용)
dnf install -y conntrack-tools
conntrack -V
# conntrack v1.4.8 (conntrack-tools)

## 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

5. [k8s-w1/w2] 설정

사전 설정

각각의 워커노드에서 해당 설정을 적용합니다.

vagrant ssh k8s-w1
# vagrant ssh k8s-w2

# root 권한(로그인 환경) 전환
echo "sudo su -" >> /home/vagrant/.bashrc
sudo su -

# Time, NTP 설정
timedatectl set-local-rtc 0

# 시스템 타임존(Timezone)을 한국(KST, UTC+9) 으로 설정 : 시스템 시간은 UTC 기준 유지, 표시만 KST로 변환
timedatectl set-timezone Asia/Seoul

# SELinux 설정 : Kubernetes는 Permissive 권장
setenforce 0

# 재부팅 시에도 Permissive 적용
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

# firewalld(방화벽) 끄기
systemctl disable --now firewalld

# Swap 비활성화
swapoff -a

# 재부팅 시에도 'Swap 비활성화' 적용되도록 /etc/fstab에서 swap 라인 주석 처리
sed -i '/swap/d' /etc/fstab

# 커널 모듈 로드
modprobe overlay
modprobe br_netfilter
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

# 커널 파라미터 설정 : 네트워크 설정 - 브릿지 트래픽이 iptables를 거치도록 함
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 설정 적용
sysctl --system >/dev/null 2>&1

# hosts 설정
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
cat << EOF >> /etc/hosts
192.168.10.100 k8s-ctr
192.168.10.101 k8s-w1
192.168.10.102 k8s-w2
EOF
cat /etc/hosts

CRI 설치 : containerd(runc) v2.1.5

# Docker 저장소 추가
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# containerd 설치
dnf install -y containerd.io-*2.1.5-1.el10*

# 기본 설정 생성 및 SystemdCgroup 활성화
containerd config default | tee /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

# systemd unit 파일 새로고침
systemctl daemon-reload

# containerd 시작 및 부팅 시 활성화
systemctl enable --now containerd

kubeadm, kubelet, kubectl 설치 v1.32.11

# Kubernetes 저장소 추가
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

# kubeadm, kubelet, kubectl 설치
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

# kubelet 부팅 시 활성화(초기화 후 실제 기동)
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

kubeadm 으로 k8s join 하기

kubeadm join은 워커 노드를 Kubernetes 클러스터에 합류시키는 과정으로, 크게 두 단계로 구성됩니다. 첫 번째는 디스커버리(Discovery) 단계로, 워커 노드가 Kubernetes API 서버를 신뢰하도록 하는 과정입니다. 두 번째는 TLS 부트스트랩(Bootstrap) 단계로, Kubernetes API 서버가 워커 노드를 신뢰하도록 하는 과정입니다.

1. Preflight Checks

워커 노드에서 kubeadm init과 동일한 사전 검증을 수행합니다. 커널 모듈, 네트워크 설정, 컨테이너 런타임, 포트 사용 여부 등을 확인하여 클러스터 합류에 필요한 최소 요구사항을 충족하는지 검증합니다.

2. Discovery: 클러스터 정보 발견 및 신뢰 검증

워커 노드는 사용자가 제공한 토큰과 --discovery-token-ca-cert-hash 값을 사용하여 API 서버의 kube-public/cluster-info ConfigMap을 익명으로 요청합니다. 이 ConfigMap은 클러스터의 공개 정보(API 서버 엔드포인트, CA 인증서 등)를 포함하고 있으며, system:unauthenticated 그룹에게도 읽기 권한이 부여되어 있습니다.

가져온 ConfigMap 내의 CA 인증서 해시값이 사용자가 입력한 --discovery-token-ca-cert-hash sha256:xxxx 값과 일치하는지 확인합니다. 이를 통해 워커 노드는 접속하려는 마스터 노드가 진짜 클러스터의 마스터인지 검증할 수 있습니다. 이 단계에서 워커 노드는 마스터를 신뢰할 수 있게 됩니다.

Discovery 방식은 --discovery-token(Shared token discovery) 또는 --discovery-file(File/https discovery)을 사용할 수 있습니다.

3. TLS Bootstrap: 인증서 발급 요청

이제 마스터를 신뢰할 수 있으므로, 워커 노드는 토큰을 사용해 API 서버에 정식으로 인증을 시도합니다. 워커 노드는 자신만의 개인키를 생성하고, API 서버에 "나를 위한 인증서를 서명해달라"는 CSR(Certificate Signing Request)을 전송합니다. 마스터의 Certificate 발급자(approver)는 이 요청이 유효한 토큰을 통한 것임을 확인하고 자동으로 승인하여 인증서를 발급합니다.

4. Kubelet 설정 및 기동

발급받은 정식 인증서를 사용하여 /etc/kubernetes/kubelet.conf 파일을 생성합니다. kubelet이 이 설정을 가지고 실행되면서 API 서버에 자신을 "Node" 리소스로 등록합니다. 마스터는 이 노드에 kube-proxy 등의 시스템 컴포넌트를 배포하며, 노드 상태가 Ready가 될 준비를 마칩니다.

# 기본 환경 정보 출력 저장
crictl images
# IMAGE               TAG                 IMAGE ID            SIZE
# join 전 워커 노드에는 아직 Kubernetes 관련 이미지가 없습니다.

crictl ps
# CONTAINER           IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID              POD                 NAMESPACE
# join 전 워커 노드에는 아직 실행 중인 컨테이너가 없습니다.

cat /etc/sysconfig/kubelet
# KUBELET_EXTRA_ARGS=
# kubelet 추가 인자 설정 파일이 비어있습니다. join 후 kubelet이 자동으로 설정됩니다.

tree /etc/kubernetes  | tee -a etc_kubernetes-1.txt
# /etc/kubernetes
# └── manifests
#
# 2 directories, 0 files
# join 전에는 manifests 디렉토리만 존재하며, 아직 static pod 매니페스트가 없습니다.

tree /var/lib/kubelet | tee -a var_lib_kubelet-1.txt
# /var/lib/kubelet
#
# 0 directories, 0 files
# join 전에는 kubelet 데이터 디렉토리가 비어있습니다. join 후 설정 파일과 인증서가 생성됩니다.

tree /run/containerd/ -L 3 | tee -a run_containerd-1.txt
# /run/containerd/
# ├── containerd.sock
# ├── containerd.sock.ttrpc
# ├── io.containerd.grpc.v1.cri
# ├── io.containerd.runtime.v2.task
# └── io.containerd.sandbox.controller.v1.shim
#
# 4 directories, 2 files
# containerd는 이미 실행 중이며, CRI 소켓과 런타임 디렉토리가 준비되어 있습니다.

pstree -alnp | tee -a pstree-1.txt
# systemd,1 --switched-root --system --deserialize=46 no_timer_check
#   |-systemd-journal,434
#   |-systemd-userdbd,469
#   |-systemd-udevd,478
#   |-rpcbind,550 -w -f
#   |-auditd,553
#   |-dbus-broker-lau,592 --scope system --audit
#   |-irqbalance,604
#   |-lsmd,605 -d
#   |-systemd-logind,608
#   |-chronyd,613 -F 2
#   |-VBoxService,710 --pidfile /var/run/vboxadd-service.sh
#   |-polkitd,762 --no-debug --log-level=err
#   |-gssproxy,793 -i
#   |-sshd,794
#   |-tuned,795 -Es /usr/sbin/tuned -l -P
#   |-rsyslogd,991 -n
#   |-atd,1034 -f
#   |-crond,1040 -n
#   |-agetty,1058 -o -- \\u --noreset --noclear - linux
#   |-fwupd,1123
#   |-gpg-agent,1263 --homedir /var/lib/fwupd/gnupg --use-standard-socket --daemon
#   |-udisksd,1416
#   |-NetworkManager,3072 --no-daemon
#   |-systemd,6560 --user
#   `-containerd,6965
# join 전 워커 노드의 프로세스 트리입니다. containerd는 실행 중이지만 kubelet은 아직 시작되지 않았습니다.

systemd-cgls --no-pager | tee -a systemd-cgls-1.txt
# CGroup /:
# -.slice
# ├─user.slice
# ├─init.scope
# │ └─1 /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_tim…
# └─system.slice
#   ├─irqbalance.service
#   ├─libstoragemgmt.service
#   ├─vboxadd-service.service
#   ├─containerd.service …
#   │ └─6965 /usr/bin/containerd
#   ├─systemd-udevd.service …
#   ├─dbus-broker.service
#   ├─polkit.service
#   ├─chronyd.service
#   ├─auditd.service
#   ├─tuned.service
#   ├─systemd-journald.service
#   ├─atd.service
#   ├─sshd.service
#   ├─fwupd.service
#   ├─crond.service
#   ├─NetworkManager.service
#   ├─gssproxy.service
#   ├─rsyslog.service
#   ├─systemd-userdbd.service
#   ├─rpcbind.service
#   ├─udisks2.service
#   ├─system-getty.slice
#   └─systemd-logind.service
# join 전 워커 노드의 cgroup 구조입니다. containerd 서비스는 실행 중이지만 kubelet 관련 cgroup은 아직 없습니다.
lsns | tee -a lsns-1.txt
#        NS TYPE   NPROCS   PID USER    COMMAND
# 4026531834 time      119     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531835 cgroup    119     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531836 pid       119     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531837 user      118     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531838 uts       110     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531839 ipc       119     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531840 net       117     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531841 mnt       101     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# ... (기타 프로세스별 네임스페이스)
# join 전 워커 노드의 네임스페이스 목록입니다. 시스템 기본 네임스페이스와 일부 서비스별 네임스페이스만 존재하며, 컨테이너 관련 네임스페이스는 아직 없습니다.

ip addr | tee -a ip_addr-1.txt 
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#     inet 127.0.0.1/8 scope host lo
#        valid_lft forever preferred_lft forever
#     inet6 ::1/128 scope host noprefixroute 
#        valid_lft forever preferred_lft forever
# 2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:90:ea:eb brd ff:ff:ff:ff:ff:ff
#     inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s8
#        valid_lft 45065sec preferred_lft 45065sec
# 3: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:1f:02:49 brd ff:ff:ff:ff:ff:ff
#     inet 192.168.10.101/24 brd 192.168.10.255 scope global noprefixroute enp0s9
#        valid_lft forever preferred_lft forever
# join 전 워커 노드의 네트워크 인터페이스입니다. lo(루프백), enp0s8(NAT), enp0s9(호스트 전용, 192.168.10.101)만 존재하며, CNI 관련 인터페이스(cni0, flannel.1 등)는 아직 없습니다.

ss -tnlp | tee -a ss-1.txt
# State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess                                                   
# LISTEN 0      128          0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=794,fd=7))                            
# LISTEN 0      4096         0.0.0.0:111        0.0.0.0:*    users:(("rpcbind",pid=550,fd=5),("systemd",pid=1,fd=194))
# LISTEN 0      4096       127.0.0.1:46103      0.0.0.0:*    users:(("containerd",pid=6965,fd=12))                    
# LISTEN 0      128             [::]:22            [::]:*    users:(("sshd",pid=794,fd=8))                            
# LISTEN 0      4096            [::]:111           [::]:*    users:(("rpcbind",pid=550,fd=7),("systemd",pid=1,fd=196))
# LISTEN 0      4096               *:9090             *:*    users:(("systemd",pid=1,fd=191))                         
# join 전 워커 노드의 리스닝 소켓입니다. sshd(22), rpcbind(111), containerd(46103)만 리스닝 중이며, kubelet(10250, 10248 등)이나 kube-proxy(10249, 10256) 관련 포트는 아직 없습니다.

df -hT | tee -a df-1.txt
# Filesystem     Type      Size  Used Avail Use% Mounted on
# /dev/sda3      xfs        60G  2.9G   57G   5% /
# devtmpfs       devtmpfs  4.0M     0  4.0M   0% /dev
# tmpfs          tmpfs     913M     0  913M   0% /dev/shm
# efivarfs       efivarfs  256K  6.3K  250K   3% /sys/firmware/efi/efivars
# tmpfs          tmpfs     366M   12M  354M   4% /run
# tmpfs          tmpfs     1.0M     0  1.0M   0% /run/credentials/systemd-journald.service
# /dev/sda1      vfat      599M   13M  587M   3% /boot/efi
# tmpfs          tmpfs     1.0M     0  1.0M   0% /run/credentials/getty@tty1.service
# tmpfs          tmpfs     183M  8.0K  183M   1% /run/user/1000
# join 전 워커 노드의 파일시스템 사용량입니다. 루트 파일시스템(/dev/sda3)은 5%만 사용 중이며, Kubernetes 관련 마운트 포인트는 아직 없습니다.

findmnt | tee -a findmnt-1.txt
# TARGET                                        SOURCE      FSTYPE      OPTIONS
# /                                             /dev/sda3   xfs         rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota
# ├─/var/lib/nfs/rpc_pipefs                     sunrpc      rpc_pipefs  rw,relatime
# ├─/dev                                        devtmpfs    devtmpfs    rw,nosuid,seclabel,size=4096k,nr_inodes=229647,mode=755,inode64
# ├─/sys                                        sysfs       sysfs       rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/sys/fs/cgroup                            cgroup2     cgroup2     rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot
# │ └─/sys/firmware/efi/efivars                 efivarfs    efivarfs    rw,nosuid,nodev,noexec,relatime
# ├─/proc                                       proc        proc        rw,nosuid,nodev,noexec,relatime
# ├─/run                                        tmpfs       tmpfs       rw,nosuid,nodev,seclabel,size=373852k,nr_inodes=819200,mode=755,inode64
# └─/boot/efi                                   /dev/sda1   vfat        rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro
# join 전 워커 노드의 마운트 포인트 목록입니다. 시스템 기본 마운트 포인트만 존재하며, Kubernetes 관련 마운트(예: /var/lib/kubelet/pods, /run/containerd/io.containerd.runtime.v2.task 등)는 아직 없습니다.

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: 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
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.406438ms
# [kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap

# This node has joined the cluster:
# * Certificate signing request was sent to apiserver and a response was received.
# * The Kubelet was informed of the new secure connection details.

# Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

# crictl 확인
crictl images
# IMAGE                                   TAG                 IMAGE ID            SIZE
# 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
# registry.k8s.io/kube-proxy              v1.32.11            dcdb790dc2bfe       27.6MB
# registry.k8s.io/pause                   3.10                afb61768ce381       268kB
# join 후 워커 노드에 필요한 이미지들이 자동으로 다운로드되었습니다. Flannel CNI 플러그인, Flannel 데몬, kube-proxy, pause 이미지가 포함되어 있습니다.

crictl ps
# CONTAINER           IMAGE               CREATED             STATE               NAME                ATTEMPT             POD ID              POD                     NAMESPACE
# 6e3da4a24a0b7       d84558c0144bc       6 seconds ago       Running             kube-flannel        0                   eecbd2b79d45e       kube-flannel-ds-dmx6v   kube-flannel
# 43a4e45996ac5       dcdb790dc2bfe       17 seconds ago      Running             kube-proxy          0                   b7584ea4268f2       kube-proxy-7vlpt        kube-system
# join 후 워커 노드에서 kube-flannel과 kube-proxy 파드가 실행 중입니다. 이들은 DaemonSet으로 배포되어 각 노드마다 하나씩 실행됩니다.

# cluster-info cm 호출 가능 확인
curl -s -k https://192.168.10.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info | jq
# {
#   "kind": "ConfigMap",
#   "apiVersion": "v1",
#   "metadata": {
#     "name": "cluster-info",
#     "namespace": "kube-public",
#     "uid": "dc9196fb-1749-48cc-9ea5-bf788fb48f21",
#     "resourceVersion": "316",
#     "creationTimestamp": "2026-01-20T17:40:59Z",
#     "managedFields": [
#       {
#         "manager": "kubeadm",
#         "operation": "Update",
#         "apiVersion": "v1",
#         "time": "2026-01-20T17:40:59Z",
#         "fieldsType": "FieldsV1",
#         "fieldsV1": {
#           "f:data": {
#             ".": {},
#             "f:kubeconfig": {}
#           }
#         }
#       },
#       {
#         "manager": "kube-controller-manager",
#         "operation": "Update",
#         "apiVersion": "v1",
#         "time": "2026-01-20T17:41:05Z",
#         "fieldsType": "FieldsV1",
#         "fieldsV1": {
#           "f:data": {
#             "f:jws-kubeconfig-123456": {}
#           }
#         }
#       }
#     ]
#   },
#   "data": {
#     "jws-kubeconfig-123456": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzQ1NiJ9..cu7Nc8yJNTLPRZOiaIUD7y6aANtYy6YKFwNZUL5LpMs",
#     "kubeconfig": "apiVersion: v1\nclusters:\n- cluster:\n    certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJUVFnd2ViNU1Fd0l3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TmpBeE1qQXhOek0xTlROYUZ3MHpOakF4TVRneE56UXdOVE5hTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUMzanZiYjFhNHVwU2dxSGdIWWZ1cnR0QVJ3akZyWCtOV0JvYTUwdFpNUGhHRWZoQVBJRHFURm00OTYKZ0FDRXk5ZVhBZldDSlVPS0pjaXlvWWdFcHk5M2E2MnVnN2VlbFRXQ0haM3ZhRUFZNEt6UVRpWTdkekZIVGYxLwpXdEZRSU1qOElTYk9oN1ZaTlQyUVRTQTJjZkhjUUNBcWtvMkRLdWxaNHhVbzhxNHVCQ0hwbStLMEhiK3FEb0JECk13c0tLNWJNakt5anZJRURHUzVQNE5KQ0lmeVhHL0ljM3pEMWJpdFFMbW9FWGpJeklyTGZvNzc4VXRwVWdOeFcKaW5YNWc1ZUJvc2FpeHNpcm9sc3ZkMVRxMDMwRHFPNTFaSGlGVmxsblQvVk9xZnh1WmlKU0d3RWx0cVZlcTRMWQp5dldpOHg5TG16aDRHcUh4TmJUaU5HVSt4ajk5QWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRa2JqWStYbHQ1eExHeUlXQ3E1aFR5UDlGZVh6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQVVEdXpURkdKRgpjU2RmYm5RL3BZUndSOG9LZ1diMHg3SGNvVEZBT2tDcERhUm9LVVFrOGhmYTJDRnpsS0dYZDdLTHRKOU9JMVBaCkdiUnFKNXJyRWR3dVFhUnB4elNJN1BtWlpmdHMwekoyNHkwYlJyV0orUEFtSlNBdFVyUUM5Y3JORVNVMkRQaHYKYUVVTjVCZGJBYjRPOWk5TFNmdkdaQWV3bW5QNDVTOFAyS1JCZTdBZit0NjU2UE5oMysxdW1jVkdSZldFdi9ZNQpJUVR2MVRIVlJ6NjRlTE1RMnVEQUFHQ0t1cGp0RlFXZEhwSjA1dzNObjdZMFhxeGovTEdaSTRXMHBnVnl4QUJkCnpwN0lZWXJFS21RZDlNUzJrZXRxZVB6WDFOa1VmcXZQckNJbWZtZ1BPVEYrTnZGS1NSM2xUY3dVVHRQVHJDUEoKUzJ6eU1rVlIraVRxCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K\n    server: https://192.168.10.100:6443\n  name: \"\"\ncontexts: null\ncurrent-context: \"\"\nkind: Config\npreferences: {}\nusers: null\n"
#   }
# }

 

join 후에도 워커 노드에서 cluster-info ConfigMap을 익명으로 조회할 수 있습니다. 이는 kube-public 네임스페이스의 cluster-info가 system:unauthenticated 그룹에게 읽기 권한이 부여되어 있기 때문입니다.

 

data 필드에는 kubeconfig(CA 인증서 포함)와 JWS(JSON Web Signature) 토큰이 포함되어 있습니다.

[k8s-ctr] k8s-w1/w2 관련 정보 확인

# join 된 워커 노드 확인
kubectl get node -owide
# NAME      STATUS   ROLES           AGE     VERSION    INTERNAL-IP      EXTERNAL-IP   OS-IMAGE                        KERNEL-VERSION                  CONTAINER-RUNTIME
# k8s-ctr   Ready    control-plane   10h     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>          8m18s   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>          6m3s    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
# 모든 노드가 Ready 상태이며, 워커 노드(k8s-w1, k8s-w2)는 ROLES가 <none>으로 표시됩니다.

# 노드별 파드 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
# 각 노드마다 /24 서브넷이 할당되어 있습니다. kube-controller-manager의 --allocate-node-cidrs=true 설정에 의해 자동으로 할당됩니다.

# 다른 노드의 파드 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 
# Flannel이 각 워커 노드의 Pod CIDR에 대한 라우팅 규칙을 자동으로 추가했습니다. flannel.1 인터페이스를 통해 VXLAN 오버레이 네트워크로 통신합니다.

# 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.10 ms
#
# --- 10.244.1.0 ping statistics ---
# 1 packets transmitted, 1 received, 0% packet loss, time 0ms
# rtt min/avg/max/mdev = 1.102/1.102/1.102/0.000 ms
# 컨트롤 플레인 노드에서 워커 노드의 Pod CIDR 게이트웨이 IP로 통신이 가능합니다. 이는 Flannel VXLAN 오버레이 네트워크가 정상적으로 동작하고 있음을 의미합니다.

# 워커 노드에 Taints 정보 확인
kc describe node k8s-w1
# Name:               k8s-w1
# Roles:              <none>
# Labels:             beta.kubernetes.io/arch=arm64
#                     beta.kubernetes.io/os=linux
#                     kubernetes.io/arch=arm64
#                     kubernetes.io/hostname=k8s-w1
#                     kubernetes.io/os=linux
# Annotations:        flannel.alpha.coreos.com/backend-data: {"VNI":1,"VtepMAC":"d6:6c:c0:b2:b0:73"}
#                     flannel.alpha.coreos.com/backend-type: vxlan
#                     flannel.alpha.coreos.com/kube-subnet-manager: true
#                     flannel.alpha.coreos.com/public-ip: 192.168.10.101
#                     kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock
#                     node.alpha.kubernetes.io/ttl: 0
#                     volumes.kubernetes.io/controller-managed-attach-detach: true
# CreationTimestamp:  Wed, 21 Jan 2026 13:31:39 +0900
# Taints:             <none>
# Unschedulable:      false
# Conditions:
#   Type                 Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
#   ----                 ------  -----------------                 ------------------                ------                       -------
#   NetworkUnavailable   False   Wed, 21 Jan 2026 13:32:04 +0900   Wed, 21 Jan 2026 13:32:04 +0900   FlannelIsUp                  Flannel is running on this node
#   MemoryPressure       False   Wed, 21 Jan 2026 13:37:15 +0900   Wed, 21 Jan 2026 13:31:39 +0900   KubeletHasSufficientMemory   kubelet has sufficient memory available
#   DiskPressure         False   Wed, 21 Jan 2026 13:37:15 +0900   Wed, 21 Jan 2026 13:31:39 +0900   KubeletHasNoDiskPressure     kubelet has no disk pressure
#   PIDPressure          False   Wed, 21 Jan 2026 13:37:15 +0900   Wed, 21 Jan 2026 13:31:39 +0900   KubeletHasSufficientPID      kubelet has sufficient PID available
#   Ready                True    Wed, 21 Jan 2026 13:37:15 +0900   Wed, 21 Jan 2026 13:32:02 +0900   KubeletReady                 kubelet is posting ready status
# Addresses:
#   InternalIP:  192.168.10.101
#   Hostname:    k8s-w1
# Capacity:
#   cpu:                2
#   ephemeral-storage:  60970Mi
#   memory:             1869252Ki
#   pods:               110
# Allocatable:
#   cpu:                2
#   ephemeral-storage:  57538510753
#   memory:             1766852Ki
#   pods:               110
# System Info:
#   Machine ID:                 a09cfafd9d7f4497a86778f2b560eb3e
#   System UUID:                a09cfafd9d7f4497a86778f2b560eb3e
#   Boot ID:                    f6c25a37-b665-4943-820d-8c7a0012ee2a
#   Kernel Version:             6.12.0-55.39.1.el10_0.aarch64
#   OS Image:                   Rocky Linux 10.0 (Red Quartz)
#   Operating System:           linux
#   Architecture:               arm64
#   Container Runtime Version:  containerd://2.1.5
#   Kubelet Version:            v1.32.11
#   Kube-Proxy Version:         v1.32.11
# PodCIDR:                      10.244.1.0/24
# PodCIDRs:                     10.244.1.0/24
# Non-terminated Pods:          (2 in total)
#   Namespace                   Name                     CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
#   ---------                   ----                     ------------  ----------  ---------------  -------------  ---
#   kube-flannel                kube-flannel-ds-dmx6v    100m (5%)     0 (0%)      50Mi (2%)        0 (0%)         8m38s
#   kube-system                 kube-proxy-7vlpt         0 (0%)        0 (0%)      0 (0%)           0 (0%)         8m38s
# Allocated resources:
#   (Total limits may be over 100 percent, i.e., overcommitted.)
#   Resource           Requests   Limits
#   --------           --------   ------
#   cpu                100m (5%)  0 (0%)
#   memory             50Mi (2%)  0 (0%)
#   ephemeral-storage  0 (0%)     0 (0%)
# 워커 노드의 상세 정보입니다. Taints가 없으므로 모든 파드를 스케줄링할 수 있습니다. Flannel 관련 어노테이션과 Pod CIDR이 설정되어 있으며, kube-flannel과 kube-proxy 파드가 실행 중입니다.

# k8s-w1 노드에 배치된 파드 확인
kubectl get pod -A -owide | grep k8s-w2
# kube-flannel   kube-flannel-ds-2dprr             1/1     Running   0          6m28s   192.168.10.102   k8s-w2    <none>           <none>
# kube-system    kube-proxy-98mm5                  1/1     Running   0          6m28s   192.168.10.102   k8s-w2    <none>           <none>
# k8s-w2 노드에는 kube-flannel과 kube-proxy 파드가 DaemonSet으로 배포되어 실행 중입니다.

kubectl get pod -A -owide | grep k8s-w1
# kube-flannel   kube-flannel-ds-dmx6v             1/1     Running   0          8m46s   192.168.10.101   k8s-w1    <none>           <none>
# kube-system    kube-proxy-7vlpt                  1/1     Running   0          8m46s   192.168.10.101   k8s-w1    <none>           <none>
# k8s-w1 노드에도 kube-flannel과 kube-proxy 파드가 DaemonSet으로 배포되어 실행 중입니다. 각 워커 노드마다 이 두 파드가 필수적으로 실행됩니다.

[k8s-w1/w2 노드 정보 확인, 기본 환경 정보 출력 비교, sysctl 변경 확인

# # kubelet 활성화 확인
systemctl status kubelet --no-pager
# ● kubelet.service - kubelet: The Kubernetes Node Agent
#      Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: disabled)
#     Drop-In: /usr/lib/systemd/system/kubelet.service.d
#              └─10-kubeadm.conf
#      Active: active (running) since Wed 2026-01-21 13:31:38 KST; 12min ago
#  Invocation: ec2bd8770af1401dad7fd81ad99b9595
#        Docs: https://kubernetes.io/docs/
#    Main PID: 9652 (kubelet)
#       Tasks: 10 (limit: 12337)
#      Memory: 30.3M (peak: 31.1M)
#         CPU: 10.528s
#      CGroup: /system.slice/kubelet.service
#              └─9652 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kub…
# join 후 워커 노드에서 kubelet이 정상적으로 실행 중입니다. bootstrap-kubeconfig를 통해 TLS 부트스트랩을 완료한 후 정식 kubeconfig를 사용하여 API 서버와 통신합니다.

# 기본 환경 정보 출력 저장
cat /etc/sysconfig/kubelet
# KUBELET_EXTRA_ARGS=
# join 후에도 kubelet 추가 인자 설정 파일은 비어있습니다. kubelet은 kubeadm이 생성한 설정 파일을 사용합니다.

tree /etc/kubernetes  | tee -a etc_kubernetes-2.txt
# /etc/kubernetes
# ├── kubelet.conf
# ├── manifests
# └── pki
#     └── ca.crt
#
# 3 directories, 2 files
# join 후 워커 노드의 /etc/kubernetes 디렉토리입니다. kubelet.conf(kubelet용 kubeconfig), manifests(비어있음, static pod 없음), pki/ca.crt(클러스터 CA 인증서)만 존재합니다.

cat /etc/kubernetes/kubelet.conf
# apiVersion: v1
# clusters:
# - cluster:
#     certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJUVFnd2ViNU1Fd0l3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5T...
#     server: https://192.168.10.100:6443
#   name: default-cluster
# contexts:
# - context:
#     cluster: default-cluster
#     namespace: default
#     user: default-auth
#   name: default-context
# current-context: default-context
# kind: Config
# preferences: {}
# users:
# - name: default-auth
#   user:
#     client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem
#     client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
# 워커 노드의 kubelet.conf입니다. TLS 부트스트랩을 통해 발급받은 인증서(/var/lib/kubelet/pki/kubelet-client-current.pem)를 사용하여 API 서버에 인증합니다.

tree /var/lib/kubelet | tee -a var_lib_kubelet-2.txt
# /var/lib/kubelet
# ├── checkpoints
# ├── config.yaml
# ├── cpu_manager_state
# ├── device-plugins
# │   └── kubelet.sock
# ├── kubeadm-flags.env
# ├── memory_manager_state
# ├── pki
# │   ├── kubelet-client-2026-01-21-13-31-39.pem
# │   ├── kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2026-01-21-13-31-39.pem
# │   ├── kubelet.crt
# │   └── kubelet.key
# ├── plugins
# ├── plugins_registry
# ├── pod-resources
# │   └── kubelet.sock
# └── pods
#     ├── 052c4cfb-1ef6-4da1-a58a-add170de6420
#     │   ├── containers
#     │   │   └── kube-proxy
#     │   ├── etc-hosts
#     │   ├── plugins
#     │   └── volumes
#     └── db0d3e41-be5f-4a3f-8068-e2d620fd2a9b
#         ├── containers
#         │   ├── install-cni
#         │   ├── install-cni-plugin
#         │   └── kube-flannel
#         ├── etc-hosts
#         ├── plugins
#         └── volumes
#
# 34 directories, 30 files
# join 후 워커 노드의 kubelet 데이터 디렉토리입니다. pki 디렉토리에 TLS 부트스트랩으로 발급받은 인증서가 저장되어 있으며, pods 디렉토리에 kube-proxy와 kube-flannel 파드가 실행 중입니다.

tree /run/containerd/ -L 3 | tee -a run_containerd-2.txt
# /run/containerd/
# ├── containerd.sock
# ├── containerd.sock.ttrpc
# ├── io.containerd.grpc.v1.cri
# │   ├── containers
# │   │   ├── 43a4e45996ac5af7015cd3bde5d7a79f75ec597d7b3f88b948ec8272a50bb72e
# │   │   ├── 542a456b89b982e240eef75e835d53e0865c61a3c0ae6cd705e7c2aa703350bf
# │   │   ├── 6e3da4a24a0b7814b66972ddacfb7443798f040653799c706bae75a5ee1fcb69
# │   │   └── de07fe95d7d875bd16d8dc8c43fe33f91a5b3eeb2ac9fac434e19b5b83fbfeaa
# │   └── sandboxes
# │       ├── b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d
# │       └── eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d
# ├── io.containerd.runtime.v2.task
# │   └── k8s.io
# │       ├── 43a4e45996ac5af7015cd3bde5d7a79f75ec597d7b3f88b948ec8272a50bb72e
# │       ├── 6e3da4a24a0b7814b66972ddacfb7443798f040653799c706bae75a5ee1fcb69
# │       ├── b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d
# │       └── eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d
# ├── io.containerd.sandbox.controller.v1.shim
# ├── runc
# │   └── k8s.io
# │       ├── 43a4e45996ac5af7015cd3bde5d7a79f75ec597d7b3f88b948ec8272a50bb72e
# │       ├── 6e3da4a24a0b7814b66972ddacfb7443798f040653799c706bae75a5ee1fcb69
# │       ├── b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d
# │       └── eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d
# └── s
#     ├── b3835352d7dcc04fd672851fb420ef2e1987820eb2ad72a603e72503f6795609
#     └── c98546d363a5ff159d3be4e14e78fc9852e1b0ca49c6599ebf5584490cb28bd2
#
# 24 directories, 4 files
# join 후 워커 노드의 containerd 런타임 디렉토리입니다. kube-proxy와 kube-flannel 파드의 컨테이너와 sandbox가 생성되어 있습니다.

pstree -alnp | tee -a pstree-2.txt
# systemd,1 --switched-root --system --deserialize=46 no_timer_check
#   |-systemd-journal,434
#   |-systemd-userdbd,469
#   |-systemd-udevd,478
#   |-rpcbind,550 -w -f
#   |-auditd,553
#   |-dbus-broker-lau,592 --scope system --audit
#   |-irqbalance,604
#   |-lsmd,605 -d
#   |-systemd-logind,608
#   |-chronyd,613 -F 2
#   |-VBoxService,710 --pidfile /var/run/vboxadd-service.sh
#   |-polkitd,762 --no-debug --log-level=err
#   |-gssproxy,793 -i
#   |-sshd,794
#   |-tuned,795 -Es /usr/sbin/tuned -l -P
#   |-rsyslogd,991 -n
#   |-atd,1034 -f
#   |-crond,1040 -n
#   |-agetty,1058 -o -- \\u --noreset --noclear - linux
#   |-fwupd,1123
#   |-gpg-agent,1263 --homedir /var/lib/fwupd/gnupg --use-standard-socket --daemon
#   |-udisksd,1416
#   |-NetworkManager,3072 --no-daemon
#   |-systemd,6560 --user
#   |-containerd,6965
#   |-kubelet,9652 --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///run/containerd/containerd.sock --node-ip=192.168.10.101 --pod-infra-container-image=registry.k8s.io/pause:3.10
#   |-containerd-shim,9720 -namespace k8s.io -id b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d -address /run/containerd/containerd.sock
#   |   |-pause,9766
#   |   |-kube-proxy,9816 --config=/var/lib/kube-proxy/config.conf --hostname-override=k8s-w1
#   |-containerd-shim,9725 -namespace k8s.io -id eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d -address /run/containerd/containerd.sock
#   |   |-pause,9768
#   |   `-flanneld,10124 --ip-masq --kube-subnet-mgr --iface=enp0s9
# join 후 워커 노드의 프로세스 트리입니다. kubelet이 실행 중이며, kube-proxy와 flanneld 프로세스가 containerd-shim을 통해 실행되고 있습니다.

systemd-cgls --no-pager | tee -a systemd-cgls-2.txt
# CGroup /:
# -.slice
# ├─user.slice
# ├─init.scope
# │ └─1 /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_tim…
# └─system.slice
#   ├─irqbalance.service
#   ├─libstoragemgmt.service
#   ├─vboxadd-service.service
#   ├─containerd.service …
#   │ ├─6965 /usr/bin/containerd
#   │ ├─9720 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id b7584ea4268f2…
#   │ └─9725 /usr/bin/containerd-shim-runc-v2 -namespace k8s.io -id eecbd2b79d45e…
#   ├─systemd-udevd.service …
#   ├─dbus-broker.service
#   ├─polkit.service
#   ├─chronyd.service
#   ├─auditd.service
#   ├─tuned.service
#   ├─kubelet.service
#   │ └─9652 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-ku…
#   ├─systemd-journald.service
#   ├─atd.service
#   ├─sshd.service
#   ├─fwupd.service
#   ├─crond.service
#   ├─NetworkManager.service
#   ├─gssproxy.service
#   ├─rsyslog.service
#   ├─systemd-userdbd.service
#   ├─rpcbind.service
#   ├─udisks2.service
#   ├─system-getty.slice
#   └─systemd-logind.service
# └─kubepods.slice
#   ├─kubepods-burstable.slice
#   │ └─kubepods-burstable-poddb0d3e41_be5f_4a3f_8068_e2d620fd2a9b.slice
#   │   ├─cri-containerd-6e3da4a24a0b7814b66972ddacfb7443798f040653799c706bae75a5ee1fcb69.scope …
#   │   │ └─10124 /opt/bin/flanneld --ip-masq --kube-subnet-mgr --iface=enp0s9
#   │   └─cri-containerd-eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d.scope …
#   │     └─9768 /pause
#   └─kubepods-besteffort.slice
#     └─kubepods-besteffort-pod052c4cfb_1ef6_4da1_a58a_add170de6420.slice
#       ├─cri-containerd-43a4e45996ac5af7015cd3bde5d7a79f75ec597d7b3f88b948ec8272a50bb72e.scope …
#       │ └─9816 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.co…
#       └─cri-containerd-b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d.scope …
#       │   └─9766 /pause
# join 후 워커 노드의 cgroup 구조입니다. kubepods.slice 하위에 kube-proxy와 kube-flannel 파드가 cgroup으로 관리되고 있습니다.
lsns | tee -a lsns-2.txt
#        NS TYPE   NPROCS   PID USER    COMMAND
# 4026531834 time      125     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531835 cgroup    124     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531836 pid       121     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531837 user      124     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531838 uts       116     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531839 ipc       121     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531840 net       123     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# 4026531841 mnt       103     1 root    /usr/lib/systemd/systemd --switched-root --system --deserialize=46 no_timer_check
# ... (기타 프로세스별 네임스페이스)
# 4026532126 mnt         1  9766 65535   /pause
# 4026532189 ipc         2  9766 65535   /pause
# 4026532191 pid         1  9766 65535   /pause
# 4026532267 mnt         1  9768 65535   /pause
# 4026532268 ipc         2  9768 65535   /pause
# 4026532269 pid         1  9768 65535   /pause
# 4026532270 mnt         1  9816 root    /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=k8s-w1
# 4026532271 pid         1  9816 root    /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=k8s-w1
# 4026532272 mnt         1 10124 root    /opt/bin/flanneld --ip-masq --kube-subnet-mgr --iface=enp0s9
# 4026532273 pid         1 10124 root    /opt/bin/flanneld --ip-masq --kube-subnet-mgr --iface=enp0s9
# 4026532274 cgroup      1 10124 root    /opt/bin/flanneld --ip-masq --kube-subnet-mgr --iface=enp0s9
# join 후 워커 노드의 네임스페이스 목록입니다. pause 컨테이너와 kube-proxy, flanneld 프로세스가 각각의 네임스페이스를 사용하고 있습니다.

ip addr | tee -a ip_addr-2.txt 
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#     inet 127.0.0.1/8 scope host lo
#        valid_lft forever preferred_lft forever
#     inet6 ::1/128 scope host noprefixroute 
#        valid_lft forever preferred_lft forever
# 2: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:90:ea:eb brd ff:ff:ff:ff:ff:ff
#     inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute enp0s8
#        valid_lft 43789sec preferred_lft 43789sec
# 3: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
#     link/ether 08:00:27:1f:02:49 brd ff:ff:ff:ff:ff:ff
#     inet 192.168.10.101/24 brd 192.168.10.255 scope global noprefixroute enp0s9
#        valid_lft forever preferred_lft forever
# 4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
#     link/ether d6:6c:c0:b2:b0:73 brd ff:ff:ff:ff:ff:ff
#     inet 10.244.1.0/32 scope global flannel.1
#        valid_lft forever preferred_lft forever
# join 후 워커 노드의 네트워크 인터페이스입니다. Flannel이 생성한 flannel.1 VXLAN 인터페이스가 추가되었으며, 이 노드의 Pod CIDR 게이트웨이 IP(10.244.1.0)가 할당되어 있습니다.

ss -tnlp | tee -a ss-2.txt
# State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess                                                   
# LISTEN 0      128          0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=794,fd=7))                            
# LISTEN 0      4096         0.0.0.0:111        0.0.0.0:*    users:(("rpcbind",pid=550,fd=5),("systemd",pid=1,fd=115))
# LISTEN 0      4096       127.0.0.1:10249      0.0.0.0:*    users:(("kube-proxy",pid=9816,fd=12))                    
# LISTEN 0      4096       127.0.0.1:10248      0.0.0.0:*    users:(("kubelet",pid=9652,fd=18))                       
# LISTEN 0      4096       127.0.0.1:46103      0.0.0.0:*    users:(("containerd",pid=6965,fd=12))                    
# LISTEN 0      4096               *:10250            *:*    users:(("kubelet",pid=9652,fd=21))                       
# LISTEN 0      128             [::]:22            [::]:*    users:(("sshd",pid=794,fd=8))                            
# LISTEN 0      4096               *:10256            *:*    users:(("kube-proxy",pid=9816,fd=10))                    
# LISTEN 0      4096            [::]:111           [::]:*    users:(("rpcbind",pid=550,fd=7),("systemd",pid=1,fd=117))
# LISTEN 0      4096               *:9090             *:*    users:(("systemd",pid=1,fd=112))                         
# join 후 워커 노드의 리스닝 소켓입니다. kubelet(10248: healthz, 10250: kubelet API)과 kube-proxy(10249: healthz, 10256: metrics) 포트가 추가되었습니다.

df -hT | tee -a df-2.txt
# Filesystem     Type      Size  Used Avail Use% Mounted on
# /dev/sda3      xfs        60G  3.2G   57G   6% /
# devtmpfs       devtmpfs  4.0M     0  4.0M   0% /dev
# tmpfs          tmpfs     913M     0  913M   0% /dev/shm
# efivarfs       efivarfs  256K  6.3K  250K   3% /sys/firmware/efi/efivars
# tmpfs          tmpfs     366M   12M  354M   4% /run
# tmpfs          tmpfs     1.0M     0  1.0M   0% /run/credentials/systemd-journald.service
# /dev/sda1      vfat      599M   13M  587M   3% /boot/efi
# tmpfs          tmpfs     1.0M     0  1.0M   0% /run/credentials/getty@tty1.service
# tmpfs          tmpfs     183M  8.0K  183M   1% /run/user/1000
# tmpfs          tmpfs     1.7G   12K  1.7G   1% /var/lib/kubelet/pods/db0d3e41-be5f-4a3f-8068-e2d620fd2a9b/volumes/kubernetes.io~projected/kube-api-access-966m8
# tmpfs          tmpfs     1.7G   12K  1.7G   1% /var/lib/kubelet/pods/052c4cfb-1ef6-4da1-a58a-add170de6420/volumes/kubernetes.io~projected/kube-api-access-2klgb
# shm            tmpfs      64M     0   64M   0% /run/containerd/io.containerd.grpc.v1.cri/sandboxes/eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d/shm
# shm            tmpfs      64M     0   64M   0% /run/containerd/io.containerd.grpc.v1.cri/sandboxes/b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d/shm
# overlay        overlay    60G  3.2G   57G   6% /run/containerd/io.containerd.runtime.v2.task/k8s.io/b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d/rootfs
# overlay        overlay    60G  3.2G   57G   6% /run/containerd/io.containerd.runtime.v2.task/k8s.io/eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d/rootfs
# overlay        overlay    60G  3.2G   57G   6% /run/containerd/io.containerd.runtime.v2.task/k8s.io/43a4e45996ac5af7015cd3bde5d7a79f75ec597d7b3f88b948ec8272a50bb72e/rootfs
# overlay        overlay    60G  3.2G   57G   6% /run/containerd/io.containerd.runtime.v2.task/k8s.io/6e3da4a24a0b7814b66972ddacfb7443798f040653799c706bae75a5ee1fcb69/rootfs
# join 후 워커 노드의 파일시스템 사용량입니다. 파드의 projected volume(kube-api-access)과 컨테이너의 overlay 파일시스템이 마운트되어 있습니다. 루트 파일시스템 사용량이 5%에서 6%로 증가했습니다.

findmnt | tee -a findmnt-2.txt
# TARGET                                                                                                                           SOURCE      FSTYPE      OPTIONS
# /                                                                                                                                /dev/sda3   xfs         rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota
# ├─/var/lib/nfs/rpc_pipefs                                                                                                        sunrpc      rpc_pipefs  rw,relatime
# ├─/dev                                                                                                                           devtmpfs    devtmpfs    rw,nosuid,seclabel,size=4096k,nr_inodes=229647,mode=755,inode64
# ├─/sys                                                                                                                           sysfs       sysfs       rw,nosuid,nodev,noexec,relatime,seclabel
# │ ├─/sys/fs/cgroup                                                                                                               cgroup2     cgroup2     rw,nosuid,nodev,noexec,relatime,seclabel
# │ └─/sys/firmware/efi/efivars                                                                                                    efivarfs    efivarfs    rw,nosuid,nodev,noexec,relatime
# ├─/proc                                                                                                                          proc        proc        rw,nosuid,nodev,noexec,relatime
# ├─/run                                                                                                                           tmpfs       tmpfs       rw,nosuid,nodev,seclabel,size=373852k,nr_inodes=819200,mode=755,inode64
# │ ├─/run/containerd/io.containerd.grpc.v1.cri/sandboxes/eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f4ec4ea91f73218e4a68533500af5d/shm     shm         tmpfs       rw,nosuid,nodev,noexec,relatime,seclabel,size=65536k,inode64
# │ ├─/run/containerd/io.containerd.grpc.v1.cri/sandboxes/b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d/shm     shm         tmpfs       rw,nosuid,nodev,noexec,relatime,seclabel,size=65536k,inode64
# │ ├─/run/containerd/io.containerd.runtime.v2.task/k8s.io/b7584ea4268f2c0540b4b0f5ec61e8e83b54b62081f4e9a26e67f9744cb8731d/rootfs overlay     overlay     rw,relatime,seclabel,lowerdir=...,upperdir=...,workdir=...,uuid=on
# │ ├─/run/containerd/io.containerd.runtime.v2.task/k8s.io/43a4e45996ac5af7015cd3bde5d7a79f75ec597d7b3f88b948ec8272a50bb72e/rootfs overlay     overlay     rw,relatime,seclabel,lowerdir=...,upperdir=...,workdir=...,uuid=on
# │ ├─/run/containerd/io.containerd.runtime.v2.task/k8s.io/eecbd2b79d45e4a681a08ac2734085ddb5f4ec4ea91f73218e4a68533500af5d/rootfs overlay     overlay     rw,relatime,seclabel,lowerdir=...,upperdir=...,workdir=...,uuid=on
# │ └─/run/containerd/io.containerd.runtime.v2.task/k8s.io/6e3da4a24a0b7814b66972ddacfb7443798f040653799c706bae75a5ee1fcb69/rootfs overlay     overlay     rw,relatime,seclabel,lowerdir=...,upperdir=...,workdir=...,uuid=on
# ├─/boot/efi                                                                                                                      /dev/sda1   vfat        rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro
# ├─/var/lib/kubelet/pods/db0d3e41-be5f-4a3f-8068-e2d620fd2a9b/volumes/kubernetes.io~projected/kube-api-access-966m8               tmpfs       tmpfs       rw,relatime,seclabel,size=1766852k,inode64,noswap
# └─/var/lib/kubelet/pods/052c4cfb-1ef6-4da1-a58a-add170de6420/volumes/kubernetes.io~projected/kube-api-access-2klgb               tmpfs       tmpfs       rw,relatime,seclabel,size=1766852k,inode64,noswap
# join 후 워커 노드의 마운트 포인트 목록입니다. 파드의 projected volume(kube-api-access)과 컨테이너의 overlay 파일시스템, shm(공유 메모리)이 마운트되어 있습니다.

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 findmnt-1.txt findmnt-2.txt 

결과 비교

구분 join 전 (sysctl-1.txt / *_1.txt) join 후 (sysctl-2.txt / *_2.txt) 의미
/etc/kubernetes manifests/만 존재 (2 directories, 0 files) kubelet.conf, pki/ca.crt 생성 (3 directories, 2 files) 워커는 static pod를 직접 갖지 않음(manifests는 비어있을 수 있음). 대신 kubelet이 API 서버 통신을 위한 kubeconfig/CA를 받음
/var/lib/kubelet 비어있음 config.yaml, kubeadm-flags.env, pki/*, pods/* 생성 TLS bootstrap 이후 kubelet이 자기 설정/인증서를 만들고, 실행 중인 파드(kube-proxy, flannel) 상태/볼륨/마운트를 관리
/run/containerd/ 소켓/기본 런타임 디렉토리만 io.containerd.grpc.v1.cri/containers, sandboxes, runtime.v2.task/k8s.io/* 증가 join 후 CRI 컨테이너/샌드박스/태스크가 생성되어 실제 파드가 실행됨
프로세스(pstree) containerd만 주로 보임 kubelet 추가 + containerd-shim 아래 kube-proxy, flanneld 실행 워커에서 kubelet이 기동되고, 필수 DaemonSet이 노드에 배포되어 동작
cgroup(systemd-cgls) kubepods.slice 없음 kubepods.slice 생성, 파드별 slice/scope 생성 kubelet이 cgroup(v2) 하위에서 파드 리소스/격리를 관리하기 시작
네임스페이스(lsns) 시스템 기본 ns만 pause, kube-proxy, flanneld 등 컨테이너/프로세스별 ns 추가 컨테이너 런타임이 pid/mnt/ipc/net 네임스페이스를 생성해 파드를 격리
네트워크(ip addr) lo, enp0s8, enp0s9만 flannel.1 추가, 10.244.1.0/32 할당 Flannel VXLAN 오버레이 구성. 노드별 Pod CIDR 라우팅에 사용
리스닝 포트(ss -tnlp) 22, 111, containerd 정도 kubelet(10248/10250), kube-proxy(10249/10256) 추가 join 후 kubelet/kube-proxy가 서비스/헬스/메트릭 포트를 노출
파일시스템(df -hT) 루트 사용량 낮음, k8s 마운트 없음 tmpfs(projected volume), shm, overlay(rootfs) 마운트 추가 파드가 뜨면서 ServiceAccount 토큰/CA(projected), 컨테이너 rootfs(overlay), sandbox shm이 마운트됨
마운트(findmnt) 시스템 기본만 /var/lib/kubelet/pods/..., /run/containerd/.../rootfs, /run/containerd/.../shm 추가 파드/컨테이너 실행을 위한 kubelet/containerd 마운트 트리가 실제로 생성됨

 

sysctl 항목 join 전 join 후 의미/추정 원인
kernel.panic 0 10 커널 패닉 시 10초 후 자동 재부팅. 노드 장애 시 자동 복구 성격(설정 적용 경로는 kubelet/시스템 정책 확인 필요)
vm.overcommit_memory 0 1 메모리 오버커밋 허용(일부 워크로드/컴포넌트 안정성/성능 목적). 변경 주체는 kubelet/런타임/OS 튜닝 설정 가능

 

6. 모니터링 툴 설치

# 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
# NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
# k8s-ctr   193m         4%       823Mi           30%
# k8s-w1    38m          1%       378Mi           21%
# k8s-w2    30m          1%       329Mi           19%
kubectl top pod -A --sort-by='cpu'
# NAMESPACE      NAME                              CPU(cores)   MEMORY(bytes)
# kube-system    kube-apiserver-k8s-ctr            82m          202Mi
# kube-system    etcd-k8s-ctr                      46m          48Mi
# kube-system    kube-controller-manager-k8s-ctr   38m          50Mi
# kube-system    kube-scheduler-k8s-ctr            12m          23Mi
# kube-flannel   kube-flannel-ds-w9s2j             12m          15Mi
# kube-flannel   kube-flannel-ds-2dprr             9m           13Mi
# kube-flannel   kube-flannel-ds-dmx6v             8m           13Mi
# kube-system    metrics-server-5dd7b49d79-4m8tk   5m           21Mi
# kube-system    coredns-668d6bf9bc-77pjv          4m           16Mi
# kube-system    coredns-668d6bf9bc-47zx5          3m           16Mi
# kube-system    kube-proxy-qhqpz                  1m           16Mi
# kube-system    kube-proxy-98mm5                  1m           17Mi
# kube-system    kube-proxy-7vlpt                  1m           17Mi
kubectl top pod -A --sort-by='memory'
# NAMESPACE      NAME                              CPU(cores)   MEMORY(bytes)
# kube-system    kube-apiserver-k8s-ctr            82m          202Mi
# kube-system    kube-controller-manager-k8s-ctr   38m          50Mi
# kube-system    etcd-k8s-ctr                      46m          48Mi
# kube-system    kube-scheduler-k8s-ctr            12m          23Mi
# kube-system    metrics-server-5dd7b49d79-4m8tk   5m           21Mi
# kube-system    kube-proxy-7vlpt                  1m           17Mi
# kube-system    kube-proxy-98mm5                  1m           17Mi
# kube-system    kube-proxy-qhqpz                  1m           16Mi
# kube-system    coredns-668d6bf9bc-77pjv          4m           16Mi
# kube-system    coredns-668d6bf9bc-47zx5          3m           16Mi
# kube-flannel   kube-flannel-ds-w9s2j             12m          15Mi
# kube-flannel   kube-flannel-ds-dmx6v             8m           13Mi
# kube-flannel   kube-flannel-ds-2dprr             9m           13Mi

프로메테우스-스택 설치

# kube-prometheus-stack 설치
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
# NAME                    NAMESPACE    REVISION   UPDATED                                 STATUS    CHART                          APP VERSION
# kube-prometheus-stack   monitoring   1          2026-01-21 14:49:56 +0900 KST            deployed   kube-prometheus-stack-80.13.3    v0.87.1

kubectl get pod,svc,ingress,pvc -n monitoring
# NAME                                                            READY   STATUS    RESTARTS   AGE
# pod/alertmanager-kube-prometheus-stack-alertmanager-0           2/2     Running   0          15m
# pod/kube-prometheus-stack-grafana-5cb7c586f9-87jg2              3/3     Running   0          15m
# pod/kube-prometheus-stack-kube-state-metrics-7846957b5b-rgvkj   1/1     Running   0          15m
# pod/kube-prometheus-stack-operator-584f446c98-zxd9l             1/1     Running   0          15m
# pod/kube-prometheus-stack-prometheus-node-exporter-fxwmf        1/1     Running   0          15m
# pod/kube-prometheus-stack-prometheus-node-exporter-rxgr4        1/1     Running   0          15m
# pod/kube-prometheus-stack-prometheus-node-exporter-wmxrz        1/1     Running   0          15m
# pod/prometheus-kube-prometheus-stack-prometheus-0               2/2     Running   0          15m
#
# NAME                                                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                         AGE
# service/alertmanager-operated                            ClusterIP   None            <none>        9093/TCP,9094/TCP,9094/UDP      15m
# service/kube-prometheus-stack-alertmanager               ClusterIP   10.96.124.194   <none>        9093/TCP,8080/TCP               15m
# service/kube-prometheus-stack-grafana                    NodePort    10.96.165.91    <none>        80:30002/TCP                    15m
# service/kube-prometheus-stack-kube-state-metrics         ClusterIP   10.96.17.224    <none>        8080/TCP                        15m
# service/kube-prometheus-stack-operator                   ClusterIP   10.96.118.10    <none>        443/TCP                         15m
# service/kube-prometheus-stack-prometheus                 NodePort    10.96.178.162   <none>        9090:30001/TCP,8080:30152/TCP   15m
# service/kube-prometheus-stack-prometheus-node-exporter   ClusterIP   10.96.148.157   <none>        9100/TCP                        15m
# service/prometheus-operated                              ClusterIP   None            <none>        9090/TCP                        15m
# (ingress/pvc는 구성에 따라 없을 수 있으며, 이 환경에서는 출력에 나타나지 않습니다.)

kubectl get prometheus,servicemonitors,alertmanagers -n monitoring
# NAME                                                                VERSION   DESIRED   READY   RECONCILED   AVAILABLE   AGE
# prometheus.monitoring.coreos.com/kube-prometheus-stack-prometheus   v3.9.1    1         1       True         True        15m
#
# NAME                                                                                  AGE
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-alertmanager               15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-apiserver                  15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-coredns                    15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-grafana                    15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kube-controller-manager    15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kube-etcd                  15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kube-scheduler             15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kube-state-metrics         15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-kubelet                    15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-operator                   15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-prometheus                 15m
# servicemonitor.monitoring.coreos.com/kube-prometheus-stack-prometheus-node-exporter   15m
#
# NAME                                                                    VERSION   REPLICAS   READY   RECONCILED   AVAILABLE   AGE
# alertmanager.monitoring.coreos.com/kube-prometheus-stack-alertmanager   v0.30.0   1          1       True         True        15m

kubectl get crd | grep monitoring
# alertmanagerconfigs.monitoring.coreos.com   2026-01-21T05:49:55Z
# alertmanagers.monitoring.coreos.com         2026-01-21T05:49:55Z
# podmonitors.monitoring.coreos.com           2026-01-21T05:49:55Z
# probes.monitoring.coreos.com                2026-01-21T05:49:55Z
# prometheusagents.monitoring.coreos.com      2026-01-21T05:49:55Z
# prometheuses.monitoring.coreos.com          2026-01-21T05:49:55Z
# prometheusrules.monitoring.coreos.com       2026-01-21T05:49:55Z
# scrapeconfigs.monitoring.coreos.com         2026-01-21T05:49:55Z
# servicemonitors.monitoring.coreos.com       2026-01-21T05:49:55Z
# thanosrulers.monitoring.coreos.com          2026-01-21T05:49:55Z
# kube-prometheus-stack 설치 시 Prometheus Operator 관련 CRD들이 함께 설치됩니다.

# 각각 웹 접속 실행 : NodePort 접속
open http://192.168.10.100:30001 # prometheus
open http://192.168.10.100:30002 # grafana : 접속 계정 admin / prom-operator

# 프로메테우스 버전 확인
kubectl exec -it sts/prometheus-kube-prometheus-stack-prometheus -n monitoring -c prometheus -- prometheus --version
prometheus, version 3.9.1

# 그라파나 버전 확인
kubectl exec -it -n monitoring deploy/kube-prometheus-stack-grafana -- grafana --version
grafana version 12.3.1

# [K8S Dashboard 추가] Dashboard → New → Import → 15661, 15757 입력 후 Load ⇒ 데이터소스(Prometheus 선택) 후 Import 클릭

Promentheus 대시보드를 확인해보면 다음과 같이 Targets가 DOWN 상태로 표시되며, connect: connection refused가 발생합니다.

이는 kube-controller-manager(10257), kube-scheduler(10259), etcd(2381)의 메트릭 엔드포인트가 기본적으로 127.0.0.1에만 바인딩되어 있는데, Prometheus(ServiceMonitor)가 노드 IP(예: 192.168.10.100) 로 접속을 시도하기 때문입니다. 따라서 아래와 같이 각 컴포넌트의 bind-address / listen-metrics-urls를 외부(노드 IP)에서도 접근 가능하도록 조정해야 정상적으로 수집됩니다.

 

kube-controller-manager, etcd, kube-scheduler 메트릭 수집 설정을 통해 모든 매트릭이 정상적으로 나올 수 있도록 합니다.

# 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

 

해당 파일들을 수정하면 static pod가 재기동 되고, 일정 시간후에 정상적으로 매트릭이 수집되는 것을 확인할 수 있습니다.

인증서 익스포터 설치

먼저 인증서들을 확인합니다.

# 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-21-02-40-56.pem
├── kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2026-01-21-02-40-56.pem
├── kubelet.crt
└── kubelet.key

 

x509 인증서 만료 모니터링 대시보드는 인증서 만료에 대한 가시성을 확보할 수 있어서 매우 유용할 것 같습니다.
  • 대시보드
    • Grafana Dashboard 13922 활용
  • 주요 기능
    • x509 인증서 만료일 및 유효성 메트릭을 Prometheus로 수집
    • 알람 : 만료 임박(28일/14일 전)에 대해 경고/크리티컬 알림
    • 지원 소스
      • Kubernetes 시크릿(TLS)
      • 시스템/디렉터리 내 PEM 인코딩 파일
      • kubeconfig(인증서 직접/파일 참조 포함)
    • 노드별로 컨트롤플레인/워커 데몬셋 2종 분리 배포
    • 주요 메트릭:
      • x509_cert_not_before, x509_cert_not_after (시작·만료일시)
      • x509_cert_expired (만료여부)
      • x509_cert_expires_in_seconds, x509_cert_valid_since_seconds (남은/지남 초, 옵션)
      • x509_cert_error, x509_read_errors (에러 감지)
      • x509_exporter_build_info (버전 등)
    • values.yaml에서 파일 경로 및 kubeconfig 경로 지정 가능
    • Grafana 대시보드 연동 및 Prometheus Alertmanager 알람 지원

values 파일 주요 내용

  • 데몬셋 2종류 배포 : 컨트롤 플레인 노드들 수집(cp), 워커 노드들 수집(nodes)
  • 수집 종류 : 인증서(crt) 파일, kubeconfig 파일 → 직접 파일 위치 설정
  • 프로메테우스 알람 설정 활성화 : warning Days Left(28 일), critical Days Left(14일)
  • 그라파나 대시보드 추가 활성화
kubectl label node k8s-w1 worker="true" --overwrite
kubectl label node k8s-w2 worker="true" --overwrite
kubectl get nodes -l worker=true
# NAME     STATUS   ROLES    AGE    VERSION
# k8s-w1   Ready    <none>   104m   v1.32.11
# k8s-w2   Ready    <none>   102m   v1.32.11
# 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=   21s
# x509-certificate-exporter-nodes   2         2         2       2            2           worker=true                              21s

# 파드 정보 확인 : 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-22v9p      1/1     Running   0          39s   10.244.0.4   k8s-ctr   <none>           <none>
# x509-certificate-exporter-nodes-5rhqf   1/1     Running   0          39s   10.244.1.5   k8s-w1    <none>           <none>
# x509-certificate-exporter-nodes-v5jg2   1/1     Running   0          39s   10.244.2.7   k8s-w2    <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.87.136   <none>        9793/TCP   91s

# NAME                                  ENDPOINTS                                         AGE
# endpoints/x509-certificate-exporter   10.244.0.4:9793,10.244.1.5:9793,10.244.2.7:9793   91s

# 컨트롤플레인 노드에 배포된 'x509 익스포터' 파드에 메트릭 호출 확인
curl -s 10.244.1.5:9793/metrics | grep '^x509' | head -n 3
# x509_cert_expired{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="4686048711720833794",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="191383947306253808361404236898124720804",subject_CN="system:node:k8s-w1",subject_O="system:nodes"} 0
# x509_cert_not_after{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="4686048711720833794",subject_CN="kubernetes"} 2.084290853e+09

# 워커 노드에 배포된 'x509 익스포터' 파드에 메트릭 호출 확인
curl -s 10.244.2.7:9793/metrics | grep '^x509' | head -n 3
# x509_cert_expired{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="4686048711720833794",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="327906239552731223600543922899282080707",subject_CN="system:node:k8s-w2",subject_O="system:nodes"} 0
# x509_cert_not_after{filename="ca.crt",filepath="/etc/kubernetes/pki/ca.crt",issuer_CN="kubernetes",serial_number="4686048711720833794",subject_CN="kubernetes"} 2.084290853e+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
# ...

프로메테우스 그라파나 대시보드 확인

아래 화면은 Prometheus에서 x509-certificate-exporter가 노출한 메트릭이 실제로 수집되는지 확인하기 위한 PromQL 예시입니다. x509_cert_not_after, x509_cert_expired, x509_cert_expires_in_seconds 같은 지표를 조회해서 인증서 만료 시각/만료 여부/남은 만료 시간을 확인합니다.

아래 Grafana 대시보드는 수집된 x509 메트릭을 기반으로 노드/경로별 인증서의 만료 임박(Warning/Critical) 현황을 시각화합니다. 인증서별 만료일(not_after)과 남은 시간, 만료 여부를 한 화면에서 확인해 운영 중 만료 리스크를 빠르게 파악할 수 있습니다.

 

7. 샘플 애플리케이션 배포

# 샘플 애플리케이션 배포
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webpod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webpod
  template:
    metadata:
      labels:
        app: webpod
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - sample-app
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: webpod
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: webpod
  labels:
    app: webpod
spec:
  selector:
    app: webpod
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
EOF

# 배포 확인
kubectl get deploy,svc,ep webpod -owide
# NAME                     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES           SELECTOR
# deployment.apps/webpod   2/2     2            2           8s    webpod       traefik/whoami   app=webpod

# NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE   SELECTOR
# service/webpod   ClusterIP   10.96.136.104   <none>        80/TCP    8s    app=webpod

# NAME               ENDPOINTS                     AGE
# endpoints/webpod   10.244.1.6:80,10.244.2.8:80   8s

# webpod service clusterip 변수 지정
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
echo $SVCIP

# 통신 확인
curl -s $SVCIP
curl -s $SVCIP | grep Hostname
# Hostname: webpod-697b545f57-p28xd

# 반복 호출(신규 터미널)
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done

8. kubeadm 인증서 갱신

현재 인증서 만료 정보 확인

kubeadm-config ConfigMap에는 기본 유효 기간이 정의되어 있습니다.

kc describe cm -n kube-system kubeadm-config | grep -i cert
# caCertificateValidityPeriod: 87600h0m0s        # CA 기본 10년
# certificateValidityPeriod: 8760h0m0s           # 클라이언트/서버 인증서 기본 1년
# certificatesDir: /etc/kubernetes/pki

현재 인증서 만료 현황을 확인합니다.

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 20, 2027 17:40 UTC   364d            ca                      no      
# apiserver                  Jan 20, 2027 17:40 UTC   364d            ca                      no      
# apiserver-etcd-client      Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# apiserver-kubelet-client   Jan 20, 2027 17:40 UTC   364d            ca                      no      
# controller-manager.conf    Jan 20, 2027 17:40 UTC   364d            ca                      no      
# etcd-healthcheck-client    Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# etcd-peer                  Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# etcd-server                Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# front-proxy-client         Jan 20, 2027 17:40 UTC   364d            front-proxy-ca          no      
# scheduler.conf             Jan 20, 2027 17:40 UTC   364d            ca                      no      
# super-admin.conf           Jan 20, 2027 17:40 UTC   364d            ca                      no      

# CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
# ca                      Jan 18, 2036 17:40 UTC   9y              no      
# etcd-ca                 Jan 18, 2036 17:40 UTC   9y              no      
# front-proxy-ca          Jan 18, 2036 17:40 UTC   9y              no      

인증서 파일의 생성 시각/경로도 함께 확인합니다.

tree /etc/kubernetes/pki/
ls -l /etc/kubernetes/pki/etcd/
# -rw-r--r--. 1 root root 1094 Jan 21 02:40 ca.crt
# -rw-------. 1 root root 1675 Jan 21 02:40 ca.key
# -rw-r--r--. 1 root root 1123 Jan 21 02:40 healthcheck-client.crt
# -rw-------. 1 root root 1675 Jan 21 02:40 healthcheck-client.key
# -rw-r--r--. 1 root root 1196 Jan 21 02:40 peer.crt
# -rw-------. 1 root root 1679 Jan 21 02:40 peer.key
# -rw-r--r--. 1 root root 1196 Jan 21 02:40 server.crt
# -rw-------. 1 root root 1679 Jan 21 02:40 server.key

ls -l /etc/kubernetes/pki | head
# -rw-r--r--. 1 root root 1281 Jan 21 02:40 apiserver.crt
# -rw-r--r--. 1 root root 1123 Jan 21 02:40 apiserver-etcd-client.crt
# -rw-------. 1 root root 1675 Jan 21 02:40 apiserver-etcd-client.key
# -rw-------. 1 root root 1675 Jan 21 02:40 apiserver.key
# -rw-r--r--. 1 root root 1176 Jan 21 02:40 apiserver-kubelet-client.crt
# -rw-------. 1 root root 1675 Jan 21 02:40 apiserver-kubelet-client.key
# -rw-r--r--. 1 root root 1107 Jan 21 02:40 ca.crt
# -rw-------. 1 root root 1675 Jan 21 02:40 ca.key
# drwxr-xr-x. 2 root root  162 Jan 21 02:40 etcd
# ...

# apiserver 인증서(예시) : Not Before/After 확인
cat /etc/kubernetes/pki/apiserver.crt | openssl x509 -text -noout | egrep 'Not Before|Not After|Subject:|Issuer:' -n
# 6:        Issuer: CN=kubernetes
# 8:            Not Before: Jan 20 17:35:53 2026 GMT
# 9:            Not After : Jan 20 17:40:53 2027 GMT
# 10:        Subject: CN=kube-apiserver

수동 인증서 갱신 개요 (kubeadm)

  • kubeadm certs renew: CA는 유지한 채(control-plane 관련) 인증서를 재서명/재발급하고, 관련 kubeconfig(admin.conf, controller-manager.conf, scheduler.conf, super-admin.conf)도 갱신됩니다.
  • CA 갱신 여부: 기본적으로 CA(ca.crt/ca.key, etcd/ca.crt/ca.key, front-proxy-ca.*)는 renew 대상 아님(기본 10년).
  • 영향도
    • k8s API 요청 / kubectl: control-plane static pod 재기동 구간에 수초~수십 초 일시 중단 가능
    • 워크로드: 일반적으로 영향 없음(노드/파드는 계속 실행)
    • etcd
      • single-node etcd: 재기동 구간에 API server 접근이 잠시 불가
      • multi-node etcd: 순차 갱신 필요(동시 수행 시 쿼럼 붕괴 위험)
    • worker node
      • kubelet client cert는 자동 갱신이 기본이며, kubelet.conf는 보통 kubeadm certs renew all 갱신 목록에 포함되지 않음(순환 가능한 인증서로 자동 갱신 구성)
      • kubeadm rejoin 불필요

참고 문서: Certificate Management with kubeadm

수동 인증서 갱신 실행 (control-plane)

# (테스트) 샘플 애플리케이션 반복 호출(신규 터미널)
SVCIP=$(kubectl get svc webpod -o jsonpath='{.spec.clusterIP}')
while true; do curl -s $SVCIP | grep Hostname; sleep 1; done

# 사전 백업 (HA control-plane이면 모든 control-plane에서 수행 권장)
cp -r /etc/kubernetes/pki /etc/kubernetes/pki.backup.$(date +%F)
mkdir -p /etc/kubernetes/backup-conf.$(date +%F)
cp /etc/kubernetes/*.conf /etc/kubernetes/backup-conf.$(date +%F)

# 갱신 전 만료 상태 확인
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 20, 2027 17:40 UTC   364d            ca                      no      
# apiserver                  Jan 20, 2027 17:40 UTC   364d            ca                      no      
# apiserver-etcd-client      Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# apiserver-kubelet-client   Jan 20, 2027 17:40 UTC   364d            ca                      no      
# controller-manager.conf    Jan 20, 2027 17:40 UTC   364d            ca                      no      
# etcd-healthcheck-client    Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# etcd-peer                  Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# etcd-server                Jan 20, 2027 17:40 UTC   364d            etcd-ca                 no      
# front-proxy-client         Jan 20, 2027 17:40 UTC   364d            front-proxy-ca          no      
# scheduler.conf             Jan 20, 2027 17:40 UTC   364d            ca                      no      
# super-admin.conf           Jan 20, 2027 17:40 UTC   364d            ca                      no      

# CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
# ca                      Jan 18, 2036 17:40 UTC   9y              no      
# etcd-ca                 Jan 18, 2036 17:40 UTC   9y              no      
# front-proxy-ca          Jan 18, 2036 
# 인증서 전체 갱신 (CA는 유지, 나머지는 재발급)
kubeadm certs renew all
# [renew] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"...
# [renew] Use 'kubeadm init phase upload-config --config your-config.yaml' to re-upload it.

# 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 21, 2027 06:38 UTC   364d            ca                      no      
# apiserver                  Jan 21, 2027 06:38 UTC   364d            ca                      no      
# apiserver-etcd-client      Jan 21, 2027 06:38 UTC   364d            etcd-ca                 no      
# apiserver-kubelet-client   Jan 21, 2027 06:38 UTC   364d            ca                      no      
# controller-manager.conf    Jan 21, 2027 06:38 UTC   364d            ca                      no      
# etcd-healthcheck-client    Jan 21, 2027 06:38 UTC   364d            etcd-ca                 no      
# etcd-peer                  Jan 21, 2027 06:38 UTC   364d            etcd-ca                 no      
# etcd-server                Jan 21, 2027 06:38 UTC   364d            etcd-ca                 no      
# front-proxy-client         Jan 21, 2027 06:38 UTC   364d            front-proxy-ca          no      
# scheduler.conf             Jan 21, 2027 06:38 UTC   364d            ca                      no      
# super-admin.conf           Jan 21, 2027 06:38 UTC   364d            ca                      no      

# CERTIFICATE AUTHORITY   EXPIRES                  RESIDUAL TIME   EXTERNALLY MANAGED
# ca                      Jan 18, 2036 17:40 UTC   9y              no      
# etcd-ca                 Jan 18, 2036 17:40 UTC   9y              no      
# front-proxy-ca          Jan 18, 2036 17:40 UTC   9y              no 

갱신 결과(파일 타임스탬프/kubeconfig 갱신)를 확인합니다.

ls -lt /etc/kubernetes/pki/ | head
# total 56
# -rw-r--r--. 1 root root 1119 Jan 21 15:38 front-proxy-client.crt
# -rw-------. 1 root root 1675 Jan 21 15:38 front-proxy-client.key
# -rw-r--r--. 1 root root 1176 Jan 21 15:38 apiserver-kubelet-client.crt
# -rw-------. 1 root root 1675 Jan 21 15:38 apiserver-kubelet-client.key
# -rw-r--r--. 1 root root 1123 Jan 21 15:38 apiserver-etcd-client.crt
# -rw-------. 1 root root 1675 Jan 21 15:38 apiserver-etcd-client.key
# -rw-r--r--. 1 root root 1281 Jan 21 15:38 apiserver.crt
# -rw-------. 1 root root 1675 Jan 21 15:38 apiserver.key
# -rw-------. 1 root root 1679 Jan 21 02:40 sa.key

ls -lt /etc/kubernetes/pki/etcd/ | head
# total 32
# -rw-r--r--. 1 root root 1196 Jan 21 15:38 server.crt
# -rw-------. 1 root root 1679 Jan 21 15:38 server.key
# -rw-r--r--. 1 root root 1196 Jan 21 15:38 peer.crt
# -rw-------. 1 root root 1675 Jan 21 15:38 peer.key
# -rw-r--r--. 1 root root 1123 Jan 21 15:38 healthcheck-client.crt
# -rw-------. 1 root root 1675 Jan 21 15:38 healthcheck-client.key
# -rw-r--r--. 1 root root 1094 Jan 21 02:40 ca.crt
# -rw-------. 1 root root 1675 Jan 21 02:40 ca.key

ls -lt /etc/kubernetes/*.conf
# -rw-------. 1 root root 5682 Jan 21 15:38 /etc/kubernetes/super-admin.conf
# -rw-------. 1 root root 5630 Jan 21 15:38 /etc/kubernetes/scheduler.conf
# -rw-------. 1 root root 5678 Jan 21 15:38 /etc/kubernetes/controller-manager.conf
# -rw-------. 1 root root 5654 Jan 21 15:38 /etc/kubernetes/admin.conf
# -rw-------. 1 root root 1974 Jan 21 02:40 /etc/kubernetes/kubelet.conf

# admin.conf 변경 확인(백업본 vs 현재)
ls -l /etc/kubernetes/admin.conf
# -rw-------. 1 root root 5654 Jan 21 15:38 /etc/kubernetes/admin.conf
ls -l /etc/kubernetes/backup-conf.$(date +%F)/admin.conf
# -rw-------. 1 root root 5658 Jan 21 15:38 /etc/kubernetes/backup-conf.2026-01-21/admin.conf
vi -d /etc/kubernetes/backup-conf.$(date +%F)/admin.conf /etc/kubernetes/admin.conf
# 2 files to edit

control-plane static pod 재기동 & kubeconfig 재적용

kube-apiserver, kube-controller-manager, kube-scheduler, etcd는 static pod로 실행되며, 인증서 동적 reload가 제한적이므로 재기동이 필요합니다.

# 사전 백업 : static pod 매니페스트
cp -r /etc/kubernetes/manifests /etc/kubernetes/manifests.backup.$(date +%F)

# static pod 모니터링(신규 터미널)
watch -d crictl ps

# 매니페스트 제거 → kubelet이 static pod 종료
rm -f /etc/kubernetes/manifests/*.yaml

# 종료 확인
crictl ps

# 매니페스트 복구 → kubelet이 static pod 재생성
cp /etc/kubernetes/manifests.backup.$(date +%F)/*.yaml /etc/kubernetes/manifests
tree /etc/kubernetes/manifests

# 기동 확인
crictl ps
# CONTAINER           IMAGE               CREATED             STATE               NAME                        ATTEMPT             POD ID              POD                                                    NAMESPACE
# 253ec71bd0839       58951ea1a0b5d       23 seconds ago      Running             kube-apiserver              1                   7d62578f02aa4       kube-apiserver-k8s-ctr                                 kube-system
# 74a54d4fbc656       82766e5f2d560       25 seconds ago      Running             kube-controller-manager     0                   f68180f56508f       kube-controller-manager-k8s-ctr                        kube-system
# 3eab35c38ab3e       cfa17ff3d6634       25 seconds ago      Running             kube-scheduler              0                   2a0de6f0a42d1       kube-scheduler-k8s-ctr                                 kube-system
# 2c600dd715e11       1211402d28f58       25 seconds ago      Running             etcd                        0                   fbba0d45cc6d0       etcd-k8s-ctr                                           kube-system
# c466ad24073b4       11873b3fefc46       25 minutes ago      Running             x509-certificate-exporter   0                   85397dc9546a8       x509-certificate-exporter-cp-22v9p                     monitoring
# c8af35d077d15       6b5bc413b280c       51 minutes ago      Running             node-exporter               0                   276c6248f1110       kube-prometheus-stack-prometheus-node-exporter-wmxrz   monitoring
# e0c55fadff42d       2f6c962e7b831       13 hours ago        Running             coredns                     0                   9d6d22b21f9c2       coredns-668d6bf9bc-47zx5                               kube-system
# 211e6cf45ecdb       2f6c962e7b831       13 hours ago        Running             coredns                     0                   7e8120ca9c5e3       coredns-668d6bf9bc-77pjv                               kube-system
# 7910a12ecad5e       d84558c0144bc       13 hours ago        Running             kube-flannel                0                   a6ab4291b889d       kube-flannel-ds-w9s2j                                  kube-flannel
# 3362d1643ecff       dcdb790dc2bfe       13 hours ago        Running             kube-proxy                  0                   e9df7b0d85975       kube-proxy-qhqpz                                       kube-system

kubectl get pod -n kube-system -owide -v=6

# admin.conf kubeconfig 재적용(로컬 kubectl)
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

인증서를 수동으로 갱신하는 과정에서 worker node 애플리케이션은 거의 멈추지 않았고, static pod 재기동시 다음과 같이 잠시 매트릭이 수집안되는 현상만 발생했습니다.

 

9. 모든 실습 완료 후 가상 머신 삭제

vagrant destroy -f && rm -rf .vagrant

마치며

이번 글에서는 kubeadm을 활용한 Kubernetes 클러스터의 설치부터 업그레이드, 인증서 갱신, 그리고 노드 리셋과 재조인까지 실제 운영 환경에서 경험할 수 있는 주요 절차들을 하나하나 실습해보았습니다.

이어지는 글에서 kubeadm을 이용한 업데이트 시나리오에 대한 내용을 정리하도록 하겠습니다.

긴 글 읽어주셔서 감사합니다!

반응형

'클라우드 컴퓨팅 & NoSQL > [K8S Deploy] K8S 디플로이 스터디' 카테고리의 다른 글

[3주차 - K8S Deploy] Kubeadm & K8S Upgrade 2/2 (26.01.23)  (0) 2026.01.23
[2주차 - K8S Deploy] Ansible 기초 (26.01.11)  (1) 2026.01.15
[1주차 - K8S Deploy] Bootstrap Kubernetes the hard way (26.01.04)  (1) 2026.01.09
    devlos
    devlos
    안녕하세요, Devlos 입니다. 새로 공부 중인 지식들을 공유하고, 명확히 이해하고자 블로그를 개설했습니다 :) 여러 DEVELOPER 분들과 자유롭게 지식을 공유하고 싶어요! 방문해 주셔서 감사합니다 😀 - DEVLOS -

    티스토리툴바