Kubespray는 Ansible 기반의 Kubernetes 클러스터 설치 자동화 도구이다. Kubernetes 공식 프로젝트이며, 프로덕션 수준의 클러스터를 손쉽게 설치하고 관리할 수 있도록 도와줍니다. 온프레미스 환경에서 Kubernetes 클러스터를 설치하고자 할 때 사용한다.
| 기능 | 설명 |
|---|---|
| Ansible 기반 | Ansible 플레이북을 활용해 YAML 설정만으로 클러스터 구성 가능 |
| 멀티 OS 지원 | Ubuntu, Debian, CentOS, RHEL, Fedora, Flatcar 등 지원 |
| 멀티 클라우드/온프레미스 지원 | AWS, GCP, Azure, OpenStack, VMware, 베어메탈 등 다양한 환경 지원 |
| 플러그인/네트워크 선택 가능 | Calico, Flannel, Cilium, Canal 등 다양한 CNI 선택 가능 |
| 보안 기능 내장 | TLS 인증서 자동 생성, RBAC 설정 가능 |
| 모듈화된 구조 | 컴포넌트 별로 구성 가능 (etcd, container runtime, DNS 등) |
| Idempotent (멱등성) | 같은 작업을 여러 번 실행해도 안정적인 상태 유지 |
🏳️🌈 [궁금한점]
🔗[목차]
VM을 3개 준비한다. IP는 192.168.56.10 (Master), 192.168.56.101 (worker1),, 192.168.56.102 (bastion) 으로 설정했다.
SSH Key 생성
- worker2를 bation 으로 활용
- worker2 접속 후 ssh key 생성
vagrant@worker2:~$ ssh-keygen -t rsa -b 4096 -C "chojeonghak@gmail.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/vagrant/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/vagrant/.ssh/id_rsa
Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:FJT8a+T+Hb5Db0E4S12NrIv6znMXcb507RDGF3F4/v0 chojeonghak@gmail.com
The key's randomart image is:
+---[RSA 4096]----+
| oo. . ++|
| o. +.=|
| .. oooo|
| . o .+=o+|
| So o.o+*+|
| = ..+o*|
| + .o==|
| ..o .oooE|
| o++..++ |
+----[SHA256]-----+
SSH key 복사
ssh-copy-id vagrant@192.168.56.10 ssh-copy-id vagrant@192.168.56.101
bastion에서 kube-spray 소스를 클론한다.
vagrant@worker2:~$ git clone https://github.com/kubernetes-sigs/kubespray
vagrant@worker2:~$ cd kubespray
vagrant@worker2:~/kubespray$ git checkout release-2.27
Switched to branch 'release-2.27'
Your branch is up to date with 'origin/release-2.27'.
인벤토리에 작업 대상 서버의 속성을 기술한다. master, worker1 과 bation으로 사용할 worker2를 구성했다.
cp -r inventory/sample inventory/mycluster
vi inventory/mycluster/hosts.yml
all:
hosts:
master:
ansible_host: 192.168.56.10
ip: 192.168.56.10
access_ip: 192.168.56.10
ansible_user: vagrant
ansible_become: true
worker1:
ansible_host: 192.168.56.101
ip: 192.168.56.101
access_ip: 192.168.56.101
ansible_user: vagrant
ansible_become: true
children:
kube_control_plane:
hosts:
master:
kube_node:
hosts:
worker1:
etcd:
hosts:
master:
k8s_cluster:
children:
kube_control_plane:
kube_node:
calico_rr:
hosts: {}
ansible 설치를 위해 python을 설치한다. bation에서 계속 진행한다.
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt update
sudo apt install -y python3.10 python3.10-venv python3.10-dev
sudo apt install -y python3-pip
sudo ln -sf /usr/bin/pip3 /usr/bin/pip
python3 --version # → Python 3.10.x
버전이 3.10.17 이 나와야 한다. 틀릴 경우 이미 설치된 버전이 있다는 것이다. 다음의 방법을 통해 버전을 변경한다.
sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1
sudo update-alternatives --config python3
vagrant@worker2:~/kubespray$ python3 --version
Python 3.10.17
참고로 Kubespray 버전별 Python 및 Ansible 호환성은 다음과 같다.
| Kubespray 버전 | 권장 Python 버전 | 권장 Ansible 버전 |
|----------------|------------------|-------------------|
| v2.22.x | 3.8 이상 | 2.12.x |
| v2.23.x | 3.9 이상 | 2.13.x |
| v2.24.x | 3.10 이상 | 2.14.x |
| v2.25.x | 3.10 이상 | 2.16.x |
vagrant@worker2:~/kubespray$# curl -sS https://bootstrap.pypa.io/get-pip.py | sudo python3
vagrant@worker2:~/kubespray$ pip3 --version
pip 25.0.1 from /usr/local/lib/python3.10/dist-packages/pip (python 3.10)
sudo pip3 install -r requirements.txt
vagrant@worker2:~/kubespray$ ansible --version
ansible [core 2.12.5]
config file = /home/vagrant/kubespray/ansible.cfg
configured module search path = ['/home/vagrant/kubespray/library']
ansible python module location = /usr/local/lib/python3.10/dist-packages/ansible
ansible collection location = /home/vagrant/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.10.17 (main, Apr 9 2025, 08:54:15) [GCC 9.4.0]
jinja version = 3.1.2
libyaml = False
vagrant@worker2:~/kubespray$ ansible all -m ping -i inventory/mycluster/hosts.yml
[WARNING]: Skipping callback plugin 'ara_default', unable to load
master | SUCCESS => {
"changed": false,
"ping": "pong"
}
worker1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
vi inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
ANSIBLE_NO_LOG=False ansible-playbook -i inventory/mycluster/hosts.yml cluster.yml -b -vvvv
-b는 sudo 권한을 의미, -v는 verbose
-v: 간단한 추가 로그
-vv: SSH 실행 내용, 변경 여부 등
-vvv: 모듈 실행의 STDOUT/STDERR 로그까지 표시 (추천)
-vvvv: 연결 디버그까지 포함 (네트워크 트러블슈팅용)
설치 확인
모든 설치 노드에서 확인systemctl status kubelet마스터에서 확인
ps aux | grep kube-apiserver
vagrant@master:~$ which kubectl
/usr/local/bin/kubectl
vagrant@master:~$ k exec -it cilium-7jvwg -n kube-system -- cilium status
Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), clean-cilium-state (init), install-cni-binaries (init)
KVStore: Ok etcd: 1/1 connected, leases=1, lock leases=1, has-quorum=true: https://192.168.56.10:2379 - 3.5.19 (Leader)
Kubernetes: Ok 1.31 (v1.31.7) [linux/amd64]
Kubernetes APIs: ["EndpointSliceOrEndpoint", "cilium/v2::CiliumClusterwideNetworkPolicy", "cilium/v2::CiliumNetworkPolicy", "cilium/v2alpha1::CiliumCIDRGroup", "core/v1::Namespace", "core/v1::Pods", "core/v1::Service", "networking.k8s.io/v1::NetworkPolicy"]
KubeProxyReplacement: Partial [eth0 10.0.2.15 fe80::a00:27ff:fecb:1a1d, eth1 192.168.56.10 fe80::a00:27ff:fec6:2224]
Host firewall: Disabled
SRv6: Disabled
CNI Chaining: none
CNI Config file: successfully wrote CNI configuration file to /host/etc/cni/net.d/05-cilium.conflist
Cilium: Ok 1.15.9 (v1.15.9-2ea0cf33)
NodeMonitor: Disabled
Cilium health daemon: Ok
IPAM: IPv4: 4/254 allocated from 10.233.64.0/24,
IPv4 BIG TCP: Disabled
IPv6 BIG TCP: Disabled
BandwidthManager: Disabled
Host Routing: Legacy
Masquerading: IPTables [IPv4: Enabled, IPv6: Disabled]
Controller Status: 34/34 healthy
Proxy Status: OK, ip 10.233.64.84, 0 redirects active on ports 10000-20000, Envoy: embedded
Global Identity Range: min 256, max 65535
Hubble: Disabled
Encryption: Disabled
Cluster health: 1/2 reachable (2025-04-13T03:43:12Z)
Name IP Node Endpoints
worker1 192.168.56.101 unreachable reachable
Modules Health: Stopped(0) Degraded(0) OK(11)
연결 설정
mkdir -p ~/.kube sudo cp /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) ~/.kube/config
bastion으로 conf 복사
마스터에서 실행sudo scp /etc/kubernetes/admin.conf vagrant@192.168.56.101:~/admin.conf sudo scp /etc/kubernetes/admin.conf vagrant@192.168.56.102:~/admin.conf베스천에서 실행, kubectl 설치
(필요 시)워크노드와 bastion에 kubectl 설치
curl -LO "https://dl.k8s.io/release/v1.29.0/bin/linux/amd64/kubectl" chmod +x kubectl
sudo mv kubectl /usr/local/bin/
> 버전 확인
``` bash
vagrant@worker2:~/kubespray$ kubectl version --client
Client Version: v1.29.0
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
busybox 테스트
# busybox.yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "3600"]
imagePullPolicy: IfNotPresent
restartPolicy: Never
vagrant@master:~$ vi busybox.yml
vagrant@master:~$ k apply -f busybox.yml
pod/busybox created
vagrant@master:~$ k get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 7s
vagrant@master:~$ kubectl exec -it busybox -- sh
/ # ping google.com
PING google.com (142.250.206.206): 56 data bytes
64 bytes from 142.250.206.206: seq=0 ttl=113 time=33.528 ms
64 bytes from 142.250.206.206: seq=1 ttl=113 time=34.806 ms
vagrant@master:~$ vi busybox2.yml
vagrant@master:~$ k apply -f busybox2.yml
pod/busybox2 created
vagrant@master:~$ k get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 2m30s
busybox2 1/1 Running 0 6s
내부 IP 확인
vagrant@master:~$ k get po -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES busybox 1/1 Running 0 3m40s 10.233.65.121 worker1 <none> <none> busybox2 1/1 Running 0 76s 10.233.65.165 worker1 <none> <none>내부 네트워크 연결 테스트 (POD 간 통신)
vagrant@master:~$ k exec -it busybox -- sh / # ping 10.233.65.165 PING 10.233.65.165 (10.233.65.165): 56 data bytes 64 bytes from 10.233.65.165: seq=0 ttl=63 time=1.057 ms 64 bytes from 10.233.65.165: seq=1 ttl=63 time=0.099 ms
# Kubernetes Control Plane images
docker pull k8s.gcr.io/kube-apiserver:v1.31.7
docker pull k8s.gcr.io/kube-controller-manager:v1.31.7
docker pull k8s.gcr.io/kube-scheduler:v1.31.7
docker pull k8s.gcr.io/kube-proxy:v1.31.7
# Cilium CNI images
docker pull quay.io/cilium/cilium:v1.15.9
docker pull quay.io/cilium/operator:v1.15.9
docker pull quay.io/cilium/hubble-relay:v1.15.9
# CoreDNS
docker pull registry.k8s.io/coredns/coredns:v1.11.3
# etc
docker pull registry.k8s.io/cpa/cluster-proportional-autoscaler:v1.8.8
docker pull registry.k8s.io/dns/k8s-dns-node-cache:1.22.28
docker pull registry.k8s.io/kube-apiserver:v1.31.7
docker pull registry.k8s.io/kube-controller-manager:v1.31.7
docker pull registry.k8s.io/pause:3.10
kubespray 버전마다 의존하는 컴포넌트 이미지의 버전이 상이하다. 버전은 꼼꼼이 확인하는 것이 좋다.
이미지 버전이나 kuby-spray 설치 시 사용하는 속성을 프로젝트 내에서 검색할때 사용한다.
grep -rnw ./ -e "kube-version"
릴리즈 노드 기준으로 설치 버전이 명기되어 있다. 정확하진 않으니 직접 확인해야 한다.
kube_version: v1.31.7
// 연관 이미지를 미리 받아 둔 상태에서 설치가능하다.
ANSIBLE_NO_LOG=False ansible-playbook -i inventory/mycluster/hosts.yml cluster.yml -b -vvvv --skip-tags download
// cni가 문제가 될 경우 먼저 cni를 제외한 후 설치한다. 이 후 수작업으로 드라이버를 설치할 수 있다.
ANSIBLE_NO_LOG=False ansible-playbook -i inventory/mycluster/hosts.yml cluster.yml -b -vvvv --skip-tags cni,download
CNI 선택
| 요구 사항 | 적합한 CNI | 주요 특징 | 네트워크 정책 지원 |
|---|---|---|---|
| 성능 중요, eBPF 활용 | Cilium | eBPF 기반, 고성능, observability, Hubble 지원 | 지원함 |
| 안정성, 많은 사례 | Calico | 전통적 방식, 널리 사용됨, BGP/Overlay 모두 지원 | 지원함 |
| 단순한 실습용 | Flannel | 가장 간단함, 빠른 설치, Overlay 네트워크 전용 | 지원 안 함 |
| 복잡한 정책 필요 | Cilium, Calico | 복잡한 네트워크 정책 및 DNS 기반 정책 가능 | 지원함 |
| 자원 적은 환경 | Flannel | 리소스 소모 적음, 구성 간단 | 지원 안 함 |
| 보안 / L7 정책 필요 | Cilium | L7 레벨 정책(e.g. HTTP 경로 기반 정책)까지 가능 | 고급 지원 |
cilium 수동 설치
curl -L --remote-name https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz tar xzvf cilium-linux-amd64.tar.gz sudo mv cilium /usr/local/bin
vagrant@master:~$ cilium status
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: disabled (using embedded mode)
\__/¯¯\__/ Hubble Relay: disabled
\__/ ClusterMesh: disabled
DaemonSet cilium Desired: 2, Ready: 2/2, Available: 2/2
Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
Containers: cilium Running: 2
cilium-operator Running: 2
clustermesh-apiserver
hubble-relay
Cluster Pods: 3/3 managed by Cilium
Helm chart version:
Image versions cilium quay.io/cilium/cilium:v1.13.0: 2
cilium-operator quay.io/cilium/operator:v1.13.0: 2
설치를 진행할 베스천과 쿠버네티스 노드는 환경(OS, Python 버전등)을 통일하는 것이 좋다.
설치 과정에서 노드에 접속한 상태면 kubectl 재시작이 진행되지 않을 수 있다. 콘솔 접속은 설치 후 진행한다.
vagrant@master:~$ k get all -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system pod/cilium-7jvwg 1/1 Running 0 9m41s
kube-system pod/cilium-operator-6c7fb55565-dzrjx 1/1 Running 0 9m41s
kube-system pod/cilium-operator-6c7fb55565-t6ldg 1/1 Running 0 9m41s
kube-system pod/cilium-sqz7c 1/1 Running 0 9m41s
kube-system pod/coredns-d665d669-fb8hp 1/1 Running 0 8m49s
kube-system pod/coredns-d665d669-knq9f 1/1 Running 0 8m45s
kube-system pod/dns-autoscaler-5cb4578f5f-2nvp2 1/1 Running 0 8m47s
kube-system pod/kube-apiserver-master 1/1 Running 1 12m
kube-system pod/kube-controller-manager-master 1/1 Running 2 12m
kube-system pod/kube-proxy-kfvxs 1/1 Running 0 10m
kube-system pod/kube-proxy-tt699 1/1 Running 0 10m
kube-system pod/kube-scheduler-master 1/1 Running 1 12m
kube-system pod/nginx-proxy-worker1 1/1 Running 0 9m54s
kube-system pod/nodelocaldns-2t7kx 1/1 Running 0 8m44s
kube-system pod/nodelocaldns-rrwfl 1/1 Running 0 8m44s
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 12m
kube-system service/coredns ClusterIP 10.233.0.3 <none> 53/UDP,53/TCP,9153/TCP 8m48s
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-system daemonset.apps/cilium 2 2 2 2 2 <none> 9m42s
kube-system daemonset.apps/kube-proxy 2 2 2 2 2 kubernetes.io/os=linux 12m
kube-system daemonset.apps/nodelocaldns 2 2 2 2 2 kubernetes.io/os=linux 8m45s
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
kube-system deployment.apps/cilium-operator 2/2 2 2 9m42s
kube-system deployment.apps/coredns 2/2 2 2 8m49s
kube-system deployment.apps/dns-autoscaler 1/1 1 1 8m47s
NAMESPACE NAME DESIRED CURRENT READY AGE
kube-system replicaset.apps/cilium-operator-6c7fb55565 2 2 2 9m42s
kube-system replicaset.apps/coredns-d665d669 2 2 2 8m49s
kube-system replicaset.apps/dns-autoscaler-5cb4578f5f 1 1 1 8m47s