Kubespray Kubernetes 구축 가이드입니다. Rocky Linux 9 노드에 Kubespray와 Ansible을 사용해 재현 가능한 클러스터를 만들고, 3개 control plane·etcd 쿼럼·API VIP·Cilium 네트워크를 배포 후 검증합니다. 명령을 실행하는 것뿐 아니라 장애와 업그레이드를 견딜 수 있는지 확인하는 데 초점을 둡니다.

기존 글의 Kubernetes 1.31.6은 2025년 11월 지원이 종료되었습니다. 이 개정판은 2026년 7월 기준 Kubespray 2.31.0의 기본 Kubernetes 1.35.4와 번들 Cilium 1.19.3을 기준으로 합니다. 다만 실제 도입 전에는 선택한 Kubespray 태그의 checksum과 Cilium의 Kubernetes 호환 매트릭스를 함께 확인해야 합니다.

Kubespray Kubernetes 구축 구조: Ansible, API VIP, 3 control plane, 3 worker, Cilium과 etcd 백업
Ansible 제어 노드에서 API VIP를 거쳐 3개 control plane·etcd와 3개 worker를 배포하고 Cilium 경로와 외부 백업을 검증하는 구조

Kubespray Kubernetes 구축 버전과 지원 범위

구성 요소 이 글의 기준 선택 이유와 주의점
Rocky Linux 9.x 최신 패치 모든 노드의 minor·kernel·시간 동기화 통일
Kubespray v2.31.0 태그 release tag와 requirements를 고정해 재현
Kubernetes v1.35.4 Kubespray 2.31.0 기본값, 지원 중인 minor
Cilium v1.19.3 Kubespray 2.31.0 번들 버전
containerd Kubespray 기본값 2.31.0 릴리스의 검증 조합 사용
etcd 3개 멤버 한 노드 장애에도 과반 합의 유지
Kubernetes 최신판이 Kubespray와 Cilium의 공동 검증 조합이라는 뜻은 아닙니다. 2026년 7월 Kubernetes 최신 stable은 1.36.2지만, Kubespray 2.31.0의 기본값은 1.35.4입니다. 또한 Cilium stable 문서의 보장된 e2e 목록을 확인하고, 목록 밖 조합은 운영 전 별도 검증하십시오.

Kubespray Kubernetes 구축 토폴로지

호스트 예시 IP 역할
ansible01 10.20.0.5 Kubespray 실행·inventory·artifact 보관
api.k8s.example.com 10.20.0.10 외부 HAProxy/로드밸런서 VIP:6443
cp01~cp03 10.20.0.11~13 kube_control_plane + etcd
wk01~wk03 10.20.0.21~23 kube_node
backup01 10.20.0.30 암호화된 etcd snapshot·inventory 백업

API VIP는 단일 HAProxy 한 대가 아니라 최소 두 프록시와 VRRP 또는 기존 로드밸런서로 구성합니다. VIP의 6443 health check는 모든 control plane의 kube-apiserver로 전달하고, 노드·Pod·Service CIDR과 사내망·VPN·스토리지망이 겹치지 않게 먼저 IP 계획을 확정합니다.

이름 해석과 포트 확인

getent hosts api.k8s.example.com cp01 cp02 cp03 wk01 wk02 wk03

for host in cp01 cp02 cp03 wk01 wk02 wk03; do
  printf '%-6s ' "$host"
  timeout 3 bash -c "</dev/tcp/${host}/22" && echo SSH_OK || echo SSH_FAIL
done

timeout 3 bash -c '</dev/tcp/api.k8s.example.com/6443'   && echo API_VIP_OK || echo API_VIP_FAIL

Kubespray Kubernetes 구축: Rocky Linux 9 노드 사전 점검

Kubespray가 containerd, kubelet, 커널 모듈과 sysctl을 관리하므로 서로 다른 설치 스크립트를 먼저 실행하지 않습니다. 대신 모든 노드에서 OS·CPU·메모리·디스크·cgroup·시간 동기화와 기존 container runtime 흔적을 감사합니다.

cat /etc/rocky-release
uname -r
timedatectl status
free -h
lsblk -f
findmnt -no FSTYPE,OPTIONS / /var
stat -fc %T /sys/fs/cgroup
swapon --show
rpm -qa | grep -E 'kube(let|adm|ctl)|containerd|docker|cri-o' || true
Kubernetes 1.35는 cgroup v2를 기본 전제로 삼습니다. 기존 cgroup v1 우회 옵션에 의존하지 말고 Rocky Linux 9의 systemd cgroup v2 구성을 유지하십시오. SELinux와 firewalld를 무조건 끄지 말고 Kubespray 릴리스 문서의 지원 방식과 조직의 네트워크 ACL을 검증합니다.

