이 글에서는 Vagrant와 Ansible을 활용해 Windows, macOS, Linux 환경 어디서든
명령어 한 줄로 Kubernetes 클러스터를 자동으로 설치하는 과정을 정리합니다.
아래는 프로젝트 디렉터리 구조이며, 각각의 역할과 실행 흐름을 단계별로 설명합니다.
📁 프로젝트 구조
ansible/
├── roles/
│ ├── common/
│ │ ├── handlers/main.yml
│ │ └── tasks/main.yml
│ ├── master/tasks/main.yml
│ └── worker/tasks/main.yml
├── inventory.ini
├── local_inventory_master.ini
├── local_inventory_worker.ini
└── playbook.yml
README.md
Vagrantfile
각 디렉터리의 역할은 다음과 같습니다:
- Vagrantfile: VM 환경 정의 및 OS별 프로비저닝 분기 처리
- ansible/playbook.yml: 전체 플레이북(공통, 마스터, 워커 역할 실행 순서)
- roles/: 역할 기반 구조로 Ansible 실행 로직 분리
⚙️ Vagrantfile - Cross-platform 환경 구성
Windows에서는 ansible_local을, macOS/Linux에서는 ansible을 자동으로 선택해 프로비저닝하는 코드입니다.
# ---------------------------------------------------------
# Vagrantfile - Cross-platform (Windows/Linux/macOS 지원)
# ---------------------------------------------------------
VAGRANT_BOX = "ubuntu/jammy64"
NAT_NETWORK = "vagrant-net"
MASTER_IP = "192.168.56.10"
WORKER1_IP = "192.168.56.11"
WORKER2_IP = "192.168.56.12"
HOST_OS = case RUBY_PLATFORM
when /win32|mingw|cygwin/ then :windows
when /darwin/ then :mac
else :linux
end
PROVISIONER = HOST_OS == :windows ? "ansible_local" : "ansible"
Vagrant.configure("2") do |config|
config.vm.box_check_update = false
config.vm.provider "virtualbox" do |vb|
vb.memory = 2048
vb.cpus = 2
end
def setup_ansible(ansible, limit = nil, inventory_file = nil)
ansible.playbook = "ansible/playbook.yml"
if RUBY_PLATFORM =~ /win32|mingw|cygwin/
ansible.inventory_path = inventory_file
else
ansible.inventory_path = "ansible/inventory.ini"
ansible.limit = limit unless limit.nil?
end
end
# Master Node
config.vm.define "k8s-master" do |node|
node.vm.box = VAGRANT_BOX
node.vm.hostname = "k8s-master"
node.vm.network "private_network", ip: MASTER_IP, virtualbox__intnet: NAT_NETWORK
node.vm.provider "virtualbox" do |vb|
vb.memory = 2048
vb.cpus = 2
end
node.vm.provision PROVISIONER do |ansible|
if HOST_OS == :windows
setup_ansible(ansible, nil, "/vagrant/ansible/local_inventory_master.ini")
else
setup_ansible(ansible, "k8s-master")
end
end
end
# Worker Nodes
[ ["k8s-worker1", WORKER1_IP], ["k8s-worker2", WORKER2_IP] ].each do |name, ip|
config.vm.define name do |node|
node.vm.box = VAGRANT_BOX
node.vm.hostname = name
node.vm.network "private_network", ip: ip, virtualbox__intnet: NAT_NETWORK
node.vm.provider "virtualbox" do |vb|
vb.memory = 1024
vb.cpus = 1
end
node.vm.provision PROVISIONER do |ansible|
if HOST_OS == :windows
setup_ansible(ansible, nil, "/vagrant/ansible/local_inventory_worker.ini")
else
setup_ansible(ansible, name)
end
end
end
end
end
이 Vagrantfile은 OS 자동 감지, 노드별 자원 할당, 프로비저닝 로직 분리까지 포함된 완성형 구조입니다.
📜 Ansible 플레이북 (playbook.yml)
- hosts: all
become: yes
roles:
- common
- hosts: master
become: yes
roles:
- master
- hosts: workers
become: yes
roles:
- worker
세 그룹(all, master, workers)에 대해 공통 → 마스터 → 워커 순서로 실행됩니다.
🗂️ 인벤토리 파일 (Windows vs Linux)
Windows (ansible_local)
[workers]
k8s-worker1 ansible_connection=local
k8s-worker2 ansible_connection=local
[master]
k8s-master ansible_connection=local master_ip=192.168.56.10
Linux/macOS (ansible)
[master]
192.168.56.10
[workers]
192.168.56.11
192.168.56.12
[all:vars]
ansible_user=vagrant
ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key
ansible_python_interpreter=/usr/bin/python3
🧱 common 역할 (기본 환경 구성)
- name: 시스템 업데이트
apt:
update_cache: yes
upgrade: yes
- name: 스왑 비활성화
shell: |
swapoff -a
sed -i '/\bswap\b/ s/^/#/' /etc/fstab
- name: 커널 모듈 설정
shell: |
modprobe overlay
modprobe br_netfilter
cat </dev/null
overlay
br_netfilter
EOF
cat </dev/null
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
sysctl --system
- name: containerd 설치
apt:
name: containerd
state: present
- name: containerd 기본 설정
shell: |
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml >/dev/null
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable containerd
systemctl restart containerd
- name: Kubernetes 패키지 저장소 추가
shell: |
mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
| gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" \
| tee /etc/apt/sources.list.d/kubernetes.list
apt-get update -y
args:
executable: /bin/bash
- name: kubeadm, kubelet, kubectl 설치
apt:
name:
- kubeadm
- kubelet
- kubectl
state: present
notify: enable services
- name: kubeadm, kubelet, kubectl 버전 고정
command: apt-mark hold kubeadm kubelet kubectl
- name: UFW 방화벽 허용
shell: |
ufw allow 6443
ufw allow 2379
ufw allow 2380
ufw allow 10250
ufw allow 10251
ufw allow 10252
ufw allow 8285
ufw allow 8472
ufw --force enable
ignore_errors: true
# roles/common/handlers/main.yml
- name: enable services
systemd:
name: "{{ item }}"
enabled: yes
state: started
loop:
- kubelet
- containerd
🧠 master 역할 (클러스터 초기화)
- name: kubeadm init
shell: |
kubeadm init \
--apiserver-advertise-address={{ master_ip | default('192.168.56.10') }} \
--pod-network-cidr=10.244.0.0/16 \
--cri-socket=unix:///run/containerd/containerd.sock \
--ignore-preflight-errors=NumCPU,Mem
args:
creates: /etc/kubernetes/admin.conf
- name: kubeconfig 설정
shell: |
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
- name: Flannel CNI 설치
shell: kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
environment:
KUBECONFIG: /etc/kubernetes/admin.conf
- name: join 커맨드 생성
shell: kubeadm token create --print-join-command
register: join_cmd
- name: join 커맨드 저장 (Windows용)
when: ansible_connection == "local"
copy:
content: |
sudo {{ join_cmd.stdout | regex_replace('10\\.0\\.2\\.15', master_ip | default('192.168.56.10')) }} \
--cri-socket=unix:///run/containerd/containerd.sock --ignore-preflight-errors=NumCPU,Mem
dest: /vagrant/ansible/join-command.sh
mode: '0755'
- name: join 커맨드 저장 (Linux/mac용)
when: ansible_connection != "local"
local_action:
module: copy
content: |
sudo {{ join_cmd.stdout | regex_replace('10\\.0\\.2\\.15', master_ip | default('192.168.56.10')) }} \
--cri-socket=unix:///run/containerd/containerd.sock --ignore-preflight-errors=NumCPU,Mem
dest: "./ansible/join-command.sh"
delegate_to: localhost
run_once: true
🧩 worker 역할 (노드 조인)
- name: join 커맨드 파일 읽기 (Windows)
when: ansible_connection == "local"
slurp:
src: "/vagrant/ansible/join-command.sh"
register: join_data_win
- name: join 커맨드 파일 읽기 (Linux/mac)
when: ansible_connection != "local"
slurp:
src: "./ansible/join-command.sh"
register: join_data_linux
delegate_to: localhost
- name: join_data 병합
set_fact:
join_data: "{{ join_data_win if join_data_win is defined else join_data_linux }}"
- name: 워커 노드 클러스터 조인
become: yes
shell: "{{ join_data.content | b64decode }}"
args:
creates: /etc/kubernetes/kubelet.conf
- name: kubelet 상태 확인
become: yes
systemd:
name: kubelet
enabled: yes
state: started
🛠️ 커스터마이징 팁
사용자의 환경 및 구축 목적에 따라 아래 항목을 수정하면 유용합니다:
- IP 주소: Vagrantfile 내
MASTER_IP / WORKER_IP값을 환경에 맞게 변경 - VM 개수: Worker 노드를 늘리고 싶다면 Vagrantfile의
[ ["k8s-worker1", WORKER1_IP], ... ]배열에 노드 추가 - 리소스 조정: 각 노드의
vb.memory/vb.cpus값 변경 - 네트워크 이름: VirtualBox NAT 네트워크 이름이 다를 경우
NAT_NETWORK변수 수정 - CNI 변경: Flannel 대신 Calico를 사용하려면 CNI 설치 명령을 변경
🧹 클러스터 초기화 / 삭제
VM을 중단하거나 클러스터를 재구축할 때는 아래 명령을 사용합니다.
# 모든 VM 정지
vagrant halt
# 모든 VM 완전 삭제
vagrant destroy -f
# SSH 접속
vagrant ssh k8s-master
vagrant halt는 일시정지, vagrant destroy -f는 VM 완전 삭제를 의미합니다.
새로 클러스터를 구축하려면 vagrant up 명령을 다시 실행하면 됩니다.
🎯 마무리
이 구성을 통해 개발 환경에서 손쉽게 Kubernetes 클러스터를 자동 생성할 수 있습니다.
모든 VM은 Vagrant가 자동으로 SSH 키를 설정하므로 추가 인증 없이 접근 가능합니다.
https://github.com/YangjiwooGN/K8S-Cluster-Vagrant-Ansible
GitHub - YangjiwooGN/K8S-Cluster-Vagrant-Ansible: Vagrant + Ansible 기반 Kubernetes Cluster 자동 구축 환경 (Virtual Box
Vagrant + Ansible 기반 Kubernetes Cluster 자동 구축 환경 (Virtual Box 환경) - YangjiwooGN/K8S-Cluster-Vagrant-Ansible
github.com
'공부 기록 > 클라우드 공부' 카테고리의 다른 글
| [OpenStack] 2. OpenStack 시작하기 – DevStack 실습 환경 구축 ① (0) | 2026.07.05 |
|---|---|
| [OpenStack] 1. OpenStack이란? (0) | 2026.07.02 |