The fifth part of my ongoing series of posts on Ansible for Networking will cover Arista’s EOS. You can view the other posts in the series below: -

All the playbooks, roles and variables used in this article are available in my Network Automation with Ansible repository

Why EOS?

Arista was formed by former Cisco employees (more information here), all of whom had major involvement in Cisco’s switching portfolio at different points in time. Since their founding in 2004, they have become a very popular option in the data centre switching market, and elsewhere.

Rather than building their own operating system, Arista is based upon Linux (similar to how JunOS is based upon FreeBSD). You can even access the Linux operating system, and run everything from standard Linux applications to Docker containers.

Arista’s CLI is very similar to Cisco. Many Cisco commands are valid on an Arista switch. For this reason, migrating from a Cisco-centric network platform (and associated Ansible playbooks!) to an Arista-centric platform is quite a smooth transition.

Arista offer top-of-rack switching, as well as BGP-speaking edge/transit routers. They often come with high capacity ports (i.e. multiple 10/40/100G ports), as well as a high density of ports.

In my career I have managed a few Arista switches. Other than making sure I was familiar with the Multi-Chassis Link Aggregation syntax (i.e. allowing LACP across multiple switches, rather than one), most of my Cisco knowledge translated instantly.

To make transitioning from another vendor even easier Arista make a virtualised Arista EOS switch available, known as vEOS. It does have come with a few limitations, but nothing which makes it unsuitable for testing or training.

IOS and EOS similarities

Due of their shared heritage, not only are the CLI commands almost identical between EOS and a Cisco IOS, but also the available Ansible modules. Rather than refactoring large sections of playbooks (like we did for JunOS), in many cases the only major differences is using the eos in place of ios.

Due to the similarities, instead of explaining each individual task and what it does, most of sections will detail how they differ from the IOS playbooks. If I went into detail explaining each section, this post would look remarkably similar to part 3!

To see the rationale behind the configuration and decisions made, I would recommend reading the Cisco IOS post, and using this post to draw comparisons.

Objectives

For each vendor, I will be using Ansible to configure two routers/switches/firewalls/appliances.

One will serve as the Edge switch, connecting to the Internet and also via BGP to the Net Server. The Net Server is a CentOS 8 Virtual Machine acting as a route server, syslog collector and TACACS+ server (detailed in The Lab Environment)

The other will be an internal switch, performing core functions (i.e. internal routing rather than external).

Edge switch

The edge switch will run the following: -

  • External BGP (eBGP) to the Net Server
    • Advertising internal networks
  • Internal BGP (iBGP) to the Internal switch
    • Advertising any routes received from the Net Server
    • Advertising a default route (for internet access)
  • OSPF
    • Advertising loopbacks and internal networks between both switches
  • IPv4 and IPv6 routing
    • Using OSPFv3 (for IPv6 support)
    • Using the IPv6 Address Family for BGP
  • SNMPv3 for monitoring
  • Logging via Syslog to the Net Server
  • Authentication, Authorization and Accounting (AAA) via TACACS+ to the Net Server
  • Access Lists on the port facing the Net Server

Usually you would also place some form of access-list or other filtering on your ports facing the internet, but this is in a lab environment and already behind a firewall.

Internal switch

The internal switch runs a subset of the functions that the edge switch does: -

  • Internal BGP (iBGP) to the Edge switch
    • Receiving any routes received from the Net Server
    • Receiving a default route (for internet access)
  • OSPF
    • Advertising loopbacks and internal networks between both switches
  • IPv4 and IPv6 routing
    • Using OSPFv3 (for IPv6 support)
    • Using the IPv6 Address Family for BGP
  • SNMPv3 for monitoring
  • Logging via Syslog to the Net Server
  • Authentication, Authorization and Accounting (AAA) via TACACS+ to the Net Server

Prerequisites

To manage an Arista EOS device with Ansible, the following steps are required to allow access to the switches. We also make some changes to the default Ansible connection configuration.

Ansible Configuration

The following defaults are required to use Ansible with Arista EOS: -

ansible_user: ansible
ansible_connection: network_cli
ansible_network_os: eos
ansible_ssh_pass: ###REDACTED###
ansible_become: yes
ansible_become_method: enable
ansible_become_password: ###REDACTED###

This is exactly the same as the Cisco IOS configuration, except the Network OS has changed to eos.

Arista EOS Configuration

The prerequisites for allowing Ansible access to manage an EOS device are: -

  • Create a user with the correct privilege level
  • Create an enable password
  • Give the device a hostname

Password encryption is enabled by default (using SHA512 rather than a proprietary method like Cisco), and SSH is enabled by default too.

Below shows how to do all of the above: -

arista> enable
arista# conf t
Enter configuration commands, one per line.  End with CNTL/Z.

! Add the ansible user - Privilege level 15 is equivalent to full admin rights
arista(config)# username ansible privilege 15 secret 0 $PASSWORD-HERE$

! Create the enable password
arista(config)# enable password 0 $ENABLE-PASSWORD$

! Give the device a hostname
arista(config)# hostname arista-01
arista-01(config)# end

! Write your configuration - Important, otherwise you will lose it if the devices powers off or reboots
arista-01# copy running-config startup-config

Once the above is done, add the device into your Ansible inventory. My inventory file looks like the below: -

[arista]
arista-01 ansible_host=10.15.30.43
arista-02 ansible_host=10.15.30.44

Verification

Can we contact both devices?

$ ansible arista -m eos_facts | grep -i hostname
        "ansible_net_hostname": "arista-01",
        "ansible_net_hostname": "arista-02",

That will do it!

Setup

The setup is similar to the IOS lab. However, I couldn’t use the VLAN Bridge. The Arista vEOS images in my lab were not able to receive frames with tagged VLANs.

I verified (using packet captures) that each vEOS virtual machine sent frames with tagged VLANs, but they would not receive them (presumably being dropped somewhere at the kernel level).

I tried this lab with two different KVM machines, and also attempted it with a Hyper-V hypervisor instance too, with no success. The same setup has worked with every other vendor I have tried so far (including some I haven’t written blogs about yet), so it does appear to be an issue with the vEOS image itself.

Instead of using VLANs, we are using dedicated interfaces (and bridges) between netsvr-01 and arista-01, and between the two arista instances.

VLANs, IP addressing and Autonomous System numbers

The ID chosen for Arista EOS is 03.

Interfaces

VLANs are not being used, due to limitations with the vEOS image. Instead, physical interfaces are used instead

  • enp10s0 on netsvr-01 is connected to ether1 on the edge switch - netsvr bridge
  • ether2 on arista-01 is connected to ether1 on the internal switch - arista bridge

IP Addressing

  • IPv4 Subnet on the netsvr bridge: 10.100.103.0/24
    • edge switch - 10.100.103.253/24
    • netsvr-01 - 10.100.103.254/24
  • IPv4 Subnet on the arista bridge: 10.100.203.0/24
    • edge switch - 10.100.203.254/24
    • internal switch - 10.100.203.253/24
  • IPv6 Subnet on the netsvr bridge: 2001:db8:103::/64
    • edge switch - 2001:db8:103::f/64
    • netsvr-01 - 2001:db8:103:ffff/64
  • IPv6 Subnet on the arista bridge: 2001:db8:203::/64
    • edge switch - 2001:db8:203::a/64
    • internal switch - 2001:db8:203:f/64
  • IPv4 Loopback Addressing
    • edge switch - 192.0.2.103/32
    • internal switch - 192.0.2.203/32
  • IPv6 Loopback Address
    • edge switch - 2001:db8:903:beef::1/128
    • internal switch - 2001:db8:903:beef::2/128

BGP Autonomous System

The BGP Autonomous System number will be AS65103.

Configuration

In this section we will go through the configuration of the switches. Due to the similarities between Arista and Cisco, much of the configuration is close to identical. Rather than explaining each task individually, I shall go through the major differences.

Also, we have remove the NAT role. This is because the Arista switches (like most switches) do not support Network Address Translation. While I have never managed an Arista router, looking at Arista documentation shows that the configuration is similar to IOS. The only major difference is that NAT is tied to an interface directly, rather than being configured at the global level and then added to an interface with ip nat inside or ip nat outside at the interface hierarchical level.

Additionally, while we will configure access-lists, they do not take effect on Arista vEOS. This is because it has no real switch hardware to apply the access-lists to. The configuration would work on a real Arista switch, but in our virtual lab, while we can configure it but it will not actually block/allow traffic.

System Tasks

As noted in previous parts, the system tasks setup basic logging, banners and the hostname.

Playbook

The contents of the playbook are below: -

---
## tasks file for system
- name: Set system hostname
  eos_system:
    hostname: "{{ inventory_hostname }}"

- name: Remove unneeded banners
  eos_banner:
    banner: "{{ item }}"
    state: absent
  loop:
  - motd

- name: Update login banner
  eos_banner:
    banner: login
    text: |
      ----------------------------------------
      |
      | This banner was generated by Ansible
      |
      ----------------------------------------
      |
      | You are logged into {{ inventory_hostname }}
      |
      ----------------------------------------
    state: present

- name: Configure syslog
  eos_logging:
    dest: host
    name: "{{ log_host }}"
    state: present

- name: Set log source
  eos_config:
    lines:
      - logging source-interface Loopback0

- name: Configure log buffer
  eos_logging:
    dest: buffered
    level: informational
    size: 5000
    state: present

The main differences between this playbook and the Cisco IOS playbook are: -

  • The word ios is replaced with eos (e.g. eos_banner instead of ios_banner)
  • We only remove the motd banner, as the exec and incoming banners are not available on EOS
  • We set the hostname
    • We could have done this in the Cisco playbook as well, using ios_system
  • We set the source of logs to be the Loopback0 interface
    • This allows logs to come from the same interface, no matter the egress point of traffic
  • Setting the log size in eos_logging
    • This is a mandatory setting in the eos_logging module
  • As noted previously, password encryption is enabled by default in Arista

While this might seem like a few changes, in practice the differences in the playbooks are quite small: -

$ diff arista/roles/system/tasks/main.yml cisco/roles/system/tasks/main.yml
3,6d2
< - name: Set system hostname
<   eos_system:
<     hostname: "{{ inventory_hostname }}"
<
8c4
<   eos_banner:
---
>   ios_banner:
12a9,10
>   - exec
>   - incoming
15c13
<   eos_banner:
---
>   ios_banner:
30c28
<   eos_logging:
---
>   ios_logging:
35,39d32
< - name: Set log source
<   eos_config:
<     lines:
<       - logging source-interface Loopback0
<
41c34
<   eos_logging:
---
>   ios_logging:
44d36
<     size: 5000
45a38,42
>
> - name: Enable service password-encryption
>   ios_config:
>     lines:
>       - service password-encryption

Compared this to the differences you’ll find in the JunOS playbooks, or the upcoming playbooks for MikroTik or Cumulus, this is minimal.

All Ansible modules are referred to and used almost identically to the IOS modules, including the use of parents for eos_config (for commands only available in certain hierarchies).

Output

The generated configuration from this playbook is below: -

logging buffered 5000 informational
logging host 10.100.103.254 514
logging source-interface Loopback0

hostname arista-01

banner login
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
EOF

Interfaces

This role configures the interfaces, descriptions and IP addressing (IPv4 and IPv6). We are not able to make use of VLANs due to the lack of VLAN tagging support on the vEOS images (or at least in the setups I have tried), so all references to subinterfaces in our host_vars have been removed.

