
SIG Cluster Lifecycle 프로젝트이며, 설계 철학은 Be Simple, Be Extensible이다.
? Bootstrapper는 시스템이나 프로그램을 실행하기 위해 가장 먼저 실행되어 필요한 환경을 준비해주는 작은 초기화 도구를 의미한다.
| 명령어 | 설명 |
|---|---|
kubeadm init | Control Plane 노드 부트스트랩 |
kubeadm join | Worker 또는 추가 Control Plane 노드를 클러스터에 조인 |
kubeadm upgrade | 클러스터를 새 버전으로 업그레이드 |
kubeadm reset | init 또는 join으로 변경된 내용을 되돌림 |
| 컴포넌트 | 유형 | 설명 |
|---|---|---|
| etcd | Static Pod | 클러스터 상태 저장소 |
| kube-apiserver | Static Pod | API 서버 |
| kube-controller-manager | Static Pod | 컨트롤러 매니저 |
| kube-scheduler | Static Pod | 스케줄러 |
| coredns | Deployment | 클러스터 DNS (addon) |
| kube-proxy | DaemonSet | 네트워크 프록시 (addon) |
| 컴포넌트 | 설명 |
|---|---|
| Container Runtime | containerd, CRI-O 등 |
| kubelet | 노드 에이전트 (바이너리 설치) |
| CNI plugin | Flannel, Calico, Cilium 등 (별도 설치) |

