
Cómo automatizar la creación de un clúster Kubernetes en VMs Ubuntu de Proxmox con Ansible
por Raúl Unzué¿Por qué Kubernetes sobre Proxmox y con Ansible?
Si trabajas con virtualización y buscas orquestar contenedores sin complicarte demasiado la vida, Kubernetes es tu mejor amigo. Pero si además usas Proxmox como plataforma de virtualización y quieres automatizarlo todo sin hacer clics a lo loco… Ansible entra en juego como el tercer mosquetero.
En esta entrada vamos a montar un clúster Kubernetes sobre máquinas virtuales Ubuntu corriendo en Proxmox, todo orquestado y automatizado con Ansible. Nada de instalaciones manuales eternas. Vamos a dejarlo todo fino, bien montado, y replicable.
Requisitos de hardware y software
Antes de liarte con comandos, asegúrate de tener lo siguiente bien claro:
En tu host Proxmox:
- Una instalación funcionando de Proxmox VE 7 o superior. El host Proxmox que usaremos será "nuc.negu.local"
- Recurso NFS montado desde un NAS (Esto es totalmente opcional)
- Usaremos una plantilla para desplegar, al menos 3 máquinas virtuales creadas con Ubuntu 22.04 LTS (1 master y 2 workers).
- Las VMs deben tener acceso a la red LAN y entre ellas (idealmente en la misma VLAN).
- Cada VM debe tener:
- 2 CPUs mínimo
- 2 GB de RAM (4 GB recomendados para el master)
- 20 GB de disco mínimo
- Una IP fija o reservada vía DHCP para cada máquina:
- MASTER - 192.168.2.100
- WORKER 1 - 192.168.2.101
- WORKER 2 - 192.168.2.102
A nivel de software:
- Ubuntu Server 22.04 (recomendado por compatibilidad con Kubernetes).
- Acceso por SSH desde tu máquina de control (la que usará Ansible) al host Proxmox (con clave pública copiada o sshpass si no usas claves).
- Ansible instalado en tu máquina local (o una máquina de administración). En el ejemplo, 2.17.11:
apt update && apt install -y python3-pippip install proxmoxer requests # Verificacionpython3 -c "import proxmoxer; print('proxmoxer OK')"
- Python 3 en las VMs (suele venir por defecto).
Paso a paso para montar Kubernetes con Ansible
- Lo primero que haremos es preparar el proyecto. Para ello generamos los ficheros y carpetas necesarios. Lo podéis hacer desde vuestra máquina Ansible con los siguientes comandos:
mkdir k8s-proxmox-ansible && cd k8s-proxmox-ansible mkdir -p group_vars host_vars files venv roles/{proxmox_vm_create,k8s_master_setup,k8s_worker_setup}/{tasks,templates,handlers} touch ansible.cfg inventory.ini site.yml group_vars/all.yml roles/{proxmox_vm_create,k8s_master_setup,k8s_worker_setup}/{tasks,handlers}/main.yml
- Estructura del proyecto:
- Modificaciones el fichero "ansible.cfg" con el siguiente contenido:
[defaults]host_key_checking = Falseinventory = inventory.ini # Forzar uso del python de tu venv interpreter_python = {{ playbook_dir }}/venv/bin/python3
- Editamos el fichero "inventory.ini" con "nano inventory.ini" y agregamos el siguiente texto, modificando la contraseña y las IPs/nombres de nuestros equipos:
[proxmox] localhost ansible_connection=local ansible_python_interpreter=/root/k8s-proxmox-ansible/venv/bin/python3 [k8s_master] k8s-master ansible_host=192.168.2.100 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa [k8s_workers] k8s-worker1 ansible_host=192.168.2.101 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa k8s-worker2 ansible_host=192.168.2.102 ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa [kubernetes:children]k8s_masterk8s_workers[k8s_workers:vars] ansible_python_interpreter=/usr/bin/python3
- Editamos el fichero "site.yml" y agregamos el siguiente contenido:
- name: Crear VMs en Proxmox hosts: proxmox gather_facts: false roles: - proxmox_vm_create- name: Configurar Kubernetes Master hosts: master become: yes roles: - k8s_master_setup- name: Configurar Kubernetes Workers hosts: workers become: yes roles: - k8s_worker_setup
- Generamos el fichero "groups_vars/all.yml" con el siguiente contenido:
---proxmox_api_host: nuc.negu.localproxmox_api_user: root@pam proxmox_api_password: password-proxmoxproxmox_api_verify_ssl: False k8s_version: "1.27.0-00"pod_network_cidr: "192.168.0.0/16" service_cidr: "10.96.0.0/12"dns_domain: "cluster.local" ssh_public_key_path: "~/.ssh/id_rsa.pub"
- Ahora generaremos un Playbook para la creación de las máquinas virtuales.
- - Definición de Playbook: Para los que sois nuevos, un Playbook, se utiliza comúnmente para referirse a los scripts o secuencias de tareas que automatizan procesos.
- La ruta del Playbook es "nano roles/proxmox_vm_create/tasks/main.yml" y colocaremos este contenido:
---- name: Leer clave SSH pública ansible.builtin.slurp: src: "{{ ssh_public_key_path }}" register: pubkey_raw- set_fact: pubkey: "{{ pubkey_raw.content | b64decode }}" - name: Crear contenedor LXC para cada nodo community.general.proxmox: api_user: "{{ proxmox_api_user }}" api_password: "{{ proxmox_api_password }}" api_host: "{{ proxmox_api_host }}" validate_certs: "{{ proxmox_api_verify_ssl }}" node: nuc vmid: "{{ item.vmid }}" ostemplate: "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst" hostname: "{{ item.hostname }}" storage: local-lvm cores: 2 memory: "{{ 4096 if item.hostname == 'k8s-master' else 2048 }}" netif: net0: "name=eth0,bridge=vmbr0,ip={{ item.ip_with_prefix }},gw=192.168.2.69" nameserver: "192.168.2.69" searchdomain: "negu.local" onboot: yes pubkey: "{{ pubkey }}" password: "kubernetes" state: present loop: "{{ lxc_nodes }}"- name: Arrancar todos los contenedores LXC community.general.proxmox: api_user: "{{ proxmox_api_user }}" api_password: "{{ proxmox_api_password }}" api_host: "{{ proxmox_api_host }}" validate_certs: "{{ proxmox_api_verify_ssl }}" node: nuc vmid: "{{ item.vmid }}" state: started loop: "{{ lxc_nodes }}"- name: Esperar a que SSH este listo en master wait_for: host: "{{ lxc_nodes[0].ip_with_prefix.split('/')[0] }}" port: 22 timeout: 300 state: started delegate_to: localhost
- Definimos el siguiente fichero "nano roles/k8s_master_setup/handlers/main.yml":
---- name: Restart SSH ansible.builtin.service: name: ssh state: restarted
- Una vez creadas las máquinas virtuales, necesitaremos configurar los roles dentro de ellas con otro Playbook, empezamos con el master, "nano roles/k8s_master_setup/tasks/main.yml":
# roles/k8s_master_setup/tasks/main.yml--- - name: Instalar dependencias en master ansible.builtin.apt: name: - apt-transport-https - ca-certificates - curl - gnupg - lsb-release state: latest update_cache: yes - name: Asegurar ruta por defecto en el master LXC ansible.builtin.shell: | ip route replace default via 192.168.2.69 args: executable: /bin/bash - name: Configurar DNS en el master LXC ansible.builtin.copy: dest: /etc/resolv.conf content: | nameserver 8.8.8.8 nameserver 1.1.1.1 mode: '0644' - name: Instalar OpenSSH Server en master ansible.builtin.apt: name: openssh-server state: present update_cache: yes - name: Asegurar directorio .ssh para root ansible.builtin.file: path: /root/.ssh state: directory mode: '0700' - name: Instalar clave publica de root ansible.builtin.authorized_key: user: root state: present key: "{{ lookup('file', ssh_public_key_path) }}" - name: Permitir login root en SSH ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^PermitRootLogin' line: 'PermitRootLogin yes' notify: Restart SSH - name: Crear /etc/hosts con todos los nodos ansible.builtin.copy: dest: /etc/hosts content: | 127.0.0.1 localhost {% for host in groups['k8s_master'] %} {{ hostvars[host].ansible_host | default(hostvars[host].ansible_default_ipv4.address) }} {{ host }} {% endfor %} {% for host in groups['k8s_workers'] %} {{ hostvars[host].ansible_host | default(hostvars[host].ansible_default_ipv4.address) }} {{ host }} {% endfor %} mode: '0644'- name: Instalar K3s (canal estable) ansible.builtin.shell: | curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644 args: creates: /usr/local/bin/k3s executable: /bin/bash - name: Obtener token de nodo para K3s ansible.builtin.slurp: src: /var/lib/rancher/k3s/server/node-token register: k3s_token_raw - name: Definir fact con el token de K3s ansible.builtin.set_fact: k3s_node_token: "{{ k3s_token_raw.content | b64decode | trim }}" - name: Esperar a que K3s este listo en master ansible.builtin.wait_for: host: "{{ hostvars[inventory_hostname].ansible_host | default(ansible_default_ipv4.address) }}" port: 6443 timeout: 300 state: started delegate_to: localhost - name: Crear directorio .kube para root file: path: /root/.kube state: directory mode: '0700'- name: Copiar kubeconfig de K3s para root ansible.builtin.copy: remote_src: yes src: /etc/rancher/k3s/k3s.yaml dest: /root/.kube/config owner: root group: root mode: '0600'
- Realizamos la misma operativa para los workers "nano roles/k8s_worker_setup/tasks/main.yml":
# roles/k8s_worker_setup/tasks/main.yml--- - name: Instalar dependencias en worker ansible.builtin.apt: name: - apt-transport-https - ca-certificates - curl - gnupg - lsb-release state: latest update_cache: yes - name: Asegurar que existe ruta por defecto en el contenedor ansible.builtin.shell: | ip route replace default via 192.168.2.69 args: executable: /bin/bash- name: Configurar DNS dentro del contenedor ansible.builtin.copy: dest: /etc/resolv.conf content: | nameserver 8.8.8.8 nameserver 1.1.1.1 mode: '0644' - name: Instalar OpenSSH Server en worker ansible.builtin.apt: name: openssh-server state: present update_cache: yes - name: Asegurar directorio .ssh para root ansible.builtin.file: path: /root/.ssh state: directory mode: '0700' - name: Instalar clave publica de root en worker ansible.builtin.authorized_key: user: root state: present key: "{{ lookup('file', ssh_public_key_path) }}" - name: Permitir login root en SSH ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^PermitRootLogin' line: 'PermitRootLogin yes'- name: Reiniciar SSHD en worker ansible.builtin.service: name: ssh state: restarted enabled: yes - name: Crear /etc/hosts con todos los nodos ansible.builtin.copy: dest: /etc/hosts content: | 127.0.0.1 localhost {% for host in groups['k8s_master'] %} {{ hostvars[host].ansible_host | default(hostvars[host].ansible_default_ipv4.address) }} {{ host }} {% endfor %} {% for host in groups['k8s_workers'] %} {{ hostvars[host].ansible_host | default(hostvars[host].ansible_default_ipv4.address) }} {{ host }} {% endfor %} mode: '0644' - name: Esperar a que SSH este listo en este worker ansible.builtin.wait_for: port: 22 state: started timeout: 120 - name: Esperar a que master K3s este accesible (puerto 6443) ansible.builtin.wait_for: host: "{{ hostvars['k8s-master'].ansible_host | default(hostvars['k8s-master'].ansible_default_ipv4.address) }}" port: 6443 state: started timeout: 300 delegate_to: localhost - name: Mostrar token de K3s (debug) ansible.builtin.debug: msg: "Token master: {{ hostvars['k8s-master'].k3s_node_token }}" - name: Unir worker al cluster K3s (invocando directamente el binario) ansible.builtin.shell: | set -x sudo /usr/local/bin/k3s agent \ --server https://{{ hostvars['k8s-master'].ansible_host }}:6443 \ --token {{ hostvars['k8s-master'].k3s_node_token }} \ --node-ip {{ ansible_default_ipv4.address }} args: executable: /bin/bash register: k3s_join failed_when: k3s_join.rc != 0 - name: Mostrar resultado de la union ansible.builtin.debug: var: k3s_join - name: Listar nodos ya unidos ansible.builtin.shell: | sudo k3s kubectl get nodes register: k3s_nodes failed_when: false - name: Verificación final de nodos ansible.builtin.debug: var: k3s_nodes.stdout_lines - name: Esperar a que el agente K3s se registre (puerto 10250) ansible.builtin.wait_for: host: "{{ ansible_default_ipv4.address }}" port: 10250 state: started timeout: 300
- Con todo preparado lanzamos la ejecución:
ansible-playbook -i inventory.ini site.yml
- Comienza el proceso, las máquinas serán generadas, arrancadas y tuneadas con las necesidades que hemos marcado:
- En unos segundos veremos como el cluster de Kubernetes empieza a levantarse y configurarse por completo.
Kubernetes sobre Proxmox
Montar Kubernetes con Ansible sobre Proxmox es una forma muy limpia y flexible de tener control total de tu clúster. Si estás montando un laboratorio serio, o incluso algo de producción para pruebas internas, este combo te da potencia y automatización sin meterte en herramientas más pesadas como Rancher (aunque podrías agregarlo después).
Con Ansible como herramienta de automatización, te ahorras toneladas de clics y errores humanos. Con este setup puedes recrear entornos fácilmente, montar pruebas CI/CD o tener tu propio laboratorio de alto nivel. Y si dominas esto, dar el salto a producción solo es cuestión de escalar bien y meterle observabilidad.
Además, te ahorras depender de nubes públicas y tienes toda la infraestructura en casa. ¿Lo mejor? Puedes replicarlo una y otra vez.
Fin del Artículo. ¡Cuéntanos algo en los Comentarios!