목적
Azure VM 위에 kubeadm 기반 바닐라 Kubernetes 클러스터를 Terraform + Cloud-init 자동화로 설치/운영하기 위한 표준 절차 정리
구축 요약
- 컨트롤 플레인(마스터): vm-dev-mvpu-k8smaster-01 (10.25.0.15)
- 워커: vm-dev-mvpu-k8sworker-01 (10.25.0.5), vm-dev-mvpu-k8sworker-02 (10.25.0.6)
- 상태: 모든 노드 Ready
- Kubernetes: v1.30.14 (패키지 버전 고정)
- Container Runtime: containerd (SystemdCgroup=true)
- CNI: Calico(기본, 192.168.0.0/16) / Cilium(옵션, kube-proxy 제거) / Kubenet(학습용)
특징
- 관리형 서비스(AKS) 없이 순수 kubeadm
- 재현성: Terraform + Cloud-init 기반 템플릿화
- 보안/일관성: 패키지 버전 고정, 자동 업데이트 방지
- 확장성: 워커 count 조정으로 수평 확장
최종 확인
kubectl get nodes
NAME STATUS ROLES VERSION
vm-dev-mvpu-k8smaster-01 Ready control-plane v1.30.14
vm-dev-mvpu-k8sworker-01 Ready <none> v1.30.14
vm-dev-mvpu-k8sworker-02 Ready <none> v1.30.14
실행 매뉴얼
사전 준비
- 권한: Azure 구독 리소스 생성 권한, 대상 RG/VNet/Subnet 확보
- 로컬 도구: Terraform, Azure CLI, OpenSSH
- 네트워크/보안: NSG/방화벽에서 22(SSH), 6443(K8s API) 허용(사내 정책 준수)
- 운영 표준:
- SSH 키 기반 접속(권장), VM의 disable_password_authentication = true
- 패키지 버전 핀(FIX): kubeadm/kubelet/kubectl 1.30.14-00
- CNI는 Calico 또는 Cilium 중 조직 표준 선택
Terraform 배포
terraform init
terraform plan -out tf.plan
terraform apply tf.plan
VM 생성 시 Cloud-init가 자동 실행되어 마스터/워커 초기 설정이 진행됩니다.
k8s-master 작업 (Cloud-init)
자동 작업 요약
- 시스템 업데이트, 기본 패키지 설치
- swapoff, 커널 모듈/sysctl 적용
- containerd 설치 + SystemdCgroup=true
- Kubernetes repo/키링 추가
- kubeadm/kubelet/kubectl 버전 고정 설치
- kubeadm init (예: --pod-network-cidr=192.168.0.0/16 --service-cidr=10.96.0.0/12 --apiserver-advertise-address=<MASTER_IP>)
- CNI 설치(Calico or Cilium)
- kubectl 컨텍스트(root/일반 사용자)
- 워커 조인 명령어 저장(~/kubeadm-join-command.sh)
#!/bin/bash
# Kubernetes 마스터노드(kubeadm) 초기화 스크립트
# VM 생성 시 자동으로 실행됩니다
set -e
# 로그 파일 설정
exec > >(tee /var/log/k8s-master-init.log) 2>&1
echo "=== Kubernetes 마스터노드 초기화 시작 ==="
echo "시작 시간: $(date)"
# 시스템 업데이트
echo "시스템 패키지 업데이트 중..."
apt-get update
apt-get upgrade -y
# 필요한 패키지 설치
echo "필요한 패키지 설치 중..."
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
# 스왑 비활성화 (쿠버네티스 요구사항)
echo "스왑 비활성화 중..."
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 시스템 설정 최적화
echo "시스템 설정 최적화 중..."
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat <<EOF | tee /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
# containerd 설치
echo "containerd 설치 중..."
apt-get install -y containerd
# containerd 설정
echo "containerd 설정 중..."
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd
systemctl enable containerd
# Kubernetes repository 추가
echo "Kubernetes repository 추가 중..."
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 [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
# Kubernetes 패키지 설치
echo "Kubernetes 패키지 설치 중..."
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
# kubelet 시작 및 활성화
systemctl enable kubelet
# kubeadm으로 클러스터 초기화
echo "kubeadm으로 클러스터 초기화 중..."
kubeadm init --pod-network-cidr=192.168.0.0/16 --apiserver-advertise-address=$(hostname -I | awk '{print $1}')
# kubectl 설정 (root 사용자용)
echo "kubectl 설정 중 (root)..."
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# kubectl 설정 (일반 사용자용)
echo "kubectl 설정 중 (${vm_user_name})..."
mkdir -p /home/${vm_user_name}/.kube
cp -i /etc/kubernetes/admin.conf /home/${vm_user_name}/.kube/config
chown ${vm_user_name}:${vm_user_name} /home/${vm_user_name}/.kube/config
# Calico 네트워크 플러그인 설치
echo "Calico 네트워크 플러그인 설치 중..."
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
# 클러스터 상태 확인을 위한 대기
echo "클러스터 구성 요소 시작 대기 중..."
sleep 60
# 클러스터 상태 확인
echo "클러스터 상태 확인 중..."
kubectl get nodes
kubectl get pods --all-namespaces
# join 명령어 생성 및 저장
echo "워커노드 조인 명령어 생성 중..."
kubeadm token create --print-join-command > /home/${vm_user_name}/kubeadm-join-command.sh
chmod +x /home/${vm_user_name}/kubeadm-join-command.sh
chown ${vm_user_name}:${vm_user_name} /home/${vm_user_name}/kubeadm-join-command.sh
echo ""
echo "=== Kubernetes 마스터노드 초기화 완료 ==="
echo "완료 시간: $(date)"
echo ""
echo "다음 정보를 확인하세요:"
echo "1. 클러스터 상태: kubectl get nodes"
echo "2. 파드 상태: kubectl get pods --all-namespaces"
echo "3. 워커노드 조인 명령어: cat /home/${vm_user_name}/kubeadm-join-command.sh"
echo ""
echo "워커노드 조인 명령어:"
cat /home/${vm_user_name}/kubeadm-join-command.sh
echo ""
# Helm 설치
echo "Helm 설치 중..."
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Azure CLI 설치 (선택사항)
echo "Azure CLI 설치 중..."
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
# 완료 표시
touch /var/log/k8s-master-init-complete
echo "=== 마스터노드 설정 완료 ==="
로그 확인
sudo tail -f /var/log/k8s-master-init.log

워커 노드 초기화(Cloud-init 자동 + 수동 조인)
- Cloud-init 자동 설정: 워커 VM 생성 시, OS 업데이트·패키지 설치·런타임(containerd) 구성·Kubernetes 패키지(kubelet/kubeadm/kubectl) 설치까지 자동화
#!/bin/bash
# Kubernetes 워커노드(kubeadm) 초기화 스크립트
# VM 생성 시 자동으로 실행됩니다
set -e
# 로그 파일 설정
exec > >(tee /var/log/k8s-worker-init.log) 2>&1
echo "=== Kubernetes 워커노드 초기화 시작 ==="
echo "시작 시간: $(date)"
# 시스템 업데이트
echo "시스템 패키지 업데이트 중..."
apt-get update
apt-get upgrade -y
# 필요한 패키지 설치
echo "필요한 패키지 설치 중..."
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
# 스왑 비활성화 (쿠버네티스 요구사항)
echo "스왑 비활성화 중..."
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 시스템 설정 최적화
echo "시스템 설정 최적화 중..."
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat <<EOF | tee /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
# containerd 설치
echo "containerd 설치 중..."
apt-get install -y containerd
# containerd 설정
echo "containerd 설정 중..."
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd
systemctl enable containerd
# Kubernetes repository 추가
echo "Kubernetes repository 추가 중..."
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 [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
# Kubernetes 패키지 설치 (워커노드는 kubectl 불필요)
echo "Kubernetes 패키지 설치 중..."
apt-get update
apt-get install -y kubelet kubeadm
apt-mark hold kubelet kubeadm
# kubelet 시작 및 활성화
systemctl enable kubelet
echo ""
echo "=== Kubernetes 워커노드 초기화 완료 ==="
echo "완료 시간: $(date)"
echo ""
echo "다음 단계:"
echo "1. 마스터노드에서 join 명령어 확인:"
echo " kubectl get nodes # 마스터노드에서 실행"
echo " kubeadm token create --print-join-command # 마스터노드에서 실행"
echo ""
echo "2. 이 워커노드를 클러스터에 조인:"
echo " sudo kubeadm join <MASTER_IP>:6443 --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH>"
echo ""
echo "3. 조인 후 마스터노드에서 확인:"
echo " kubectl get nodes"
echo ""
# 조인을 위한 헬퍼 스크립트 생성
cat <<EOF > /home/${vm_user_name}/join-cluster.sh
#!/bin/bash
# 이 스크립트를 사용하여 클러스터에 조인하세요
# 사용법: ./join-cluster.sh <MASTER_IP> <TOKEN> <DISCOVERY_TOKEN_CA_CERT_HASH>
if [ \$# -ne 3 ]; then
echo "사용법: \$0 <MASTER_IP> <TOKEN> <DISCOVERY_TOKEN_CA_CERT_HASH>"
echo "예시: \$0 10.0.1.4 abc123.def456ghi789 sha256:123abc..."
exit 1
fi
MASTER_IP=\$1
TOKEN=\$2
HASH=\$3
echo "클러스터 조인 중..."
echo "마스터 IP: \$MASTER_IP"
echo "토큰: \$TOKEN"
echo "해시: \$HASH"
sudo kubeadm join \$MASTER_IP:6443 --token \$TOKEN --discovery-token-ca-cert-hash \$HASH
if [ \$? -eq 0 ]; then
echo "클러스터 조인 성공!"
echo "마스터노드에서 'kubectl get nodes'로 확인하세요."
else
echo "클러스터 조인 실패!"
echo "로그 확인: journalctl -u kubelet"
fi
EOF
chmod +x /home/${vm_user_name}/join-cluster.sh
chown ${vm_user_name}:${vm_user_name} /home/${vm_user_name}/join-cluster.sh
echo "조인 헬퍼 스크립트가 /home/${vm_user_name}/join-cluster.sh에 생성되었습니다."
# 완료 표시
touch /var/log/k8s-worker-init-complete
echo "=== 워커노드 설정 완료 ==="
- 수동 조인: 마스터에서 발급한 kubeadm join 명령어를 워커 노드에서 실행해 클러스터에 수동 조인
# 마스터
cat /home/<USER>/kubeadm-join-command.sh
# 워커에서 (예)
sudo kubeadm join 10.25.0.15:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>



부록 – 스크립트/TF Code
1) Calico
#!/bin/bash
set -Eeuo pipefail
export DEBIAN_FRONTEND=noninteractive
exec > >(tee /var/log/k8s-master-init.log) 2>&1
log() { echo "[$(date +'%F %T')] $*"; }
log "=== master init start ==="
mkdir -p /etc/apt/keyrings && chmod 755 /etc/apt/keyrings
apt-get update && apt-get -y -o Dpkg::Options::="--force-confnew" upgrade
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release
swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
cat <https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
| gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
cat >/etc/apt/sources.list.d/kubernetes.list <<'EOF'
deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /
EOF
apt-get update
apt-get install -y kubelet=1.30.14-00 kubeadm=1.30.14-00 kubectl=1.30.14-00
apt-mark hold kubelet kubeadm kubectl
systemctl enable kubelet
MASTER_IF="eth0"
MASTER_IP=$(ip -4 addr show "$MASTER_IF" | awk '/inet/ {print $2}' | cut -d/ -f1)
POD_CIDR="192.168.0.0/16"
SERVICE_CIDR="10.96.0.0/12"
kubeadm init \
--pod-network-cidr="$POD_CIDR" \
--service-cidr="$SERVICE_CIDR" \
--apiserver-advertise-address="$MASTER_IP"
mkdir -p $HOME/.kube && cp -i /etc/kubernetes/admin.conf $HOME/.kube/config && chown $(id -u):$(id -g) $HOME/.kube/config
mkdir -p /home/${vm_user_name}/.kube && cp -i /etc/kubernetes/admin.conf /home/${vm_user_name}/.kube/config && chown ${vm_user_name}:${vm_user_name} /home/${vm_user_name}/.kube/config
# Calico (기본)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
kubectl -n kube-system rollout status ds/calico-node --timeout=180s || true
kubeadm token create --print-join-command > /home/${vm_user_name}/kubeadm-join-command.sh
chmod +x /home/${vm_user_name}/kubeadm-join-command.sh && chown ${vm_user_name}:${vm_user_name} /home/${vm_user_name}/kubeadm-join-command.sh
touch /var/log/k8s-master-init-complete
log "=== master init done ==="
2) Cilium
# kube-proxy 미설치(또는 스킵) + Cilium kube-proxy replacement
kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--apiserver-advertise-address="$MASTER_IP" \
--skip-phases=addon/kube-proxy
# Cilium 설치(버전 고정 권장)
helm repo add cilium https://helm.cilium.io
helm repo update
helm install cilium cilium/cilium \
--version 1.15.0 \
--namespace kube-system \
--set kubeProxyReplacement=strict \
--set k8sServiceHost="$MASTER_IP" \
--set k8sServicePort=6443 \
--set ipam.mode=cluster-pool \
--set ipam.operator.clusterPoolIPv4PodCIDRList="{10.244.0.0/16}" \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true
3) Kubenet
# Kubenet은 네트워크 정책 미지원, 멀티노드/라우팅 제약
# 운영보다는 교육/테스트 목적 권장
kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=10.96.0.0/12 \
--apiserver-advertise-address="$MASTER_IP"
4) Terraform
providers.tf
terraform {
required_version = ">= 1.6.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.115"
}
}
}
provider "azurerm" {
features {}
}
variables.tf
variable "vm_user_name" { default = "azureadmin" }
variable "vm_size_name" { default = "Standard_D4s_v3" } # master 예시
variable "os_disk_info" {
type = map(string)
default = {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
disk_size_gb = "50"
}
}
variable "golden_image_info" {
type = map(string)
default = {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
}
main.tf (master VM)
resource "azurerm_linux_virtual_machine" "k8s_master" {
name = "vm-${var.infix_env}-${var.infix_service_name}-k8smaster-${var.suffix_seq}"
resource_group_name = var.target_resource_group_name
location = var.resource_group_location
size = var.vm_size_name
disable_password_authentication = true # SSH 키 기반 권장
admin_username = var.vm_user_name
admin_ssh_key {
username = var.vm_user_name
public_key = file(var.ssh_public_key_path)
}
network_interface_ids = [azurerm_network_interface.k8s_master_nic.id]
custom_data = base64encode(templatefile("${path.module}/scripts/init-k8s-master.tmpl", {
vm_user_name = var.vm_user_name
}))
os_disk {
caching = var.os_disk_info.caching
storage_account_type = var.os_disk_info.storage_account_type
disk_size_gb = tonumber(var.os_disk_info.disk_size_gb)
}
source_image_reference {
publisher = var.golden_image_info.publisher
offer = var.golden_image_info.offer
sku = var.golden_image_info.sku
version = var.golden_image_info.version
}
tags = {
NodeType = "k8s-master"
ClusterName = "vanilla-k8s-cluster"
}
}
outputs.tf
output "master_ip" {
value = azurerm_network_interface.k8s_master_nic.private_ip_address
}
참고 문서
'Kubernetes' 카테고리의 다른 글
| Private AKS 내부망에서 Harbor OCI Registry를 활용한 Container 환경 구성 (1) | 2025.08.28 |
|---|---|
| Kubernetes에서 외부로부터 들어오는 요청의 Source IP 파악하기 (0) | 2025.07.30 |
| ArgoCD Multi Cluster Setup Guide (GitOps 기반 자동 배포) (3) | 2025.07.29 |
| KubeVirt VM 로그 수집 및 시각화 구성 (Promtail → Loki → Grafana) (1) | 2025.07.02 |
| FastAPI 애플리케이션을 AKS에 배포하고 Prometheus + Grafana로 모니터링 하기 (0) | 2025.06.09 |