1. Escenario del laboratorio
Partimos de tres dispositivos virtuales conectados mediante dos enlaces punto a punto y una red de gestión independiente.
Cisco IOSvL2: switch Cisco con funciones L2 y, si la imagen lo permite, L3.
Arista cEOS: switch Arista con interfaces Ethernet y routing.
VyOS: router Linux especializado en networking.
Para evitar depender de funciones L3 de una imagen Cisco L2, usaré Cisco IOSv con soporte L3 para OSPF y reservaré la práctica VLAN/trunk para equipos o imágenes con capacidad de switching. Podemos sustituir IOSv por IOSvL2, IOS-XE o una imagen compatible según el ejercicio.
%%{init: {
"theme": "base",
"themeVariables": {
"fontFamily": "sans-serif",
"fontSize": "13px",
"edgeLabelBackground": "#ffffff"
}
} }%%
flowchart LR
classDef router fill:#2b3e50,stroke:#1a252f,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef host fill:#16a085,stroke:#117a65,stroke-width:2px,color:#fff,rx:8,ry:8;
C["<b>Cisco IOSv</b><br/>10.0.12.1/30"]:::router
A["<b>Arista EOS</b><br/>10.0.12.2/30<br/>10.0.23.1/30"]:::router
V["<b>VyOS</b><br/>10.0.23.2/30"]:::router
C ---|"OSPF Área 0"| A
A ---|"OSPF + eBGP"| V
M["<b>Controlador Ansible</b><br/>Manjaro Linux"]:::host
M -. "SSH / Gestión" .-> C
M -. "SSH / Gestión" .-> A
M -. "SSH / Gestión" .-> V
linkStyle default stroke:#7f8c8d,stroke-width:1.5px;
Nuestra asignación de direcciones será:
| Equipo | Gestión | Interfaz de datos |
|---|---|---|
| Cisco | 192.168.56.11 | Gi0/1: 10.0.12.1/30 |
| Arista | 192.168.56.12 | Ethernet1: 10.0.12.2/30 |
| Arista | 192.168.56.12 | Ethernet2: 10.0.23.1/30 |
| VyOS | 192.168.56.13 | eth1: 10.0.23.2/30 |
También utilizaremos tres loopbacks, que nos servirán para comprobar el intercambio de rutas.
| Equipo | Loopback |
|---|---|
| Cisco | 10.255.0.1/32 |
| Arista | 10.255.0.2/32 |
| VyOS | 10.255.0.3/32 |
En esta práctica usaremos GNS3, EVE-NG, Cisco CML o Containerlab, dependiendo de las imágenes disponibles. Cisco requiere imágenes compatibles y autorizadas; cEOS debe descargarse desde Arista. No es necesario instalar Python en los dispositivos.
Supondremos que ya disponemos de acceso SSH y que las interfaces de gestión están configuradas. La conexión física o virtual de los enlaces de datos también debe estar preparada antes de ejecutar los playbooks.
2. Preparar el proyecto
En el controlador creamos el directorio de trabajo:
mkdir -p ansible-network/{group_vars,host_vars,playbooks,backups}
cd ansible-network
Instalamos las colecciones necesarias:
ansible-galaxy collection install \
ansible.netcommon \
ansible.utils \
cisco.ios \
arista.eos \
vyos.vyos
Nuestro proyecto tendrá esta estructura:
ansible-network/
├── ansible.cfg
├── inventory.yml
├── group_vars/
│ ├── cisco.yml
│ ├── arista.yml
│ └── vyos.yml
├── host_vars/
├── playbooks/
│ ├── 01-audit.yml
│ ├── 02-backup.yml
│ ├── 03-interfaces.yml
│ ├── 04-vlans.yml
│ ├── 05-ospf.yml
│ ├── 06-bgp.yml
│ └── 07-validate.yml
└── backups/
Configuramos ansible.cfg:
[defaults]
inventory = inventory.yml
host_key_checking = True
retry_files_enabled = False
timeout = 30
[persistent_connection]
connect_timeout = 30
command_timeout = 60
En el laboratorio utilizaremos autenticación SSH ya preparada. Debemos aceptar previamente las claves de los equipos y evitar desactivar la comprobación de identidad SSH.
3. Inventario multi-vendor
Creamos inventory.yml:
---
all:
children:
network:
children:
cisco:
hosts:
cisco01:
ansible_host: 192.168.56.11
arista:
hosts:
arista01:
ansible_host: 192.168.56.12
vyos:
hosts:
vyos01:
ansible_host: 192.168.56.13
Ahora configuramos las variables por fabricante.
group_vars/cisco.yml:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_user: ansible
ansible_become: true
ansible_become_method: enable
group_vars/arista.yml:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: arista.eos.eos
ansible_user: ansible
group_vars/vyos.yml:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: vyos.vyos.vyos
ansible_user: ansible
Las credenciales y contraseñas de enable, si se necesitan, se almacenarán mediante Ansible Vault.
Verificamos el inventario:
ansible-inventory --graph
Con esto ya tenemos una estructura de trabajo independiente del fabricante.
4. Primera automatización: auditoría
Antes de aplicar cambios necesitamos comprobar que Ansible puede comunicarse con los dispositivos.
Creamos playbooks/01-audit.yml:
---
- name: Auditoria inicial de red
hosts: network
gather_facts: false
tasks:
- name: Obtener version del dispositivo
ansible.netcommon.cli_command:
command: show version
register: version
- name: Mostrar informacion
ansible.builtin.debug:
msg:
- "Equipo: {{ inventory_hostname }}"
- "{{ version.stdout }}"
Ejecutamos:
ansible-playbook playbooks/01-audit.yml
Este primer playbook utiliza cli_command, un módulo genérico compatible con los tres sistemas operativos.
Si alguno falla, revisaremos la conectividad de gestión, la autenticación, el transporte network_cli y los permisos del usuario antes de continuar.
5. Backup automático de configuraciones
Antes de automatizar cualquier cambio, guardaremos la configuración actual.
Creamos playbooks/02-backup.yml:
---
- name: Backup Cisco
hosts: cisco
gather_facts: false
tasks:
- name: Guardar running-config
cisco.ios.ios_config:
backup: true
backup_options:
dir_path: "{{ playbook_dir }}/../backups"
filename: "{{ inventory_hostname }}.cfg"
- name: Backup Arista
hosts: arista
gather_facts: false
tasks:
- name: Guardar running-config
arista.eos.eos_config:
backup: true
backup_options:
dir_path: "{{ playbook_dir }}/../backups"
filename: "{{ inventory_hostname }}.cfg"
- name: Backup VyOS
hosts: vyos
gather_facts: false
tasks:
- name: Guardar configuracion
vyos.vyos.vyos_config:
backup: true
backup_options:
dir_path: "{{ playbook_dir }}/../backups"
filename: "{{ inventory_hostname }}.cfg"
Ejecutamos:
ansible-playbook playbooks/02-backup.yml
Los archivos se guardarán en backups/ dentro de nuestro proyecto.
Como hemos definido nombres fijos, los backups siguientes sustituirán los anteriores. En un entorno real utilizaríamos fechas y horas en el nombre del archivo, además de conservar varias versiones.
6. Configurar interfaces L3
Vamos a configurar los enlaces punto a punto y las loopbacks.
Creamos playbooks/03-interfaces.yml:
---
- name: Interfaces Cisco
hosts: cisco
gather_facts: false
tasks:
- name: Configurar interfaces
cisco.ios.ios_config:
lines:
- "{{ item.line }}"
parents:
- "interface {{ item.name }}"
loop:
- name: GigabitEthernet0/1
line: "ip address 10.0.12.1 255.255.255.252"
- name: Loopback0
line: "ip address 10.255.0.1 255.255.255.255"
- name: Activar enlace
cisco.ios.ios_config:
parents:
- interface GigabitEthernet0/1
lines:
- no shutdown
- name: Interfaces Arista
hosts: arista
gather_facts: false
tasks:
- name: Configurar interfaces
arista.eos.eos_config:
lines:
- "ip address {{ item.ip }}"
- no shutdown
parents:
- "interface {{ item.name }}"
loop:
- name: Ethernet1
ip: 10.0.12.2/30
- name: Ethernet2
ip: 10.0.23.1/30
- name: Loopback0
ip: 10.255.0.2/32
- name: Interfaces VyOS
hosts: vyos
gather_facts: false
tasks:
- name: Configurar interfaces
vyos.vyos.vyos_config:
lines:
- set interfaces ethernet eth1 address '10.0.23.2/30'
- set interfaces loopback lo address '10.255.0.3/32'
save: true
En equipos Cisco y Arista debemos asegurarnos de que los puertos Ethernet sean interfaces enrutadas, no switchports. Dependiendo de la imagen, habrá que aplicar no switchport y, en Cisco, disponer de una plataforma que permita configurar IP directamente en la interfaz.
Ejecutamos:
ansible-playbook playbooks/03-interfaces.yml \
--check --diff
Revisamos los cambios propuestos y, si son correctos:
ansible-playbook playbooks/03-interfaces.yml
La primera comprobación consiste en verificar conectividad IP entre vecinos directamente conectados.
Desde Cisco:
ping 10.0.12.2
Desde Arista:
ping 10.0.23.2
Si los enlaces físicos están activos, las interfaces pertenecen a la misma subred y no existen filtros que lo impidan, ambos deberían responder.
7. Automatizar VLAN y trunk
Esta parte la realizaremos en imágenes con capacidad L2, como Cisco IOSvL2 y Arista cEOS.
Creamos tres VLAN:
vlans:
- vlan_id: 10
name: USERS
- vlan_id: 20
name: VOICE
- vlan_id: 30
name: WIFI
Para la práctica utilizaremos esta misma lista en ambos fabricantes, pero en una estructura definitiva la almacenaríamos en variables compartidas.
playbooks/04-vlans.yml:
---
- name: VLAN Cisco
hosts: cisco
gather_facts: false
vars:
vlans:
- { vlan_id: 10, name: USERS }
- { vlan_id: 20, name: VOICE }
- { vlan_id: 30, name: WIFI }
tasks:
- name: Crear VLANs
cisco.ios.ios_vlans:
config: "{{ vlans }}"
state: merged
- name: VLAN Arista
hosts: arista
gather_facts: false
vars:
vlans:
- { vlan_id: 10, name: USERS }
- { vlan_id: 20, name: VOICE }
- { vlan_id: 30, name: WIFI }
tasks:
- name: Crear VLANs
arista.eos.eos_vlans:
config: "{{ vlans }}"
state: merged
Ejecutamos:
ansible-playbook playbooks/04-vlans.yml
Para configurar un trunk en Cisco:
- name: Configurar trunk Cisco
cisco.ios.ios_l2_interfaces:
config:
- name: GigabitEthernet0/2
mode: trunk
trunk:
allowed_vlans: "10,20,30"
state: merged
En Arista:
- name: Configurar trunk Arista
arista.eos.eos_l2_interfaces:
config:
- name: Ethernet3
mode: trunk
trunk:
trunk_allowed_vlans:
- "10"
- "20"
- "30"
state: merged
Para comprobar el resultado:
Cisco:
show vlan brief
show interfaces trunk
Arista:
show vlan
show interfaces trunk
Esta práctica requiere un enlace L2 entre los puertos seleccionados. No debemos convertir por error los enlaces enrutados de OSPF en trunks; utilizaremos interfaces y conexiones adicionales.
Los módulos ios_l2_interfaces y eos_l2_interfaces permiten gestionar estos recursos declarativamente. <Cite refs={["turn693166search1","turn693166search0"]}/>
8. Configurar OSPF entre los tres fabricantes
Ahora vamos a formar adyacencias OSPF.
Utilizaremos una única área:
Area 0
Y los router ID:
Cisco -> 10.255.0.1
Arista -> 10.255.0.2
VyOS -> 10.255.0.3
Creamos playbooks/05-ospf.yml:
---
- name: OSPF Cisco
hosts: cisco
gather_facts: false
tasks:
- name: Configurar OSPF
cisco.ios.ios_config:
parents:
- router ospf 1
lines:
- router-id 10.255.0.1
- network 10.0.12.0 0.0.0.3 area 0
- network 10.255.0.1 0.0.0.0 area 0
- name: OSPF Arista
hosts: arista
gather_facts: false
tasks:
- name: Configurar OSPF
arista.eos.eos_config:
parents:
- router ospf 1
lines:
- router-id 10.255.0.2
- network 10.0.12.0/30 area 0.0.0.0
- network 10.0.23.0/30 area 0.0.0.0
- network 10.255.0.2/32 area 0.0.0.0
- name: OSPF VyOS
hosts: vyos
gather_facts: false
tasks:
- name: Configurar OSPF
vyos.vyos.vyos_config:
lines:
- set protocols ospf parameters router-id '10.255.0.3'
- set protocols ospf area 0 network '10.0.23.0/30'
- set protocols ospf area 0 network '10.255.0.3/32'
save: true
La sintaxis de VyOS corresponde a la rama 1.4/1.5. En cada entorno hay que comprobar que la versión utilizada admite los comandos. <Cite refs={["turn855232search1","turn693166search8"]}/>
Ejecutamos:
ansible-playbook playbooks/05-ospf.yml
Comprobamos las adyacencias:
Cisco:
show ip ospf neighbor
show ip route ospf
Arista:
show ip ospf neighbor
show ip route ospf
VyOS:
show ip ospf neighbor
show ip route ospf
Cuando todo funcione, Cisco debería conocer las rutas hacia la loopback de VyOS, y VyOS debería conocer la loopback de Cisco.
Por ejemplo, desde Cisco:
ping 10.255.0.3 source Loopback0
La conectividad extremo a extremo confirmará que no solo tenemos vecinos OSPF, sino también rutas utilizables.
9. Crear una sesión eBGP entre Arista y VyOS
Ahora vamos a configurar BGP entre Arista y VyOS.
Trabajaremos con:
Arista AS 65001
VyOS AS 65002
Los vecinos serán:
Arista: 10.0.23.1
VyOS: 10.0.23.2
Creamos playbooks/06-bgp.yml:
---
- name: BGP Arista
hosts: arista
gather_facts: false
tasks:
- name: Configurar vecino eBGP
arista.eos.eos_config:
parents:
- router bgp 65001
lines:
- router-id 10.255.0.2
- neighbor 10.0.23.2 remote-as 65002
- name: BGP VyOS
hosts: vyos
gather_facts: false
tasks:
- name: Configurar vecino eBGP
vyos.vyos.vyos_config:
lines:
- set protocols bgp system-as '65002'
- set protocols bgp parameters router-id '10.255.0.3'
- set protocols bgp neighbor 10.0.23.1 remote-as '65001'
- set protocols bgp address-family ipv4-unicast network '10.255.0.3/32'
save: true
La configuración de VyOS 1.4/1.5 utiliza system-as, a diferencia de la sintaxis clásica router bgp ASN de Cisco y Arista. <Cite ref="turn855232search0"/>
Ejecutamos:
ansible-playbook playbooks/06-bgp.yml
Comprobamos:
Arista:
show ip bgp summary
show ip bgp
VyOS:
show bgp summary
show ip bgp
Debemos verificar que el estado de la sesión sea Established y que los prefijos esperados se intercambien.
En el ejemplo VyOS anuncia su loopback /32, que ya existe en su tabla de rutas. Arista no anuncia inicialmente ningún prefijo BGP. Por tanto, esta práctica sirve para comprobar una sesión eBGP y la recepción de un prefijo, no una política completa de routing.
En producción aplicaríamos filtros de entrada y salida, límites de prefijos, políticas de routing y autenticación cuando sea compatible. Tampoco redistribuiríamos automáticamente rutas BGP y OSPF sin una política explícita.
10. Validar automáticamente el estado de la red
Ejecutar los cambios es solamente una parte del trabajo. Necesitamos comprobar los resultados.
Creamos playbooks/07-validate.yml:
---
- name: Validar OSPF Cisco
hosts: cisco
gather_facts: false
tasks:
- name: Esperar vecino OSPF
cisco.ios.ios_command:
commands:
- show ip ospf neighbor
wait_for:
- result[0] contains FULL
retries: 10
interval: 5
- name: Validar OSPF Arista
hosts: arista
gather_facts: false
tasks:
- name: Esperar vecinos OSPF
arista.eos.eos_command:
commands:
- show ip ospf neighbor
wait_for:
- result[0] contains FULL
retries: 10
interval: 5
- name: Validar BGP Arista
hosts: arista
gather_facts: false
tasks:
- name: Consultar estado BGP
arista.eos.eos_command:
commands:
- show ip bgp summary
register: bgp_status
- name: Mostrar estado
ansible.builtin.debug:
var: bgp_status.stdout
Ejecutamos:
ansible-playbook playbooks/07-validate.yml
Este primer validador OSPF busca al menos un vecino en estado FULL. Es útil para el laboratorio, pero no garantiza que todos los vecinos esperados estén establecidos.
En un entorno de producción mejoraríamos estas pruebas convirtiendo la salida de los comandos en datos estructurados y comprobando las IP de los vecinos, su estado y la cantidad de prefijos recibidos. La comprobación BGP anterior es informativa y todavía requiere inspección; no debe considerarse una prueba automática completa.
11. Ejecutar todo el laboratorio
Una vez revisados los playbooks, podemos ejecutar la secuencia:
# 1. Auditoria
ansible-playbook playbooks/01-audit.yml
# 2. Backup
ansible-playbook playbooks/02-backup.yml
# 3. Interfaces
ansible-playbook playbooks/03-interfaces.yml
# 4. OSPF
ansible-playbook playbooks/05-ospf.yml
# 5. BGP
ansible-playbook playbooks/06-bgp.yml
# 6. Validacion
ansible-playbook playbooks/07-validate.yml
La práctica L2 se ejecutará aparte, sobre dispositivos con interfaces de switching:
ansible-playbook playbooks/04-vlans.yml
Para cualquier cambio de configuración que soporte simulación:
ansible-playbook playbooks/03-interfaces.yml --check --diff
Hay que recordar que --check no garantiza una simulación completa en todos los módulos de networking. Comprobaremos la compatibilidad antes de confiar en su resultado.
12. Incorporar controles de producción
Con el laboratorio funcionando, podemos mejorar el proceso de despliegue.
Una estrategia razonable consiste en limitar los cambios a un equipo inicialmente:
ansible-playbook playbooks/05-ospf.yml \
--limit arista01
Después ampliar el alcance a los demás dispositivos.
También conviene añadir serial para evitar cambiar toda una red simultáneamente:
- name: Cambio progresivo de red
hosts: network
gather_facts: false
serial: 1
Para cambios de routing que puedan cortar el acceso de gestión, necesitamos un mecanismo de recuperación independiente: acceso por consola, OOB o funcionalidades de rollback/confirmed commit cuando estén disponibles.
Por último, los cambios deben registrarse. Ansible puede integrarse con Git, sistemas CI/CD, NetBox y plataformas de observabilidad para disponer de inventario, control de versiones y comprobaciones antes y después de cada modificación.
Resultado del laboratorio
Al completar las pruebas tendremos un entorno donde Ansible se comunica con tres fabricantes diferentes desde un único controlador.
Habremos trabajado con interfaces L2/L3, VLAN, trunks, OSPF, BGP y backups, pero sobre todo habremos establecido un procedimiento reproducible: consultar el estado, guardar la configuración, aplicar los cambios y validarlos.
La siguiente evolución natural sería almacenar los datos de red en una fuente única, como NetBox, y utilizar Ansible para convertir ese modelo en configuraciones específicas para cada fabricante. De esta manera evitamos mantener los mismos datos repetidos en múltiples inventarios y playbooks.