Ansible para Networking
Automatizar servidores con Ansible y automatizar equipos de red se parecen en la estructura de los playbooks, pero internamente funcionan de forma diferente.
En un servidor Linux, Ansible normalmente accede por SSH y ejecuta módulos mediante Python en el host remoto. Un switch, router o firewall normalmente no dispone de ese entorno. Los módulos de networking se ejecutan en el nodo de control y se comunican con el dispositivo mediante CLI/SSH, NETCONF o APIs HTTP.
En este tutorial parto de que Ansible ya está instalado y sabemos manejar inventarios, variables, Vault, Jinja2, roles y playbooks. El objetivo es trabajar directamente con automatización de red.
1. El modelo de trabajo
En networking hay cuatro piezas que debemos tener claras:
Ansible
|
+-- network_cli ---- SSH / CLI
|
+-- netconf -------- NETCONF / XML / YANG
|
+-- httpapi -------- REST API / API del fabricante
|
+-- colección ------ módulos específicos del NOS
Los tres transportes más habituales son: ansible_connection: nsible.netcommon.network_cli para CLI sobre SSH, ansible_connection: nsible.netcommon.netconf para NETCONF, y ansible_connection: nsible.netcommon.httpapi para equipos gestionados mediante API. Ansible mantiene estas conexiones de forma persistente, lo que evita establecer una sesión nueva para cada tarea.
Además necesitamos indicar qué sistema operativo de red estamos controlando: ansible_network_os: cisco.ios.ios`.
Por ejemplo:
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
Ansible ya sabe que debe abrir una sesión CLI y cómo interpretar los prompts y modos de configuración de Cisco IOS.
2. Colecciones que vamos a utilizar
La automatización moderna de red con Ansible está organizada principalmente mediante colecciones.
Un requirements.yml razonable para un entorno multi-vendor puede ser:
---
collections:
- name: ansible.netcommon
- name: ansible.utils
- name: cisco.ios
- name: cisco.nxos
- name: cisco.iosxr
- name: arista.eos
- name: junipernetworks.junos
- name: arubanetworks.aoscx
- name: vyos.vyos
- name: community.routeros
- name: fortinet.fortios
- name: paloaltonetworks.panos
- name: check_point.mgmt
- name: f5networks.f5_modules
Instalamos todo con:
ansible-galaxy collection install -r requirements.yml
Y comprobamos:
ansible-galaxy collection list
Conviene fijar versiones en un entorno de producción para evitar que una actualización cambie parámetros o comportamiento.
Una consideración actual es Junos: junipernetworks.junos dejó de formar parte del paquete Ansible a partir de Ansible 14, aunque la colección puede seguir instalándose separadamente.
3. Estructura del proyecto
Para un entorno real prefiero separar inventario, datos, templates y automatizaciones:
network-ansible/
│
├── ansible.cfg
├── requirements.yml
├── inventory.yml
│
├── group_vars/
│ ├── all.yml
│ ├── cisco_ios.yml
│ ├── cisco_nxos.yml
│ ├── arista.yml
│ ├── juniper.yml
│ └── vault.yml
│
├── host_vars/
│
├── playbooks/
│ ├── audit.yml
│ ├── backup.yml
│ ├── vlans.yml
│ ├── interfaces.yml
│ └── ntp.yml
│
├── templates/
│ ├── ios/
│ ├── eos/
│ └── junos/
│
└── roles/
└── network_base/
El inventario describe los dispositivos. Las variables describen cómo conectarse y qué configuración queremos. Los playbooks deberían contener principalmente la lógica.
4. Inventario multi-vendor
Un inventario sencillo:
---
all:
children:
cisco_ios:
hosts:
sw01:
ansible_host: 10.10.10.11
sw02:
ansible_host: 10.10.10.12
cisco_nxos:
hosts:
nx01:
ansible_host: 10.10.20.11
arista:
hosts:
eos01:
ansible_host: 10.10.30.11
juniper:
hosts:
junos01:
ansible_host: 10.10.40.11
mikrotik:
hosts:
rtr01:
ansible_host: 10.10.50.11
vyos:
hosts:
vyos01:
ansible_host: 10.10.60.11
Ahora definimos el transporte por fabricante.
group_vars/cisco_ios.yml:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_user: ansible
ansible_become: true
ansible_become_method: enable
Cisco IOS permite utilizar become con become_method: enable para entrar en modo privilegiado.
Para NX-OS:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.nxos.nxos
ansible_user: ansible
Arista:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: arista.eos.eos
ansible_user: ansible
Juniper:
---
ansible_connection: ansible.netcommon.netconf
ansible_network_os: junipernetworks.junos.junos
ansible_user: ansible
NETCONF debe estar habilitado previamente en el equipo.
VyOS:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: vyos.vyos.vyos
ansible_user: ansible
MikroTik por SSH:
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: community.routeros.routeros
ansible_user: ansible
RouterOS también puede gestionarse directamente mediante su API, algo especialmente interesante cuando queremos realizar modificaciones estructuradas.
Las contraseñas nunca deberían estar directamente en estos archivos. Para eso utilizamos Vault:
ansible-vault create group_vars/vault.yml
5. El primer audit multi-vendor
Antes de modificar configuraciones conviene comprobar conectividad y obtener información básica.
Con los dispositivos basados en CLI podemos utilizar:
---
- name: Auditoria de dispositivos de red
hosts:
- cisco_ios
- cisco_nxos
- arista
- vyos
gather_facts: false
tasks:
- name: Ejecutar show version
ansible.netcommon.cli_command:
command: show version
register: version
- name: Mostrar resultado
ansible.builtin.debug:
var: version.stdout
ansible.netcommon.cli_command permite ejecutar comandos operativos sobre plataformas soportadas por network_cli, evitando utilizar un módulo diferente para cada fabricante cuando no es necesario.
Pero los comandos no siempre coinciden entre sistemas operativos.
Podemos almacenar el comando como dato:
Cisco:
show_interfaces_command: show ip interface brief
Arista:
show_interfaces_command: show ip interface brief
VyOS:
show_interfaces_command: show interfaces
Y mantener un único playbook:
- name: Estado de interfaces
ansible.netcommon.cli_command:
command: "{{ show_interfaces_command }}"
register: interfaces
Este patrón es muy útil en entornos multi-vendor: separar qué queremos consultar de cómo lo implementa cada fabricante.
6. Módulos command y config
Prácticamente todas las colecciones de networking siguen un patrón parecido.
En Cisco IOS tenemos:
ios_command
ios_config
ios_facts
Arista:
eos_command
eos_config
eos_facts
NX-OS:
nxos_command
nxos_config
nxos_facts
VyOS:
vyos_command
vyos_config
vyos_facts
Los módulos command son para comandos operativos.
Ejemplo:
- name: Consultar vecinos OSPF
cisco.ios.ios_command:
commands:
- show ip ospf neighbor
- show ip route ospf
No debemos utilizarlos para entrar manualmente en configure terminal. Para modificar configuración utilizamos ios_config o, preferiblemente cuando exista, un resource module.
Ejemplo:
- name: Configurar NTP
cisco.ios.ios_config:
lines:
- ntp server 10.10.1.10
- ntp server 10.10.1.11
7. Resource Modules: la forma más interesante de automatizar red
Cuando existe un resource module prefiero utilizarlo antes que enviar líneas de configuración.
Por ejemplo:
- name: Configurar interfaz L3
cisco.ios.ios_l3_interfaces:
config:
- name: GigabitEthernet1
ipv4:
- address: 10.20.30.1/30
state: merged
Aquí no estamos escribiendo comandos Cisco. Estamos describiendo el estado deseado.
Los resource modules normalizan recursos como:
interfaces
L2 interfaces
L3 interfaces
VLAN
ACL
BGP
OSPF
LACP
LLDP
rutas
hostname
logging
NTP
dependiendo de la plataforma.
Además permiten trabajar con estados como:
merged
replaced
overridden
deleted
gathered
rendered
parsed
según el módulo.
Por ejemplo:
state: merged
añade o modifica únicamente lo indicado.
state: replaced
reemplaza la configuración del recurso indicado.
Y:
state: overridden
puede convertir lo declarado en Ansible en la fuente de verdad del recurso completo.
Este último debe usarse con mucho cuidado.
8. Crear VLAN: mismo objetivo, diferentes fabricantes
Cisco IOS / IOS-XE
- name: Crear VLAN
cisco.ios.ios_vlans:
config:
- vlan_id: 120
name: SERVIDORES
state: merged
Cisco NX-OS
- name: Crear VLAN
cisco.nxos.nxos_vlans:
config:
- vlan_id: 120
name: SERVIDORES
state: merged
NX-OS dispone de resource modules específicos para VLAN, BGP, interfaces, ACL, EVPN y otros recursos.
Arista EOS
- name: Crear VLAN
arista.eos.eos_vlans:
config:
- vlan_id: 120
name: SERVIDORES
state: merged
Aquí vemos una de las ventajas de los resource modules: la estructura de datos puede ser prácticamente idéntica aunque cambie el fabricante.
9. Configurar un puerto de acceso
Cisco IOS:
- name: Configurar puerto L2
cisco.ios.ios_l2_interfaces:
config:
- name: GigabitEthernet1/0/10
mode: access
access:
vlan: 120
state: merged
Podemos separar además la configuración física:
- name: Configurar interfaz
cisco.ios.ios_interfaces:
config:
- name: GigabitEthernet1/0/10
description: SERVER-01
enabled: true
state: merged
La intención queda mucho más clara que enviando:
interface Gi1/0/10
description SERVER-01
switchport mode access
switchport access vlan 120
no shutdown
Y además obtenemos idempotencia real a nivel del recurso.
10. Configuración mediante bloques CLI
No siempre existe un resource module para lo que necesitamos.
Entonces utilizamos el módulo config.
Por ejemplo:
- name: Configurar interfaz
cisco.ios.ios_config:
parents:
- interface GigabitEthernet1/0/20
lines:
- description UPLINK-SW02
- switchport mode trunk
- switchport trunk allowed vlan 10,20,30,120
parents indica el contexto.
Ansible genera conceptualmente:
interface GigabitEthernet1/0/20
description UPLINK-SW02
switchport mode trunk
switchport trunk allowed vlan 10,20,30,120
Esta técnica sigue siendo muy útil para configuraciones que no disponen de módulo específico.
11. Configuración genérica con cli_config
También existe una alternativa independiente del fabricante:
- name: Aplicar configuracion CLI
ansible.netcommon.cli_config:
config: |
interface Loopback100
description Ansible
cli_config permite enviar configuración textual mediante network_cli y puede generar backup antes de realizar el cambio.
También podemos utilizar una plantilla:
- name: Aplicar configuracion
ansible.netcommon.cli_config:
config: "{{ lookup('ansible.builtin.template', 'ios/base.j2') }}"
backup: true
Es especialmente útil cuando queremos construir una capa propia de automatización multi-vendor.
12. Separar datos de configuración
Una buena automatización no debería tener los datos incrustados en el playbook.
Por ejemplo:
vlans:
- id: 10
name: USERS
- id: 20
name: VOICE
- id: 30
name: WIFI
- id: 120
name: SERVERS
El mismo modelo puede utilizarse para diferentes fabricantes.
Cisco:
- name: Configurar VLANs Cisco
cisco.ios.ios_vlans:
config: "{{ vlans | map('combine', {}) | list }}"
state: merged
En proyectos grandes suelo llevar este concepto algo más lejos:
datos de red
|
v
modelo normalizado
|
+---- Cisco
+---- Arista
+---- Juniper
+---- Aruba
La topología y la política de red son los datos. El fabricante solo determina cómo traducimos esos datos al equipo.
13. Jinja2 sigue siendo muy útil
Aunque existan resource modules, habrá configuraciones donde una plantilla sea más práctica.
Por ejemplo:
{% for vlan in vlans %}
vlan {{ vlan.id }}
name {{ vlan.name }}
{% endfor %}
Con los datos:
vlans:
- id: 10
name: USERS
- id: 20
name: VOICE
podemos generar:
vlan 10
name USERS
vlan 20
name VOICE
Y aplicarlo:
- name: Generar y aplicar configuracion
ansible.netcommon.cli_config:
config: "{{ lookup('ansible.builtin.template', 'ios/vlans.j2') }}"
No utilizaría Jinja para todo. Si existe un resource module estable para un recurso, normalmente es preferible utilizarlo.
14. Juniper Junos
Junos funciona especialmente bien con NETCONF.
La conexión:
ansible_connection: ansible.netcommon.netconf
ansible_network_os: junipernetworks.junos.junos
Una configuración sencilla:
- name: Configurar hostname
junipernetworks.junos.junos_config:
lines:
- set system host-name junos01
También podemos realizar backup:
- name: Backup Junos
junipernetworks.junos.junos_config:
backup: true
NETCONF resulta interesante porque dejamos de automatizar una sesión de terminal y empezamos a trabajar mediante un protocolo diseñado específicamente para configuración de red.
Para dispositivos que implementen NETCONF estándar también podemos utilizar directamente:
- name: Aplicar configuracion NETCONF
ansible.netcommon.netconf_config:
content: "{{ lookup('file', 'config.xml') }}"
netconf_config trabaja con configuración XML y puede aprovechar capacidades como candidate y confirmed commit cuando el dispositivo las soporta.
15. Aruba AOS-CX
AOS-CX permite trabajar mediante REST API o CLI.
Un inventario para API puede utilizar:
ansible_network_os: arubanetworks.aoscx.aoscx
ansible_connection: arubanetworks.aoscx.aoscx
ansible_aoscx_validate_certs: false
ansible_aoscx_use_proxy: false
ansible_acx_no_proxy: true
Crear una VLAN:
- name: Crear VLAN 120
arubanetworks.aoscx.aoscx_vlan:
vlan_id: 120
name: SERVERS
description: Servers VLAN
También podemos utilizar SSH:
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: arubanetworks.aoscx.aoscx
Aruba recomienda separar en plays diferentes las operaciones API y CLI cuando necesitamos utilizar ambos métodos sobre el mismo switch.
16. MikroTik RouterOS
Con RouterOS podemos elegir SSH o API.
Por SSH:
- name: Consultar recursos
community.routeros.command:
commands:
- /system resource print
Los comandos SSH deben especificarse con la ruta completa:
/system resource print
/ip address print
/interface print
Para configuraciones más estructuradas prefiero la API.
Ejemplo de modificación:
- name: Configurar NAT
community.routeros.api_modify:
hostname: "{{ ansible_host }}"
username: "{{ routeros_user }}"
password: "{{ routeros_password }}"
path: ip firewall nat
data:
- action: masquerade
chain: srcnat
comment: NAT to WAN
out-interface-list: WAN
api_modify es idempotente para las rutas que soporta y dispone de check_mode y diff_mode.
17. FortiGate / FortiOS
En firewalls tiene mucho más sentido trabajar con la API que intentar automatizar la CLI.
Fortinet proporciona la colección:
fortinet.fortios
y actualmente recomienda httpapi frente al antiguo fortiosapi.
Ejemplo de interfaz:
- name: Configurar interfaz FortiGate
fortinet.fortios.fortios_system_interface:
vdom: root
state: present
access_token: "{{ fortigate_token }}"
system_interface:
name: port3
alias: LAN-SERVERS
ip: 10.20.30.1 255.255.255.0
allowaccess:
- ping
- https
La colección dispone de módulos para interfaces, políticas, objetos, VPN, routing, SD-WAN, HA y gran parte de la configuración de FortiOS.
18. Palo Alto Networks
PAN-OS dispone de su propia colección:
ansible-galaxy collection install paloaltonetworks.panos
La automatización se realiza mediante la API de PAN-OS.
Podemos definir el dispositivo:
provider:
ip_address: "{{ ansible_host }}"
username: "{{ panos_user }}"
password: "{{ panos_password }}"
Y crear un objeto:
- name: Crear objeto de red
paloaltonetworks.panos.panos_address_object:
provider: "{{ provider }}"
name: SRV-WEB
value: 10.20.30.100
description: Web server
tag:
- PROD
A partir de ahí podemos automatizar reglas de seguridad, NAT, interfaces, zonas, objetos o configuración de Panorama.
19. Check Point
Check Point utiliza su Management API.
La colección actual es:
check_point.mgmt
Ejemplo:
- name: Crear objeto host
check_point.mgmt.cp_mgmt_host:
name: SRV-WEB
ip_address: 10.20.30.100
state: present
auto_publish_session: true
Las operaciones se realizan contra el Management Server mediante Web Services API.
Esto permite automatizar objetos, grupos, reglas, NAT, capas de acceso y publicación de políticas.
20. F5 BIG-IP
Para BIG-IP tenemos:
f5networks.f5_modules
Por ejemplo, crear un pool:
- name: Crear pool
f5networks.f5_modules.bigip_pool:
name: WEB_POOL
partition: Common
lb_method: least-connections-member
provider:
server: "{{ ansible_host }}"
user: "{{ f5_user }}"
password: "{{ f5_password }}"
validate_certs: false
delegate_to: localhost
Podemos añadir miembros:
- name: Añadir servidor
f5networks.f5_modules.bigip_pool_member:
pool: WEB_POOL
name: WEB01
host: 10.20.30.101
port: 443
provider: "{{ f5_provider }}"
delegate_to: localhost
En BIG-IP hay que distinguir entre modificar la configuración activa y guardarla de forma persistente; la documentación de F5 indica utilizar bigip_config cuando sea necesario guardar la configuración en disco.
21. Huawei y otros fabricantes NETCONF
No recomiendo construir una automatización nueva dependiendo de módulos abandonados simplemente porque en el pasado estuvieran incluidos en community.network.
community.network fue retirada del paquete Ansible 12.
En equipos Huawei que dispongan de NETCONF podemos plantear una automatización basada directamente en:
ansible_connection: ansible.netcommon.netconf
y:
- name: Aplicar configuracion NETCONF
ansible.netcommon.netconf_config:
content: "{{ lookup('template', 'huawei/interface.xml.j2') }}"
El XML utilizado dependerá del modelo YANG soportado por el equipo.
Este enfoque también sirve para otras plataformas que implementen NETCONF estándar aunque Ansible no tenga un módulo específico para cada función.
22. Backup antes de tocar nada
Una de las automatizaciones más útiles y fáciles de implementar es el backup masivo.
Para dispositivos soportados por network_cli:
---
- name: Backup de red
hosts: network_cli_devices
gather_facts: false
tasks:
- name: Backup running-config
ansible.netcommon.cli_backup:
dir_path: backups
filename: "{{ inventory_hostname }}.cfg"
cli_backup proporciona un mecanismo común para guardar la configuración en el nodo de control.
También podemos generar backup como parte de un cambio:
- name: Aplicar configuracion
ansible.netcommon.cli_config:
config: "{{ lookup('template', 'config.j2') }}"
backup: true
backup_options:
dir_path: backups
filename: "{{ inventory_hostname }}.cfg"
23. --check y --diff
Antes de modificar una red real conviene acostumbrarse a ejecutar:
ansible-playbook playbooks/vlans.yml --check
y cuando el módulo lo soporte:
ansible-playbook playbooks/vlans.yml --check --diff
El resultado debe revisarse exactamente igual que revisaríamos un cambio manual antes de introducirlo.
Para un cambio importante mi secuencia habitual sería:
pre-check
↓
backup
↓
check/diff
↓
cambio
↓
post-check
No considero que una tarea haya terminado simplemente porque Ansible devuelva:
changed=1 failed=0
El estado operacional de la red debe validarse después.
24. Validaciones antes y después del cambio
Los módulos command permiten esperar a que aparezca una determinada condición.
Ejemplo Cisco:
- name: Esperar vecino OSPF
cisco.ios.ios_command:
commands:
- show ip ospf neighbor
wait_for:
- result[0] contains FULL
retries: 10
interval: 5
Con esto el playbook no solo configura OSPF: comprueba que realmente aparece una adyacencia.
También podemos utilizar assert:
- name: Validar numero de vecinos
ansible.builtin.assert:
that:
- ospf_neighbors | length >= 2
fail_msg: "No se han formado todos los vecinos OSPF"
Aquí es donde la automatización empieza a resultar realmente interesante: pasamos de ejecutar comandos a comprobar el estado de la red.
25. Convertir CLI en datos estructurados
Uno de los problemas clásicos de networking es recibir:
GigabitEthernet1/0/1 up up
GigabitEthernet1/0/2 down down
GigabitEthernet1/0/3 up up
como texto.
Para automatizar necesitamos datos.
ansible.utils.cli_parse permite convertir salida CLI en estructuras que Ansible puede procesar mediante parsers nativos, TextFSM, ntc_templates, TTP, pyATS, XML o JSON.
Ejemplo:
- name: Obtener interfaces estructuradas
ansible.utils.cli_parse:
command: show interfaces
parser:
name: ansible.netcommon.ntc_templates
set_fact: interfaces
Entonces podemos trabajar con:
- name: Detectar interfaces caidas
ansible.builtin.debug:
msg: "{{ item.interface }} esta DOWN"
loop: "{{ interfaces }}"
when:
- item.link_status == "down"
Esto permite construir automatizaciones de troubleshooting, auditoría y compliance bastante más potentes.
26. Compliance de configuración
Supongamos que todos los switches deben cumplir estas condiciones:
required_ntp:
- 10.10.1.10
- 10.10.1.11
required_dns:
- 10.10.2.10
required_syslog:
- 10.10.3.10
Podemos obtener facts o configuración del equipo y compararlos con este modelo.
Conceptualmente:
%%{init: {
"theme": "base",
"themeVariables": {
"fontFamily": "sans-serif",
"fontSize": "13px",
"edgeLabelBackground": "#ffffff"
}
} }%%
flowchart TB
classDef state fill:#2b3e50,stroke:#1a252f,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef process fill:#2980b9,stroke:#1f618d,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef decision fill:#e67e22,stroke:#d35400,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef ok fill:#16a085,stroke:#117a65,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef drift fill:#c0392b,stroke:#962d22,stroke-width:2px,color:#fff,rx:8,ry:8;
DES["<b>CONFIGURACIÓN DESEADA</b>"]:::state
ANS["<b>ANSIBLE</b><br/>Motor de Automatización"]:::process
REAL["<b>CONFIGURACIÓN REAL</b><br/>Estado en el Nodo"]:::state
CMP["<b>COMPARACIÓN</b><br/>Motor de Verificación"]:::process
subgraph RESULTS ["Resultado del Estado"]
direction LR
OK["<b>OK</b><br/>Sin cambios / Sincronizado"]:::ok
DRIFT["<b>DRIFT</b><br/>Diferencia detectada"]:::drift
end
DES --> ANS
ANS --> REAL
REAL --> CMP
CMP --> OK
CMP --> DRIFT
linkStyle default stroke:#7f8c8d,stroke-width:1.5px;
Esto convierte Ansible en una herramienta de control de configuración, no simplemente en una forma de ejecutar comandos más rápido.
27. Desplegar cambios por lotes
Nunca aplicaría inicialmente un cambio importante a 300 switches simultáneamente.
Podemos utilizar:
---
- name: Cambio progresivo
hosts: access_switches
gather_facts: false
serial: 5
tasks:
- name: Aplicar cambio
...
Primero cinco equipos.
Si todo funciona, Ansible continúa con los siguientes.
Para un cambio especialmente sensible:
serial: 1
Un dispositivo cada vez.
También podemos limitar manualmente:
ansible-playbook change.yml --limit sw01
Después:
ansible-playbook change.yml --limit site_barcelona
Y finalmente:
ansible-playbook change.yml
Automatizar no significa desplegar cambios sin control.
28. Manejar timeouts
Los dispositivos de red pueden tardar bastante más que un servidor en responder a ciertas operaciones.
Podemos ajustar:
ansible_command_timeout: 60
ansible_connect_timeout: 30
O en ansible.cfg:
[persistent_connection]
command_timeout = 60
connect_timeout = 30
Ansible dispone específicamente de timeouts para las conexiones persistentes de networking.
Es algo especialmente habitual durante:
show tech
copias de firmware
commits grandes
consultas BGP
backups
operaciones API largas
29. Un playbook multi-vendor de verdad
Supongamos que queremos configurar VLAN 120 en Cisco IOS, NX-OS y Arista.
Los datos:
network_vlans:
- vlan_id: 120
name: SERVERS
Nuestro playbook puede ser:
---
- name: Configurar VLANs
hosts: switches
gather_facts: false
tasks:
- name: Cisco IOS
cisco.ios.ios_vlans:
config: "{{ network_vlans }}"
state: merged
when:
- ansible_network_os == "cisco.ios.ios"
- name: Cisco NXOS
cisco.nxos.nxos_vlans:
config: "{{ network_vlans }}"
state: merged
when:
- ansible_network_os == "cisco.nxos.nxos"
- name: Arista EOS
arista.eos.eos_vlans:
config: "{{ network_vlans }}"
state: merged
when:
- ansible_network_os == "arista.eos.eos"
Funciona, pero cuando el proyecto crezca no dejaría esta lógica en un único playbook.
La movería a roles:
roles/
└── vlans/
└── tasks/
├── main.yml
├── ios.yml
├── nxos.yml
└── eos.yml
main.yml:
- name: Cargar tareas de plataforma
ansible.builtin.include_tasks:
file: "{{ platform }}.yml"
Y en cada grupo:
platform: ios
o:
platform: nxos
El playbook termina siendo:
---
- name: Configurar VLANs
hosts: switches
gather_facts: false
roles:
- vlans
La complejidad del fabricante queda encapsulada dentro del role.
30. Diseño recomendado para una automatización seria
Una arquitectura madura debería separar claramente cuatro capas:
%%{init: {
"theme": "base",
"themeVariables": {
"fontFamily": "sans-serif",
"fontSize": "13px",
"edgeLabelBackground": "#ffffff"
}
} }%%
flowchart TB
classDef layer fill:#2b3e50,stroke:#1a252f,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef logic fill:#2980b9,stroke:#1f618d,stroke-width:2px,color:#fff,rx:8,ry:8;
classDef device fill:#16a085,stroke:#117a65,stroke-width:2px,color:#fff,rx:8,ry:8;
INV["<b>INVENTARIO</b><br/>Hosts y Grupos"]:::layer
NET["<b>DATOS DE RED</b><br/>Variables y Parámetros"]:::layer
ROLE["<b>ROLES / LÓGICA</b><br/>Tareas y Playbooks"]:::logic
DRV["<b>DRIVERS / COLECCIONES</b><br/>Módulos de Conexión"]:::logic
DEV["<b>DISPOSITIVOS</b><br/>Nodos Gestionados"]:::device
INV ==> NET
NET ==> ROLE
ROLE ==> DRV
DRV ==> DEV
linkStyle default stroke:#7f8c8d,stroke-width:1.5px;
El inventario dice:
qué dispositivos existen
Los datos dicen:
cómo debería ser la red
Los roles dicen:
qué operaciones hay que realizar
Las colecciones resuelven:
cómo hablar con cada fabricante
Eso permite cambiar un switch Cisco por un Arista sin tener que rediseñar todo el modelo de automatización.
31. Qué automatizar primero
En una red existente no empezaría desplegando BGP automáticamente.
Empezaría por tareas de riesgo bajo:
show commands
inventario
facts
versiones
serial numbers
interfaces
vecinos
CDP/LLDP
backups
auditorías
NTP
DNS
syslog
SNMP
Después pasaría a:
VLAN
interfaces
trunks
ACL
rutas
OSPF
BGP
firewalls
actualizaciones
Para entonces ya tendremos inventarios, modelos de datos, validaciones y backups suficientemente probados.
32. Flujo práctico de un cambio
Supongamos que tenemos que añadir la VLAN 120 en todos los switches de acceso.
Primero comprobamos dónde vamos a actuar:
ansible-inventory --graph
Probamos un único switch:
ansible-playbook playbooks/vlans.yml \
--limit sw01 \
--check \
--diff
Ejecutamos:
ansible-playbook playbooks/vlans.yml \
--limit sw01
Validamos:
ansible-playbook playbooks/audit_vlan.yml \
--limit sw01
Probamos una sede:
ansible-playbook playbooks/vlans.yml \
--limit site_barcelona
Y solo cuando el resultado es correcto desplegamos al grupo completo:
ansible-playbook playbooks/vlans.yml
Este método conserva algo que es fácil perder cuando empezamos a automatizar: el control sobre el cambio.
33. El objetivo final: gestionar intención, no comandos
El salto importante aparece cuando dejamos de pensar:
quiero ejecutar este comando
y empezamos a pensar:
quiero que esta sea la configuración de la red
En lugar de:
commands:
- vlan 120
- name SERVERS
definimos:
vlans:
- vlan_id: 120
name: SERVERS
En lugar de guardar cientos de configuraciones independientes:
sw01.cfg
sw02.cfg
sw03.cfg
guardamos el modelo de la red:
sites:
barcelona:
vlans:
users: 10
voice: 20
wifi: 30
servers: 120
Y dejamos que Ansible traduzca esa intención a Cisco, Arista, Juniper, Aruba, MikroTik o cualquier otra plataforma.
Ese es el punto donde Ansible deja de ser simplemente una forma de ejecutar comandos en muchos equipos y pasa a convertirse en una herramienta de automatización de red.