티스토리 뷰
이 포스팅은 Cloud@net의 기시다님의 온라인 강좌 내용을 정리한 스터디 요약 자료입니다.
kubespary 소개
kubespray는 Ansible 기반으로 쿠버네티스(Kubernetes) 클러스터를 자동으로 설치·업그레이드·관리하기 위한
오픈소스 배포 도구이다.
프로덕션 레벨의 클러스터를 손쉽게 구축할 수 있으며, 다양한 CNI 플러그인과 애드온을 지원한다.
주요 기능
- 퍼블릭/폐쇄망 서버 환경에서도 쿠버네티스 배포 가능

퍼블릭 서버 환경은 IaC(Terraform 등)으로 구성 → kubespary 로 K8S 배포


실습 배포 환경 분석
실습 환경 순서

주요 설치 항목 버전

실습 환경 배포를 위한 디렉터리 생성 및 스크립트 다운로드
mkdir k8s-ha-upgrade
cd k8s-ha-upgrade
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-ha-upgrade/Vagrantfile
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-ha-upgrade/admin-lb.sh
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-ha-upgrade/init_cfg.sh
curl -O https://raw.githubusercontent.com/gasida/vagrant-lab/refs/heads/main/k8s-ha-upgrade/k8s-ctr.sh
배포
vagrant up
vagrant status
kubeam에 의한 k8s 배포
HAProxy 동작 확인
# 로드밸런서 진입
vagrant ssh admin-lb
# haproxy config파일 확인
cat /etc/haproxy/haproxy.cfg
# 확인
systemctl status haproxy.service --no-pager
journalctl -u haproxy.service --no-pager
ss -tnlp | grep haproxy
open http://192.168.10.10:9000/haproxy_stats
Vagrantfile
# Base Image https://portal.cloud.hashicorp.com/vagrant/discover/bento/rockylinux-10.0
BOX_IMAGE = "bento/rockylinux-10.0"
BOX_VERSION = "202510.26.0"
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", "/Kubespray-Lab"]
vb.customize ["modifyvm", :id, "--nicpromisc2", "allow-all"]
vb.name = "k8s-ctr"
vb.cpus = 4
vb.memory = 4096
vb.linked_clone = true
end
subconfig.vm.host_name = "k8s-ctr"
subconfig.vm.network "private_network", ip: "192.168.10.10"
subconfig.vm.network "forwarded_port", guest: 22, host: "60100", auto_correct: true, id: "ssh"
subconfig.vm.synced_folder "./", "/vagrant", disabled: true
subconfig.vm.provision "shell", path: "init_cfg.sh"
end
end
init_cfg.sh
#!/usr/bin/env bash
echo ">>>> Initial Config Start <<<<"
echo "[TASK 1] Change Timezone and Enable NTP"
timedatectl set-local-rtc 0
timedatectl set-timezone Asia/Seoul
echo "[TASK 2] Disable firewalld and selinux"
systemctl disable --now firewalld >/dev/null 2>&1
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
echo "[TASK 3] Disable and turn off SWAP & Delete swap partitions"
swapoff -a
sed -i '/swap/d' /etc/fstab
sfdisk --delete /dev/sda 2 >/dev/null 2>&1
partprobe /dev/sda >/dev/null 2>&1
echo "[TASK 4] Config kernel & module"
cat << EOF > /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay >/dev/null 2>&1
modprobe br_netfilter >/dev/null 2>&1
cat << EOF >/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
echo "[TASK 5] Setting Local DNS Using Hosts file"
sed -i '/^127\.0\.\(1\|2\)\.1/d' /etc/hosts
cat << EOF >> /etc/hosts
192.168.10.10 k8s-ctr
EOF
echo "[TASK 6] Delete default routing - enp0s9 NIC" # setenforce 0 설정 필요
nmcli connection modify enp0s9 ipv4.never-default yes
nmcli connection up enp0s9 >/dev/null 2>&1
echo "sudo su -" >> /home/vagrant/.bashrc
echo ">>>> Initial Config End <<<<"
사전 설정 수행 & git clone
# user 확인
whoami
pwd
# Linux Kernel Requirements : 5.8+ 이상 권장
uname -a
Linux k8s-ctr 6.12.0-55.39.1.el10_0.aarch64 #1 SMP PREEMPT_DYNAMIC Wed Oct 15 11:18:23 EDT 2025 aarch64 GNU/Linux
# Python : 3.10 ~ 3.12 : (참고) bento/rockylinux-9 경우 3.9
which python && python -V
which python3 && python3 -V
3.12.9
# pip , git 설치
dnf install -y python3-pip git
which pip && pip -V
which pip3 && pip3 -V
pip 23.3.2 from /usr/lib/python3.12/site-packages/pip (python 3.12)
# /etc/hosts 확인
ip -br -c -4 addr
cat /etc/hosts
ping -c 1 k8s-ctr
# SSH 접속을 위한 설정
# -----------------
echo "root:qwe123" | chpasswd
cat << EOF >> /etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
EOF
systemctl restart sshd
# Setting SSH Key
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa
ls -l ~/.ssh
# ssh-copy-id
ssh-copy-id -o StrictHostKeyChecking=no root@192.168.10.10
root@192.168.10.10's password: qwe123
# ssh 접속 확인 : IP, hostname
cat /root/.ssh/authorized_keys
ssh root@192.168.10.10 hostname
ssh -o StrictHostKeyChecking=no root@k8s-ctr hostname
ssh root@k8s-ctr hostname
# -----------------
# Clone Kubespray Repository
git clone -b v2.29.1 https://github.com/kubernetes-sigs/kubespray.git /root/kubespray
cd /root/kubespray
# (옵션) IDE에서 VM SSH 접속(root/qwe123)해서 편집 창 열기
# 최상단 plybook 확인 -> 각각 import_playbook 확인
ls -l *.yml
-rw-r--r--. 1 root root 88 Jan 24 20:55 cluster.yml # ansible.builtin.import_playbook: playbooks/cluster.yml
-rw-r--r--. 1 root root 30 Jan 24 20:55 _config.yml
-rw-r--r--. 1 root root 747 Jan 24 20:55 galaxy.yml
-rw-r--r--. 1 root root 105 Jan 24 20:55 recover-control-plane.yml
-rw-r--r--. 1 root root 85 Jan 24 20:55 remove-node.yml
-rw-r--r--. 1 root root 85 Jan 24 20:55 remove_node.yml
-rw-r--r--. 1 root root 85 Jan 24 20:55 reset.yml
-rw-r--r--. 1 root root 85 Jan 24 20:55 scale.yml # ansible.builtin.import_playbook: playbooks/scale.yml
-rw-r--r--. 1 root root 93 Jan 24 20:55 upgrade-cluster.yml
-rw-r--r--. 1 root root 93 Jan 24 20:55 upgrade_cluster.yml
#
tree -L 2
...
├── inventory
│ ├── local
│ └── sample
...
├── playbooks
│ ├── ansible_version.yml
│ ├── boilerplate.yml
│ ├── cluster.yml*
│ ├── facts.yml
│ ├── install_etcd.yml
│ ├── internal_facts.yml
│ ├── recover_control_plane.yml
│ ├── remove_node.yml
│ ├── reset.yml
│ ├── scale.yml
│ └── upgrade_cluster.yml
...
├── roles
│ ├── adduser
│ ├── bastion-ssh-config
│ ├── bootstrap-os
│ ├── bootstrap_os
│ ├── container-engine
│ ├── download
│ ├── dynamic_groups
│ ├── etcd
│ ├── etcdctl_etcdutl
│ ├── etcd_defaults
│ ├── helm-apps
│ ├── kubernetes
│ ├── kubernetes-apps
│ ├── kubespray-defaults
│ ├── kubespray_defaults
│ ├── network_facts
│ ├── network_plugin
│ ├── recover_control_plane
│ ├── remove-node
│ ├── remove_node
│ ├── reset
│ ├── system_packages
│ ├── upgrade
│ ├── validate_inventory
│ └── win_nodes
...
# Install Python Dependencies
cat requirements.txt
ansible==10.7.0
# Needed for community.crypto module
cryptography==46.0.3
# Needed for jinja2 json_query templating
jmespath==1.0.1
# Needed for ansible.utils.ipaddr
netaddr==1.3.0
pip3 install -r /root/kubespray/requirements.txt
Successfully installed MarkupSafe-3.0.3 ansible-10.7.0 ansible-core-2.17.14 cffi-2.0.0 cryptography-46.0.2 jinja2-3.1.6 jmespath-1.0.1 netaddr-1.3.0 pycparser-3.0 resolvelib-1.0.1
# ansible 버전 확인 : Ansible 2.17.3 이상
which ansible
ansible --version
ansible [core 2.17.14]
config file = /root/kubespray/ansible.cfg
...
python version = 3.12.9 (main, Aug 14 2025, 00:00:00) [GCC 14.2.1 20250110 (Red Hat 14.2.1-7)] (/usr/bin/python3)
jinja version = 3.1.6
libyaml = True
# pip list 확인
pip list
Package Version
------------------------- -----------
ansible 10.7.0
ansible-core 2.17.14
...
Jinja2 3.1.6
jmespath 1.0.1
...
netaddr 1.3.0
...
# 해당 폴더에서 ansible-playbook 실행 시 적용되는 ansible.cfg
cat ansible.cfg
[ssh_connection] # 통신 속도 및 안정성 최적화
pipelining=True # SSH 세션을 여러 번 열지 않고 하나의 세션에서 여러 명령을 한꺼번에 실행
ssh_args = -o ControlMaster=auto -o ControlPersist=30m -o ConnectionAttempts=100 -o UserKnownHostsFile=/dev/null
## ControlMaster=auto -o ControlPersist=30m: 한 번 연결된 SSH 커넥션을 30분 동안 유지합니다. 매번 로그인할 필요가 없어 성능이 향상됩니다.
## ConnectionAttempts=100: 네트워크 불안정으로 연결 실패 시 100번까지 재시도합니다.
## UserKnownHostsFile=/dev/null: 접속 대상의 지문(fingerprint)을 저장하지 않아 관리가 편해집니다.
#control_path = ~/.ssh/ansible-%%r@%%h:%%p
[defaults]
# https://github.com/ansible/ansible/issues/56930 (to ignore group names with - and .)
force_valid_group_names = ignore # Ansible은 원래 그룹 이름에 -나 . 사용을 제한하지만, 쿠버네티스 리소스 명칭 규칙상 이를 허용하도록 설정
host_key_checking=False # 새 서버 접속 시 "Are you sure you want to continue connecting?"이라는 확인 창이 뜨지 않게 합니다.
gathering = smart # 대상 서버의 정보(Fact)를 한 번만 수집하고 /tmp에 JSON 파일로 저장합니다. (아래 설명 이어서)
fact_caching = jsonfile # 재실행 시 서버 정보를 다시 수집하지 않아 시간이 단축됩니다. 86400(24시간) 동안 캐시를 유지합니다.
fact_caching_connection = /tmp
fact_caching_timeout = 86400
timeout = 300
stdout_callback = default
display_skipped_hosts = no
library = ./library
callbacks_enabled = profile_tasks # 각 Task가 실행되는 데 걸리는 시간을 표시해 줍니다. 어떤 단계에서 병목이 생기는지 확인할 때 매우 유용합니다.
roles_path = roles:$VIRTUAL_ENV/usr/local/share/kubespray/roles:$VIRTUAL_ENV/usr/local/share/ansible/roles:/usr/share/kubespray/roles
deprecation_warnings=False
inventory_ignore_extensions = ~, .orig, .bak, .ini, .cfg, .retry, .pyc, .pyo, .creds, .gpg # 백업용이나 임시 파일을 인벤토리로 인식하여 에러가 발생하는 것을 방지합니다.
[inventory]
ignore_patterns = artifacts, credentials # 배포 결과물(artifacts)이나 중요 정보(credentials) 폴더 내의 파일을 인벤토리 스캔 대상에서 제외합니다.
# (참고) Vagrantfile
cat Vagrantfile
Kubespary 를 통한 k8s 배포
# inventory 디렉터리 복사
cp -rfp /root/kubespray/inventory/sample /root/kubespray/inventory/mycluster
tree inventory/mycluster/
inventory/mycluster/
├── group_vars
│ ├── all
│ │ ├── all.yml
│ │ ├── aws.yml
│ │ ├── azure.yml
│ │ ├── containerd.yml
│ │ ├── coreos.yml
│ │ ├── cri-o.yml
│ │ ├── docker.yml
│ │ ├── etcd.yml
│ │ ├── gcp.yml
│ │ ├── hcloud.yml
│ │ ├── huaweicloud.yml
│ │ ├── oci.yml
│ │ ├── offline.yml
│ │ ├── openstack.yml
│ │ ├── upcloud.yml
│ │ └── vsphere.yml
│ └── k8s_cluster
│ ├── addons.yml
│ ├── k8s-cluster.yml
│ ├── k8s-net-calico.yml
│ ├── k8s-net-cilium.yml
│ ├── k8s-net-custom-cni.yml
│ ├── k8s-net-flannel.yml
│ ├── k8s-net-kube-ovn.yml
│ ├── k8s-net-kube-router.yml
│ ├── k8s-net-macvlan.yml
│ └── kube_control_plane.yml
└── inventory.ini
# inventory.ini 작성
cat << EOF > /root/kubespray/inventory/mycluster/inventory.ini
k8s-ctr ansible_host=192.168.10.10 ip=192.168.10.10
[kube_control_plane]
k8s-ctr
[etcd:children]
kube_control_plane
[kube_node]
k8s-ctr
EOF
cat /root/kubespray/inventory/mycluster/inventory.ini
# https://github.com/kubernetes-sigs/kubespray/blob/master/docs/ansible/vars.md
## <your-favorite-editor> inventory/mycluster/group_vars/all.yml # for every node, including etcd
grep "^[^#]" inventory/mycluster/group_vars/all/all.yml
---
bin_dir: /usr/local/bin
loadbalancer_apiserver_port: 6443
loadbalancer_apiserver_healthcheck_port: 8081
no_proxy_exclude_workers: false
kube_webhook_token_auth: false
kube_webhook_token_auth_url_skip_tls_verify: false
ntp_enabled: false
ntp_manage_config: false
ntp_servers:
- "0.pool.ntp.org iburst"
- "1.pool.ntp.org iburst"
- "2.pool.ntp.org iburst"
- "3.pool.ntp.org iburst"
unsafe_show_logs: false
allow_unsupported_distribution_setup: false
## <your-favorite-editor> inventory/mycluster/group_vars/k8s_cluster.yml # for every node in the cluster (not etcd when it's separate)
grep "^[^#]" inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
---
kube_config_dir: /etc/kubernetes
kube_script_dir: "{{ bin_dir }}/kubernetes-scripts"
kube_manifest_dir: "{{ kube_config_dir }}/manifests"
kube_cert_dir: "{{ kube_config_dir }}/ssl"
kube_token_dir: "{{ kube_config_dir }}/tokens"
kube_api_anonymous_auth: true
local_release_dir: "/tmp/releases"
retry_stagger: 5
kube_owner: kube
kube_cert_group: kube-cert
kube_log_level: 2
credentials_dir: "{{ inventory_dir }}/credentials"
kube_network_plugin: calico
kube_network_plugin_multus: false
kube_service_addresses: 10.233.0.0/18
kube_pods_subnet: 10.233.64.0/18
kube_network_node_prefix: 24
kube_service_addresses_ipv6: fd85:ee78:d8a6:8607::1000/116
kube_pods_subnet_ipv6: fd85:ee78:d8a6:8607::1:0000/112
kube_network_node_prefix_ipv6: 120
kube_apiserver_ip: "{{ kube_service_subnets.split(',') | first | ansible.utils.ipaddr('net') | ansible.utils.ipaddr(1) | ansible.utils.ipaddr('address') }}"
kube_apiserver_port: 6443 # (https)
kube_proxy_mode: ipvs
kube_proxy_strict_arp: false
kube_proxy_nodeport_addresses: >-
{%- if kube_proxy_nodeport_addresses_cidr is defined -%}
[{{ kube_proxy_nodeport_addresses_cidr }}]
{%- else -%}
[]
{%- endif -%}
kube_encrypt_secret_data: false
cluster_name: cluster.local
ndots: 2
dns_mode: coredns
enable_nodelocaldns: true
enable_nodelocaldns_secondary: false
nodelocaldns_ip: 169.254.25.10
nodelocaldns_health_port: 9254
nodelocaldns_second_health_port: 9256
nodelocaldns_bind_metrics_host_ip: false
nodelocaldns_secondary_skew_seconds: 5
enable_coredns_k8s_external: false
coredns_k8s_external_zone: k8s_external.local
enable_coredns_k8s_endpoint_pod_names: false
resolvconf_mode: host_resolvconf
deploy_netchecker: false
skydns_server: "{{ kube_service_subnets.split(',') | first | ansible.utils.ipaddr('net') | ansible.utils.ipaddr(3) | ansible.utils.ipaddr('address') }}"
skydns_server_secondary: "{{ kube_service_subnets.split(',') | first | ansible.utils.ipaddr('net') | ansible.utils.ipaddr(4) | ansible.utils.ipaddr('address') }}"
dns_domain: "{{ cluster_name }}"
container_manager: containerd
kata_containers_enabled: false
kubeadm_certificate_key: "{{ lookup('password', credentials_dir + '/kubeadm_certificate_key.creds length=64 chars=hexdigits') | lower }}"
k8s_image_pull_policy: IfNotPresent
kubernetes_audit: false
default_kubelet_config_dir: "{{ kube_config_dir }}/dynamic_kubelet_dir"
volume_cross_zone_attachment: false
persistent_volumes_enabled: false
event_ttl_duration: "1h0m0s"
auto_renew_certificates: false
# auto_renew_certificates_systemd_calendar: "Mon *-*-1,2,3,4,5,6,7 03:00:00" # First Monday of each month
kubeadm_patches_dir: "{{ kube_config_dir }}/patches"
kubeadm_patches: []
remove_anonymous_access: false
# 테스트할 기능 관련 수정
sed -i 's|kube_network_plugin: calico|kube_network_plugin: flannel|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|kube_proxy_mode: ipvs|kube_proxy_mode: iptables|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|enable_nodelocaldns: true|enable_nodelocaldns: false|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|auto_renew_certificates: false|auto_renew_certificates: true|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
sed -i 's|# auto_renew_certificates_systemd_calendar|auto_renew_certificates_systemd_calendar|g' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
grep -iE 'kube_network_plugin:|kube_proxy_mode|enable_nodelocaldns:|^auto_renew_certificates' inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml
## flannel 설정 수정 inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml
cat inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml
echo "flannel_interface: enp0s9" >> inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml
grep "^[^#]" inventory/mycluster/group_vars/k8s_cluster/k8s-net-flannel.yml
## <your-favorite-editor> inventory/mycluster/group_vars/kube_control_plane.yml # for the control plane
cat inventory/mycluster/group_vars/k8s_cluster/kube_control_plane.yml
# Reservation for control plane kubernetes components
# kube_memory_reserved: 512Mi
# kube_cpu_reserved: 200m
# kube_ephemeral_storage_reserved: 2Gi
# kube_pid_reserved: "1000"
# Reservation for control plane host system
# system_memory_reserved: 256Mi
# system_cpu_reserved: 250m
# system_ephemeral_storage_reserved: 2Gi
# system_pid_reserved: "1000"
## <your-favorite-editor> addons.yml
grep "^[^#]" inventory/mycluster/group_vars/k8s_cluster/addons.yml
---
helm_enabled: false
registry_enabled: false
metrics_server_enabled: false
local_path_provisioner_enabled: false
local_volume_provisioner_enabled: false
gateway_api_enabled: false
ingress_nginx_enabled: false
ingress_publish_status_address: ""
ingress_alb_enabled: false
cert_manager_enabled: false
metallb_enabled: false
metallb_speaker_enabled: "{{ metallb_enabled }}"
metallb_namespace: "metallb-system"
argocd_enabled: false
kube_vip_enabled: false
node_feature_discovery_enabled: false
# 테스트할 기능 관련 수정
sed -i 's|helm_enabled: false|helm_enabled: true|g' inventory/mycluster/group_vars/k8s_cluster/addons.yml
sed -i 's|metrics_server_enabled: false|metrics_server_enabled: true|g' inventory/mycluster/group_vars/k8s_cluster/addons.yml
sed -i 's|node_feature_discovery_enabled: false|node_feature_discovery_enabled: true|g' inventory/mycluster/group_vars/k8s_cluster/addons.yml
grep -iE 'helm_enabled:|metrics_server_enabled:|node_feature_discovery_enabled:' inventory/mycluster/group_vars/k8s_cluster/addons.yml
# etcd.yml : 파드가 아닌 systemd unit
grep "^[^#]" inventory/mycluster/group_vars/all/etcd.yml
---
etcd_data_dir: /var/lib/etcd
etcd_deployment_type: host
# containerd.yml
cat inventory/mycluster/group_vars/all/containerd.yml
---
# Please see roles/container-engine/containerd/defaults/main.yml for more configuration options
# containerd_storage_dir: "/var/lib/containerd"
# containerd_state_dir: "/run/containerd"
# containerd_oom_score: 0
# containerd_default_runtime: "runc"
# containerd_snapshotter: "native"
# containerd_runc_runtime:
# name: runc
# type: "io.containerd.runc.v2"
# engine: ""
...(생략)...
# 기본 환경 정보 출력 저장
ip addr | tee -a ip_addr-1.txt
ss -tnlp | tee -a ss-1.txt
df -hT | tee -a df-1.txt
findmnt | tee -a findmnt-1.txt
sysctl -a | tee -a sysctl-1.txt
# 지원 버전 정보 확인
cat roles/kubespray_defaults/vars/main/checksums.yml | grep -i kube -A40
# 배포: 아래처럼 반드시 ~/kubespray 디렉토리에서 ansible-playbook 를 실행하자!
# Deploy Kubespray with Ansible Playbook - run the playbook as root
# The option `--become` is required, as for example writing SSL keys in /etc/,
# installing packages and interacting with various systemd daemons.
ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml -e kube_version="1.33.3" --list-tasks # 배포 전, Task 목록 확인
ANSIBLE_FORCE_COLOR=true ansible-playbook -i inventory/mycluster/inventory.ini -v cluster.yml -e kube_version="1.33.3" | tee kubespray_install.log
# 설치 확인 : /root/.kube/config
more kubespray_install.log
kubectl get node -v=6
cat /root/.kube/config
# k8s
kubectl get node -owide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-ctr Ready control-plane 113s v1.33.3 192.168.10.10 <none> Rocky Linux 10.0 (Red Quartz) 6.12.0-55.39.1.el10_0.aarch64 containerd://2.1.5
kubectl get pod -A
...
# 기본 환경 정보 출력 저장
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
# 파일 출력 비교 : 빠져나오기 ':q' -> ':q' => 변경된 부분이 어떤 동작과 역할인지 조사해보기! , ctrl + f / b
vi -d ip_addr-1.txt ip_addr-2.txt
vi -d ss-1.txt ss-2.txt
vi -d df-1.txt df-2.txt
vi -d findmnt-1.txt findmnt-2.txt
vi -d sysctl-1.txt sysctl-2.txt
k8s 설치 전후 서버 환경 비교
https://github.com/solidcellaMoon/opkhs-2026-k8s-deploy/tree/main/4-kubespray/diff-about-k8s
alias, 자동 완성 및 k9s 설치
# Source the completion
source <(kubectl completion bash)
source <(kubeadm completion bash)
# Alias kubectl to k
alias k=kubectl
complete -o default -F __start_kubectl k
# 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
Ansible Playbook & Role 분석
Ansible Play → Task 확인