kubeadm은 Composable Solution의 일부로, Cluster API, cluster-addons, etcdadm, ComponentConfig, image builder 등과 함께 k8s cluster provisioners를 구성한다.
참고: etcdadm repo는 현재 archived 상태
https://github.com/kubernetes-retired/etcdadm
| 항목 | 버전 | 호환성 |
|---|---|---|
| Rocky Linux | 10.0 | RHEL 10 기반 |
| containerd | v2.1.5 | k8s 1.32~1.35 지원 |
| runc | v1.3.3 | OCI 런타임 |
| kubelet | v1.32.11 | |
| kubeadm | v1.32.11 | |
| kubectl | v1.32.11 | |
| helm | v3.18.6 | k8s 1.30.x~1.33.x |
| flannel | v0.27.3 | k8s 1.28+ |
| 노드 | 호스트명 | IP | CPU | Memory |
|---|---|---|---|---|
| Control Plane | k8s-ctr | 192.168.10.100 | 4 | 3GB |
| Worker 1 | k8s-w1 | 192.168.10.101 | 2 | 2GB |
| Worker 2 | k8s-w2 | 192.168.10.102 | 2 | 2GB |
macOS/Windows에서
bento/rockylinux-10.0구동 실패 시 최신 버전 설치 필요
BOX_IMAGE = "bento/rockylinux-10.0"
BOX_VERSION = "202510.26.0"
N = 2
Vagrant.configure("2") do |config|
# Control Plane Node
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
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
# Vagrantfile 다운로드
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-kubeadm/Vagrantfile
# VM 생성 및 시작
vagrant up
# 상태 확인
vagrant status
# SSH 접속
vagrant ssh k8s-ctr
클러스터 구성 절차 개요
1. [공통] 사전 설정 2. [공통] CRI 설치 (containerd) 3. [공통] kubeadm, kubelet, kubectl 설치 4. [Control Plane] kubeadm init → CNI 설치 5. [Worker] kubeadm join 6. 모니터링 툴 설치 7. 샘플 애플리케이션 배포 8. 인증서 갱신
# swap 비활성화
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# 방화벽 비활성화 (테스트 환경)
sudo systemctl stop firewalld
sudo systemctl disable firewalld
# SELinux permissive 모드
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# 커널 모듈 로드
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# sysctl 설정
cat <<EOF | sudo 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
sudo sysctl --system
| 단계 | 명령/설정 | 하는 일 |
|---|---|---|
| Swap 비활성화 | swapoff -a, /etc/fstab 수정 | 메모리 swap 기능을 끄고 재부팅 후에도 swap이 켜지지 않도록 설정 |
| 방화벽 비활성화 | systemctl stop firewalld, systemctl disable firewalld | 테스트 환경에서 방화벽을 끄고, 부팅 시 자동 실행되지 않도록 설정 |
| SELinux Permissive 모드 | setenforce 0, /etc/selinux/config 수정 | SELinux를 강제(enforcing) 모드에서 완화(permissive) 모드로 변경 |
| 커널 모듈 로드 | overlay, br_netfilter | 컨테이너 및 네트워크 브리지 관련 기능을 지원하는 커널 모듈 활성화 |
| sysctl 네트워크 설정 | bridge-nf-call-iptables, bridge-nf-call-ip6tables, ip_forward | 브리지 네트워크 패킷을 iptables로 처리하고, IPv4 패킷 포워딩을 허용 |
containerd는 Docker 저장소에서 설치한다. dockerd는 설치하지 않고 containerd만 설치한다.
# dnf == yum 확인
dnf --version
# Docker 저장소 추가
dnf repolist
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf repolist
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 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 2.1.5 설치
dnf install -y containerd.io-2.1.5-1.el10
# 설치된 파일 확인
which runc && runc --version
which containerd && containerd --version
which containerd-shim-runc-v2 && containerd-shim-runc-v2 -v
which ctr && ctr --version
# 기본 설정 생성
containerd config default | tee /etc/containerd/config.toml
생성된 설정 파일 구조 (containerd 2.x는 version = 3):
version = 3
root = '/var/lib/containerd'
state = '/run/containerd'
...
containerd 버전별 설정 차이
- containerd 2.x:
version = 3,[plugins.'io.containerd.cri.v1.images']- containerd 1.x:
version = 2,[plugins."io.containerd.grpc.v1.cri".containerd]
# 현재 설정 확인
cat /etc/containerd/config.toml | grep -i systemdcgroup
# SystemdCgroup = true로 변경
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml
# 변경 확인
cat /etc/containerd/config.toml | grep -i systemdcgroup
왜 SystemdCgroup = true가 필요한가?
- cgroupfs 드라이버는 kubelet의 기본 cgroup 드라이버지만, systemd가 init 시스템인 경우 권장되지 않는다.
- systemd는 시스템에 단 하나의 cgroup 관리자만 있을 것으로 기대한다.
- cgroup v2를 사용할 경우에도 systemd cgroup 드라이버를 사용해야 한다.
- kubelet과 containerd의 cgroup 드라이버가 일치하지 않으면 Pod가 정상 동작하지 않는다.
# systemd unit 파일 최신 상태 읽기
systemctl daemon-reload
# containerd 시작 및 부팅 시 자동 시작 활성화
systemctl enable --now containerd
# 상태 확인
systemctl status containerd --no-pager
journalctl -u containerd.service --no-pager
# containerd 유닉스 도메인 소켓 확인
# kubelet과 containerd client(ctr, nerdctl, crictl)가 이 소켓을 사용
containerd config dump | grep -n containerd.sock
ls -l /run/containerd/containerd.sock
ss -xl | grep containerd
# 플러그인 확인
ctr --address /run/containerd/containerd.sock version
ctr plugins ls
TYPE ID PLATFORMS STATUS
io.containerd.content.v1 content - ok # 이미지 레이어 저장
io.containerd.snapshotter.v1 overlayfs linux/arm64/v8 ok # K8s 기본 snapshotter
io.containerd.metadata.v1 bolt - ok # 메타데이터 DB
...
# 프로세스 트리 확인
pstree -alnp
# cgroup 계층 구조 확인
systemd-cgls --no-pager
| 단계 | 명령/설정 | 하는 일 |
|---|---|---|
| 저장소 추가 | dnf config-manager --add-repo ... | Docker 공식 저장소를 추가하여 containerd 패키지를 설치할 수 있게 함 |
| containerd 설치 | dnf install -y containerd.io | containerd 런타임을 시스템에 설치 |
| 기본 설정 생성 | containerd config default > /etc/containerd/config.toml | containerd 기본 설정 파일을 생성 |
| systemd cgroup 드라이버 활성화 | sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' ... | containerd가 systemd 기반 cgroup을 사용하도록 설정 (Kubernetes 호환성 위해 중요) |
| 서비스 시작 및 자동 실행 | systemctl enable --now containerd | containerd 데몬을 즉시 실행하고, 부팅 시 자동으로 시작되도록 설정 |
👉 containerd 런타임을 설치하고, Kubernetes와 호환되도록 systemd cgroup을 활성화한 뒤 서비스로 등록하는 과정
중요: systemd 기반 시스템에서는 반드시
SystemdCgroup = true설정이 필요하다.
kubelet과 container runtime의 cgroup 드라이버가 일치해야 한다.
Linux에서 cgroup(control group)은 프로세스의 리소스(CPU, 메모리, I/O 등)를 제한하고 격리하는 커널 기능이다. Kubernetes에서 kubelet과 container runtime이 cgroup을 관리하는 방식에 두 가지 드라이버가 있다.
동작 방식:
/sys/fs/cgroup)에 접근하여 cgroup을 생성하고 관리한다.특징:
문제점:
동작 방식:
특징:
장점:
| 항목 | cgroupfs | systemd |
|---|---|---|
| cgroup 관리 주체 | kubelet/runtime 직접 | systemd를 통해 간접 |
| 권장 환경 | systemd 없는 환경 | systemd 기반 배포판 (대부분) |
| 안정성 | systemd와 충돌 가능 | 단일 관리자로 안정적 |
| Kubernetes 권장 | 비권장 (v1.22+) | 권장 |
| cgroup 경로 | /sys/fs/cgroup/... 직접 | systemd slice 기반 |
Kubernetes v1.22부터 systemd cgroup 드라이버가 기본값으로 권장된다.
systemd 기반 Linux 배포판(Ubuntu, RHEL, Rocky Linux 등)에서는 반드시 kubelet과 container runtime 모두 동일한 cgroup 드라이버를 사용해야 한다.