전용 관리 계정과 SSH host key 등록

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/kubespray_ed25519

for host in cp01 cp02 cp03 wk01 wk02 wk03; do
  ssh-copy-id -i ~/.ssh/kubespray_ed25519.pub "ansible@${host}"
  ssh-keyscan -H "$host" >> ~/.ssh/known_hosts.new
done

sort -u ~/.ssh/known_hosts.new >> ~/.ssh/known_hosts
rm -f ~/.ssh/known_hosts.new
chmod 0600 ~/.ssh/known_hosts

ssh-keyscan 결과는 신뢰할 수 있는 콘솔이나 자산 관리 시스템의 fingerprint와 대조한 뒤 등록합니다. StrictHostKeyChecking을 끄면 중간자 공격을 탐지할 수 없습니다. ansible 계정에는 필요한 sudo 권한만 주고 root 직접 SSH 로그인은 허용하지 않습니다.

Kubespray Kubernetes 구축: 2.31.0 실행 환경 고정

Kubespray Kubernetes 구축 파일은 main 브랜치가 아니라 release tag로 고정합니다. Python 가상환경과 requirements.txt를 사용하면 시스템 Python과 Ansible 의존성을 섞지 않고 동일한 제어 환경을 재현할 수 있습니다.

sudo dnf install -y git python3.12 python3.12-pip

git clone --branch v2.31.0 --depth 1   https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
git verify-tag v2.31.0 || git show --show-signature --no-patch v2.31.0

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install --requirement requirements.txt

python --version
ansible --version
git describe --tags --always
Git 서명 검증은 신뢰할 maintainer key를 별도 경로로 확인한 경우에만 의미가 있습니다. 폐쇄망에서는 release archive, Python wheel, container image의 checksum과 provenance를 연결망에서 검증한 뒤 승인된 저장소로 반입하십시오.

Kubespray Kubernetes 구축 inventory 작성

inventory/prod/inventory.yml

cp -a inventory/sample inventory/prod
cat > inventory/prod/inventory.yml <<'YAML'
all:
  hosts:
    cp01: {ansible_host: 10.20.0.11, ip: 10.20.0.11, access_ip: 10.20.0.11}
    cp02: {ansible_host: 10.20.0.12, ip: 10.20.0.12, access_ip: 10.20.0.12}
    cp03: {ansible_host: 10.20.0.13, ip: 10.20.0.13, access_ip: 10.20.0.13}
    wk01: {ansible_host: 10.20.0.21, ip: 10.20.0.21, access_ip: 10.20.0.21}
    wk02: {ansible_host: 10.20.0.22, ip: 10.20.0.22, access_ip: 10.20.0.22}
    wk03: {ansible_host: 10.20.0.23, ip: 10.20.0.23, access_ip: 10.20.0.23}
  children:
    kube_control_plane:
      hosts: {cp01: {}, cp02: {}, cp03: {}}
    kube_node:
      hosts: {wk01: {}, wk02: {}, wk03: {}}
    etcd:
      hosts: {cp01: {}, cp02: {}, cp03: {}}
    k8s_cluster:
      children:
        kube_control_plane: {}
        kube_node: {}
    calico_rr:
      hosts: {}
YAML

etcd 멤버 수는 홀수로 유지합니다. control plane과 worker를 섞어 정의하지 말고, ansible_host는 SSH 접속 주소, ip·access_ip는 노드 간 통신 주소로 구분합니다. NAT·다중 NIC 환경은 advertised address와 라우팅을 별도로 검증하십시오.

inventory/prod/group_vars/all/all.yml 핵심 값

cat >> inventory/prod/group_vars/all/all.yml <<'YAML'

ansible_user: ansible
ansible_ssh_private_key_file: ~/.ssh/kubespray_ed25519

loadbalancer_apiserver:
  address: 10.20.0.10
  port: 6443

kubeconfig_localhost: true
kubectl_localhost: true
YAML

Kubernetes와 보안 기본값

cat >> inventory/prod/group_vars/k8s_cluster/k8s-cluster.yml <<'YAML'

kube_version: v1.35.4
container_manager: containerd
kube_network_plugin: cilium

kube_service_addresses: 10.233.0.0/18
kube_pods_subnet: 10.233.64.0/18
cluster_name: cluster.local

