I recently picked up a 2960-x series switch, and ouch, Im reminded how clunky these things are to configure.
All this system critical / sla critical mutable state - with nothing but an imperative cli that forces one to type everything out?[1] Yikes, sounds like a recipe for human error and configuration drift.
[1]: Yes I know about tab complete and interface ranges. What I’m referring to here is the tedium of ’en. conf t. interface GigabitEthernet0/1. no shut. end. copy run start’. Thats six commands to enable an interface and save the change.
IOS rant aside; lets set this thing up with ansible so I never have to log into it again.
First off, configure an ip on some port:
en
conf t
interface FastEthernet0
no shut
ip address dhcp
end
configure ssh and a user. im a little fuzzy on the details here, i may have missed something:
en
conf t
ip domain-name local
crypto key generate rsa
ip ssh time-out 60
ip ssh authentication-retries 2
username nhensel password 0 123pass
end
Eventually I got to the point where I could log into mine as so:
ssh 172.30.220.13 -oKexAlgorithms=+diffie-hellman-group1-sha1 -oCiphers=aes256-cbc
A few hours later, heres a working ansible inventory:
cisco:
hosts:
172.30.220.13
vars:
ansible_host: 172.30.220.13
ansible_ssh_extra_args: '-oKexAlgorithms=+diffie-hellman-group1-sha1 -oCiphers=aes256-cbc'
ansible_user: nhensel
remote_user: nhensel
ansible_password: 123pass
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
ansible_become: yes
ansible_become_pass: 123pass
ansible_become_method: enable
And main.yml
:
- hosts: all
tasks:
- name: enable GigabitEthernet0/(1-26)
cisco.ios.ios_interfaces:
config:
- name: GigabitEthernet0/{{item}}
enabled: true
state: merged
with_sequence: 1-26
- name: copy run start
cisco.ios.ios_config:
save_when: modified
On debian bookworm, I needed these packages to run the cisco.ios module:
apt install python3-paramiko sshpass
pip3 install ansible-pylibssh
And with that we can finally run ansible. If we had many campus access switches of a similar model, it would be trivial to deploy changes across the whole network.
$ ansible-playbook main.yml -i inventory
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [172.30.220.13]
TASK [enable GigabitEthernet0/(1-26)] ******************************************
ok: [172.30.220.13] => (item=1)
ok: [172.30.220.13] => (item=2)
ok: [172.30.220.13] => (item=3)
ok: [172.30.220.13] => (item=4)
ok: [172.30.220.13] => (item=5)
ok: [172.30.220.13] => (item=6)
ok: [172.30.220.13] => (item=7)
ok: [172.30.220.13] => (item=8)
ok: [172.30.220.13] => (item=9)
ok: [172.30.220.13] => (item=10)
ok: [172.30.220.13] => (item=11)
ok: [172.30.220.13] => (item=12)
ok: [172.30.220.13] => (item=13)
ok: [172.30.220.13] => (item=14)
ok: [172.30.220.13] => (item=15)
ok: [172.30.220.13] => (item=16)
ok: [172.30.220.13] => (item=17)
ok: [172.30.220.13] => (item=18)
ok: [172.30.220.13] => (item=19)
ok: [172.30.220.13] => (item=20)
ok: [172.30.220.13] => (item=21)
ok: [172.30.220.13] => (item=22)
ok: [172.30.220.13] => (item=23)
ok: [172.30.220.13] => (item=24)
ok: [172.30.220.13] => (item=25)
ok: [172.30.220.13] => (item=26)
TASK [copy run start] **********************************************************
ok: [172.30.220.13]
PLAY RECAP *********************************************************************
172.30.220.13 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
These sort of ’tasks’ aren’t as composable as a true template, but with tricks like with_sequence: 1-26
we can still get dry-ish results.
Theres a fairly serious problem with the sustainability of this setup however. Because of the lack of true templating, whenever resources are deleted in the playbook they aren’t deleted on the remote.
If I create a port channel:
- name: create port channel on 25-26
cisco.ios.ios_lag_interfaces:
config:
- name: Port-channel1
members:
- member: GigabitEthernet0/25
mode: auto
- member: GigabitEthernet0/26
mode: auto
TASK [create port channel on 25-26] ********************************************
changed: [172.30.220.13]
TASK [copy run start] **********************************************************
changed: [172.30.220.13]
ws-c2960x-24ts-ll-ede82#sho etherchannel
Channel-group listing:
----------------------
Group: 1
----------
Group state = L2
Ports: 2 Maxports = 8
Port-channels: 1 Max Port-channels = 1
Protocol: PAgP
Minimum Links: 0
And then remove the task from the playbook…
$ ansible-playbook main.yml -i inventory
PLAY [all] *********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [172.30.220.13]
TASK [enable GigabitEthernet0/(1-26)] ******************************************
ok: [172.30.220.13] => (item=1)
ok: [172.30.220.13] => (item=2)
ok: [172.30.220.13] => (item=3)
ok: [172.30.220.13] => (item=4)
ok: [172.30.220.13] => (item=5)
ok: [172.30.220.13] => (item=6)
ok: [172.30.220.13] => (item=7)
ok: [172.30.220.13] => (item=8)
ok: [172.30.220.13] => (item=9)
ok: [172.30.220.13] => (item=10)
ok: [172.30.220.13] => (item=11)
ok: [172.30.220.13] => (item=12)
ok: [172.30.220.13] => (item=13)
ok: [172.30.220.13] => (item=14)
ok: [172.30.220.13] => (item=15)
ok: [172.30.220.13] => (item=16)
ok: [172.30.220.13] => (item=17)
ok: [172.30.220.13] => (item=18)
ok: [172.30.220.13] => (item=19)
ok: [172.30.220.13] => (item=20)
ok: [172.30.220.13] => (item=21)
ok: [172.30.220.13] => (item=22)
ok: [172.30.220.13] => (item=23)
ok: [172.30.220.13] => (item=24)
ok: [172.30.220.13] => (item=25)
ok: [172.30.220.13] => (item=26)
TASK [copy run start] **********************************************************
ok: [172.30.220.13]
PLAY RECAP *********************************************************************
172.30.220.13 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
We still have an port channel left over (or etherchannel. thanks cisco). Ansible simply forgets about it:
ws-c2960x-24ts-ll-ede82#sho etherchannel
Channel-group listing:
----------------------
Group: 1
----------
Group state = L2
Ports: 2 Maxports = 8
Port-channels: 1 Max Port-channels = 1
Protocol: PAgP
Minimum Links: 0
With complex, oft-changing configurations, this will get tedius fast.
Instead of removing some lines from a definitive template and reloading, resulting in a resource being dropped - we would have to write anti-tasks, or perhaps some catch-all cleanup task to remove configuration.
So have we solved the problem or just moved it? In reality we’re just running imperative ios commands with more steps. There are benefits, yes - we get more familiar and useful syntax - but the downsides are still there, and they’re big ones. Order matters, actions have side-effects, and theres external state that ansible doesn’t know about.
If you’re not extremely careful and intentional about how you use this tool (with team buy-in), I expect this approach would devolve into something not much more managable than our initial state.
In fact, I have seen this play out in my professional career.
For these reasons I do not consider this (as presented here) a viable option for managing a network. Network configuration needs to be declarative and free of all guesswork.
My next idea would be to write (or find) a tool to repeatably template out the configs for all the cisco infrastructure every time you run it, and then have a script that ssh’s into devices and runs the command to pull startup-config from ftp. Perhaps we can still use ansible for that last bit.
Looks like Vincent Bernat et al came to a similar conclusion.
The ultimate solution here to my mind is to skip cisco and run linux on the network in the first place. Cumulus is the obvious first choice but its unclear to me if i can buy an onie switch on ebay and expect the asic license to transfer. Recently I learned Arista EOS is based on fedora, and ‘Full Access to Linux shell and tools’ is a first class feature - the catch being I just can’t reinstall/upgrade the os without a license. I’ll therefore be picking up something Arista to see what I can do with it.