
Kubernetes sobre Hyper-V
La virtualización se ha convertido en un estándar en entornos empresariales, y Kubernetes es el orquestador por excelencia para gestionar contenedores a gran escala. Si combinas ambas tecnologías en una infraestructura de Hyper-V, puedes lograr un entorno flexible, escalable y altamente automatizado.
Pero ¿por qué hacerlo manualmente si podemos automatizarlo? PowerShell nos permite desplegar un clúster de Kubernetes en Hyper-V de forma rápida y eficiente, asegurando una configuración homogénea y minimizando errores humanos.
En esta guía, te mostraremos paso a paso cómo automatizar la creación de máquinas virtuales mediante una plantilla de Debian, la configuración de redes, la instalación de Kubernetes y la validación del clúster, todo a través de PowerShell. Además, incluiremos pruebas de conectividad y monitoreo para garantizar que todo funcione correctamente dentro de tu red LAN.
Al final de esta guía, tendrás un clúster de Kubernetes funcional en Hyper-V, listo para desplegar aplicaciones de manera eficiente y sin intervención manual.
Requisitos previos
Antes de ejecutar el script del Powershell (si queréis saber como construir vuestro primer script, os dejamos una entrada), asegúrate de contar con lo siguiente:
- Mínimo Windows Server 2022 o Windows 10/11 Pro/Enterprise con Hyper-V activado. Lo podéis instalar con el siguiente comando:
- Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
- Mínimo 16 GB de RAM.
- Procesador con virtualización habilitada (VT-x o AMD-V).
- PowerShell con permisos de administrador.
- En nuestro caso, usaremos una plantilla de Debian Cloud para generar todas las máquinas virtuales.
- Acceso a internet en la máquina host para descargar paquetes.
- Instalar paquete en plantilla "hv_kvp":
sudo apt update && sudo apt install linux-cloud-tools-common linux-cloud-tools-generic linux-cloud-tools-$(uname -r)
sudo systemctl enable hv-kvp-daemon
sudo systemctl start hv-kvp-daemon
- Para instalar "cloud-init", que es una herramienta utilizada para la configuración automática de instancias en la nube o máquinas virtuales en su primer arranque. Permite personalizar servidores con configuraciones específicas como usuarios, claves SSH, paquetes, scripts de inicialización y más, sin intervención manual:
sudo apt update && sudo apt install -y cloud-init
Para sellar la máquina virtual utilizar el siguiente comando:
sudo cloud-init clean --logs
- Instalar Kubernetes en plantilla Debian:
apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo tee /etc/apt/keyrings/kubernetes-apt-keyring.asc > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.asc] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt update
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
- Paquete ADK instalado en el host de Hyper-V:
Crear una Plantilla de Debian en Hyper-V
Si utilizamos una plantilla pre-creada de Debian en Hyper-V, podemos clonar y personalizar las máquinas virtuales en el momento del arranque usando PowerShell y Cloud-Init.
Esta estrategia optimiza el despliegue, ya que evita reinstalar el sistema operativo desde cero y permite modificar cada nodo (master o worker) en función de su rol.
Os explicamos como lo podemos hacer:
- Descargamos la imagen oficial de Debian Cloud (Qcow2 o Raw), lo podéis hacer vía comando o directamente con la URL:
- wget https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2
- Convierte la imagen a formato VHDX (para Hyper-V) con qemu-img:
- qemu-img convert -f qcow2 -O vhdx debian-12-genericcloud-amd64.qcow2 debian-template.vhdx
- Crea una VM en Hyper-V con el disco
debian-template.vhdx
y Cloud-Init activado. - Configura SSH y Cloud-Init en la plantilla:
- apt update && apt install -y cloud-init
systemctl enable cloud-init
- cloud-init clean
-
Crea el archivo
/etc/cloud/cloud.cfg.d/99-hyperv.cfg
y añade:datasource_list: [NoCloud, None]
- apt update && apt install -y cloud-init
- Apaga la máquina y conviértela en una plantilla de solo lectura:
- Set-VM -Name "Debian-Template" -CheckpointType Disabled
Script Powershell para generar plantilla de VM en Hyper-V
- Opcionalmente, podéis usar un script de powershell para generarla una vez parametrizada:
PowerShell: Script para Crear la Infraestructura
El siguiente script, automatiza la creación del clúster Kubernetes generando las máquinas virtuales (1 Master y 2 Workers), configurando redes y preparando los nodos para Kubernetes:
# ========================
# CONFIGURACIÓN DEL SCRIPT
# Definimos variables de configuración
# ========================
# Variables de configuración
$VMSwitchName = "K8S-External-Network"
$VMPath = "D:\Hyper-V"
$CloudInitPath = "$VMPath\CloudInit"
$TemplateVHD = "$VMPath\Templates\debian-template.vhdx"
$OscdimgPath = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
# Validar si oscdimg.exe existe
if (!(Test-Path $OscdimgPath)) {
Write-Host "Error: oscdimg.exe no encontrado en $OscdimgPath. Instala el Windows ADK."
exit
}
# Crear directorio de Cloud-Init si no existe
if (!(Test-Path $CloudInitPath)) {
Write-Host "Creando directorio de Cloud-Init..."
New-Item -ItemType Directory -Path $CloudInitPath | Out-Null
}
# ========================
# SOLICITAR DATOS AL USUARIO
# Pide un usuario y contraseña para las VMs
# ========================
$Username = Read-Host "Introduce el nombre de usuario para los nodos"
$Password = Read-Host "Introduce la contraseña para los nodos" -AsSecureString
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
# Generar clave SSH si no existe
$SSHKeyPath = "$env:USERPROFILE\.ssh\id_rsa"
if (!(Test-Path $SSHKeyPath)) {
Write-Host "Generando clave SSH..."
ssh-keygen -t rsa -b 4096 -N "" -f $SSHKeyPath
}
$SSHKeyPub = Get-Content "$SSHKeyPath.pub"
# ========================
# CREAR SWITCH VIRTUAL EXTERNO SI NO EXISTE
# Creamos la red para nuestras VMs
# ========================
if (!(Get-VMSwitch -Name $VMSwitchName -ErrorAction SilentlyContinue)) {
$NetAdapter = (Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | Select-Object -ExpandProperty Name -First 1)
if ($NetAdapter) {
Write-Host "Creando switch virtual externo: $VMSwitchName en $NetAdapter"
New-VMSwitch -Name $VMSwitchName -NetAdapterName $NetAdapter -AllowManagementOS $true
} else {
Write-Host "Error: No se encontró un adaptador de red activo."
exit
}
}
# ========================
# CREAR LAS VMs Y CONFIGURAR CLOUD-INIT
# Definimos las características de las VMs y los ficheros para Cloud-Init
# ========================
$VMs = @(
@{ Name = "K8S-Master"; RAM = 4GB; CPU = 2; Role = "master"; IP = "192.168.2.120" },
@{ Name = "K8S-Worker1"; RAM = 3GB; CPU = 2; Role = "worker"; IP = "192.168.2.121" },
@{ Name = "K8S-Worker2"; RAM = 3GB; CPU = 2; Role = "worker"; IP = "192.168.2.122" }
)
foreach ($VM in $VMs) {
$VHDPath = "$VMPath\$($VM.Name).vhdx"
$CloudISO = "$CloudInitPath\$($VM.Name)-Config.iso"
if (Get-VM -Name $VM.Name -ErrorAction SilentlyContinue) {
Write-Host "La máquina virtual $($VM.Name) ya existe. Omitiendo..."
continue
}
Write-Host "Creando VM: $($VM.Name)"
Copy-Item -Path $TemplateVHD -Destination $VHDPath -Force
# ========================
# GENERAR ARCHIVOS DE CLOUD-INIT
# ========================
Write-Host "Generando archivos de Cloud-Init para $($VM.Name)..."
# Meta-Data
$MetaData = @"
instance-id: $($VM.Name)
local-hostname: $($VM.Name)
"@
$MetaData | Set-Content -Path "$CloudInitPath\meta-data" -Encoding UTF8
# User-Data
$UserData = @"
#cloud-config
users:
- default
- name: ${Username}
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ${SSHKeyPub}
ssh_import_id:
- gh:${Username}
lock_passwd: false
passwd: "${PlainPassword}"
chpasswd: { expire: false }
groups: sudo, users
home: /home/${Username}
chpasswd:
list: |
root:${PlainPassword}
${Username}:${PlainPassword}
expire: false
ssh_pwauth: true
disable_root: false
runcmd:
- sudo apt-get update
- sudo apt-get install -y qemu-guest-agent curl openssh-server net-tools
- sudo systemctl enable --now qemu-guest-agent
- sudo mkdir -p /home/${Username}/.ssh
- echo "${SSHKeyPub}" | sudo tee /home/${Username}/.ssh/authorized_keys
- sudo chown -R ${Username}:${Username} /home/${Username}/.ssh
- sudo chmod 700 /home/${Username}/.ssh
- sudo chmod 600 /home/${Username}/.ssh/authorized_keys
- echo "PermitRootLogin yes" | sudo tee -a /etc/ssh/sshd_config
- echo "PasswordAuthentication yes" | sudo tee -a /etc/ssh/sshd_config
- echo "AllowUsers ${Username}" | sudo tee -a /etc/ssh/sshd_config
- sudo systemctl restart sshd
- sudo cloud-init clean
"@
$UserData | Set-Content -Path "$CloudInitPath\user-data" -Encoding UTF8
# Configuración de IP estática en la LAN
$NetworkConfig = @"
version: 2
ethernets:
eth0:
addresses:
- $($VM.IP)/24
gateway4: 192.168.2.69
nameservers:
addresses:
- 8.8.8.8
- 1.1.1.1
"@
$NetworkConfig | Set-Content -Path "$CloudInitPath\network-config" -Encoding UTF8
# ========================
# GENERAR ISO DE CLOUD-INIT
# ========================
Write-Host "Generando ISO de Cloud-Init para $($VM.Name)..."
Start-Process -FilePath $OscdimgPath `
-ArgumentList "-d -m `"$CloudInitPath`" `"$CloudISO`"" `
-NoNewWindow -Wait
if (!(Test-Path $CloudISO)) {
Write-Host "ERROR: No se pudo generar el archivo Cloud-Init ISO en $CloudISO"
exit
}
# ========================
# CREAR VM
# ========================
New-VM -Name $VM.Name -MemoryStartupBytes $VM.RAM -Generation 2 `
-VHDPath $VHDPath -SwitchName $VMSwitchName
Set-VMProcessor -VMName $VM.Name -Count $VM.CPU
Set-VMMemory -VMName $VM.Name -DynamicMemoryEnabled $true `
-MinimumBytes 512MB -MaximumBytes 8GB
Add-VMDvdDrive -VMName $VM.Name
Set-VMDvdDrive -VMName $VM.Name -Path $CloudISO
# Configurar firmware
Set-VMFirmware -VMName $VM.Name -EnableSecureBoot Off
$HardDrive = Get-VMHardDiskDrive -VMName $VM.Name
Set-VMFirmware -VMName $VM.Name -BootOrder $HardDrive
# Iniciar VM
Start-VM -Name $VM.Name
Start-Sleep -Seconds 10
}
# ========================
# VALIDACIÓN DE RED
# Espera 60 segundos para obtener IP
# ========================
Write-Host "Esperando que las VMs obtengan IP..."
Start-Sleep -Seconds 60
Write-Host "Comprobando IPs de las VMs..."
foreach ($VM in $VMs) {
$VMIP = (Get-VMNetworkAdapter -VMName $VM.Name).IPAddresses
if ($VMIP) {
Write-Host "$($VM.Name) tiene la IP: $VMIP"
} else {
Write-Host "ERROR: $($VM.Name) sigue sin IP."
}
}
# ========================
# VALIDACIÓN DE CLOUD-INIT Y SSH
# ========================
Write-Host "Esperando que Cloud-Init termine en $($VM.Name)..."
Start-Sleep -Seconds 120 # Espera inicial antes de validar
Write-Host "Verificando conectividad con ${VM.IP}..."
$pingTest = Test-Connection -ComputerName $VM.IP -Count 2 -Quiet
if (-not $pingTest) {
Write-Host "No se pudo hacer ping a ${VM.IP}. Revisa la configuración de red."
continue
}
Write-Host "Intentando acceso SSH a $($VM.Name) en ${VM.IP}..."
for ($i=1; $i -le 5; $i++) {
$SSHTest = ssh -o "StrictHostKeyChecking=no" -o "ConnectTimeout=5" "$Username@${VM.IP}" "echo 'SSH funcionando'"
if ($SSHTest -match "SSH funcionando") {
Write-Host "SSH funcionando en $($VM.Name)."
break
} else {
Write-Host "Intento $i No se pudo acceder por SSH a $($VM.Name). Reintentando..."
Start-Sleep -Seconds 10
}
}
}
# ========================
# VALIDACIÓN DE CLUSTER KUBERNETES
# Validamos que el cluster Kubernetes está bien creado
# ========================
Write-Host "Validando el clúster Kubernetes..."
$MasterVM = "K8S-Master"
$K8SStatus = Invoke-Command -VMName $MasterVM -ScriptBlock {
if (Test-Path "/etc/kubernetes/admin.conf") {
$Status = kubectl get nodes --no-headers | ForEach-Object { $_ -match ' Ready ' }
if ($Status) {
return "OK"
} else {
return "ERROR"
}
} else {
return "ERROR"
}
}
if ($K8SStatus -eq "OK") {
Write-Host "Kubernetes está funcionando correctamente en el nodo master."
} else {
Write-Host "ERROR: Kubernetes no está correctamente configurado en el nodo master."
}
Write-Host "¡Clúster Kubernetes desplegado exitosamente!"
Ejecución Script Powershell
Ejecuta PowerShell como administrador y lanza el script de la siguiente forma:
Set-ExecutionPolicy Bypass -Scope Process -Force
O lanzamos la aplicación Powershell_ISE o la consola de Powershell como administrador:
Ejecutamos script, que irá haciendo varias validaciones:
.\Deploy-KubernetesCluster.ps1
Veremos como se van generando cada fichero de cloud-init:
Si todo ha ido correctamente, podréis ver las máquinas que irán arrancando una a una y el conmutador virtual creados:
Podremos acceder a cada máquina, ya sea vía SSH como vía consola:
Y comprobar el estado de los nodos:
kubernetes@K8S-Master:~$ kubectl get node
NAME STATUS ROLES AGE VERSION
K8S-Master Ready master 6d v1.32.115
K8S-Worker1 Ready 7m7s v1.32.115
K8S-Worker2 Ready 6d v1.32.115
Conclusión: La automatización de procesos ahorra trabajo y fallos
La automatización de procesos permite optimizar el tiempo y reducir la posibilidad de errores humanos. Al implementar soluciones automatizadas, se mejora la eficiencia, se minimizan fallos y se garantiza una mayor precisión en las tareas repetitivas. Esto no solo ahorra trabajo, sino que también facilita la gestión y el mantenimiento de sistemas complejos.
Fin del Artículo. ¡Cuéntanos algo en los Comentarios!