kube_api_anonymous_auth: false
remove_anonymous_access: true
kubernetes_audit: true
supplementary_addresses_in_ssl_keys:
  - 10.20.0.10
  - api.k8s.example.com
YAML

Pod·Service CIDR은 라우터, VPN, 데이터센터와 절대 겹치지 않아야 합니다. 이미 사용 중인 CIDR을 나중에 바꾸는 일은 새 클러스터로 이전하는 수준의 작업이 될 수 있습니다. API VIP와 DNS 이름은 kube-apiserver 인증서 SAN에 포함합니다.

Cilium overlay와 Hubble 설정

cat >> inventory/prod/group_vars/k8s_cluster/k8s-net-cilium.yml <<'YAML'

cilium_version: v1.19.3
cilium_tunnel_mode: vxlan
cilium_identity_allocation_mode: crd
cilium_ipam_mode: kubernetes
cilium_cni_exclusive: true

cilium_enable_hubble: true
cilium_hubble_install: true
cilium_hubble_tls_generate: true
cilium_enable_hubble_ui: false
YAML
kube-proxy replacement는 단순 성능 옵션이 아니라 service datapath 변경입니다. 이 기본 가이드에서는 kube-proxy를 유지합니다. replacement를 사용할 경우 cilium_kube_proxy_replacement, API global endpoint, DSR·SNAT와 host firewall 동작을 별도 설계하고 부하·장애 시험을 수행하십시오.

Kubespray Kubernetes 구축 전 검증

CIDR 충돌과 inventory 구조

ip route
ip -4 address show

grep -R --line-number -E   'kube_(service_addresses|pods_subnet)|loadbalancer_apiserver|kube_version|cilium_version'   inventory/prod/group_vars

ansible-inventory -i inventory/prod/inventory.yml --graph
ansible-inventory -i inventory/prod/inventory.yml --list   > inventory/prod/inventory-expanded.json

SSH·sudo·Python 확인

ansible -i inventory/prod/inventory.yml all -m ping
ansible -i inventory/prod/inventory.yml all --become   -m command -a 'id'
ansible -i inventory/prod/inventory.yml all --become   -m shell -a 'python3 --version; stat -fc %T /sys/fs/cgroup; swapon --show'

ping 모듈 성공만으로는 충분하지 않습니다. become이 비대화식으로 작동하는지, Python interpreter와 cgroup v2가 모든 노드에서 일치하는지 확인합니다. Ansible Vault 비밀번호 파일과 SSH private key는 저장소에 커밋하지 않습니다.

playbook 문법과 변경 목록 검토

ansible-playbook -i inventory/prod/inventory.yml   cluster.yml --syntax-check

git status --short
git diff -- inventory/prod/group_vars inventory/prod/inventory.yml

tar --exclude='credentials' --exclude='artifacts'   -czf "inventory-prod-$(date +%F).tgz" inventory/prod

Kubespray Kubernetes 구축 실행

처음부터 전체 노드에 임의 태그를 붙여 실행하지 않습니다. 검증된 inventory로 공식 cluster.yml을 실행하고, 실패 시 같은 명령을 재실행하기 전에 최초 실패 task와 노드 상태를 확인합니다. Kubespray는 멱등성을 목표로 하지만 외부 로드밸런서와 네트워크는 별도 상태입니다.

mkdir -p logs

ansible-playbook -i inventory/prod/inventory.yml   cluster.yml --become   2>&1 | tee "logs/cluster-$(date +%F-%H%M%S).log"

test ${PIPESTATUS[0]} -eq 0
tee를 사용하면 셸의 마지막 종료 코드는 tee가 될 수 있으므로 PIPESTATUS[0]으로 ansible-playbook 결과를 확인합니다. 로그에는 호스트명·IP·task 결과가 포함될 수 있으므로 접근 권한과 보존 기간을 정하고 외부 공유 전 민감 정보를 제거하십시오.

Kubespray Kubernetes 구축 결과 검증

kubeconfig와 control plane endpoint

find inventory/prod/artifacts -maxdepth 2 -type f -ls

install -d -m 0700 "$HOME/.kube"
install -m 0600 inventory/prod/artifacts/admin.conf "$HOME/.kube/config"

kubectl config view --minify
kubectl cluster-info
kubectl get --raw='/readyz?verbose'

artifact 파일 이름은 선택한 Kubespray 태그와 설정에 따라 다를 수 있으므로 find 결과를 먼저 확인합니다. kubeconfig에는 cluster-admin 자격정보가 들어 있으므로 공용 홈 디렉터리나 CI artifact에 그대로 올리지 말고 운영자는 OIDC와 최소 권한 RBAC를 사용합니다.

