Rocky 9.6 K8s 완벽 구축 & 트러블슈팅 가이드 (Kubespray 2.28.1 • K8s 1.31.6 • Cilium 1.17)
대상/전제
토폴로지: bastion-001(192.168.10.20) / master-001(192.168.10.30) / worker-001(192.168.10.40) / worker-002(192.168.10.41)
OS: Rocky Linux 9.6 (x86_64, cgroups v2)
런타임: containerd
CNI: Cilium (초기 kube-proxy 유지 → 이후 proxyless 전환 가이드 포함)
설치 도구: Ansible + Kubespray
0) 버전/대상 매트릭스 (검증됨)
구성요소 | 권장/검증 버전 | 비고 |
---|---|---|
Kubespray | 2.28.1 | 본 가이드 기준 |
ansible-core | 2.17.x (≥2.17.3, <2.18) | venv 사용 권장 |
Python | 3.11 | 3.12 미권장(일부 플러그인 호환성) |
Kubernetes | 1.31.6 | Kubespray 2.28.1에서 etcd 매트릭스 호환 |
Cilium | 1.17.7 | K8s 1.31과 호환 |
Helm | 3.18.4 | 3.12+ 권장 |
⚠️ 중요:
kube_version
은 문자열이며 접두사 “ 없이 지정해야 합니다.
예) “1.31.6” (❌ “v1.31.6” 금지).
v
가 있으면 Kubespray 내부의 버전 파싱/비교에서kube_major_version
오류, etcd 매트릭스 키 미존재 등의 문제가 발생합니다.
1) 네트워크/클러스터 기본값 (권장)
- Pod CIDR:
10.244.0.0/16
- Service CIDR:
10.96.0.0/12
- CoreDNS IP:
10.96.0.10
(Service CIDR 10번째) - MTU:
1500
- kube-proxy: 초기엔 iptables 유지 → 안정화 후 Cilium proxyless 전환
- Cilium 터널: VXLAN (단순하고 PoC에 안정적)
왜 이렇게?
- Service CIDR의 10번째 IP를 CoreDNS로 고정하면 kubeadm 경고/혼동을 줄입니다.
- VXLAN은 L2 복잡도 없이 바로 동작(라우팅/BGP 지식 불필요), 에어갭/내부망에서도 무난합니다.
- 초기엔 kube-proxy를 유지하면 CNI 문제 발생 시 롤백이 빠릅니다. 안정화 후 proxyless로 전환하세요.
2) 노드 공통 사전 준비 (Rocky 9.6)
sudo dnf -y install epel-release yum-utils curl wget vim git tar unzip \
net-tools lsof telnet bash-completion python3 python3-pip rsync chrony
sudo systemctl enable --now chronyd
# SELinux: 학습/PoC에선 Permissive (운영 전환 시 Enforcing+정책 권장)
sudo setenforce 0
sudo sed -ri 's/^(SELINUX=).*/\1permissive/' /etc/selinux/config
# swap off
sudo swapoff -a
sudo sed -ri 's/^([^#].*swap.*)/# \1/' /etc/fstab
# 방화벽 off (PoC 집중용)
sudo systemctl disable --now firewalld || true
# 커널 파라미터
echo 'net.bridge.bridge-nf-call-iptables = 1' | sudo tee /etc/sysctl.d/99-kubernetes.conf
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-kubernetes.conf
sudo sysctl --system
# /etc/hosts 고정
cat <<'EOF' | sudo tee -a /etc/hosts
192.168.10.30 master-001
192.168.10.40 worker-001
192.168.10.41 worker-002
EOF
# CNI 바이너리 디렉터리 보장 (Cilium init 권한 문제 예방)
sudo mkdir -p /opt/cni/bin && sudo chown root:root /opt/cni/bin && sudo chmod 0755 /opt/cni/bin
로컬 DNS(노드 로컬 캐시) 비활성화: PoC에서는 혼선을 줄이기 위해 NodeLocalDNS를 끄는 것을 권장합니다.
Kubespray 변수에서enable_nodelocaldns: false
로 설정하고, 이미 떠있다면kubectl -n kube-system delete ds nodelocaldns
로 제거하세요.
3) 베스천 준비 (SSH + Python venv)
# SSH 키 교환
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
for h in 192.168.10.30 192.168.10.40 192.168.10.41; do
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@$h
done
# Python 3.11 + venv
sudo dnf -y remove ansible || true
sudo dnf -y install git python3.11 python3.11-pip python3.11-devel
python3.11 -m venv ~/venvs/kubespray
source ~/venvs/kubespray/bin/activate
pip install --upgrade pip setuptools wheel
4) Kubespray 체크아웃 & 인벤토리
source ~/venvs/kubespray/bin/activate
cd ~
[ -d kubespray ] || git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
pip install -r requirements.txt
ansible --version # 2.17.x 확인
cp -rfp inventory/sample inventory/study
inventory/study/hosts.yaml
all:
hosts:
master-001:
ansible_host: 192.168.10.30
ip: 192.168.10.30
worker-001:
ansible_host: 192.168.10.40
ip: 192.168.10.40
worker-002:
ansible_host: 192.168.10.41
ip: 192.168.10.41
children:
kube_control_plane:
hosts:
master-001: {}
kube_node:
hosts:
worker-001: {}
worker-002: {}
etcd:
hosts:
master-001: {}
k8s_cluster:
children:
kube_control_plane: {}
kube_node: {}
calico_rr: {}
inventory/study/group_vars/k8s_cluster/k8s-cluster.yml (핵심)
# ★ 버전(문자열, v 제거) 버전 미 지정 시 최신 버전으로 자동 설치 됨
#kube_version: "1.32.8"
kube_version: "1.31.6"
# 네트워킹
kube_network_plugin: cilium
kube_service_addresses: 10.96.0.0/12
kube_pods_subnet: 10.244.0.0/16
kube_dns_server: 10.96.0.10
kube_proxy_mode: iptables # 초기 유지
# NodeLocalDNS 비활성화
enable_nodelocaldns: false
# Cilium (초기)
cilium_version: "1.17.7"
cilium_enable_hubble: false
# kube-proxy_script 비활성화
kube_proxy_remove: true
# Cilium VXLAN 모드 & kube-proxy 유지에 필요한 값(helm/cli 재설치 시 사용)
# 별도 값 파일에서 아래를 넘겨주는 방식을 권장
# k8sServiceHost/Port는 자동탐지가 실패할 수 있어 명시 필요
# kubeProxyReplacement는 true/false를 명확히 지정
inventory/study/group_vars/all/download.yml (다운로드/캐시)
download_localhost: true
# ↑를 쓰면 반드시 ↓도 true (클라이언트가 인터넷이 된다면 false)
download_run_once: true
# 캐시 경로
download_cache_dir: "/tmp/kubespray-cache"
local_release_dir: "/tmp/releases"
5) 설치 실행
ansible -i inventory/study/hosts.yaml all -m ping
ansible-playbook -i inventory/study/hosts.yaml -b -v cluster.yml
# kubeconfig를 베스천으로
mkdir -p ~/.kube
scp root@192.168.10.30:/etc/kubernetes/admin.conf ~/.kube/config
export KUBECONFIG=~/.kube/config
kubectl get nodes -o wide
kubectl -n kube-system get pods -o wide
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
6) Cilium CLI/Helm로 재설치(필요시) + VXLAN + kube-proxy 유지
값 파일(베스천 또는 마스터; kubeconfig 필요)
sudo tee /etc/kubernetes/cilium-extra-values.yaml >/dev/null <<'EOF'
k8sServiceHost: 192.168.10.30
k8sServicePort: 6443
kubeProxyReplacement: false # 초기엔 kube-proxy 유지
tunnel: vxlan # VXLAN 사용
EOF
(A) Cilium CLI 사용
# CLI 설치
sudo mkdir -p /usr/local/bin
curl -L --fail -o /tmp/cilium.tgz \
https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz
sudo tar -xzf /tmp/cilium.tgz -C /usr/local/bin cilium
# 설치/재설치
cilium install --version 1.17.7 \
-f /etc/kubernetes/cilium-extra-values.yaml \
--wait
cilium status --verbose
(B) Helm 사용
helm repo add cilium https://helm.cilium.io
helm repo update
helm upgrade --install cilium cilium/cilium \
-n kube-system \
--version 1.17.7 \
-f /etc/kubernetes/cilium-extra-values.yaml \
--wait
왜 필요한가?
Init:CrashLoopBackOff
+ 로그에https://auto:auto
류가 보이면 API 서버 자동탐지 실패 →k8sServiceHost/Port
를 값으로 반드시 명시.kubeProxyReplacement must be explicitly set
에러는 true/false 미지정 탓 → 값 파일에 명시.cp /hostbin/cilium-mount: Permission denied
는 대개 /opt/cni/bin 권한/존재 문제 → 공통 준비 스크립트에서 디렉터리/권한 보장.
7) 설치 후 기본 점검
kubectl get nodes -o wide
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
kubectl -n kube-system get svc kube-dns -o wide # 10.96.0.10 확인
# 간단 배포/통신 테스트
kubectl create deploy demo-nginx --image=nginx:1.25-alpine --replicas=2
kubectl expose deploy demo-nginx --port=80 --target-port=80 --type=ClusterIP
kubectl get deploy,rs,pods,svc -o wide
8) Cilium 동작 테스트 (기본/심화)
8-1) 기본 L3/L4 연결성 테스트
# 클러스터 내부에서 서비스 접근
kubectl run -it --rm tmp --image=busybox:1.36 --restart=Never -- sh -c 'wget -qO- http://demo-nginx'
8-2) Cilium CLI Connectivity Test (권장)
cilium connectivity test --test basic
# 모든 테스트 통과 여부 확인
8-3) BPF LB 상태/서비스 맵 확인(Proxyless 전환 후 유용)
cilium status --verbose
cilium service list # BPF 기반 K8s Service 라우팅 테이블
9) kube-proxy → Cilium Proxyless 전환 (선택)
목표: kube-proxy 제거, Cilium이 eBPF 로드밸런서로 서비스 트래픽 처리.
- 값 변경
sudo tee /etc/kubernetes/cilium-proxyless.yaml >/dev/null <<'EOF'
k8sServiceHost: 192.168.10.30
k8sServicePort: 6443
kubeProxyReplacement: true
# 권장: kubeProxyReplacementStrict: true (필요 시)
tunnel: vxlan
EOF
- 적용 (CLI 또는 Helm)
cilium upgrade --version 1.17.7 -f /etc/kubernetes/cilium-proxyless.yaml --wait
# 또는
helm upgrade cilium cilium/cilium -n kube-system \
--version 1.17.7 -f /etc/kubernetes/cilium-proxyless.yaml --wait
- kube-proxy 제거
kubectl -n kube-system delete ds kube-proxy
# 이후 Kubespray 재실행 시 kube-proxy가 재배포되지 않도록
# inventory에 다음 중 하나를 반영(버전별로 변동 가능)
# kube_proxy_mode: "none" # 또는
# kube_proxy_remove: true # (환경에 따라 키 명 다를 수 있어 Helm/CLI 방식 유지도 방법)
- 검증(중요)
cilium status | grep -i kube-proxy
# => KubeProxyReplacement: Strict|Partial/Enabled
cilium service list
# => K8s Service들이 BPF LB에 등록되어 있어야 함
kubectl -n kube-system get ds kube-proxy # NotFound 여야 정상
왜 kube-proxy를 끄나?
- iptables 규칙 폭증/CPU 사용을 줄이고, eBPF 기반으로 낮은 지연/높은 처리량을 얻기 위함.
- Cilium이 BPF 로드밸런서를 통해 서비스 VIP→엔드포인트 매핑을 직접 처리합니다.
10) Hubble 구성 (가시성)
# CLI로 활성화 (UI 포함)
cilium hubble enable --ui
kubectl -n kube-system get pods -l k8s-app=hubble-relay -o wide
kubectl -n kube-system get svc hubble-ui -o wide
# UI 접근: NodePort로 노출되었는지 확인 후
# http://<worker-IP>:<nodePort>
# CLI 뷰
cilium hubble port-forward & # 4245→ localhost
hubble status
hubble observe --from-pod kube-system/coredns-<id> --http
11) k9s 설치 (운영 편의)
# 최신 릴리스 바이너리 설치 (amd64)# 사용
export KUBECONFIG=~/.kube/config
k9s
12) 자주 만난 오류 → 원인/해결 요약
- Ansible 버전 오류: venv에서
ansible --version
2.17.x 확인. - “:
kube_version
에v
제거, 문자열로. - “: Kubespray가 해당 K8s major 지원X →
1.31.6
사용. - “: download.yml에
download_run_once: true
추가. - “:
download_cache_dir
,local_release_dir
누락 → 추가. - Cilium “/API 탐지 실패: 값 파일에
k8sServiceHost/Port
명시. - Cilium init ************************“: 각 노드
/opt/cni/bin
존재/0755/소유자 root 보장. - CoreDNS Pending: Cilium 정상화 후 해결. NodeLocalDNS 끄기 권장.
13) 재설치/리셋 루틴
source ~/venvs/kubespray/bin/activate
ansible-playbook -i inventory/study/hosts.yaml -b -v reset.yml
# 필요 시 잔여물 제거
# for d in /etc/kubernetes /var/lib/etcd /var/lib/kubelet /etc/cni/net.d /var/lib/cni; do sudo rm -rf $d; done
ansible-playbook -i inventory/study/hosts.yaml -b -v cluster.yml
14) 어디서 뭘 실행하나? (요약)
- Ansible/Kubespray: 베스천에서 실행.
- cilium CLI/helm: kubeconfig와 값 파일이 있는 베스천 또는 master-001 어디서든 가능.
부록: 문제 진단 명령 모음
# 버전
python -V; ansible --version; helm version; kubectl version --short
# Ansible vars 즉시 확인
d='inventory/study/hosts.yaml'
ansible -i $d all -m debug -a 'var=kube_version'
ansible -i $d all -m debug -a 'var=download_localhost'
ansible -i $d all -m debug -a 'var=download_run_once'
ansible -i $d all -m debug -a 'var=download_cache_dir'
ansible -i $d all -m debug -a 'var=local_release_dir'
# Cilium 상태/logs
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
kubectl -n kube-system logs ds/cilium -c cilium-agent --tail=120
# DNS/CoreDNS
kubectl -n kube-system get svc kube-dns -o wide
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide || \
kubectl -n kube-system get pods -l k8s-app=coredns -o wide
이 문서는 설치 절차 + 선택 전환(proxyless) + 실제 오류/해결/검증을 한 번에 정리했습니다.
PoC가 안정화되면kubeProxyReplacement: true
로 전환하고, Hubble/k9s/MetalLB/Ingress/GitOps 등으로 확장하세요.
확장편
본 문서는 기존 가이드에 테스트/검증, Cilium 연결성, kube-proxy 대체(KPR), NIC 지정, VXLAN 설정, 그리고 빠른 재적용 방법을 보강합니다.
1) 빠른 테스트 체크리스트
# 전체 상태
kubectl get nodes -o wide
kubectl -n kube-system get pods -o wide
kubectl -n kube-system get pods -l k8s-app=cilium -o wide
# cilium 상태(에이전트 내부)
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium status --verbose
2) Cilium 연결성 테스트 (CLI)
Cilium CLI가 설치되어 있고, KUBECONFIG가 설정되어 있어야 합니다.
# 기본 연결성 테스트 (추천)
cilium connectivity test --test basic
# 결과 해석 팁
# - All tests passed: 정상
# - 특정 방향/정책 실패: 테스트 리포트 내 failing test ID 중심으로 원인 파악
3) k8s-net-cilium.yml
예시 (VXLAN + kube-proxy 대체 + NodePort + NIC 지정)
아래 값은 Helm/Cilium CLI로 재적용 가능한 Value 파일 예시입니다.
# 예시: VXLAN + kube-proxy 대체 + NodePort 사용 + NIC 지정
# 파일명 예: /etc/kubernetes/k8s-net-cilium.yml
# (자동탐지 실패 대비) K8s API 주소/포트 명시
k8sServiceHost: 192.168.10.30
k8sServicePort: 6443
# 터널링 모드: VXLAN (단순/안정)
tunnel: "vxlan"
# Cilium 1.17부터 KPR은 boolean으로 명시 필요 (strict/partial 문자열 대신)
# true: kube-proxy 제거(대체), false: kube-proxy 유지
kubeProxyReplacement: true
# (멀티 NIC/본딩 환경) BPF를 붙일 인터페이스를 명시적으로 지정
# bond0, eth0 등 실제 환경에 맞게 변경
devices:
- "bond0"
# 흔히 쓰는 옵션들
bpf:
masquerade: true # SNAT을 eBPF로 처리(성능/간결)
hubble:
enabled: true # 가시성 활성화(추후 cilium hubble enable --ui도 가능)
# (선택) NodePort LB 모드 등 추가 옵션이 필요하면 아래에 확장 가능
# nodePort:
# enabled: true
반영(재설치/업그레이드) 방법
- Cilium CLI
cilium upgrade --version 1.17.7 \ -f /etc/kubernetes/k8s-net-cilium.yml \ --wait cilium status --verbose
- Helm
helm upgrade --install cilium cilium/cilium \ -n kube-system \ --version 1.17.7 \ -f /etc/kubernetes/k8s-net-cilium.yml \ --wait
❗ 주의: kube-proxy를 실제로 제거하려면, KPR=true 적용 후
kube-proxy
DaemonSet을 삭제하고 재생성되지 않도록 Kubespray 변수(예:kube_proxy_mode: "none"
또는kube_proxy_remove: true
)를 관리하세요. 전환 직후에는cilium service list
와 실제 서비스 접근이 모두 정상인지 반드시 확인합니다.
4) Ansible 태그를 이용한 Cilium만 재적용
Cilium 관련 태그만 실행해 빠르게 재적용/수정할 수 있습니다.
ansible-playbook -i inventory/study/hosts.yaml -b \
--tags cilium cluster.yml
5) kube-proxy를 끄는(대체하는) 이유
- iptables 기반 서비스 처리를 eBPF 로드밸런서로 대체 → 낮은 지연/높은 처리량
- iptables 규칙 폭증/관리 복잡도 완화
- Cilium이 Service VIP → Pod 엔드포인트 매핑을 BPF 맵으로 처리
확인 방법
# KPR 상태
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium status | grep -i kube-proxy
# 예) KubeProxyReplacement: Enabled (Strict)
# BPF 기반 서비스 맵
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium service list
# => ClusterIP/NodePort 서비스가 BPF LB에 등록되어 있어야 함
6) 로컬 DNS(NodeLocalDNS) 비활성화 이유/방법
- 학습/초기 구축 단계에서 DNS 경로 단순화로 문제 범위를 줄임
- Cilium/Pod-DNS/CoreDNS 경로만 확인하면 되어 트러블슈팅이 쉬움
# inventory/study/group_vars/k8s_cluster/k8s-cluster.yml
enable_nodelocaldns: false
이미 배포되었다면:
kubectl -n kube-system delete ds nodelocaldns || true
7) VXLAN을 권장하는 이유
- 라우팅/BGP 지식 없이 즉시 동작(PoC, 단일 L2/L3 환경에 적합)
- 운영에서 네이티브 라우팅(BGP)로 확장 가능하나, PoC/내부망에선 VXLAN이 가장 단순/안성
8) Cilium 동작 검증 플로우 (요약)
# 1) Cilium 상태
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium status --verbose
# 2) BPF 서비스 테이블
kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium service list
# 3) 간단 워크로드 + 접근
kubectl create deploy demo-nginx --image=nginx:1.25-alpine --replicas=2
kubectl expose deploy demo-nginx --port=80 --target-port=80 --type=ClusterIP
kubectl run -it --rm tmp --image=busybox:1.36 --restart=Never -- sh -c 'wget -qO- http://demo-nginx'
# 4) Cilium CLI 연결성 테스트
cilium connectivity test --test basic
9) 자주 하는 실수/주의점 (추가)
kube_version
에 접두사v
금지 (예:"1.31.6"
OK,"v1.31.6"
NOK)kubeProxyReplacement
는 반드시 boolean (true/false)로 명시- API 서버 자동탐지 실패 시 **
k8sServiceHost/Port
**를 값 파일에 명시 - CNI 디렉터리 권한/존재 보장:
/opt/cni/bin
(0755, root:root) - 베스천에서 CLI/Helm 실행 시 KUBECONFIG/값 파일 경로 확인
10) 부록: k9s / Hubble 빠른 구성
# k9s (베스천)
export KUBECONFIG=~/.kube/config
# 최신 릴리스 바이너리 설치 후 실행 (배포판별 설치 방식 상이)
k9s
# Hubble (Cilium CLI)
cilium hubble enable --ui
# Hubble NodePort 지정 생성
kubectl -n kube-system patch svc hubble-ui \
-p '{"spec":{"type":"NodePort","ports":[{"name":"http","port":80,"targetPort":8081,"protocol":"TCP","nodePort":31234}]}}'
kubectl -n kube-system get svc hubble-ui -o wide # NodePort 확인
# 브라우저: http://<worker-IP>:<nodePort>
# CLI 관측
cilium hubble port-forward &
hubble status
hubble observe --http --follow
참고) 단계별 롤백/리셋
# kube-proxy 대체 전 상태로 되돌리기 (예: KPR=false)
helm upgrade --install cilium cilium/cilium \
-n kube-system --version 1.17.7 \
-f /etc/kubernetes/cilium-extra-values.yaml --wait
# 클러스터 전체 리셋(필요 시)
ansible-playbook -i inventory/study/hosts.yaml -b reset.yml
# 잔여물 수동 정리 후 재설치
ansible-playbook -i inventory/study/hosts.yaml -b cluster.yml