/etc/containerd/config.toml)[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
/var/lib/kubelet/config.yaml)apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
kubeadm-config.yaml)apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
kubelet과 container runtime의 cgroup 드라이버가 다르면:
| 상황 | 권장 드라이버 |
|---|---|
| Ubuntu, RHEL, Rocky Linux 등 | systemd |
| systemd 없는 환경 (컨테이너 내부 등) | cgroupfs |
| Kubernetes v1.22+ | systemd (기본값) |
대부분의 프로덕션 환경에서는 systemd cgroup 드라이버를 사용하는 것이 올바른 선택이다.
# Kubernetes 저장소 추가
cat <<EOF | sudo 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
# 설치
sudo dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
# kubelet 활성화
sudo systemctl enable --now kubelet
| 단계 | 명령/설정 | 하는 일 |
|---|---|---|
| 저장소 추가 | /etc/yum.repos.d/kubernetes.repo 생성 | Kubernetes 공식 패키지 저장소를 등록하여 패키지 설치 가능하게 함 |
| Kubernetes 패키지 설치 | dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes | Kubernetes 핵심 구성 요소 설치 - kubelet: 노드에서 Pod 실행 관리 - kubeadm: 클러스터 초기화 및 관리 도구 - kubectl: 클러스터 제어용 CLI |
| kubelet 활성화 | systemctl enable --now kubelet | kubelet 서비스를 즉시 실행하고, 부팅 시 자동 시작되도록 설정 |
👉 Kubernetes 저장소를 추가하고, 클러스터 운영에 필요한 핵심 패키지(kubelet, kubeadm, kubectl)를 설치 및 활성화하는 단계