노드·etcd·시스템 Pod 상태

kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get --raw='/livez?verbose'

ansible -i inventory/prod/inventory.yml etcd --become   -m shell -a 'crictl ps --name etcd'

kubectl -n kube-system get endpoints kube-dns
kubectl get events -A --sort-by='.lastTimestamp' | tail -n 50

Cilium과 Hubble 상태

kubectl -n kube-system rollout status daemonset/cilium --timeout=10m
kubectl -n kube-system get pods -l k8s-app=cilium -o wide

cilium status --wait --wait-duration 10m
cilium connectivity test

kubectl -n kube-system get secret | grep -i hubble
kubectl -n kube-system logs deployment/hubble-relay --tail=100

cilium connectivity test는 여러 namespace와 Pod를 만들므로 운영 정책에 맞는 점검 namespace에서 실행하고 종료 후 정리합니다. 모든 노드의 Cilium DaemonSet, operator, CoreDNS와 service routing이 정상이어야 workload 배포로 넘어갑니다.

Kubespray Kubernetes 구축: 워크로드와 NetworkPolicy 검증

배포·Service·DNS smoke test

kubectl create namespace smoke-test
kubectl -n smoke-test create deployment web   --image=registry.k8s.io/e2e-test-images/agnhost:2.53   -- /agnhost netexec --http-port=8080
kubectl -n smoke-test expose deployment web --port=80 --target-port=8080
kubectl -n smoke-test rollout status deployment/web --timeout=5m

kubectl -n smoke-test run client --rm -it --restart=Never   --image=registry.k8s.io/e2e-test-images/agnhost:2.53 --   /bin/sh -c 'nslookup web; wget -qO- http://web/'

기본 거부 후 명시적 허용

cat <<'YAML' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: web-default-deny
  namespace: smoke-test
spec:
  podSelector: {matchLabels: {app: web}}
  policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: web-allow-client
  namespace: smoke-test
spec:
  podSelector: {matchLabels: {app: web}}
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector: {matchLabels: {run: client}}
      ports:
        - {protocol: TCP, port: 8080}
YAML

kubectl -n smoke-test get networkpolicy

정책 적용 전 성공, 기본 거부 후 실패, 명시적 허용 후 성공을 각각 확인해야 합니다. DNS egress를 함께 제한한다면 kube-dns 서비스의 실제 selector와 UDP·TCP 53을 확인하십시오. 테스트 후 kubectl delete namespace smoke-test로 자원을 정리합니다.

etcd 스냅샷과 복구 준비

Kubespray Kubernetes 구축 완료 직후 etcd 스냅샷을 자동화합니다. snapshot은 한 멤버에서 만들어 클러스터 밖의 암호화된 저장소로 복사하고, inventory·인증서·Kubespray tag와 함께 복구 절차를 기록합니다.

sudo grep -E --   '--(listen-client-urls|trusted-ca-file|cert-file|key-file)'   /etc/kubernetes/manifests/etcd.yaml

sudo install -d -m 0700 /var/backups/etcd
export ETCDCTL_API=3
sudo -E etcdctl   --endpoints=https://127.0.0.1:2379   --cacert=/etc/ssl/etcd/ssl/ca.pem   --cert="/etc/ssl/etcd/ssl/admin-$(hostname -s).pem"   --key="/etc/ssl/etcd/ssl/admin-$(hostname -s)-key.pem"   snapshot save "/var/backups/etcd/snapshot-$(date +%F-%H%M%S).db"

sudo -E etcdctl snapshot status   "$(sudo find /var/backups/etcd -name 'snapshot-*.db' -type f | sort | tail -n 1)"   --write-out=table
인증서 경로와 파일명은 inventory와 Kubespray 버전에 따라 다를 수 있습니다. etcd manifest에서 실제 경로를 먼저 확인하십시오. 같은 클러스터에서 복구를 실험하지 말고 격리된 환경에서 snapshot restore, API 기동, 객체 정합성까지 정기적으로 검증합니다.

인증서·감사·운영 점검

sudo kubeadm certs check-expiration
kubectl auth can-i --list
kubectl auth can-i create clusterrolebindings --as=system:anonymous

kubectl get --raw='/metrics' | grep -E   'apiserver_request_total|apiserver_request_duration_seconds' | head