Playbook

The contents of the playbook are below: -

- name: Configure subinterfaces first
  eos_config:
    src: subints.j2

- name: Configure routed ports
  eos_config:
    lines:
      - "no switchport"
    parents:
      - interface {{ item.eos_if }}
  when:
    - item.routed is defined
  loop: "{{ interfaces }}"

- name: Configure interfaces - Status and Descriptions
  eos_interfaces:
    config:
      - name: "{{ item.eos_if }}"
        description: "{{ item.desc }}"
        enabled: "{{ item.enabled }}"
  loop: "{{ interfaces }}"

- name: Configure interfaces - L3 IPv4
  eos_l3_interfaces:
    config:
      - name: "{{ item.eos_if }}"
        ipv4:
        - address: "{{ item.ipv4 }}"
  when: item.ipv4 is defined
  loop: "{{ interfaces }}"

- name: Configure interfaces - L3 IPv6
  eos_l3_interfaces:
    config:
      - name: "{{ item.eos_if }}"
        ipv6:
        - address: "{{ item.ipv6 }}"
  when: item.ipv6 is defined
  loop: "{{ interfaces }}"

The differences between this and the IOS interfaces playbook are: -

  • Setting ports to routed mode, rather than switched mode
    • Switched ports cannot have IPs assigned to them
    • The same configuration would be required on a Cisco IOS switch
  • The word ios is replaced with eos (e.g. eos_interfaces instead of ios_interfaces

The actual differences are minimal: -

$ diff arista/roles/system/main.yml cisco/roles/interfaces/tasks/main.yml
1a2
> # tasks file for interfaces
3c4
<   eos_config:
---
>   ios_config:
6,15d6
< - name: Configure routed ports
<   eos_config:
<     lines:
<       - "no switchport"
<     parents:
<       - interface {{ item.eos_if }}
<   when:
<     - item.routed is defined
<   loop: "{{ interfaces }}"
<
17c8
<   eos_interfaces:
---
>   ios_interfaces:
19c10
<       - name: "{{ item.eos_if }}"
---
>       - name: "{{ item.ios_if }}"
25c16
<   eos_l3_interfaces:
---
>   ios_l3_interfaces:
27c18
<       - name: "{{ item.eos_if }}"
---
>       - name: "{{ item.ios_if }}"
34c25
<   eos_l3_interfaces:
---
>   ios_l3_interfaces:
36c27
<       - name: "{{ item.eos_if }}"
---
>       - name: "{{ item.ios_if }}"

The routed variable comes from our host_vars, which can be seen below: -

interfaces:
  - eos_if: "Management1"
    desc: "Management"
    enabled: true
    ipv4: "10.15.30.43/24"
  - eos_if: "Ethernet1"
    desc: "To netsvr"
    enabled: true
    routed: true
    ipv4: "10.100.103.253/24"
    ipv6: "2001:db8:103::f/64"
  - eos_if: "Ethernet2"
    desc: "To arista-02"
    routed: true
    enabled: true
    ipv4: "10.100.203.254/24"
    ipv6: "2001:db8:203::a/64"
  - eos_if: "Ethernet3"
    desc: "To the Internet"
    enabled: true
    routed: true
    ipv4: "dhcp"
  - eos_if: "Loopback0"
    desc: "Loopback"
    enabled: true
    ipv4: "192.0.2.103/32"
    ipv6: "2001:db8:903:beef::1/128"

The Management port and Loopback interface do not require the routed option, as they are routed by default.

If you run this playbook on real hardware, and have access to sub-interfaces, then the below will be relevant: -

$ diff arista/roles/interfaces/templates/subints.j2 cisco/roles/interfaces/templates/subints.j2
4,5c4,5
< interface {{ interface['eos_if'] }}.{{ vlan }}
<  encapsulation dot1q vlan {{ vlan }}
---
> interface {{ interface['ios_if'] }}.{{ vlan }}
>  encapsulation dot1q {{ vlan }}

Output

The generated configuration looks like the below: -

interface Ethernet1
   description To netsvr
   no switchport
   ip address 10.100.103.253/24
   ipv6 address 2001:db8:103::f/64
!
interface Ethernet2
   description To arista-02
   no switchport
   ip address 10.100.203.254/24
   ipv6 address 2001:db8:203::a/64
!
interface Ethernet3
   description To the Internet
   no switchport
   ip address dhcp
!
interface Loopback0
   description Loopback
   ip address 192.0.2.103/32
   ipv6 address 2001:db8:903:beef::1/128
!
interface Management1
   description Management
   ip address 10.15.30.43/24

Again, if you are used to Cisco IOS, this should look very familiar. The only major difference is the use of CIDR notation (i.e. 10.100.103.253/24) rather than network masks (i.e. 10.100.103.253 255.255.255.0) for IPv4 addresses.

Verification

arista-01

! Show IPs
rista-01#show ip int brief
                                                                                  Address
Interface         IP Address              Status       Protocol            MTU    Owner
----------------- ----------------------- ------------ -------------- ----------- -------
Ethernet1         10.100.103.253/24       up           up                 1500
Ethernet2         10.100.203.254/24       up           up                 1500
Ethernet3         192.168.122.72/24       up           up                 1500
Loopback0         192.0.2.103/32          up           up                65535
Management1       10.15.30.43/24          up           up                 1500

arista-01#show ipv6 interface brief
   Interface       Status         MTU       IPv6 Address                     Addr State    Addr Source
--------------- ------------ ----------- -------------------------------- ---------------- -----------
   Et1             up            1500       fe80::5054:ff:fec5:df3c/64       up            link local
                                            2001:db8:103::f/64               up            config
   Et2             up            1500       fe80::5054:ff:fec5:df3c/64       up            link local
                                            2001:db8:203::a/64               up            config
   Lo0             up           65535       fe80::ff:fe00:0/64               up            link local
                                            2001:db8:903:beef::1/128         up            config


! Show interface statuses and descriptions
arista-01#show interfaces description
Interface                      Status         Protocol           Description
Et1                            up             up                 To netsvr
Et2                            up             up                 To arista-02
Et3                            up             up                 To the Internet
Lo0                            up             up                 Loopback
Ma1                            up             up                 Management

! Ping to netsvr-01 on IPv4 and IPv6
arista-01#ping 10.100.103.254
PING 10.100.103.254 (10.100.103.254) 72(100) bytes of data.
80 bytes from 10.100.103.254: icmp_seq=1 ttl=64 time=1.62 ms
80 bytes from 10.100.103.254: icmp_seq=2 ttl=64 time=1.08 ms
80 bytes from 10.100.103.254: icmp_seq=3 ttl=64 time=1.19 ms
80 bytes from 10.100.103.254: icmp_seq=4 ttl=64 time=1.01 ms
80 bytes from 10.100.103.254: icmp_seq=5 ttl=64 time=1.53 ms

--- 10.100.103.254 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 8ms
rtt min/avg/max/mdev = 1.010/1.289/1.622/0.244 ms, ipg/ewma 2.017/1.458 ms

arista-01#ping 2001:db8:103::ffff
PING 2001:db8:103::ffff(2001:db8:103::ffff) 72 data bytes
80 bytes from 2001:db8:103::ffff: icmp_seq=1 ttl=64 time=1.53 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=2 ttl=64 time=1.02 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=3 ttl=64 time=1.04 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=4 ttl=64 time=1.06 ms
80 bytes from 2001:db8:103::ffff: icmp_seq=5 ttl=64 time=1.37 ms

--- 2001:db8:103::ffff ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.021/1.209/1.539/0.210 ms, ipg/ewma 1.879/1.376 ms

! Ping to cisco-02 on IPv4 and IPv6
arista-01#ping 10.100.203.253
PING 10.100.203.253 (10.100.203.253) 72(100) bytes of data.
80 bytes from 10.100.203.253: icmp_seq=1 ttl=64 time=3.43 ms
80 bytes from 10.100.203.253: icmp_seq=2 ttl=64 time=2.52 ms
80 bytes from 10.100.203.253: icmp_seq=3 ttl=64 time=2.66 ms
80 bytes from 10.100.203.253: icmp_seq=4 ttl=64 time=3.39 ms
80 bytes from 10.100.203.253: icmp_seq=5 ttl=64 time=3.63 ms

--- 10.100.203.253 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 14ms
rtt min/avg/max/mdev = 2.523/3.128/3.630/0.451 ms, ipg/ewma 3.622/3.306 ms

arista-01#ping 2001:db8:203::f
PING 2001:db8:203::f(2001:db8:203::f) 72 data bytes
80 bytes from 2001:db8:203::f: icmp_seq=1 ttl=64 time=8.17 ms
80 bytes from 2001:db8:203::f: icmp_seq=2 ttl=64 time=3.34 ms
80 bytes from 2001:db8:203::f: icmp_seq=3 ttl=64 time=2.88 ms
80 bytes from 2001:db8:203::f: icmp_seq=4 ttl=64 time=2.80 ms
80 bytes from 2001:db8:203::f: icmp_seq=5 ttl=64 time=2.67 ms

--- 2001:db8:203::f ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 30ms
rtt min/avg/max/mdev = 2.676/3.977/8.172/2.110 ms, ipg/ewma 7.608/5.988 ms

For those who use Linux, you’ll probably recognise the layout of the ping output, as it is exactly the same as on a Linux machine. The Linux underpinnings of Arista do creep through every now and then!

arista-02

! Show IPs
arista-02#show ip interface brief
                                                                                  Address
Interface         IP Address              Status       Protocol            MTU    Owner
----------------- ----------------------- ------------ -------------- ----------- -------
Ethernet1         10.100.203.253/24       up           up                 1500
Loopback0         192.0.2.203/32          up           up                65535
Management1       10.15.30.44/24          up           up                 1500

arista-02#show ipv6 interface brief
   Interface       Status         MTU       IPv6 Address                     Addr State    Addr Source
--------------- ------------ ----------- -------------------------------- ---------------- -----------
   Et1             up            1500       fe80::5054:ff:fed8:9755/64       up            link local
                                            2001:db8:203::f/64               up            config
   Lo0             up           65535       fe80::ff:fe00:0/64               up            link local
                                            2001:db8:903:beef::2/128         up            config

! Show interface statuses and descriptions
arista-02#show interfaces description
Interface                      Status         Protocol           Description
Et1                            up             up                 To arista-01
Lo0                            up             up                 Loopback
Ma1                            up             up                 Management

! Ping to cisco-01 on IPv4 and IPv6
arista-02#ping 10.100.203.254
PING 10.100.203.254 (10.100.203.254) 72(100) bytes of data.
80 bytes from 10.100.203.254: icmp_seq=1 ttl=64 time=3.13 ms
80 bytes from 10.100.203.254: icmp_seq=2 ttl=64 time=2.57 ms
80 bytes from 10.100.203.254: icmp_seq=3 ttl=64 time=2.51 ms
80 bytes from 10.100.203.254: icmp_seq=4 ttl=64 time=2.47 ms
80 bytes from 10.100.203.254: icmp_seq=5 ttl=64 time=3.26 ms

--- 10.100.203.254 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.477/2.793/3.262/0.337 ms, ipg/ewma 3.272/2.972 ms

arista-02#ping 2001:db8:203::a
PING 2001:db8:203::a(2001:db8:203::a) 72 data bytes
80 bytes from 2001:db8:203::a: icmp_seq=1 ttl=64 time=3.18 ms
80 bytes from 2001:db8:203::a: icmp_seq=2 ttl=64 time=2.66 ms
80 bytes from 2001:db8:203::a: icmp_seq=3 ttl=64 time=2.53 ms
80 bytes from 2001:db8:203::a: icmp_seq=4 ttl=64 time=2.67 ms
80 bytes from 2001:db8:203::a: icmp_seq=5 ttl=64 time=3.19 ms

--- 2001:db8:203::a ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.534/2.849/3.195/0.284 ms, ipg/ewma 3.388/3.024 ms

All looking good so far!

Access Lists

As noted earlier, the access-lists applied in this task will be part of the configuration, but will not take effect and therefore will not be able to verify they are correct. If you run these tasks on real Arista hardware though, they should function correctly.

Playbook

The contents of the playbook are below: -

---
## tasks file for acl
- name: Edge Access List - Outbound IPv4
  eos_config:
    lines:
      - 10 permit icmp any any log
      - 20 permit udp any host {{ log_host }}  eq syslog log
      - 30 permit tcp any host {{ tacacs['ipv4'] }}  eq tacacs log
      - 1000 deny ip any any log
    parents:
      - ip access-list extended EDGE-OUT
  when:
    - rtr_role is search("edge")
  tags:
    - acl
    - acl_ipv4

- name: Edge Access List - Inbound IPv4
  eos_config:
    lines:
      - 10 permit icmp any any log
      - 20 permit tcp host {{ tacacs['ipv4'] }} eq tacacs any log
      - 1000 deny ip any any log
    parents:
      - ip access-list extended EDGE-IN
  when:
    - rtr_role is search("edge")
  tags:
    - acl
    - acl_ipv4

- name: Edge Access List - BGP Outbound IPv4
  eos_config:
    lines:
      - "no {{ item.acl.acl_index }}"
      - "{{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
    parents:
      - ip access-list extended EDGE-OUT
  when:
    - rtr_role is search("edge")
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv4 is defined
    - item.acl is defined
  loop: "{{ bgp.neighbors.ipv4 }}"
  tags:
    - acl
    - acl_ipv4

- name: Edge Access List - BGP Inbound IPv4
  eos_config:
    lines:
      - "no {{ item.acl.acl_index }}"
      - "{{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
    parents:
      - ip access-list extended EDGE-IN
  when:
    - rtr_role is search("edge")
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv4 is defined
    - item.acl is defined
  loop: "{{ bgp.neighbors.ipv4 }}"
  tags:
    - acl
    - acl_ipv4

- name: Apply Inbound IPv4 ACL
  eos_config:
    lines:
      - ip access-group {{ item.acl.ipv4.in }} in
    parents:
      - interface {{ item.eos_if }}
  when:
    - item.acl is defined
    - item.acl.ipv4 is defined
    - item.acl.ipv4.in is defined
  loop: "{{ interfaces }}"
  tags:
    - acl
    - acl_ipv4

- name: Apply Outbound IPv4 ACL
  eos_config:
    lines:
      - ip access-group {{ item.acl.ipv4.out }} out
    parents:
      - interface {{ item.eos_if }}
  when:
    - item.acl is defined
    - item.acl.ipv4 is defined
    - item.acl.ipv4.out is defined
  loop: "{{ interfaces }}"
  tags:
    - acl
    - acl_ipv4

- name: Edge Access List - Outbound IPv6
  eos_config:
    lines:
      - 10 permit icmp any any log
      - 1000 deny ipv6 any any log
    parents:
      - ipv6 access-list EDGEv6-OUT
  when:
    - rtr_role is search("edge")
  tags:
    - acl
    - acl_ipv6

- name: Edge Access List - Inbound IPv6
  eos_config:
    lines:
      - 10 permit icmp any any log
      - 1000 deny ipv6 any any log
    parents:
      - ipv6 access-list EDGEv6-IN
  when:
    - rtr_role is search("edge")
  tags:
    - acl
    - acl_ipv6

- name: Edge Access List - BGP Outbound IPv6
  eos_config:
    lines:
      - "no {{ item.acl.acl_index }}"
      - "{{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
    parents:
      - ipv6 access-list EDGEv6-OUT
  when:
    - rtr_role is search("edge")
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv6 is defined
    - item.acl is defined
  loop: "{{ bgp.neighbors.ipv6 }}"
  tags:
    - acl
    - acl_ipv6

- name: Edge Access List - BGP Inbound IPv6
  eos_config:
    lines:
      - "no {{ item.acl.acl_index }}"
      - "{{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
    parents:
      - ipv6 access-list EDGEv6-IN
  when:
    - rtr_role is search("edge")
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv6 is defined
    - item.acl is defined
  loop: "{{ bgp.neighbors.ipv6 }}"
  tags:
    - acl
    - acl_ipv6

- name: Apply Inbound IPv6 ACL
  eos_config:
    lines:
      - ipv6 access-group {{ item.acl.ipv6.in }} in
    parents:
      - interface {{ item.eos_if }}
  when:
    - item.acl is defined
    - item.acl.ipv6 is defined
    - item.acl.ipv6.in is defined
  loop: "{{ interfaces }}"
  tags:
    - acl
    - acl_ipv6

- name: Apply Outbound IPv6 ACL
  eos_config:
    lines:
      - ipv6 access-group {{ item.acl.ipv6.out }} out
    parents:
      - interface {{ item.eos_if }}
  when:
    - item.acl is defined
    - item.acl.ipv6 is defined
    - item.acl.ipv6.out is defined
  loop: "{{ interfaces }}"
  tags:
    - acl
    - acl_ipv6

The differences between this and the IOS playbook are: -

  • We are remove existing entries in an access-list, so that we do not have out-of-date configuration
  • We do not need to prefix numbered rules in an IPv6 access-list with sequence
  • We use ipv6 access-group to apply IPv6 access-lists to an interface, rather than ipv6 traffic-filter
    • This is consistent with IPv4 (applied using ip access-group)
  • The word ios is replaced with eos (e.g. eos_config instead of ios_config)

The full differences are shown below: -

$ diff arista/roles/acl/tasks/main.yml cisco/roles/acl/tasks/main.yml
<   eos_config:
---
>   ios_config:
19c19
<   eos_config:
---
>   ios_config:
33c33
<   eos_config:
---
>   ios_config:
35d34
<       - "no {{ item.acl.acl_index }}"
51c50
<   eos_config:
---
>   ios_config:
53d51
<       - "no {{ item.acl.acl_index }}"
69c67
<   eos_config:
---
>   ios_config:
73c71
<       - interface {{ item.eos_if }}
---
>       - interface {{ item.ios_if }}
84c82
<   eos_config:
---
>   ios_config:
88c86
<       - interface {{ item.eos_if }}
---
>       - interface {{ item.ios_if }}
99c97
<   eos_config:
---
>   ios_config:
101,102c99,100
<       - 10 permit icmp any any log
<       - 1000 deny ipv6 any any log
---
>       - sequence 10 permit icmp any any log
>       - sequence 1000 deny ipv6 any any log
112c110
<   eos_config:
---
>   ios_config:
114,115c112,113
<       - 10 permit icmp any any log
<       - 1000 deny ipv6 any any log
---
>       - sequence 10 permit icmp any any log
>       - sequence 1000 deny ipv6 any any log
125c123
<   eos_config:
---
>   ios_config:
127,128c125
<       - "no {{ item.acl.acl_index }}"
<       - "{{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
---
>       - "sequence {{ item.acl.acl_index }} permit tcp any host {{ item.peer }} eq bgp log"
143c140
<   eos_config:
---
>   ios_config:
145,146c142
<       - "no {{ item.acl.acl_index }}"
<       - "{{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
---
>       - "sequence {{ item.acl.acl_index }} permit tcp host {{ item.peer }} eq bgp any log"
161c157
<   eos_config:
---
>   ios_config:
163c159
<       - ipv6 access-group {{ item.acl.ipv6.in }} in
---
>       - ipv6 traffic-filter {{ item.acl.ipv6.in }} in
165c161
<       - interface {{ item.eos_if }}
---
>       - interface {{ item.ios_if }}
176c172
<   eos_config:
---
>   ios_config:
178c174
<       - ipv6 access-group {{ item.acl.ipv6.out }} out
---
>       - ipv6 traffic-filter {{ item.acl.ipv6.out }} out
180c176
<       - interface {{ item.eos_if }}
---
>       - interface {{ item.ios_if }}

The below is the relevant section in our host_vars: -

bgp:
  neighbors:
    ipv4:
     - peer: 10.100.103.254
       acl:
         acl_index: 110
    ipv6:
     - peer: "2001:db8:103::ffff"
       acl:
         acl_index: 110
interfaces:
  - eos_if: "Ethernet1"
    acl:
      ipv4:
        in: EDGE-IN
        out: EDGE-OUT
      ipv6:
        in: EDGEv6-IN
        out: EDGEv6-OUT

The access-lists create rules based upon BGP peers, syslog and tacacs.

Generated configuration

The configuration the above generates is below: -

interface Ethernet1
   ip access-group EDGE-IN in
   ip access-group EDGE-OUT out
   ipv6 access-group EDGEv6-IN in
   ipv6 access-group EDGEv6-OUT out
!
ipv6 access-list EDGEv6-IN
   10 permit icmpv6 any any log
   110 permit tcp host 2001:db8:103::ffff eq bgp any log
   1000 deny ipv6 any any log
!
ipv6 access-list EDGEv6-OUT
   10 permit icmpv6 any any log
   110 permit tcp any host 2001:db8:103::ffff eq bgp log
   1000 deny ipv6 any any log
!
ip access-list EDGE-IN
   10 permit icmp any any log
   20 permit tcp host 192.0.2.1 eq tacacs any log
   110 permit tcp host 10.100.103.254 eq bgp any log
   1000 deny ip any any log
!
ip access-list EDGE-OUT
   10 permit icmp any any log
   20 permit udp any host 10.100.103.254 eq syslog log
   30 permit tcp any host 192.0.2.1 eq tacacs log
   110 permit tcp any host 10.100.103.254 eq bgp log
   1000 deny ip any any log
!

If you apply this on a vEOS image, the access-lists will be created. However you will see these error messages when applying the access-group configuration: -

arista-01(config)#interface Ethernet1
arista-01(config-if-Et1)#   ip access-group EDGE-IN in
% Unavailable command (not supported on this hardware platform)

arista-01(config-if-Et1)#   ip access-group EDGE-OUT out
% Unavailable command (not supported on this hardware platform)

arista-01(config-if-Et1)#   ipv6 access-group EDGEv6-IN in
% Unavailable command (not supported on this hardware platform)

arista-01(config-if-Et1)#   ipv6 access-group EDGEv6-OUT out
% Unavailable command (not supported on this hardware platform)

Routing

This section is where we will configure BGP, OSPF and OSPFv3. As per the previous parts, we are using OSPF (for IPv4 routing) and OSPFv3 (for IPv6 routing) because not all vendors support IPv4 in OSPFv3. If you are targeting an Arista-only network, or deemed IPv4 support in OSPFv3 an acceptance criteria on any network hardware choices, then you could remove the need for OSPF.

Main Playbook

The main playbook looks like the below: -

---
## tasks file for routing
##
- name: Include OSPF routing
  include: ospf.yml

- name: Include OSPFv3 routing
  include: ospfv3.yml

- name: Include BGP routing
  include: bgp.yml

This is identical to the IOS playbook, as we are not doing anything except including other playbooks (for ease of reading/debugging).

OSPF Playbook

The contents of the OSPF playbook are below: -

---
## tasks file for routing
##
- name: Enable IPv4 routing
  eos_config:
    lines:
      - "ip routing"
  tags:
    - ospf

- name: OSPF Process
  eos_config:
    lines:
      - "router ospf 1"
  tags:
    - ospf

- name: OSPF Process - Router ID
  eos_config:
    lines:
      - "router-id {{ router_id }}"
    parents: router ospf 1
  tags:
    - ospf

- name: OSPF Interfaces
  eos_config:
    lines:
      - ip ospf area {{ item.ospf.area }}
    parents: interface {{ item.eos_if }}
  when: item.ospf is defined
  loop: "{{ interfaces }}"
  tags:
    - ospf

- name: OSPF Interfaces - Passive
  eos_config:
    lines:
      - passive-interface {{ item.eos_if }}
    parents: router ospf 1
  when:
    - item.ospf is defined
    - item.ospf.passive is defined
  loop: "{{ interfaces }}"
  tags:
    - ospf

There are two major differences with this playbook and the IOS playbook: -

  • We enable ip routing
    • We are emulating an Arista switch, and many switches tend to have routing disabled by default
  • We do not set a process ID in the ip ospf area command

The reason for the latter is because Cisco allows multiple OSPF processes in the default routing table. By contrast, Arista do not. Arista allow multiple OSPF processes, but they must be configured as part of different VRFs (Virtual Routing and Forwarding). VRFs are used to create separate and unique routing tables, with different interfaces (or subinterfaces) bound to them. This makes it so each customer can have their own private routing table, with different egress and ingress points to their network, while still running over the same physical core network as each other.

Besides that, all other details are the same.

The below host_vars are relevant to the above playbook: -

  - eos_if: "Ethernet1"
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true
  - eos_if: "Ethernet2"
    ospf:
      area: "0.0.0.0"
    ospfv3:
      area: "0.0.0.0"
  - eos_if: "Loopback0"
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true
Generated configuration

The configuration this generates is below: -

interface Ethernet1
   ip ospf area 0.0.0.0
!
interface Ethernet2
   ip ospf area 0.0.0.0
!
interface Loopback0
   ip ospf area 0.0.0.0
!
router ospf 1
   router-id 192.0.2.103
   passive-interface Ethernet1
   passive-interface Loopback0
!
Verification

arista-01

! Show OSPF interfaces
arista-01#show ip ospf interface brief
   Interface    Instance VRF        Area            IP Address         Cost  State      Nbrs
   Et2          1        default    0.0.0.0         10.100.203.254/24  10    Backup DR  1
   Lo0          1        default    0.0.0.0         192.0.2.103/32     10    DR         0
   Et1          1        default    0.0.0.0         10.100.103.253/24  10    DR         0

! Show OSPF neighbours
arista-01#show ip ospf neighbor
Neighbor ID     Instance VRF      Pri State                  Dead Time   Address         Interface
192.0.2.203     1        default  1   FULL/DR                00:00:29    10.100.203.253  Ethernet2

! Show routing table
arista-01#show ip route ospf

VRF: default
Codes: C - connected, S - static, K - kernel,
       O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1,
       E2 - OSPF external type 2, N1 - OSPF NSSA external type 1,
       N2 - OSPF NSSA external type2, B - BGP, B I - iBGP, B E - eBGP,
       R - RIP, I L1 - IS-IS level 1, I L2 - IS-IS level 2,
       O3 - OSPFv3, A B - BGP Aggregate, A O - OSPF Summary,
       NG - Nexthop Group Static Route, V - VXLAN Control Service,
       DH - DHCP client installed default route, M - Martian,
       DP - Dynamic Policy Route, L - VRF Leaked

 O        192.0.2.203/32 [110/20] via 10.100.203.253, Ethernet2

! Ping!
arista-01#ping 192.0.2.203
PING 192.0.2.203 (192.0.2.203) 72(100) bytes of data.
80 bytes from 192.0.2.203: icmp_seq=1 ttl=64 time=3.54 ms
80 bytes from 192.0.2.203: icmp_seq=2 ttl=64 time=2.57 ms
80 bytes from 192.0.2.203: icmp_seq=3 ttl=64 time=2.57 ms
80 bytes from 192.0.2.203: icmp_seq=4 ttl=64 time=2.87 ms
80 bytes from 192.0.2.203: icmp_seq=5 ttl=64 time=2.92 ms

--- 192.0.2.203 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 14ms
rtt min/avg/max/mdev = 2.574/2.898/3.549/0.362 ms, ipg/ewma 3.525/3.222 ms

arista-01#ping 192.0.2.203 source 192.0.2.103
PING 192.0.2.203 (192.0.2.203) from 192.0.2.103 : 72(100) bytes of data.
80 bytes from 192.0.2.203: icmp_seq=1 ttl=64 time=3.04 ms
80 bytes from 192.0.2.203: icmp_seq=2 ttl=64 time=2.60 ms
80 bytes from 192.0.2.203: icmp_seq=3 ttl=64 time=2.58 ms
80 bytes from 192.0.2.203: icmp_seq=4 ttl=64 time=2.88 ms
80 bytes from 192.0.2.203: icmp_seq=5 ttl=64 time=3.23 ms

--- 192.0.2.203 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 14ms
rtt min/avg/max/mdev = 2.583/2.871/3.235/0.252 ms, ipg/ewma 3.524/2.971 ms

arista-02

! Show OSPF interfaces
arista-02#show ip ospf interface brief
   Interface    Instance VRF        Area            IP Address         Cost  State      Nbrs
   Lo0          1        default    0.0.0.0         192.0.2.203/32     10    DR         0
   Et1          1        default    0.0.0.0         10.100.203.253/24  10    DR         1

! Show OSPF neighbours
arista-02#show ip ospf neighbor
Neighbor ID     Instance VRF      Pri State                  Dead Time   Address         Interface
192.0.2.103     1        default  1   FULL/BDR               00:00:32    10.100.203.254  Ethernet1

! Show routing table
arista-02#show ip route ospf

VRF: default
Codes: C - connected, S - static, K - kernel,
       O - OSPF, IA - OSPF inter area, E1 - OSPF external type 1,
       E2 - OSPF external type 2, N1 - OSPF NSSA external type 1,
       N2 - OSPF NSSA external type2, B - BGP, B I - iBGP, B E - eBGP,
       R - RIP, I L1 - IS-IS level 1, I L2 - IS-IS level 2,
       O3 - OSPFv3, A B - BGP Aggregate, A O - OSPF Summary,
       NG - Nexthop Group Static Route, V - VXLAN Control Service,
       DH - DHCP client installed default route, M - Martian,
       DP - Dynamic Policy Route, L - VRF Leaked

 O        10.100.103.0/24 [110/20] via 10.100.203.254, Ethernet1
 O        192.0.2.103/32 [110/20] via 10.100.203.254, Ethernet1

! Ping!
arista-02#ping 192.0.2.103
PING 192.0.2.103 (192.0.2.103) 72(100) bytes of data.
80 bytes from 192.0.2.103: icmp_seq=1 ttl=64 time=3.23 ms
80 bytes from 192.0.2.103: icmp_seq=2 ttl=64 time=2.55 ms
80 bytes from 192.0.2.103: icmp_seq=3 ttl=64 time=2.61 ms
80 bytes from 192.0.2.103: icmp_seq=4 ttl=64 time=2.75 ms
80 bytes from 192.0.2.103: icmp_seq=5 ttl=64 time=3.12 ms

--- 192.0.2.103 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.550/2.854/3.233/0.279 ms, ipg/ewma 3.352/3.050 ms

arista-02#ping 192.0.2.103 source lo0
PING 192.0.2.103 (192.0.2.103) from 192.0.2.203 : 72(100) bytes of data.
80 bytes from 192.0.2.103: icmp_seq=1 ttl=64 time=3.13 ms
80 bytes from 192.0.2.103: icmp_seq=2 ttl=64 time=2.50 ms
80 bytes from 192.0.2.103: icmp_seq=3 ttl=64 time=2.59 ms
80 bytes from 192.0.2.103: icmp_seq=4 ttl=64 time=2.78 ms
80 bytes from 192.0.2.103: icmp_seq=5 ttl=64 time=3.18 ms

--- 192.0.2.103 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.505/2.842/3.188/0.277 ms, ipg/ewma 3.291/3.001 ms

arista-02#ping 10.100.103.253 source lo0
PING 10.100.103.253 (10.100.103.253) from 192.0.2.203 : 72(100) bytes of data.
80 bytes from 10.100.103.253: icmp_seq=1 ttl=64 time=3.26 ms
80 bytes from 10.100.103.253: icmp_seq=2 ttl=64 time=2.75 ms
80 bytes from 10.100.103.253: icmp_seq=3 ttl=64 time=2.76 ms
80 bytes from 10.100.103.253: icmp_seq=4 ttl=64 time=4.98 ms
80 bytes from 10.100.103.253: icmp_seq=5 ttl=64 time=3.42 ms

--- 10.100.103.253 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 15ms
rtt min/avg/max/mdev = 2.759/3.440/4.983/0.815 ms, ipg/ewma 3.841/3.384 ms

All looking good!

OSPFv3 Playbook

As noted, we run OSPFv3 for IPv6 routing. It is technically capable of IPv4 routing to, but not all vendors support this.

The contents of the playbook are below: -

---
## tasks file for routing
##

- name: Enable IPv6 routing
  eos_config:
    lines:
      - "ipv6 unicast-routing"
  tags:
    - ospfv3

- name: OSPFv3 Process
  eos_config:
    lines:
      - "router ospfv3"
  tags:
    - ospfv3

- name: OSPFv3 Process - Router ID
  eos_config:
    lines:
      - "router-id {{ router_id }}"
    parents: router ospfv3
  tags:
    - ospfv3

- name: OSPFv3 Interfaces
  eos_config:
    lines:
      - ospf ipv6 area {{ item.ospfv3.area }}
    parents: interface {{ item.eos_if }}
  when: item.ospfv3 is defined
  loop: "{{ interfaces }}"
  tags:
    - ospfv3

- name: OSPFv3 Interfaces - Passive
  eos_config:
    lines:
      - ospf ipv6 passive-interface
    parents: interface {{ item.eos_if }}
  when:
    - item.ospfv3 is defined
    - item.ospfv3.passive is defined
  loop: "{{ interfaces }}"
  tags:
    - ospfv3

- name: Removed IPv4 address family
  eos_config:
    lines:
      - "no address-family ipv4"
    parents: router ospfv3
  tags:
    - ospfv3

There are some small differences in this playbook from the IOS playbook: -

  • No OSPF process ID is specified anywhere in Arista (for the same reasons as IPv4)
  • ospf ipv6 area is used under an interface rather than ipv6 ospf 1 area
  • Passive interface configuration is at the interface level, rather than the process level (parents: interface rather than parents: router ospfv3 1)
  • no address-family ipv4 disables the IPv4 address family, the unicast option is not required

The differences between the playbooks themselves are below: -

diff arista/roles/routing/tasks/ospfv3.yml cisco/roles/routing/tasks/ospfv3.yml
6c6
<   eos_config:
---
>   ios_config:
9,10d8
<   tags:
<     - ospfv3
13c11
<   eos_config:
---
>   ios_config:
15,17c13
<       - "router ospfv3"
<   tags:
<     - ospfv3
---
>       - "router ospfv3 1"
20c16
<   eos_config:
---
>   ios_config:
23,25c19
<     parents: router ospfv3
<   tags:
<     - ospfv3
---
>     parents: router ospfv3 1
28c22
<   eos_config:
---
>   ios_config:
30,31c24,25
<       - ospf ipv6 area {{ item.ospfv3.area }}
<     parents: interface {{ item.eos_if }}
---
>       - ipv6 ospf 1 area {{ item.ospfv3.area }}
>     parents: interface {{ item.ios_if }}
34,35d27
<   tags:
<     - ospfv3
38c30
<   eos_config:
---
>   ios_config:
40,41c32,33
<       - ospf ipv6 passive-interface
<     parents: interface {{ item.eos_if }}
---
>       - passive-interface {{ item.ios_if }}
>     parents: router ospfv3 1
46,47d37
<   tags:
<     - ospfv3
50c40
<   eos_config:
---
>   ios_config:
52,55c42,43
<       - "no address-family ipv4"
<     parents: router ospfv3
<   tags:
<     - ospfv3
---
>       - "no address-family ipv4 unicast"
>     parents: router ospfv3 1

In some ways I prefer this configuration. All of the OSPF configuration for an interface is grouped together, rather than partly under the process, partly under the interface.

The relevant host_vars are below: -

  - eos_if: "Ethernet1"
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true
  - eos_if: "Ethernet2"
    ospf:
      area: "0.0.0.0"
    ospfv3:
      area: "0.0.0.0"
  - eos_if: "Loopback0"
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true
Generated configuration

The generated configuration is below: -

interface Ethernet1
   ospfv3 ipv6 passive-interface
   ospfv3 ipv6 area 0.0.0.0
!
interface Ethernet2
   ospfv3 ipv6 area 0.0.0.0
!
interface Loopback0
   ospfv3 ipv6 passive-interface
   ospfv3 ipv6 area 0.0.0.0
!
router ospfv3
   router-id 192.0.2.103
   !
   address-family ipv6

Arista appears to translate the ospf ipv6 command to ospfv3 ipv6, so you could change the playbook to this style instead.

Verification

arista-01

! Show OSPF interfaces (no brief option available)
arista-01# show ospfv3 interface
OSPFv3 address-family ipv6
Ethernet2 is up
  Interface Address fe80::5054:ff:fec5:df3c, VRF default, Area 0.0.0.0
  Network Type Broadcast, Cost 10
  Transmit Delay is 1 sec, State Backup DR, Priority 1
  Designated Router is 192.0.2.203
  Backup Designated Router is 192.0.2.103
  Timer intervals configured, Hello 10, Dead 40, Retransmit 5
  Neighbor Count is 1
  Options are R E V6
Ethernet1 is up
  Interface Address fe80::5054:ff:fec5:df3c, VRF default, Area 0.0.0.0
  Network Type Broadcast, Cost 10
  Transmit Delay is 1 sec, State DR, Priority 1
  Designated Router is 192.0.2.103
  Backup Designated Router is 0.0.0.0
  Timer intervals configured, Hello 10, Dead 40, Retransmit 5
  Neighbor Count is 0 (Passive Interface)
  Options are R E V6
Loopback0 is up
  Interface Address unassigned, VRF default, Area 0.0.0.0
  Network Type Broadcast, Cost 10
  Transmit Delay is 1 sec, State DR, Priority 1
  Designated Router is 192.0.2.103
  Backup Designated Router is 0.0.0.0
  Timer intervals configured, Hello 10, Dead 40, Retransmit 5
  Neighbor Count is 0 (Passive Interface)
  Options are R E V6

! Show OSPF neighbours
arista-01#show ipv6 ospf neighbor
Routing Process "ospf 0":
Neighbor 192.0.2.203 VRF default priority is 1, state is Full
  In area 0.0.0.0 interface Ethernet2
  DR is 192.0.2.203 BDR is 192.0.2.103
  Options is R E V6
  Dead timer is due in 29 seconds
  Graceful-restart-helper mode is Inactive
  Graceful-restart attempts: 0

! Show routing table
arista-01#show ipv6 route ospf

VRF: default
Displaying 1 of 11 IPv6 routing table entries
Codes: C - connected, S - static, K - kernel, O3 - OSPFv3, B - BGP, R - RIP, A B - BGP Aggregate, I L1 - IS-IS level 1, I L2 - IS-IS level 2, DH - DHCP, NG - Nexthop Group Static Route, M - Martian, DP - Dynamic Policy Route, L - VRF Leaked

 O3       2001:db8:903:beef::2/128 [110/20]
           via fe80::5054:ff:fed8:9755, Ethernet2

! Ping!
arista-01#ping 2001:db8:903:beef::2
PING 2001:db8:903:beef::2(2001:db8:903:beef::2) 72 data bytes
80 bytes from 2001:db8:903:beef::2: icmp_seq=1 ttl=64 time=7.31 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=2 ttl=64 time=2.62 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=3 ttl=64 time=2.55 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=4 ttl=64 time=2.54 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=5 ttl=64 time=2.51 ms

--- 2001:db8:903:beef::2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 27ms
rtt min/avg/max/mdev = 2.513/3.509/7.310/1.901 ms, ipg/ewma 6.827/5.341 ms

arista-01#ping 2001:db8:903:beef::2 source lo0
PING 2001:db8:903:beef::2(2001:db8:903:beef::2) from 2001:db8:903:beef::1 : 72 data bytes
80 bytes from 2001:db8:903:beef::2: icmp_seq=1 ttl=64 time=3.02 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=2 ttl=64 time=2.39 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=3 ttl=64 time=2.44 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=4 ttl=64 time=2.67 ms
80 bytes from 2001:db8:903:beef::2: icmp_seq=5 ttl=64 time=3.12 ms

--- 2001:db8:903:beef::2 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.393/2.732/3.121/0.297 ms, ipg/ewma 3.388/2.890 ms

arista-02

! Show OSPF interfaces
arista-02#show ipv6 ospf interface
Ethernet1 is up
  Interface Address fe80::5054:ff:fed8:9755, VRF default, Area 0.0.0.0
  Network Type Broadcast, Cost 10
  Transmit Delay is 1 sec, State DR, Priority 1
  Designated Router is 192.0.2.203
  Backup Designated Router is 192.0.2.103
  Timer intervals configured, Hello 10, Dead 40, Retransmit 5
  Neighbor Count is 1
  Options are R E V6
Loopback0 is up
  Interface Address unassigned, VRF default, Area 0.0.0.0
  Network Type Broadcast, Cost 10
  Transmit Delay is 1 sec, State DR, Priority 1
  Designated Router is 192.0.2.203
  Backup Designated Router is 0.0.0.0
  Timer intervals configured, Hello 10, Dead 40, Retransmit 5
  Neighbor Count is 0 (Passive Interface)
  Options are R E V6

! Show OSPF neighbours
arista-02#show ipv6 ospf neighbor
Routing Process "ospf 0":
Neighbor 192.0.2.103 VRF default priority is 1, state is Full
  In area 0.0.0.0 interface Ethernet1
  DR is 192.0.2.203 BDR is 192.0.2.103
  Options is R E V6
  Dead timer is due in 31 seconds
  Graceful-restart-helper mode is Inactive
  Graceful-restart attempts: 0

! Show routing table
arista-02#show ipv6 route ospf

VRF: default
Displaying 2 of 10 IPv6 routing table entries
Codes: C - connected, S - static, K - kernel, O3 - OSPFv3, B - BGP, R - RIP, A B - BGP Aggregate, I L1 - IS-IS level 1, I L2 - IS-IS level 2, DH - DHCP, NG - Nexthop Group Static Route, M - Martian, DP - Dynamic Policy Route, L - VRF Leaked

 O3       2001:db8:103::/64 [110/20]
           via fe80::5054:ff:fec5:df3c, Ethernet1
 O3       2001:db8:903:beef::1/128 [110/20]
           via fe80::5054:ff:fec5:df3c, Ethernet1

! Ping!
arista-02#ping 2001:db8:903:beef::1
PING 2001:db8:903:beef::1(2001:db8:903:beef::1) 72 data bytes
80 bytes from 2001:db8:903:beef::1: icmp_seq=1 ttl=64 time=6.94 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=2 ttl=64 time=2.67 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=3 ttl=64 time=2.64 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=4 ttl=64 time=2.64 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=5 ttl=64 time=2.47 ms

--- 2001:db8:903:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 25ms
rtt min/avg/max/mdev = 2.474/3.477/6.946/1.736 ms, ipg/ewma 6.387/5.147 ms

arista-02#ping 2001:db8:903:beef::1 source lo0
PING 2001:db8:903:beef::1(2001:db8:903:beef::1) from 2001:db8:903:beef::2 : 72 data bytes
80 bytes from 2001:db8:903:beef::1: icmp_seq=1 ttl=64 time=3.17 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=2 ttl=64 time=2.61 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=3 ttl=64 time=2.48 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=4 ttl=64 time=2.47 ms
80 bytes from 2001:db8:903:beef::1: icmp_seq=5 ttl=64 time=3.59 ms

--- 2001:db8:903:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.476/2.868/3.592/0.447 ms, ipg/ewma 3.267/3.039 ms

arista-02#ping 2001:db8:103::f
PING 2001:db8:103::f(2001:db8:103::f) 72 data bytes
80 bytes from 2001:db8:103::f: icmp_seq=1 ttl=64 time=3.10 ms
80 bytes from 2001:db8:103::f: icmp_seq=2 ttl=64 time=2.51 ms
80 bytes from 2001:db8:103::f: icmp_seq=3 ttl=64 time=2.47 ms
80 bytes from 2001:db8:103::f: icmp_seq=4 ttl=64 time=2.99 ms
80 bytes from 2001:db8:103::f: icmp_seq=5 ttl=64 time=3.26 ms

--- 2001:db8:103::f ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 13ms
rtt min/avg/max/mdev = 2.476/2.872/3.269/0.322 ms, ipg/ewma 3.308/3.002 ms

Unfortunately the IPv6 verification commands for OSPF are a little on the verbose side, with no brief options available. It is nice to see the extra information, but it makes it harder to quickly glance at the output to see a status for all neighbours and interfaces.

Other than though, everything is looking good!

BGP Playbook

We are using BGP to communicate with netsvr-01, as well as advertising routes received from netsvr-01 from the edge switch to the internal switch.

The contents of the playbook are: -

---
## tasks file for routing
##
- name: Configure BGP - eBGP v4 peers
  eos_bgp:
    config:
      bgp_as: "{{ bgp['local_as'] }}"
      log_neighbor_changes: True
      router_id: "{{ router_id }}"
      neighbors:
      - neighbor: "{{ item.peer }}"
        remote_as: "{{ item.remote_as }}"
  when:
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv4 is defined
    - item.ebgp is defined
  loop: "{{ bgp.neighbors.ipv4 }}"
  tags:
  - bgp
  - bgp_v4

- name: Configure BGP - eBGP v6 peers
  eos_bgp:
    config:
      bgp_as: "{{ bgp['local_as'] }}"
      log_neighbor_changes: True
      router_id: "{{ router_id }}"
      neighbors:
      - neighbor: "{{ item.peer }}"
        remote_as: "{{ item.remote_as }}"
      address_family:
        - afi: ipv6
          neighbors:
          - neighbor: "{{ item.peer }}"
            activate: true
  when:
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv6 is defined
    - item.ebgp is defined
  loop: "{{ bgp.neighbors.ipv6 }}"
  tags:
  - bgp
  - bgp_v6

- name: Configure BGP - iBGP v4 peers
  eos_bgp:
    config:
      bgp_as: "{{ bgp['local_as'] }}"
      log_neighbor_changes: True
      router_id: "{{ router_id }}"
      neighbors:
      - neighbor: "{{ item.peer }}"
        remote_as: "{{ item.remote_as }}"
        update_source: "{{ item.update_source }}"
  when:
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv4 is defined
    - item.ibgp is defined
  loop: "{{ bgp.neighbors.ipv4 }}"
  tags:
  - bgp
  - bgp_v4

- name: Configure BGP - iBGP v6 peers
  eos_bgp:
    config:
      bgp_as: "{{ bgp['local_as'] }}"
      log_neighbor_changes: True
      router_id: "{{ router_id }}"
      neighbors:
      - neighbor: "{{ item.peer }}"
        remote_as: "{{ item.remote_as }}"
        update_source: "{{ item.update_source }}"
      address_family:
        - afi: ipv6
          neighbors:
          - neighbor: "{{ item.peer }}"
            activate: true
  when:
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv6 is defined
    - item.ibgp is defined
  loop: "{{ bgp.neighbors.ipv6 }}"
  tags:
  - bgp
  - bgp_v6

- name: Configure BGP - Redistribute OSPF
  eos_bgp:
    config:
      bgp_as: "{{ bgp['local_as'] }}"
      redistribute:
        - protocol: ospf
        - protocol: connected
  when:
    - bgp is defined
    - bgp.redist is defined
    - bgp.redist.ospf is defined
  tags:
  - bgp
  - bgp_v4

- name: Configure BGP - Redistribute OSPFv3
  eos_bgp:
    config:
      bgp_as: "{{ bgp['local_as'] }}"
      address_family:
        - afi: ipv6
          redistribute:
            - protocol: ospf
  when:
    - bgp is defined
    - bgp.redist is defined
    - bgp.redist.ospfv3 is defined
  tags:
  - bgp
  - bgp_v6

- name: Configure BGP - iBGP v4 Default Originate
  eos_config:
    lines:
      - neighbor {{ item.peer }} default-originate
    parents:
      - router bgp {{ bgp['local_as'] }}
  when:
    - bgp is defined
    - bgp.neighbors is defined
    - bgp.neighbors.ipv4 is defined
    - item.ibgp is defined
    - item.default_originate is defined
  loop: "{{ bgp.neighbors.ipv4 }}"
  tags:
  - bgp
  - bgp_v4

The main differences here are: -

  • Arista does not support the include-connected statement when redistributing OSPF
  • We redistribute connected routes, due to the above
  • Most of the IPv4-specific statements do not go under address-family ipv4 (e.g. redistribution)

Other than that, there are no major differences: -

<   eos_bgp:
---
>   ios_bgp:
24c24
<   eos_bgp:
---
>   ios_bgp:
48c48
<   eos_bgp:
---
>   ios_bgp:
68c68
<   eos_bgp:
---
>   ios_bgp:
92,93c92,93
< - name: Configure BGP - Redistribute OSPF and connected routes
<   eos_bgp:
---
> - name: Configure BGP - Redistribute OSPF
>   ios_bgp:
96,98c96,100
<       redistribute:
<         - protocol: ospf
<         - protocol: connected
---
>       address_family:
>         - afi: ipv4
>           redistribute:
>             - protocol: ospf
>               id: 1
107,108c109,110
< - name: Configure BGP - Redistribute OSPFv3 and connected routes
<   eos_bgp:
---
> - name: Configure BGP - Redistribute OSPFv3
>   ios_bgp:
115c117
<             - protocol: connected
---
>               id: 1
123a126,140
> - name: Configure BGP - Redistribute OSPFv3 and include connected networks
>   ios_config:
>     lines:
>       - redistribute ospf 1 include-connected
>     parents:
>       - router bgp {{ bgp['local_as'] }}
>       - address-family ipv6 unicast
>   when:
>     - bgp is defined
>     - bgp.redist is defined
>     - bgp.redist.ospfv3 is defined
>   tags:
>   - bgp
>   - bgp_v6
>
125c142
<   eos_config:
---
>   ios_config:
129a147
>       - address-family ipv4 unicast

The relevant host_vars for this are: -

bgp:
  local_as: 65103
  redist:
    ospf: true
    ospfv3: true
  neighbors:
    ipv4:
     - peer: 10.100.103.254
       remote_as: 65430
       ebgp: true
       acl:
         acl_index: 110
     - peer: 192.0.2.203
       remote_as: 65103
       ibgp: true
       update_source: Loopback0
       default_originate: true
    ipv6:
     - peer: "2001:db8:103::ffff"
       remote_as: 65430
       ebgp: true
       acl:
         acl_index: 110
     - peer: "2001:db8:903:beef::2"
       remote_as: 65103
       ibgp: true
       update_source: Loopback0
Generated configuration

The generated configuration from the above is: -

router bgp 65103
   router-id 192.0.2.103
   neighbor 10.100.103.254 remote-as 65430
   neighbor 192.0.2.203 remote-as 65103
   neighbor 192.0.2.203 update-source Loopback0
   neighbor 192.0.2.203 default-originate
   neighbor 192.0.2.203 maximum-routes 12000
   neighbor 2001:db8:103::ffff remote-as 65430
   neighbor 2001:db8:903:beef::2 remote-as 65103
   neighbor 2001:db8:903:beef::2 update-source Loopback0
   redistribute connected
   redistribute ospf
   !
   address-family ipv6
      neighbor 2001:db8:103::ffff activate
      neighbor 2001:db8:903:beef::2 activate
      redistribute ospfv3

Other than the slightly shorter address-family commands, this would qualify as valid Cisco IOS configuration too!

Verification

arista-01

! Show BGP neighbours on IPv4 and IPv6
arista-01#show ip bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Neighbor Status Codes: m - Under maintenance
  Neighbor         V  AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  10.100.103.254   4  65430            403       403    0    0 06:36:38 Estab   1      1
  192.0.2.203      4  65103            399       404    0    0 06:35:11 Estab   0      0

arista-01#show ipv6 bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Neighbor Status Codes: m - Under maintenance
  Neighbor         V  AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  2001:db8:103::ffff 4  65430            402       402    0    0 06:36:56 Estab   1      1
  2001:db8:903:beef::2 4  65103            399       402    0    0 06:35:31 Estab   0      0

! Show BGP routes
arista-01#show ip bgp
BGP routing table information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
                    S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

         Network                Next Hop            Metric  LocPref Weight  Path
 * >     10.15.30.0/24          -                     1       0       -       i
 * >     10.100.103.0/24        -                     1       0       -       i
 * >     10.100.203.0/24        -                     1       0       -       i
 * >     192.0.2.1/32           10.100.103.254        0       100     0       65430 i
 * >     192.0.2.103/32         -                     0       0       -       i
 * >     192.0.2.203/32         -                     20      0       -       i
 * >     192.168.122.0/24       -                     1       0       -       i

arista-01#show ipv6 bgp
BGP routing table information for VRF default
Router identifier 192.0.2.103, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
                    S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

         Network                Next Hop            Metric  LocPref Weight  Path
 * >     2001:db8:103::/64      -                     1       0       -       i
 * >     2001:db8:203::/64      -                     1       0       -       i
 * >     2001:db8:903:beef::1/128 -                     0       0       -       i
 * >     2001:db8:903:beef::2/128 -                     20      0       -       i
 * >     2001:db8:999:beef::1/128 2001:db8:103::ffff    0       100     0       65430 i

! Ping the netsvr Loopback (192.0.2.1 and 2001:DB8:999:BEEF::1)
arista-01#ping 192.0.2.1
PING 192.0.2.1 (192.0.2.1) 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.52 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.08 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=64 time=1.01 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=64 time=1.12 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=64 time=1.56 ms

--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.017/1.263/1.564/0.232 ms, ipg/ewma 1.899/1.398 ms

arista-01#ping 192.0.2.1 source lo0
PING 192.0.2.1 (192.0.2.1) from 192.0.2.103 : 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.48 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.04 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=64 time=1.05 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=64 time=1.11 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=64 time=1.47 ms

--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.040/1.233/1.486/0.204 ms, ipg/ewma 1.916/1.365 ms

arista-01#ping 192.0.2.1 source 10.100.203.254
PING 192.0.2.1 (192.0.2.1) from 10.100.203.254 : 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.47 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.09 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=64 time=1.03 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=64 time=1.23 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=64 time=1.60 ms

--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.038/1.290/1.607/0.219 ms, ipg/ewma 1.825/1.392 ms

arista-01#ping 2001:db8:999:beef::1
PING 2001:db8:999:beef::1(2001:db8:999:beef::1) 72 data bytes
80 bytes from 2001:db8:999:beef::1: icmp_seq=1 ttl=64 time=1.42 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=2 ttl=64 time=1.09 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=3 ttl=64 time=1.19 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=4 ttl=64 time=1.59 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=5 ttl=64 time=1.76 ms

--- 2001:db8:999:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.092/1.413/1.764/0.249 ms, ipg/ewma 1.903/1.435 ms

arista-01#ping 2001:db8:999:beef::1 source lo0
PING 2001:db8:999:beef::1(2001:db8:999:beef::1) from 2001:db8:903:beef::1 : 72 data bytes
80 bytes from 2001:db8:999:beef::1: icmp_seq=1 ttl=64 time=1.52 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=2 ttl=64 time=1.08 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=3 ttl=64 time=1.05 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=4 ttl=64 time=1.11 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=5 ttl=64 time=1.71 ms

--- 2001:db8:999:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 7ms
rtt min/avg/max/mdev = 1.056/1.297/1.713/0.271 ms, ipg/ewma 1.851/1.420 ms

arista-02

! Show BGP neighbours on IPv4 and IPv6
arista-02#show ip bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Neighbor Status Codes: m - Under maintenance
  Neighbor         V  AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  192.0.2.103      4  65103            412       407    0    0 06:43:44 Estab   7      7

arista-02#show ipv6 bgp summary
BGP summary information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Neighbor Status Codes: m - Under maintenance
  Neighbor         V  AS           MsgRcvd   MsgSent  InQ OutQ  Up/Down State   PfxRcd PfxAcc
  2001:db8:903:beef::1 4  65103            410       407    0    0 06:43:45 Estab   5      5

! Show BGP routes
BGP routing table information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
                    S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

         Network                Next Hop            Metric  LocPref Weight  Path
 * >     0.0.0.0/0              192.0.2.103           0       100     0       ?
 * #     10.15.30.0/24          192.0.2.103           0       100     0       i
 * #     10.100.103.0/24        192.0.2.103           0       100     0       i
 * #     10.100.203.0/24        192.0.2.103           0       100     0       i
 * >     192.0.2.1/32           10.100.103.254        0       100     0       65430 i
 * #     192.0.2.103/32         192.0.2.103           0       100     0       i
 * >     192.168.122.0/24       192.0.2.103           0       100     0       i

arista-02#show ipv6 bgp
BGP routing table information for VRF default
Router identifier 192.0.2.203, local AS number 65103
Route status codes: s - suppressed, * - valid, > - active, # - not installed, E - ECMP head, e - ECMP
                    S - Stale, c - Contributing to ECMP, b - backup, L - labeled-unicast
Origin codes: i - IGP, e - EGP, ? - incomplete
AS Path Attributes: Or-ID - Originator ID, C-LST - Cluster List, LL Nexthop - Link Local Nexthop

         Network                Next Hop            Metric  LocPref Weight  Path
 * #     2001:db8:103::/64      2001:db8:903:beef::1  0       100     0       i
 * #     2001:db8:203::/64      2001:db8:903:beef::1  0       100     0       i
 * #     2001:db8:903:beef::1/128 2001:db8:903:beef::1  0       100     0       i
 * #     2001:db8:903:beef::2/128 2001:db8:903:beef::1  0       100     0       i
 * >     2001:db8:999:beef::1/128 2001:db8:103::ffff    0       100     0       65430 i

! Ping the netsvr Loopback (192.0.2.1 and 2001:db8:999:beef::1)
arista-02#ping 192.0.2.1
PING 192.0.2.1 (192.0.2.1) 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=63 time=4.88 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=63 time=4.38 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=63 time=4.62 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=63 time=4.52 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=63 time=5.12 ms

--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 20ms
rtt min/avg/max/mdev = 4.383/4.707/5.120/0.262 ms, ipg/ewma 5.142/4.806 ms

arista-02#ping 192.0.2.1 source lo0
PING 192.0.2.1 (192.0.2.1) from 192.0.2.203 : 72(100) bytes of data.
80 bytes from 192.0.2.1: icmp_seq=1 ttl=63 time=4.51 ms
80 bytes from 192.0.2.1: icmp_seq=2 ttl=63 time=3.86 ms
80 bytes from 192.0.2.1: icmp_seq=3 ttl=63 time=4.29 ms
80 bytes from 192.0.2.1: icmp_seq=4 ttl=63 time=4.60 ms
80 bytes from 192.0.2.1: icmp_seq=5 ttl=63 time=4.61 ms

--- 192.0.2.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 18ms
rtt min/avg/max/mdev = 3.866/4.378/4.613/0.287 ms, ipg/ewma 4.706/4.460 ms

arista-02#ping 2001:db8:999:beef::1 source lo0
PING 2001:db8:999:beef::1(2001:db8:999:beef::1) from 2001:db8:903:beef::2 : 72 data bytes
80 bytes from 2001:db8:999:beef::1: icmp_seq=1 ttl=63 time=4.47 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=2 ttl=63 time=3.99 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=3 ttl=63 time=4.11 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=4 ttl=63 time=4.53 ms
80 bytes from 2001:db8:999:beef::1: icmp_seq=5 ttl=63 time=5.06 ms

--- 2001:db8:999:beef::1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 18ms
rtt min/avg/max/mdev = 3.999/4.436/5.063/0.382 ms, ipg/ewma 4.644/4.479 ms

All looking good! Because of including connected routes, we do not have the same reachability issues we encountered in the Cisco post.

Ideally we should be filtering what connected routes are redistributed too (like in Part 4 - Juniper JunOS), but we are just redistributing all the routes for now.

NAT

In the Cisco post, I covered doing NAT. However as mentioned the vEOS images do not support NAT (due to them emulating switches rather than routers). WIth this being the case, I have omitted this role, as I have no way of verifying that the commands will even apply. I hope to one day have access to a routed-based Arista image (or an Arista router) to test the role out.

SNMP

SNMP is enabled so that we would be able to add the vEOS images into a monitoring system.

Playbook

The contents of the playbook are: -

---
## tasks file for snmp
- name: Enable SNMPv3
  eos_config:
    src: snmpv3.j2
  tags:
    - snmp

This is identical to the IOS playbook, except we use eos_config rather than ios_config. The template itself is below: -

snmp-server location {{ snmp['location'] }}
snmp-server contact {{ snmp['contact'] }}
snmp-server group {{ snmp['group'] }} v3 priv
snmp-server user {{ snmp['user'] }} {{ snmp['group'] }} v3 auth sha {{ snmp['auth_key'] }} priv aes {{ snmp['priv_key'] }}

The only difference between this template and the Cisco template is that in the Arista template, we specify priv aes for AES128-based encryption, whereas in Cisco you specify priv aes 128. Everything else is identical.

Our group_vars relevant to this are below: -

snmp:
  location: Yeti Home
  contact: The Hairy One
  user: yetiops
  group: yetiops_group
  auth_key: !vault |
            $ANSIBLE_VAULT;1.1;AES256
            383###REDACTED###############################################################566
            363###REDACTED###############################################################366
            343###REDACTED###############################################################565
            326###REDACTED###############################################################362
            3431
  priv_key: !vault |
            $ANSIBLE_VAULT;1.1;AES256
            386###REDACTED###############################################################764
            613###REDACTED###############################################################630
            646###REDACTED###############################################################331
            376###REDACTED###############################################################137
            3563

These are the same as for our Cisco playbooks, and again we use Ansible Vault to encrypt the authentication and privacy keys.

Generated configuration

The generated configuration looks like the below: -

snmp-server contact The Hairy One
snmp-server location Yeti Home
snmp-server group yetiops_group v3 priv
snmp-server user yetiops yetiops_group v3 localized f5717f525400c5df3c00 auth sha ###AUTH-KEY### priv aes ###PRIV-KEY### 

The localized command is based upon the engineID that is generated by the image itself. For this image, this is snmp-server engineID local f5717f525400c5df3c00.

Verification

To check this, we’ll check using snmpwalk: -

$ snmpwalk -v3 -u yetiops -a SHA -A yeti_hash123 -x AES -X yeti_key123 -l authPriv 10.15.30.43
iso.3.6.1.2.1.1.1.0 = STRING: "Linux arista-01 4.9.122.Ar-14427155.4231F #1 SMP PREEMPT Wed Nov 27 21:50:11 PST 2019 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.30065.1.2759
iso.3.6.1.2.1.1.3.0 = Timeticks: (2573396) 7:08:53.96
iso.3.6.1.2.1.1.4.0 = STRING: "The Hairy One"
iso.3.6.1.2.1.1.5.0 = STRING: "arista-01"
iso.3.6.1.2.1.1.6.0 = STRING: "Yeti Home"
iso.3.6.1.2.1.1.7.0 = INTEGER: 14
[...]

$ snmpwalk -v3 -u yetiops -a SHA -A yeti_hash123 -x AES -X yeti_key123 -l authPriv 10.15.30.44 | head -n 7
iso.3.6.1.2.1.1.1.0 = STRING: "Linux arista-02 4.9.122.Ar-14427155.4231F #1 SMP PREEMPT Wed Nov 27 21:50:11 PST 2019 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.30065.1.2759
iso.3.6.1.2.1.1.3.0 = Timeticks: (2579360) 7:09:53.60
iso.3.6.1.2.1.1.4.0 = STRING: "The Hairy One"
iso.3.6.1.2.1.1.5.0 = STRING: "arista-02"
iso.3.6.1.2.1.1.6.0 = STRING: "Yeti Home"
iso.3.6.1.2.1.1.7.0 = INTEGER: 14
[...]

All looking good!

AAA

We use AAA (via the tac_plus instance on our netsvr-01) to allow central management of user login, command authorization, and accounting of all commands ran on the devices.

Playbook

The contents of the playbook are as follows: -

---
## tasks file for aaa
- name: Enable TACACS+
  eos_config:
    src: tacacs.j2
  tags:
  - aaa

The only difference here to the IOS task is that we use eos_config rather than ios_config. The template itself looks like the below: -

aaa authentication enable default group tacacs+ local
aaa authorization exec default group tacacs+ none
aaa authorization commands 0 default group tacacs+ none
aaa authorization commands 1 default group tacacs+ none
aaa authorization commands 15 default group tacacs+ none
aaa accounting exec default start-stop group tacacs+
aaa accounting commands 0 default start-stop group tacacs+
aaa accounting commands 1 default start-stop group tacacs+
aaa accounting commands 15 default start-stop group tacacs+
tacacs-server host {{ tacacs['ipv4'] }} key 0 {{ tacacs['secret'] }} timeout 1
ip tacacs source-interface Loopback0

There are a few differences here from the Cisco template, which are: -

  • We don’t specify aaa new-model, as the default model for AAA on Arista is equivalent to Cisco’s “new” model
  • enable default group tacacs+ local, as IOS uses the word enable instead of local as the last part of the command
  • We source TACACS+ from our Loopback interface, to ensure that it is not tied to physical interface
  • We do not need to specify line vty and line console commands to enable TACACS+ for remote and console users
  • The tacacs server syntax in IOS does not work on EOS, so we use the “older” style syntax

The reason I say “older” syntax for specifying tacacs-server host's is that if you apply this command on any recent Cisco router, it would give a warning that the syntax style will be deprecated soon, to be replaced by: -

 tacacs server NETSVR
  address ipv4 $IP-ADDRESS 
  key 0 $SECRET 
  timeout 1

Despite all of this, the configuration being applied is still very similar to Cisco. This is another case where if you are used to Cisco IOS, you are going to be immediately familiar with most of an Arista EOS configuration.

The group_vars that are relevant to this are: -

tacacs:
  ipv4: 192.0.2.1
  secret: supersecret

Generated configuration

The configuration that is generated looks like the below: -

aaa authentication enable default group tacacs+ local
aaa authorization exec default group tacacs+ none
aaa authorization commands 0 default group tacacs+ none
aaa authorization commands 1 default group tacacs+ none
aaa authorization commands 15 default group tacacs+ none
aaa accounting exec default start-stop group tacacs+
aaa accounting commands 0 default start-stop group tacacs+
aaa accounting commands 1 default start-stop group tacacs+
aaa accounting commands 15 default start-stop group tacacs+
tacacs-server host 192.0.2.1 key 0 supersecret timeout 1 
ip tacacs source-interface Loopback0

After it has been applied, the running configuration will look like: -

aaa authentication login default group tacacs+ local
aaa authentication enable default group tacacs+ local
aaa authorization exec default group tacacs+ none
aaa authorization commands 0-1,15 default group tacacs+ none
aaa accounting exec default start-stop group tacacs+
aaa accounting commands 0-1,15 default start-stop group tacacs+
!
tacacs-server host 192.0.2.1 key 7 095F5B191C170417081E013E6B30213E302D06135652
!
ip tacacs source-interface Loopback0

In the above, the separate commands for 0, 1 and 15 (the privilege levels) are grouped together as one command. Also the TACACS+ secret key is encrypted.

Verification

! Can we login with our yetiops user, and be placed into enable mode instantly?
$ ssh [email protected]
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
Password:
arista-01#

! What about a user that doesn't exist?
$ ssh [email protected]
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
Password:
Password:
Password:
[email protected]: Permission denied (publickey,keyboard-interactive).

! What do we see in our accounting log?
Apr 18 20:22:12	192.0.2.103	ansible	vty3	10.15.30.1	stop	task_id=1	service=shell	start_time=1587237505	timezone=UTC	elapsed_time=227
Apr 18 20:22:18	192.0.2.103	yetiops	ssh	10.15.30.1	start	task_id=5	service=shell	start_time=1587237737	timezone=UTC
Apr 18 20:22:37	192.0.2.103	yetiops	vty3	10.15.30.1	stop	task_id=6	service=shell	priv-lvl=15	start_time=1587237756	timezone=UTC	cmd=exit <cr>
Apr 18 20:22:37	192.0.2.103	yetiops	vty3	10.15.30.1	stop	task_id=5	service=shell	start_time=1587237737	timezone=UTC	elapsed_time=20

! What about if the TACACS+ server goes away?
$ sudo systemctl stop tac_plus
$ sudo systemctl status tac_plus
● tac_plus.service - LSB: TACACS+ server based on Cisco source release
   Loaded: loaded (/etc/rc.d/init.d/tac_plus; generated)
   Active: inactive (dead) since Sat 2020-04-18 20:24:08 BST; 11s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 7549 ExecStop=/etc/rc.d/init.d/tac_plus stop (code=exited, status=0/SUCCESS)
  Process: 7388 ExecStart=/etc/rc.d/init.d/tac_plus start (code=exited, status=0/SUCCESS)

Apr 18 20:22:50 netsvr-01 tac_plus[7522]: login failure: bob 192.0.2.103 (192.0.2.103) ssh
Apr 18 20:23:33 netsvr-01 tac_plus[7524]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:39 netsvr-01 tac_plus[7528]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:39 netsvr-01 tac_plus[7529]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:40 netsvr-01 tac_plus[7530]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:44 netsvr-01 tac_plus[7531]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:23:48 netsvr-01 tac_plus[7533]: connect from 192.0.2.103 [192.0.2.103]
Apr 18 20:24:08 netsvr-01 systemd[1]: Stopping LSB: TACACS+ server based on Cisco source release...
Apr 18 20:24:08 netsvr-01 tac_plus[7549]: Shutting down tacacs+: [  OK  ]
Apr 18 20:24:08 netsvr-01 systemd[1]: Stopped LSB: TACACS+ server based on Cisco source release.

$ ssh [email protected]
----------------------------------------
|
| This banner was generated by Ansible
|
----------------------------------------
|
| You are logged into arista-01
|
----------------------------------------
Password:
arista-01>en
Password:
arista-01#

All looking good!

Parent playbook

The parent playbook (i.e. the playbook that brings all the roles together) is below: -

---
- hosts: arista
  gather_facts: no
  tasks:
  - import_role:
      name: system
  - import_role:
      name: interfaces
  - import_role:
      name: acl
  - import_role:
      name: routing
  - import_role:
      name: snmp
      #  - import_role:
      #      name: nat
  - import_role:
      name: aaa
  - name: save running to startup when modified
    eos_config:
      save_when: modified

Again, this is the same as the IOS playbook. The only exception is that we have commented out the NAT role until it can be tested.

Role Order

The role order is similar to the IOS playbook, except the SNMP task is applied later in the playbook. As SNMP has no dependencies, we can place it anywhere.

To reiterate the reasons for the order of the roles: -

  • system - Basic setup, and enabling logging
  • interfaces - A device cannot route traffic without IPs!
  • acl - Ensures that we do not allow nefarious traffic when external routing is enabled
  • routing - Required for NAT and AAA
  • snmp - As mentioned, this can go anywhere in the playbook
  • nat - If we could enable NAT, traffic from the internal router would not reach the NAT interfaces until routing is established
  • aaa - Ensures that if TACACS+ is not functioning correctly, the device is not in a half configured (and potentially broken) state

Artifacts

The final directory structure looks like the below: -

$ tree -L 2
.
├── ansible.cfg
├── ansible.log
├── arista.yaml
├── group_vars
│   └── arista
├── host_vars
│   ├── arista-01.yml
│   └── arista-02.yml
├── inventory
└── roles
    ├── aaa
    ├── acl
    ├── interfaces
    ├── lldp
    ├── nat
    ├── routing
    ├── snmp
    └── system

11 directories, 7 files

The final contents of our group_vars are: -

ansible_user: ansible
ansible_connection: network_cli
ansible_network_os: eos
ansible_ssh_pass: !vault |
                  $ANSIBLE_VAULT;1.1;AES256
                  383###REDACTED###############################################################566
                  363###REDACTED###############################################################366
                  343###REDACTED###############################################################565
                  326###REDACTED###############################################################362
                  3431Ta
ansible_become: yes
ansible_become_method: enable
ansible_become_password: !vault |
                  $ANSIBLE_VAULT;1.1;AES256
                  383###REDACTED###############################################################566
                  363###REDACTED###############################################################366
                  343###REDACTED###############################################################565
                  326###REDACTED###############################################################362
                  3431Ta
log_host: 10.100.101.254
tacacs:
  ipv4: 192.0.2.1
  secret: supersecret
snmp:
  location: Yeti Home
  contact: The Hairy One
  user: yetiops
  group: yetiops_group
  auth_key: !vault |
            $ANSIBLE_VAULT;1.1;AES256
            383###REDACTED###############################################################566
            363###REDACTED###############################################################366
            343###REDACTED###############################################################565
            326###REDACTED###############################################################362
            3431
  priv_key: !vault |
            $ANSIBLE_VAULT;1.1;AES256
            386###REDACTED###############################################################764
            613###REDACTED###############################################################630
            646###REDACTED###############################################################331
            376###REDACTED###############################################################137
            3563

The final contents of our host_vars are: -

arista-01.yaml

outer_id: 192.0.2.103
rtr_role: edge
bgp:
  local_as: 65103
  redist:
    ospf: true
    ospfv3: true
  neighbors:
    ipv4:
     - peer: 10.100.103.254
       remote_as: 65430
       ebgp: true
       acl:
         acl_index: 110
     - peer: 192.0.2.203
       remote_as: 65103
       ibgp: true
       update_source: Loopback0
       default_originate: true
    ipv6:
     - peer: "2001:db8:103::ffff"
       remote_as: 65430
       ebgp: true
       acl:
         acl_index: 110
     - peer: "2001:db8:903:beef::2"
       remote_as: 65103
       ibgp: true
       update_source: Loopback0
interfaces:
  - eos_if: "Management1"
    desc: "Management"
    enabled: true
    ipv4: "10.15.30.43/24"
  - eos_if: "Ethernet1"
    desc: "To netsvr"
    enabled: true
    routed: true
    ipv4: "10.100.103.253/24"
    ipv6: "2001:db8:103::f/64"
    acl:
      ipv4:
        in: EDGE-IN
        out: EDGE-OUT
      ipv6:
        in: EDGEv6-IN
        out: EDGEv6-OUT
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true
  - eos_if: "Ethernet2"
    desc: "To arista-02"
    routed: true
    enabled: true
    ipv4: "10.100.203.254/24"
    ipv6: "2001:db8:203::a/64"
    ospf:
      area: "0.0.0.0"
    ospfv3:
      area: "0.0.0.0"
  - eos_if: "Ethernet3"
    desc: "To the Internet"
    enabled: true
    routed: true
    ipv4: "dhcp"
  - eos_if: "Loopback0"
    desc: "Loopback"
    enabled: true
    ipv4: "192.0.2.103/32"
    ipv6: "2001:db8:903:beef::1/128"
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true

arista-02-yaml

router_id: 192.0.2.203
rtr_role: internal
bgp:
  local_as: 65103
  neighbors:
    ipv4:
     - peer: 192.0.2.103
       remote_as: 65103
       ibgp: true
       update_source: Loopback0
    ipv6:
     - peer: "2001:db8:903:beef::1"
       remote_as: 65103
       ibgp: true
       update_source: Loopback0
interfaces:
  - eos_if: "Management1"
    desc: "Management"
    enabled: true
  - eos_if: "Ethernet1"
    desc: "To arista-01"
    enabled: true
    routed: true
    ipv4: "10.100.203.253/24"
    ipv6: "2001:db8:203::f/64"
    ospf:
      area: "0.0.0.0"
    ospfv3:
      area: "0.0.0.0"
  - eos_if: "Loopback0"
    desc: "Loopback"
    enabled: true
    ipv4: "192.0.2.203/32"
    ipv6: "2001:db8:903:beef::2/128"
    ospf:
      area: "0.0.0.0"
      passive: true
    ospfv3:
      area: "0.0.0.0"
      passive: true

Running the playbooks

Below is an Asciinema output of my terminal when running the playbooks, so you can see them being applied: -

Compared to the IOS playbooks, only a couple of tasks were marked as changed, despite applying the same configuration.

When looking through the TACACS+ accounting logs, you can see the command show session-config diffs, which is a method similar to JunOS’s show | compare. Arista allows you to enter a “session” for configuration and compare candidate configuration to the running configuration before committing. This isn’t the case with the Cisco IOS tasks. Tasks are marked as changed based upon the difference between the commands being applied, and the resulting output in the running configuration.

Native modules versus eos_config

Below is a summary of how many different modules are used, and also how many in total were native modules (compared to using eos_config).

Module Used
eos_config 31
eos_bgp 6
eos_logging 2
eos_l3_interfaces 2
eos_banner 2
eos_system 1
eos_interfaces 1

As with the IOS playbooks, we are using the eos_config module significantly more than the others. The reason we use the eos_config module more than in the Cisco module is because we are setting a log source for syslog, and because we need to enable IPv4 routing (due to emulating a switch rather than a router).

Thoughts compared to IOS and JunOS

If we compare the Arista playbooks to the JunOS playbooks, there are a number of differences. This is to be expected, as the configuration style of JunOS is very different from Arista EOS (or Cisco IOS). Until either common APIs or data modelling languages like YANG become commonplace, this will probably always be the case.

However if we compare them to IOS, there are so many similarities, we could almost get away with making no changes except module names.

To build the playbooks for this module, rather than rewriting the tasks, I did a mass find and replace for the word ios and replaced it with eos. The majority of the tasks applied cleanly, with only a few failing. Within a couple of hours of comparison, reading Arista documentation and checking against the Arista command line, I had a finished set of tasks and playbooks.

In the first post in this series, I said the following: -

I also believe that showing it across multiple vendors (and how similar some aspects are) will make it easier for people to move from one vendor to another, without worrying about needing to relearn syntax or tools for managing their networking estate.

The differences between IOS and EOS (in CLI syntax and in Ansible module usage) are a perfect demonstration of this. If you were to decide to move from an IOS platform to an EOS platform, migrating your Ansible playbooks would take very little effort.

In contrast, the differences between the IOS and JunOS playbooks are significant. A few days or weeks familiarising yourself with JunOS syntax would probably be enough to move from one vendor to another, but it is not quite as straightforward as the move from IOS to EOS. This is not to say that JunOS (or other vendors) are impossible to migrate between. It just means that if you do rely on Ansible, you’ll need to account for the extra time in refactoring your roles and playbooks.

Summary

While the previous two posts in the series were more about using Ansible with either Cisco or Juniper (and how each task/playbook works), this post has mainly covered how to the differences between IOS playbooks and EOS playbooks.

However this is not a criticism of Arista. It is quite easy to see how Arista has made the gains they have in the networking industry. With Cisco being such a massive part of the networking industry, using similar (and in some cases identical) syntax allows comparisons and purchasing decisions to be made entirely on price and features. I have seen first hand companies unwilling to change their networking vendor purely out of familiarity.

The final configs from the switches are in my Network Automation with Ansible repository. The next part of this series will be configuring MikroTik devices, running RouterOS.