PLAY 과정
#
cat kubespray_install.log | grep -E 'PLAY'
PLAY [Check Ansible version] ****************************** # Kubespray가 지원하는 Ansible 버전인지 확인
PLAY [Inventory setup and validation] ********************* # inventory 설정 정합성 검증 : kube_control_plane, etcd 그룹 존재 여부, etcd 노드 수 (odd 권장), Pod CIDR / Service CIDR 유효성, Kubernetes 버전 지원 여부
PLAY [Install bastion ssh config] ************************* # Bastion(점프 호스트) 환경 지원 : bastion 미사용 시 대부분 skip
PLAY [Bootstrap hosts for Ansible] ************************ # 모든 노드를 Ansible 실행 가능한 상태로 만듦 : Python 설치, sudo 권한 확보, 기본 패키지 설치, /usr/bin/python 보장
PLAY [Gather facts] *************************************** # Ansible fact 수집 : 이후 TASK들이: when: ansible_os_family == "Debian" 같은 조건 분기에서 사용됨
PLAY [Prepare for etcd install] *************************** # etcd 설치 전 사전 준비 : etcd user 생성, 디렉터리 생성, 방화벽 / 포트, cert 경로 준비
PLAY [Add worker nodes to the etcd play if needed] ******** # worker + etcd 겸용 노드 지원 : kube_node: + etcd: 둘 다 포함된 노드를 etcd PLAY에 추가
PLAY [Install etcd] *************************************** # etcd 설치 : etcd binary 설치, TLS 인증서 생성, systemd 등록, 클러스터 구성
PLAY [Install Kubernetes nodes] *************************** # 모든 노드에 공통 K8s 컴포넌트 설치, 아직 클러스터 join은 안 함
PLAY [Install the control plane] ************************** # kube_control_plane 그룹 : control-plane 노드 구성
PLAY [Invoke kubeadm and install a CNI] ******************* # kubeadm init / join 실행 , 네트워크(CNI) 설치
PLAY [Install Calico Route Reflector] ********************* # Calico BGP 미사용환경이면 skip
PLAY [Patch Kubernetes for Windows] *********************** # Linux-only 환경이면 skip
PLAY [Install Kubernetes apps] **************************** # 기본 애드온 설치 : CoreDNS, metrics-server 등
PLAY [Apply resolv.conf changes now that cluster DNS is up] # CoreDNS 설치 후 노드의 DNS 설정 최종 정리 : bootstrap 단계에선 임시 resolv.conf 사용, 클러스터 DNS 안정화 후 되돌림
PLAY RECAP ************************************************
TASK
# 세부 task 559개
cat kubespray_install.log | grep -E 'TASK' | wc -l
559
cat kubespray_install.log | grep -E 'TASK'
TASK [Check 2.17.3 <= Ansible version < 2.18.0] ********************************
TASK [dynamic_groups : Match needed groups by their old names or definition] ***
TASK [validate_inventory : Stop if removed tags are used] **********************
TASK [bootstrap_os : Fetch /etc/os-release] ************************************
TASK [system_packages : Gather OS information] *********************************
TASK [bootstrap_os : Create remote_tmp for it is used by another module] *******
TASK [network_facts : Gather ansible_default_ipv4] *****************************
TASK [Gather minimal facts] ****************************************************
TASK [adduser : User | Create User Group] **************************************
TASK [kubernetes/preinstall : Check if /etc/fstab exists] **********************
TASK [container-engine/validate-container-engine : Validate-container-engine | check if fedora coreos] ***
TASK [download : Prep_download | Set a few facts] ******************************
TASK [Gathering Facts] *********************************************************
TASK [Check if nodes needs etcd client certs (depends on network_plugin)] ******
TASK [adduser : User | Create User Group] **************************************
TASK [etcd : Check etcd certs] *************************************************
TASK [etcdctl_etcdutl : Download etcd binary] **********************************
TASK [etcd : Install etcd] *****************************************************
TASK [kubernetes/node : Set kubelet_cgroup_driver_detected fact for containerd] ***
TASK [kubernetes/control-plane : Pre-upgrade | Delete control plane manifests if etcd secrets changed] ***
TASK [kubernetes/client : Set external kube-apiserver endpoint] ****************
TASK [kubernetes-apps/cluster_roles : Kubernetes Apps | Wait for kube-apiserver] ***
TASK [kubernetes/kubeadm : Set kubeadm_discovery_address] **********************
TASK [kubernetes/node-label : Set role node label to empty list] ***************
TASK [network_plugin/cni : CNI | make sure /opt/cni/bin exists] ****************
TASK [network_plugin/flannel : Flannel | Create Flannel manifests] *************
TASK [win_nodes/kubernetes_patch : Ensure that user manifests directory exists] ***
TASK [kubernetes-apps/ansible : Kubernetes Apps | Wait for kube-apiserver] *****
TASK [kubernetes-apps/helm : Helm | Gather os specific variables] **************
TASK [kubernetes-apps/metrics_server : Metrics Server | Delete addon dir] ******
TASK [adduser : User | Create User Group] **************************************
TASK [kubernetes/preinstall : Check resolvconf] ********************************
playbooks/cluster.yml 설치 절차
- /root/kubespray/cluster.yml → playbooks/cluster.yml
#
cat /root/kubespray/cluster.yml
---
- name: Install Kubernetes ansible.builtin.import_playbook: playbooks/cluster.yml
- cluster.yml은 Kubespray의 메인 플레이북으로, 클러스터 생성의 전체 과정을 정의



