Hello Internet! While working through the CCNP ENARSI Lab Manual, I got pretty tired of having to reset the lab and reconfigure basic IP configurations on each device. That is how this little project started, and my curiosity with messing with the new Cisco IOS collection. The goal of this repo is to help me or anyone out there working through the lab manual.
Long story short, and this will be a long story! This repo will help build the initial configurations for each device before starting a lab. Once the user completes the lab, there is also a destroy playbook to reset the lab. Side note, this repository is not a replacement for the lab manual. It is still required to go through the labs. This is only a complimentary tool, not a replacement. One more note, I did not use any official Cisco Network Academy training so I do not have access to the troubleshooting labs in the book. Maybe I'll add some troubleshooting labs later on if this gets popular :).
Feel free to copy the topology I created above. I also included the export under the eve_topology folder. Once that is done make sure each node has a connection to the same network as your Ansible control machine (light gray connections in the topology above). Please note, this topology can be created using GNS3 or CML. Topology links would have to match or many updates would be required in vars files.
Once the nodes have all of their connections. The setup for all nodes is the same. Create MGMT VRF, assign management port to management VRF, and enable ssh. Below is a sample of the commands used on a Cisco device.
hostname RTR1 ip domain name lab.com vrf definition MGMT ! address-family ipv4 exit-address-family username cisco privilege 15 password 0 cisco interface GigabitEthernet0/3 vrf forwarding MGMT ip address 192.168.10.119 255.255.255.0 no shut crypto key generate rsa 1024 ip route vrf MGMT 0.0.0.0 0.0.0.0 192.168.10.1 ip ssh ver 2 line vty 0 4 login local transport input all
Feel free to modify the hosts file to match whatever IP schema you choose.
This guide assumes you have some machine that can run Ansible and has connectivity to your nodes.
git clone https://github.com/JulioPDX/ccnp_enarsi_lab_manual.git
cd ccnp_enarsi_lab_manual
pip3 install -r requirements.txt
ansible-galaxy collection install cisco.ios
Every lab has an individual folder for each node. This is used when running the playbook and loading in the correct variables.
labs/01_2.1.2/ ├── A1.yaml ├── D1.yaml ├── D2.yaml ├── RTR1.yaml ├── RTR2.yaml └── RTR3.yaml
Lets take a look at one of the node lab files.
# From labs/14_16.1.2/RTR3.yaml hostname: RTR3 interfaces: - name: GigabitEthernet0/0 sub_int: False ipv4: 10.1.23.3/24 ipv6: 2001:db8:acad:23::3/64 link_local: fe80::23:3 - name: GigabitEthernet0/1 sub_int: False ipv4: 10.1.32.1/24 ipv6: 2001:db8:acad:32::3/64 link_local: fe80::32:3 - name: Loopback0 sub_int: False ipv4: 10.3.3.3/24 ipv6: 2001:db8:acad:3::3/64 link_local: fe80::3:3 destroy: eigrp: as: - CISCO
The deploy playbook will handle configuring the interfaces, setting static routes, routing, banners, tunnel, loopback interfaces, and more. There is also a destroy
variable. Items defined in destroy will be removed from the devices after the user is done working through a lab. The interface that is attached to the MGMT VRF is not defined in these files for safety, if they are not included, then there is no chance the playbook or user may inadvertently reset the interface.
The deploy file is nothing too fancy. Its just calling out certain roles that will take care of the configuration. There is a simple assert at the beginning to make sure the user passes in a lab variable. This is where the individual lab folder comes in. When the user passes in the lab variable, Ansible will load the correct variable files to configure or destroy the nodes.
# deploy.yaml --- - name: Checking for valid lab definition hosts: localhost gather_facts: False tasks: - name: Check that lab is defined assert: that: lab is defined fail_msg: "no lab defined, please add extra vars defining lab variable" # Example ansible-playbook deploy.yaml -e lab=01_2.1.2 - name: CCNP ENARSI Lab Manual Switches hosts: switches #switches gather_facts: False strategy: free vars_files: - ./labs/{{ lab }}/{{ inventory_hostname }}.yaml roles: - role: banner - role: set_interfaces - role: vlans when: vlans is defined - role: sw_interfaces - role: static_routes - role: ospf_config when: ospf is defined - role: eigrp when: eigrp.ipv4 is defined - role: save_config - name: CCNP ENARSI Lab Manual Routers hosts: routers #routers gather_facts: False strategy: free vars_files: - ./labs/{{ lab }}/{{ inventory_hostname }}.yaml roles: - role: banner - role: set_interfaces - role: rtr_interfaces - role: static_routes - role: ospf_config when: ospf is defined - role: dhcp_config - role: dmvpn when: lab == '20_19.1.4' or lab =='22_20.1.2' - role: eigrp when: eigrp.ipv4 is defined - role: save_config
Most all of the roles for building the lab use the same structure. The task in the main.yaml file in each role kick off when certain conditions are met. To be honest, this is probably not the best way to handle this and I'm sure there's a more efficient method. Now is better than never and done is better than perfect.
# ./roles/rtr_interfaces/tasks/main.yaml - name: enable ipv6 routing on routers cisco.ios.ios_config: lines: ipv6 unicast-routing - name: configure ipv4 L3 interfaces that are not sub interfaces cisco.ios.ios_l3_interfaces: config: - name: "{{ item.name }}" ipv4: - address: "{{ item.ipv4 }}" loop: "{{ interfaces }}" loop_control: label: "{{ item.name }}" when: "not item.sub_int and item.ipv4 is defined and 'Loopback' not in item.name" - name: configure ipv6 L3 interfaces that are not sub interfaces cisco.ios.ios_l3_interfaces: config: - name: "{{ item.name }}" ipv6: - address: "{{ item.ipv6 }}" loop: "{{ interfaces }}" loop_control: label: "{{ item.name }}" when: "not item.sub_int and item.ipv6 is defined and 'Loopback' not in item.name" # snip
The configuration portion for the most part has pretty specific roles. Each having a designated.... role :). Below I will give some overview on the destroy playbook.
# destroy.yaml --- - name: Checking for valid lab definition hosts: localhost gather_facts: False tasks: - name: Check that lab is defined assert: that: lab is defined fail_msg: "no lab defined, please add extra vars defining lab variable" # Example ansible-playbook destroy.yaml -e lab=01_2.1.2 - name: reset switch layer 2 stuffs hosts: switches gather_facts: False strategy: free vars_files: - ./labs/{{ lab }}/{{ inventory_hostname }}.yaml roles: - destroy_lab - destroy_vlans - reset_reload - name: Reset routers hosts: routers gather_facts: False strategy: free vars_files: - ./labs/{{ lab }}/{{ inventory_hostname }}.yaml roles: - destroy_lab # - reset_reload # Not working for IOSv Routers tasks: - name: save config before reload cisco.ios.ios_config: save_when: always
In the second playbook above, there is only one role called. But in actuality it is calling many reset files. This was mainly done for my sanity as the number of roles kept growing. I tried to make the destroy portion one small role... but that ended up growing either way. Anyhow, below is a snip of the main.yaml file in the destroy_lab role.
# main.yaml - name: include reset_routing include: reset_routing.yaml - name: include reset_static_routes include: reset_static_routes.yaml - name: include reset_interfaces include: reset_interfaces.yaml - name: include reset_dhcp include: reset_dhcp.yaml - name: include reset_key_chains include: reset_key_chains.yaml - name: include reset_route_maps include: reset_route_maps.yaml - name: include reset_acl_prefix include: reset_acl_prefix.yaml - name: include reset_policy include: reset_policy.yaml - name: include reset_crypto include: reset_crypto.yaml - name: include reset_vrf include: reset_vrf.yaml - name: include reset_banner include: reset_banner.yaml
Below is the reset_routing task file that is called from the main.yaml file above
# ./roles/destroy_lab/tasks/reset_routing.yaml - name: delete eigrp process cisco.ios.ios_config: lines: - no router eigrp {{ item }} when: destroy.eigrp.as is defined loop: "{{ destroy.eigrp.as }}" - name: delete classic IPv6 eigrp process cisco.ios.ios_config: lines: - no ipv6 router eigrp {{ item }} when: destroy.eigrp.ipv6.as is defined loop: "{{ destroy.eigrp.ipv6.as }}" - name: delete all OSPFv2 processes cisco.ios.ios_ospfv2: state: deleted ignore_errors: yes - name: delete all OSPFv3 processes cisco.ios.ios_ospfv3: state: deleted ignore_errors: yes - name: delete bgp cisco.ios.ios_bgp: config: bgp_as: "{{ destroy.bgp.as }}" operation: delete when: destroy.bgp.as is defined
When creating this I worked through the book from front to back. Build lab, work through lab, and destroy lab once complete. The way this project was structured you can jump around between labs. As long as you reset the lab before moving on to the next.
Here from the hub router (RTR1) perspective, none of the devices are configured. This particular lab asks you to begin with a configured DMVPN phase 3 that was completed from an earlier lab. This deploy will configure the interfaces(main, loopback, tunnel), EIGRP routing, and DMVPN configuration for example.
In the destroy phase, we will look from a spoke router(RTR2) perspective. We are viewing the interface, DMVPN configuration, and the entire lab will be reset
Code Exchange Community
Get help, share code, and collaborate with other developers in the Code Exchange community.View Community