kubectl get nodes -o json | jq -r   '.items[] | [.metadata.name,.status.nodeInfo.kubeletVersion,.status.nodeInfo.containerRuntimeVersion] | @tsv'
  • API VIP, DNS, control plane 한 노드 중지 시 kubectl 요청이 계속 성공하는지 확인합니다.
  • etcd 멤버·리더·DB 크기와 snapshot 성공 여부를 경보로 연결합니다.
  • 노드 filesystem, inode, memory pressure, PID pressure와 image filesystem 용량을 감시합니다.
  • kube-apiserver audit log를 중앙 저장소로 전송하고 변조 방지·보존 정책을 적용합니다.
  • cluster-admin kubeconfig를 일상 계정에서 제거하고 OIDC·RBAC·짧은 세션을 사용합니다.
  • Cilium drop, policy verdict, DNS error와 Hubble certificate 만료를 관찰합니다.

Kubespray Kubernetes 업그레이드

Kubernetes minor는 한 단계씩 올리고 version skew 정책을 지킵니다. 먼저 목표 Kubernetes patch와 Cilium 버전이 목표 Kubespray tag의 checksum과 지원 목록에 있는지 확인합니다. 그다음 etcd snapshot, workload backup, PodDisruptionBudget와 여유 용량을 점검합니다.

git fetch --tags --prune
git tag --sort=-version:refname | head

# 새 clone/venv에서 기존 inventory를 복사한 뒤 차이를 검토합니다.
git diff v2.31.0..'<TARGET_KUBESPRAY_TAG>' --   inventory/sample roles/kubespray_defaults docs

ansible-playbook -i inventory/prod/inventory.yml   upgrade-cluster.yml --become --syntax-check

ansible-playbook -i inventory/prod/inventory.yml   upgrade-cluster.yml --become   2>&1 | tee "logs/upgrade-$(date +%F-%H%M%S).log"

test ${PIPESTATUS[0]} -eq 0

upgrade-cluster.yml을 실행하기 전에 새 release의 urgent upgrade note를 읽습니다. 특히 Kubespray 2.31은 cgroup v1, retired ingress-nginx, archive된 Kubernetes Dashboard, etcd 선행 버전과 제거된 변수를 주의해야 합니다. control plane과 worker를 한꺼번에 재부팅하지 않습니다.

자주 하는 실수와 안전한 대안

실수 영향 대안
main 브랜치 직접 배포 의존성과 기본값이 수시로 변경 release tag·requirements·checksum 고정
2개 etcd 멤버 한 노드 장애 시 과반 상실 3개 또는 5개 홀수 멤버
API 단일 endpoint control plane SPOF 다중 LB와 VIP health check
CIDR 중복 Pod·Service·사내망 라우팅 충돌 배포 전 IPAM과 route 검증
SSH host key 검사 해제 중간자 공격 탐지 불가 fingerprint 검증 후 known_hosts 등록
cluster-admin kubeconfig 공유 클러스터 전체 권한 노출 OIDC·RBAC·짧은 세션
snapshot 파일만 생성 복구 가능성 미확인 격리 환경 정기 restore 훈련

Kubespray Kubernetes 구축 체크리스트

  1. Kubespray tag와 Python·Ansible requirements를 고정했습니다.
  2. Kubernetes·Cilium·containerd 조합의 지원 문서를 확인했습니다.
  3. 3개 control plane·etcd와 다중 API load balancer를 준비했습니다.
  4. Pod·Service·노드·VPN·스토리지 CIDR이 겹치지 않습니다.
  5. SSH host key, sudo 범위, Vault와 private key 보관을 검증했습니다.
  6. 노드·API·CoreDNS·Cilium connectivity와 NetworkPolicy를 시험했습니다.
  7. etcd snapshot을 외부에 보관하고 격리 복구 훈련을 완료했습니다.
  8. 인증서 만료, audit, resource pressure, Cilium drop을 모니터링합니다.
  9. 업그레이드 전 urgent note와 version skew, PDB·여유 용량을 검토합니다.

관련 자료

Kubespray Kubernetes 구축 정리

Kubespray Kubernetes 구축은 cluster.yml 한 번의 성공보다 버전 조합, 3노드 합의, API endpoint, 네트워크와 복구 계획을 함께 검증하는 일입니다. release tag와 inventory를 고정하고, anonymous access를 제한하며, Cilium connectivity·NetworkPolicy·etcd restore를 실제로 시험하십시오. 이 기준을 통과해야 Rocky Linux 9 실습을 운영 가능한 Kubernetes 플랫폼으로 확장할 수 있습니다.