https://velog.io/@bytebliss/kubespray-cluster.yaml

https://sirzzang.github.io/kubernetes/Kubernetes-Kubespray-04-01-00/
playbooks/cluster.yml: 전체 과정
A. 초기화 및 정보 수집 (Boilerplate & Facts)
- 모든 노드에 공통적으로 필요한 설정을 적용하고, 각 서버의 사양 정보를 수집하여 이후 설치 단계에서 변수로 활용합니다.
B. 인프라 및 엔진 준비 (Prepare for etcd & container-engine)
- kubernetes/preinstall: 방화벽, 커널 파라미터, Swap 비활성화 등 K8s 설치를 위한 OS 최적화를 수행합니다.
- container-engine: Docker나 Containerd 같은 컨테이너 런타임을 설치합니다.
- download: 설치에 필요한 모든 바이너리와 이미지들을 미리 다운로드합니다.
C. 데이터 저장소 및 노드 구성 (Etcd & K8s Nodes)
- etcd: 쿠버네티스의 상태 정보를 저장하는 DB 클러스터를 구축합니다.
- kubernetes/node: 모든 노드에 Kubelet, Kube-proxy 등 기초 컴포넌트를 설치합니다.
D. 컨트롤 플레인 및 네트워크 (Control Plane & CNI)
- control-plane: 마스터 노드에 API 서버, 스케줄러 등을 설정합니다.
- kubeadm: kubeadm init 또는 join을 통해 클러스터를 하나로 묶습니다.
- network_plugin: Calico, Flannel 등 CNI를 설치하여 파드 간 통신을 가능하게 합니다.
E. 부가 서비스 설치 (Apps & DNS)
- Ingress Controller, Storage Provisioner 등 클러스터 운영에 필요한 앱들을 배포하고, 최종적으로 노드의 DNS(resolv.conf)가 클러스터 내부 DNS를 바라보도록 수정합니다.
파일 내용 확인
cat playbooks/cluster.yml
---
- name: Common tasks for every playbooks
import_playbook: boilerplate.yml
- name: Gather facts
import_playbook: internal_facts.yml
- name: Prepare for etcd install
hosts: k8s_cluster:etcd # 이 작업은 쿠버네티스 클러스터에 속한 모든 노드(k8s_cluster)와 etcd 전용 노드(etcd) 전체를 대상으로 실행.
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}" # 하나라도 에러가 발생하면 전체 배포 프로세스를 즉시 중단합니다.
environment: "{{ proxy_disable_env }}" # 인터넷 통신 시 프록시 설정을 제어합니다. 주로 내부 통신 시 프록시를 우회하도록 설정할 때 사용됩니다.
roles:
- { role: kubespray_defaults } # Kubespray 전체에서 사용하는 공통 변수(경로, 버전 등)를 로드합니다.
- { role: kubernetes/preinstall, tags: preinstall } # OS 레벨의 사전 설정을 수행합니다. 예) 커널 파라미터(sysctl) 최적화 등
- { role: "container-engine", tags: "container-engine", when: deploy_container_engine } # 컨테이너 런타임(containerd, Docker 등)을 설치합니다.
- { role: download, tags: download, when: "not skip_downloads" } # 설치에 필요한 모든 바이너리와 컨테이너 이미지를 다운로드합니다.
- name: Install etcd
vars:
etcd_cluster_setup: true # etcd를 “클러스터 모드”로 초기화. 단일 노드라도 내부 로직은 클러스터 기준으로 동작. 신규 클러스터 생성 시 반드시 true
etcd_events_cluster_setup: "{{ etcd_events_cluster_enabled }}" # true 일 경우 events 전용 etcd 클러스터를 별도로 구성, 대부분 환경에서는 false
import_playbook: install_etcd.yml # Playbook을 import 하는 구조, 즉, 여기에는 hosts:가 없고 install_etcd.yml 안에 실제 Play들이 있음.
- name: Install Kubernetes nodes # 이 노드를 "kubelet이 돌아가는 Kubernetes 노드"로 만든다.
hosts: k8s_cluster # 모든 k8s 노드 : control-plane , worker
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults }
- { role: kubernetes/node, tags: node } # 예) kubelet 설치 등
- name: Install the control plane
hosts: kube_control_plane # control-plane 노드만
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults }
- { role: kubernetes/control-plane, tags: control-plane } # 설치 : apiserver, kcm, scheduler
- { role: kubernetes/client, tags: client } # kubectl 설치, admin kubeconfig 생성 준비
- { role: kubernetes-apps/cluster_roles, tags: cluster-roles } # Kubernetes 기본 ClusterRole 생성, bootstrap / node / admin 권한 관련
- name: Invoke kubeadm and install a CNI
hosts: k8s_cluster
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults }
- { role: kubernetes/kubeadm, tags: kubeadm} # kubeadm init* / join* 실행
- { role: kubernetes/node-label, tags: node-label } # inventory 기반으로 node label 설정
- { role: kubernetes/node-taint, tags: node-taint } # control-plane taint 설정
- { role: kubernetes-apps/common_crds } # CNI / ingress / storage에서 사용하는 공통 CRD 먼저 설치
- { role: network_plugin, tags: network } # 실제 CNI 플러그인 설치 단계* : Calico / Cilium / Flannel / Weave 등
- name: Install Calico Route Reflector
hosts: calico_rr
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults }
- { role: network_plugin/calico/rr, tags: ['network', 'calico_rr'] }
- name: Patch Kubernetes for Windows
hosts: kube_control_plane[0]
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults }
- { role: win_nodes/kubernetes_patch, tags: ["control-plane", "win_nodes"] }
- name: Install Kubernetes apps
hosts: kube_control_plane # control-plane 노드만 : 실제로는 클러스터 전체에 리소스를 배포하지만, 명령 실행 위치만 control-plane
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults } # 공통 변수 로딩, apps 관련 enable/disable 값 정리
- { role: kubernetes-apps/external_cloud_controller, tags: external-cloud-controller } # cloud-controller-manager 외부 실행 버전, 베어메탈 / lab 환경이면 보통 비활성
- { role: kubernetes-apps/policy_controller, tags: policy-controller } # Kubernetes Policy 계열 컨트롤러, 예) PodSecurity Admission, OPA / Gatekeeper 연계
- { role: kubernetes-apps/ingress_controller, tags: ingress-controller } # Ingress Controller 설치, 예) NGINX Ingress, HAProxy Ingress, Traefik
- { role: kubernetes-apps/external_provisioner, tags: external-provisioner } # 외부 스토리지 프로비저너, 예) CSI provisioner, NFS external provisioner, Ceph RBD / CephFS
- { role: kubernetes-apps, tags: apps } # CoreDNS (이미 설치됐지만 재확인), Metrics Server, Local Path Provisioner, Helm add-ons
- name: Apply resolv.conf changes now that cluster DNS is up
hosts: k8s_cluster # 모든 노드
gather_facts: false
any_errors_fatal: "{{ any_errors_fatal | default(true) }}"
environment: "{{ proxy_disable_env }}"
roles:
- { role: kubespray_defaults }
- { role: kubernetes/preinstall, when: "dns_mode != 'none' and resolvconf_mode == 'host_resolvconf'", tags: resolvconf, dns_late: true }
'Kubernetes' 카테고리의 다른 글
| [6주차] Kubespray offline 설치 (0) | 2026.02.15 |
|---|---|
| [5주차] Kubespary HA & Upgrade (0) | 2026.02.06 |
| [3주차] Kubeadm & K8S Upgrade (0) | 2026.01.20 |
| [2주차] Ansible 기초 (0) | 2026.01.12 |
| [1주차] Bootstrap Kubernetes the hard way (0) | 2026.01.08 |