kubeadm init은 다음 단계를 순차적으로 수행한다:
| 단계 | 하는 일 |
|---|---|
| Preflight Checks | CRI(Container Runtime Interface) 엔드포인트 확인, 루트 권한 확인, kubelet 버전 검증 |
| Certificates | /etc/kubernetes/pki 경로에 클러스터용 인증서 및 키 생성 |
| Kubeconfig | /etc/kubernetes 경로에 kubeconfig 파일 생성 (API 서버와 통신용) |
| Static Pods | Control Plane 컴포넌트(API Server, Controller Manager, Scheduler) 매니페스트 생성 후 kubelet이 Static Pod으로 실행 |
| kubelet 시작 | API Server의 healthz 엔드포인트가 정상 응답할 때까지 대기 |
| ConfigMap 저장 | kubeadm-config ConfigMap을 클러스터에 저장 (설정 공유 목적) |
| 노드 라벨/Taint | 해당 노드에 control-plane 라벨 부여 및 NoSchedule taint 적용 (워커 노드 스케줄 방지) |
| Bootstrap Token | 워커 노드가 클러스터에 조인할 수 있도록 토큰 및 RBAC 권한 설정 |
| Addons 설치 | 기본 애드온(CoreDNS, kube-proxy) 배포 |
👉 kubeadm init은 클러스터 제어 플레인 노드를 준비하기 위해 인증서 → 설정 → 컨트롤 플레인 Static Pod → 기본 애드온 설치까지 자동으로 처리하는 과정
# init 전 상태 저장 (나중에 비교용)
crictl images
crictl ps
tree /etc/kubernetes | tee -a etc_kubernetes-1.txt
tree /var/lib/kubelet | tee -a var_lib_kubelet-1.txt
pstree -alnp | tee -a pstree-1.txt
systemd-cgls --no-pager | tee -a systemd-cgls-1.txt
ip addr | tee -a ip_addr-1.txt
ss -tnlp | tee -a ss-1.txt
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
# (옵션) 컨테이너 이미지 미리 다운로드 - 업그레이드 시 시간 단축
kubeadm config images pull
# dry-run으로 먼저 확인
kubeadm init --config="kubeadm-init.yaml" --dry-run
# 실제 init 수행
kubeadm init --config="kubeadm-init.yaml"
init 실행 시 출력 내용:
[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
...
[kubeconfig] Writing "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] 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"
...
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy
crictl images
IMAGE TAG IMAGE ID SIZE
registry.k8s.io/coredns/coredns v1.11.3 2f6c962e7b831 16.9MB
registry.k8s.io/etcd 3.5.24-0 1211402d28f58 21.9MB
registry.k8s.io/kube-apiserver v1.32.11 58951ea1a0b5d 26.4MB
registry.k8s.io/kube-controller-manager v1.32.11 82766e5f2d560 24.2MB
registry.k8s.io/kube-proxy v1.32.11 dcdb790dc2bfe 27.6MB
registry.k8s.io/kube-scheduler v1.32.11 cfa17ff3d6634 19.2MB
registry.k8s.io/pause 3.10 afb61768ce381 268kB
crictl ps
CONTAINER IMAGE CREATED STATE NAME POD ID POD
a04be00090580 dcdb790dc2bfe 26 seconds ago Running kube-proxy 1fd91b0a982bb kube-proxy-7w44b
b005f34739da5 82766e5f2d560 37 seconds ago Running kube-controller-manager 555d146c3ec07 kube-controller-manager-k8s-ctr
eb42b9c47fdce cfa17ff3d6634 37 seconds ago Running kube-scheduler e649514d0a1b7 kube-scheduler-k8s-ctr
bbe8495d2a205 58951ea1a0b5d 37 seconds ago Running kube-apiserver be25c00dd555c kube-apiserver-k8s-ctr
c00a944599500 1211402d28f58 37 seconds ago Running etcd ce6b89dea28da etcd-k8s-ctr
mkdir -p /root/.kube
cp -i /etc/kubernetes/admin.conf /root/.kube/config
chown $(id -u):$(id -g) /root/.kube/config
# 확인
kubectl cluster-info
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr NotReady control-plane 6m v1.32.11 192.168.10.100 Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10.aarch64 containerd://2.1.5
NotReady 상태인 이유: CNI가 아직 설치되지 않았기 때문
# cluster-info는 '신원 확인 전, 최소한의 신뢰 부트스트랩 데이터'
kubectl -n kube-public get configmap cluster-info
kubectl -n kube-public get configmap cluster-info -o yaml
# 인증 없이도 접근 가능 (kubeadm join 전 노드가 사용)
curl -s -k https://192.168.10.100:6443/api/v1/namespaces/kube-public/configmaps/cluster-info | jq
# vagrant 접속 시 자동 root 전환
echo "sudo su -" >> /home/vagrant/.bashrc
# kubectl/kubeadm 자동 완성
source <(kubectl completion bash)
source <(kubeadm completion bash)
echo 'source <(kubectl completion bash)' >> /etc/profile
echo 'source <(kubeadm completion bash)' >> /etc/profile
# alias 설정
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
# kubecolor 설치 (컬러 출력)
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --add-repo https://kubecolor.github.io/packages/rpm/kubecolor.repo
dnf install -y kubecolor
alias kc=kubecolor
echo 'alias kc=kubecolor' >> /etc/profile
# 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
# kube-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
# helm 설치
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | DESIRED_VERSION=v3.18.6 bash
helm version
# 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
mv k9s /usr/local/bin/
chmod +x /usr/local/bin/k9s
재접속 후:
exit
exit
vagrant ssh k8s-ctr
# 컨텍스트 이름 변경
kubectl config rename-context "kubernetes-admin@kubernetes" "HomeLab"
kubens default
CNI가 설치되어야 노드가 Ready 상태가 되고, coredns Pod도 정상 기동된다.
# kube-controller-manager에서 cluster-cidr 확인
kc describe pod -n kube-system kube-controller-manager-k8s-ctr | grep cluster-cidr
# --cluster-cidr=10.244.0.0/16
# 노드별 Pod CIDR 확인
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
# k8s-ctr 10.244.0.0/24
# Flannel Helm repo 추가
helm repo add flannel https://flannel-io.github.io/flannel
helm repo update
# namespace 생성
kubectl create namespace kube-flannel
# values 파일 작성
cat << EOF > flannel.yaml
podCidr: "10.244.0.0/16"
flannel:
cniBinDir: "/opt/cni/bin"
cniConfDir: "/etc/cni/net.d"
args:
- "--ip-masq"
- "--kube-subnet-mgr"
- "--iface=enp0s9" # Vagrant private network 인터페이스
backend: "vxlan"
EOF
# Flannel 설치
helm install flannel flannel/flannel --namespace kube-flannel --version 0.27.3 -f flannel.yaml
# 확인
helm list -A
kubectl get ds,pod,cm -n kube-flannel -owide
# CNI 바이너리 확인
ls -l /opt/cni/bin/
# -rwxr-xr-x. 1 root root 2974540 Jan 17 01:35 flannel
# CNI 설정 확인
cat /etc/cni/net.d/10-flannel.conflist | jq
# crictl로 CNI 상태 확인
crictl info | jq '.status.conditions'
[
{ "type": "RuntimeReady", "status": true },
{ "type": "NetworkReady", "status": true }
]
# coredns Pod 정상 기동 확인
kubectl get pod -n kube-system -owide
NAME READY STATUS RESTARTS IP NODE
coredns-668d6bf9bc-bmdjw 1/1 Running 0 10.244.0.3 k8s-ctr
coredns-668d6bf9bc-cbtn9 1/1 Running 0 10.244.0.2 k8s-ctr
etcd-k8s-ctr 1/1 Running 0 192.168.10.100 k8s-ctr
kube-apiserver-k8s-ctr 1/1 Running 0 192.168.10.100 k8s-ctr
...
# 노드 Ready 상태 확인
kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-ctr Ready control-plane 10m v1.32.11
# 라우팅 테이블
ip -c route | grep 10.244.
# 네트워크 인터페이스 (cni0, flannel.1 확인)
ip addr
# 브릿지 링크
bridge link
# iptables 규칙
iptables -t nat -S | head -20
kubeadm certs check-expiration
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY
admin.conf Jan 16, 2027 14:33 UTC 364d ca
apiserver Jan 16, 2027 14:33 UTC 364d ca
apiserver-etcd-client Jan 16, 2027 14:33 UTC 364d etcd-ca
apiserver-kubelet-client Jan 16, 2027 14:33 UTC 364d ca
controller-manager.conf Jan 16, 2027 14:33 UTC 364d ca
...
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME
ca Jan 14, 2036 14:33 UTC 9y
etcd-ca Jan 14, 2036 14:33 UTC 9y
front-proxy-ca Jan 14, 2036 14:33 UTC 9y
참고: CA 인증서는 10년, 일반 인증서는 1년 유효
tree /etc/kubernetes/pki
/etc/kubernetes/pki
├── apiserver.crt
├── apiserver.key
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.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
# CA 인증서 확인
cat /etc/kubernetes/pki/ca.crt | openssl x509 -text -noout | grep -A2 "Subject:"
# Subject: CN=kubernetes
# CA:TRUE
# API Server 인증서 확인 (SAN 포함)
cat /etc/kubernetes/pki/apiserver.crt | openssl x509 -text -noout | grep -A5 "Subject Alternative Name"
# DNS:k8s-ctr, DNS:kubernetes, DNS:kubernetes.default, ...
# IP Address:10.96.0.1, IP Address:192.168.10.100
Static Pod 매니페스트는 /etc/kubernetes/manifests/에 위치한다.
tree /etc/kubernetes/manifests/
/etc/kubernetes/manifests/
├── etcd.yaml
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml
cat /var/lib/kubelet/config.yaml | grep staticPodPath
# staticPodPath: /etc/kubernetes/manifests
# etcd - client/metrics 포트 확인
cat /etc/kubernetes/manifests/etcd.yaml | grep -E "listen-client|listen-metrics"
# --listen-client-urls=https://127.0.0.1:2379,https://192.168.10.100:2379
# --listen-metrics-urls=http://127.0.0.1:2381
# kube-apiserver - 포트 및 etcd 연결 확인
cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep -E "secure-port|etcd-servers"
# --secure-port=6443
# --etcd-servers=https://127.0.0.1:2379
# Listen 포트 확인
ss -tnlp | grep -E "apiserver|scheduler|controller|etcd"
kubectl get deploy,svc -n kube-system -l k8s-app=kube-dns
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/coredns 2/2 2 2
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP
# CoreDNS 설정 확인
kc describe cm -n kube-system coredns
kubectl get ds -n kube-system kube-proxy
NAME DESIRED CURRENT READY UP-TO-DATE NODE SELECTOR
kube-proxy 1 1 1 1 kubernetes.io/os=linux
# kube-proxy 설정 확인
kc describe cm -n kube-system kube-proxy | grep -E "mode:|clusterCIDR:"
# clusterCIDR: 10.244.0.0/16
# mode: "" # 기본값: iptables
# conntrack 도구 설치 및 확인
dnf install -y conntrack-tools
conntrack -L | head
# Control Plane에서 토큰 확인
kubeadm token list
# 토큰 만료 시 재생성
kubeadm token create --print-join-command
# Worker 노드에서 실행
sudo kubeadm join 192.168.10.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
# 노드 상태
kubectl get nodes -o wide
# 시스템 Pod 확인
kubectl get pods -n kube-system -o wide
# 클러스터 정보
kubectl cluster-info

