티스토리 뷰

Kubernetes

[4주차] Kubespary 배포 분석

넷오빠 2026. 2. 6. 10:55

 

이 포스팅은 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-Ansible-Playbook-Role-분석

 

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
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함