Hello my friend,

In one of the previous articles we have introduced new vendor to our interoperability tests, which is Arista. Its virtual router vEOS has shown good capabilities for data center activities, especially in context EVPN/VXLAN. In this article we’ll take a look on how to automate its deployment.

Disclaimer

In this article we’ll review solely automation of Arista EOS configuration using Ansible. Automation of network deployment for other vendors, like Nokia (Alcatel-Lucent) SR OS, Cisco IOS XR and Cumulus Linux, have been provided recently in separated article.

Brief description

Frankly speaking. there are several options, how Arista EOS can be automated. Arista EOS can be configured in total using these 3 main approaches:

It means any of these interfaces can be used to automate Arista EOS products. In this article we’ll focus on the 2nd option, which is CLI using SSH. For that we’ll use Ansible with corresponding templates, which add this necessary degree of automation.

OpenConfig seems to be the most advanced and future proof, therefore we’ll cover it in separate article for all the vendors

Regarding Ansible and Arista, there are plenty of modules developed to configure various aspects of Arista EOS and they can be good, especially if you deploy single new customers. But in this article I’ll use single module, which in nutshell accepts all the configuration commands. In conjunction with proper jinja2 templates, I believe, it’s the best way for deploying infrastructure.

What we are going to test?

Following the same approach, we did for automation of Cumulus Linux, we’ll deploy playbooks for:

  • Configuration of network interfaces to interconnect routers within data centre IP fabric
  • Configuration of BGP both for undelay and overlay

Software version

The following infrastructure is used in my lab:

  • CentOS 7 with python 2.7.
  • Ansible 2.6 (NEW!)
  • Arista EOS 4.20.5F

See the previous article to get details how to build the lab

I have auto updates turned on in my CentOS Linux, that’s why somewhere recently Ansible has been updated from 2.4.2 to 2.6. I don’t when it happened as it was done automatically. So, be aware of the changes.

Topology

Following the same approach, we’ve done recently for Cumulus Linux, we add new node to our automation lab:

The logical topology comes from the previous lab about joint data center operation of Nokia (Alcatel-Lucent) SR OS and Arista EOS:

As we mentioned previously, we deal only with automation of Arista EOS in this lab, therefore we provide the final configuration files for the rest vendors from the previous lab, and for the Arista we provide just simple pre-configuration with configured management interface and user for remote access:

Preparing environment for Ansible playbooks

As a basis for this article we take the Ansible structure with roles and templates we shared with you some time ago, that’s why we’ll omit description of role’s structure.

The structure of the interrelated folders and files are just the same as in the case of Cumulus’s automation:

Knowing how the structure looks like, we need to create it and update some other information:

#1. Update of Ansible host

1
2
3
4
$ sudo vim /etc/ansible/hosts
[arista]
vEOS1
vEOS2

#2. Update of Linux hosts

1
2
3
$ sudo vim /etc/hosts
192.168.44.81 vEOS1
192.168.44.82 vEOS2

#3. Test connectivity

1
2
3
4
5
6
[aaa@sandbox ansible]$ ping vEOS2 -c 1
PING vEOS2 (192.168.44.82) 56(84) bytes of data.
64 bytes from vEOS2 (192.168.44.82): icmp_seq=1 ttl=64 time=3.21 ms
--- vEOS2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 3.211/3.211/3.211/0.000 ms

#4. Create file with authentication data for Arista EOS

If you are following my series (link), you can see some more commands comparing to file for Cumulus Linux (link). Here, for Arista EOS, it’s more extended I believe, so I’ll update it for Cumulus Linux later on as well to fix some authentication workarounds:

1
2
3
4
5
6
7
8
9
10
11
12
$ cd ~/ansible/
$ mkdir group_vars/arista
$ touch group_vars/arista/arista_host.yml
$ vim group_vars/arista/arista_host.yml
---
ansible_connection: network_cli
ansible_network_os: eos
ansible_user: aaa
ansible_ssh_pass: aaa
ansible_become: yes
ansible_become_method: enable
...

As we use Ansible 2.6, we don’t create separated file for providers. Since 2.5 it’s deprecated and we follow new guidelines.

#5. Create new role and sub-structure for Arista EOS

1
2
3
4
5
6
7
8
9
$ touch 122_lab.yml
$ cd ~ansible/roles/
$ mkdir arista
$ cd ~ansible/roles/arista
$ mkdir 122_lab
$ mkdir tasks templates vars
$ touch tasks/main.yml
$ touch templates/iface.j2
$ touch templates/bgp.j2

#6. Create Ansible playbook to launch the role

1
2
3
4
5
6
7
---
- hosts
: arista
tags
: arista_part
connection
: network_cli
roles
:
- { role
: arista/122_lab }
...

As proposed in the module description, we put here connection type “network_cli”. Though it might be not necessary, as we mentioned it initially in group variables.

Let’s start filling in our jinja2 templates and Ansible playbooks with life.

General approach for automation for Arista (and not only)

We have used such approach earlier in different articles, so here we’ll just recap all the pieces together:

  • Creating files with per-node variables
  • Creating appropriate templates
  • Creating files with tasks

As we follow the same approach both for configuration of interfaces and BGP, we won’t split them here and we’ll show together.

#1. Creating files with per-node variables

Such structure was used many times (earlier and recent examples). We focus here on interfaces and BGP part mainly, as virtual Arista EOS router doesn’t have cards, models and so on:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
.[aaa@sandbox ansible]$ pwd
./home/aaa/ansible
.[aaa@sandbox ansible]$ cat .roles/arista/122_lab/vars/vEOS2.yml
.---
.node_var
:
.  hostname
: vEOS2
.  chassis_mode
: not_defined
.  card1
:
.    id
: not_defined
.    type
: not_defined
.    state
: false
.    mda1
:
.      id
: not_defined
.      type
: not_defined
.      state
: false
.      port1
:
.        id
: not_defined
.        type
: not_defined
.        enc
: not_defined
.        mtu
: not_defined
.        state
: false
.  interfaces
:
.    - id
: 0
.      name
: system
.      port
: loopback 0
.      vlan
: 0
.      ipv4
: 10.0.0.22/32
.      ipv6_ula
: fc00::10:0:0:22/128
.      ipv6_lla
: fe80::22
.      state
: true
.    - id
: 1
.      name
: to_SR1
.      port
: Ethernet1
.      vlan
: 12
.      ipv4
: 10.11.22.22/24
.      ipv6_ula
: fc00::10:11:22:22/112
.      ipv6_lla
: fe80::22
.      state
: true
.    - id
: 2
.      name
: to_XR3
.      port
: Ethernet1
.      vlan
: 23
.      ipv4
: 10.22.33.22/24
.      ipv6_ula
: fc00::10:22:33:22/112
.      ipv6_lla
: fe80::22
.      state
: true
.    - id
: 3
.      name
: to_XR4
.      port
: Ethernet1
.      vlan
: 24
.      ipv4
: 10.22.44.22/24
.      ipv6_ula
: fc00::10:22:44:22/112
.      ipv6_lla
: fe80::22
.      state
: true
.  routing
:
.    bgp
:
.      configured
: true
.      asn
: 65012
.      router_id
: 10.0.0.22
.      as_path_relax
: true
.      neighbors
:
.        - id
: 0
.          peer_ip
: 10.11.22.11
.          peer_asn
: 65011
.          password
: FABRIC
.          ebgp_multihop
: not_defined
.          update_source
: not_defined
.          ipv4
: true
.          evpn
: false
.        - id
: 1
.          peer_ip
: 10.22.33.33
.          peer_asn
: 65001
.          password
: FABRIC
.          ebgp_multihop
: not_defined
.          update_source
: not_defined
.          ipv4
: true
.          evpn
: false
.        - id
: 2
.          peer_ip
: 10.22.44.44
.          peer_asn
: 65002
.          password
: FABRIC
.          ebgp_multihop
: not_defined
.          update_source
: not_defined
.          ipv4
: true
.          evpn
: false
.        - id
: 3
.          peer_ip
: 10.0.0.11
.          peer_asn
: 65011
.          password
: OVERLAY
.          ebgp_multihop
: 5
.          update_source
: loopback 0
.          ipv4
: false
.          evpn
: true
....

The BGP part is tailored for our deployment, in terms of defined address families, but it’s quite easy to extended it to any other one.

#2. Creating appropriate templates

As we have two major tasks, we create two templates: for interfaces and for BGP.

The first one is for creation of interfaces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.[aaa@sandbox templates]$ pwd
./home/aaa/ansible/roles/arista/122_lab/templates
.[aaa@sandbox templates]$ cat iface.j2
.ip routing
.ip routing vrf mgmt
.!
.{% for int in node_var.interfaces %}
.interface {{ int.port }}
.{% if int.state %}    no shutdown {% endif %}
.
.{% if "loopback" not in int.port %}    no switchport {% endif %}
.
.exit
.!
.interface {{ int.port }}{% if int.vlan != 0 %}.{{ int.vlan }} {% endif %}
.
.{% if int.vlan != 0 %}    encapsulation dot1q vlan {{ int.vlan }} {% endif %}
.
.    ip address {{ int.ipv4 }}
.    ipv6 address {{ int.ipv6_ula }}
.    logging event link-status
.{% if int.state %}    no shutdown {% endif %}
.
.exit
.!
.{% endfor %}

Jinja2 is awesome, isn’t it? We don’t define any exact number of ports; the template automatically multiplies configuration to the number of interfaces defined in the files with variables. There are also some additional tweaks, like putting “switchport” on interfaces besides loopback or “encapsulation” only if VLAN besides 0 is configured.

These things are very simple and useful, to make deployment really automated and makes script “thinking instead of you”.

The second one is for the BGP configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
.[aaa@sandbox templates]$ pwd
.home/aaa/ansible/roles/arista/122_lab/templates
.[aaa@sandbox templates]$ cat bgp.j2
.service routing protocols model multi-agent
.!
.{% if node_var.routing.bgp.configured %}
.router bgp {{ node_var.routing.bgp.asn }}
.    router-id {{ node_var.routing.bgp.router_id }}
.{% if node_var.routing.bgp.as_path_relax %}
.    bgp bestpath as-path multipath-relax
.{% endif %}
.    !
.{% for nei in node_var.routing.bgp.neighbors %}
.    neighbor {{ nei.peer_ip }} remote-as {{ nei.peer_asn }}
.    neighbor {{ nei.peer_ip }} timers 5 15
.    neighbor {{ nei.peer_ip }} password {{ nei.password }}
.{% if nei.ebgp_multihop != "not_defined" %}
.    neighbor {{ nei.peer_ip }} ebgp-multihop {{ nei.ebgp_multihop }}
.{% endif %}
.{% if nei.update_source != "not_defined" %}
.    neighbor {{ nei.peer_ip }} update-source {{ nei.update_source }}
.{% endif %}
.{% if nei.ipv4 %}
.    !
.    address-family ipv4
.        neighbor {{ nei.peer_ip }} activate
.    !
.{% else %}
.    !
.    address-family ipv4
.        no neighbor {{ nei.peer_ip }} activate
.    !
.{% endif %}
.{% if nei.evpn %}
.    neighbor {{ nei.peer_ip }} send-community extended
.    !
.    address-family evpn
.        neighbor {{ nei.peer_ip }} activate
.    !
.{% else %}
.    !
.    address-family evpn
.        no neighbor {{ nei.peer_ip }} activate
.    !
.{% endif %}
.{% endfor %}
.    address-family ipv4
.        network {{ node_var.interfaces[0].ipv4 }}
.    !
.exit
.{% endif %}

In the BGP template we define some basic parameters (like ASN and router id) and then, based on the neighbour parameters, we configure them and activate for corresponding address families. One tweak I added for future, the whole this part configured only if BGP is configured, otherwise the whole template will be skipped. If we’d like to have any other underlay routing protocol, we can easily extend the template.

#3. Creating files with tasks

When the variables and templates are defined, we just need to put them together for execution it tasks’ files. To make things more manageable, I will create configuration-specific files in addition to “main.yml”. But first of all, let’s start with “main.yml” itself:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.[aaa@sandbox tasks]$ pwd
./home/aaa/ansible/roles/arista/122_lab/tasks
.[aaa@sandbox tasks]$ ls -l
.total 12
.-rwxr-xr-x. 1 aaa aaa 416 May 20 19:38 configure_bgp.yml
.-rwxr-xr-x. 1 aaa aaa 433 May 20 18:59 .configure_interfaces.yml
.-rwxr-xr-x. 1 aaa aaa 236 May 20 19:08 main.yml
.[aaa@sandbox tasks]$ cat main.yml
.---
.- name
: IMPORTING TASK-SPECIFIC DATA
.  include_vars
:
.    file
: "{{ inventory_hostname }}.yml"
.
.- name
: CONFIGURING INTERFACES
.  include_tasks
: configure_interfaces.yml
.
.- name
: CONFIGURING BGP
.  include_tasks
: configure_bgp.yml
....

In the main file with tasks we only import variable and call the specific configuration sub tasks. These configuration subtasks are almost identical:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.[aaa@sandbox tasks]$ cat configure_interfaces.yml
.---
.- name
: CREATING TEMPORARY CONFIG FILE // INTERFACES
.  template
:
.    src
: iface.j2
.    dest
: /tmp/{{ inventory_hostname }}_iface.conf
.
.- name
: APPLYING CONFIGURATION TO DEVICE // INTERFACES
.  eos_config
:
.    match
: none
.    src
: /tmp/{{ inventory_hostname }}_iface.conf
.    save_when
: modified
.
.- name
: DELETING TEMPORARY CONFIG FILE // INTERFACES
.  file
:
.    path
: /tmp/{{ inventory_hostname }}_iface.conf
.    state
: absent
....
!
!
.[aaa@sandbox tasks]$ cat configure_bgp.yml
.---
.- name
: CREATING TEMPORARY CONFIG FILE // ROUTING
.  template
:
.    src
: bgp.j2
.    dest
: /tmp/{{ inventory_hostname }}_bgp.conf
.
.- name
: APPLYING CONFIGURATION TO DEVICE // ROUTING
.  eos_config
:
.    match
: none
.    src
: /tmp/{{ inventory_hostname }}_bgp.conf
.    save_when
: modified
.
.- name
: DELETING TEMPORARY CONFIG FILE // ROUTING
.  file
:
.    path
: /tmp/{{ inventory_hostname }}_bgp.conf
.    state
: absent
....

Frankly speaking, they are absolutely identical. What differs is the template now and name of the temporary file to store config. They logic is very straightforward:

  • Create temporary configuration file based on template and variables
  • Update the configuration of the network element
  • Delete temporary file

Let’s review how our automation works

Verification

There is the single way to verify if your automation is working: test it. So, we just launch this Ansible role and check the vEOS2 data centre leaf based on Arista EOS afterwards:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
.[aaa@sandbox ansible]$ ansible-playbook 122_lab.yml
.
.PLAY [arista] ******************************************************************
.
.TASK [Gathering Facts] *********************************************************
.fatal: [vEOS1]: FAILED! => {"msg": "[Errno None] Unable to connect to port 22 on 192.168.44.81"}
ok: [vEOS2]
.
.TASK [arista/122_lab : IMPORTING TASK-SPECIFIC DATA] ***************************
.ok: [vEOS2]
.
.TASK [arista/122_lab : CONFIGURING INTERFACES] *********************************
.included: /home/aaa/ansible/roles/arista/122_lab/tasks/configure_interfaces.yml for vEOS2
.
.TASK [arista/122_lab : CREATING TEMPORARY CONFIG FILE // INTERFACES] ***********
.changed: [vEOS2]
.
.TASK [arista/122_lab : APPLYING CONFIGURATION TO DEVICE // INTERFACES] *********
.changed: [vEOS2]
.
.TASK [arista/122_lab : DELETING TEMPORARY CONFIG FILE // INTERFACES] ***********
.changed: [vEOS2]
.
.TASK [arista/122_lab : CONFIGURING BGP] ****************************************
.included: /home/aaa/ansible/roles/arista/122_lab/tasks/configure_bgp.yml for vEOS2
.
.TASK [arista/122_lab : CREATING TEMPORARY CONFIG FILE // ROUTING] **************
.changed: [vEOS2]
.
.TASK [arista/122_lab : APPLYING CONFIGURATION TO DEVICE // ROUTING] ************
.changed: [vEOS2]
.
.TASK [arista/122_lab : DELETING TEMPORARY CONFIG FILE // ROUTING] **************
.changed: [vEOS2]
.   to retry, use: --limit @/home/aaa/ansible/122_lab.retry
.
.PLAY RECAP *********************************************************************
.vEOS1                      : ok=0    changed=0    unreachable=0    failed=1
.vEOS2                      : ok=10   changed=6    unreachable=0    failed=0

Don’t pay attention to vEOS1 failures, as it isn’t launched

After the configuration is applied, we review the configuration file from vEOS2 (in the Ansible playbook, we have mentioned that config should we saved if modified, that’s why we check the startup-config):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
.vEOS2#show startup-config
.! Command: show startup-config
.! Startup-config last modified at  Sun May 20 22:54:00 2018 by aaa
.! device: vEOS2 (vEOS, EOS-4.20.1F)
.!
.! boot system flash:/vEOS-lab.swi
.!
.transceiver qsfp default-mode 4x10G
.!
.service routing protocols model multi-agent
.!
.logging console debugging
.!
.hostname vEOS2
.!
.spanning-tree mode mstp
.!
.no aaa root
.!
.username aaa privilege 15 secret sha512 $6$FVYaM.FQCFKZcbT8$Mp.ru.eKEqDGCZTecqzCLQPcf1ns0GBuJ0d3Y2eTs5457jHh5nFQkZ.eHxHy4Kuw3lUeahArRklizxzGEKSPq0
.!
.vrf definition mgmt
.!
.interface Ethernet1
.   no switchport
.!
.interface Ethernet1.12
.   logging event link-status
.   encapsulation dot1q vlan 12
.   ip address 10.11.22.22/24
.   ipv6 address fc00::10:11:22:22/112
.!
.interface Ethernet1.23
.   logging event link-status
.   encapsulation dot1q vlan 23
.   ip address 10.22.33.22/24
.   ipv6 address fc00::10:22:33:22/112
.!
.interface Ethernet1.24
.   logging event link-status
.   encapsulation dot1q vlan 24
.   ip address 10.22.44.22/24
.   ipv6 address fc00::10:22:44:22/112
.!
.interface Ethernet2
.!
.interface Ethernet3
.!
.interface Loopback0
.   logging event link-status
.   ip address 10.0.0.22/32
.   ipv6 address fc00::10:0:0:22/128
.!
.interface Management1
.   vrf forwarding mgmt
.   ip address 192.168.44.82/24
.!
.ip routing
.ip routing vrf mgmt
.!
.router bgp 65012
.   router-id 10.0.0.22
.   neighbor 10.0.0.11 remote-as 65011
.   neighbor 10.0.0.11 update-source Loopback0
.   neighbor 10.0.0.11 ebgp-multihop 5
.   neighbor 10.0.0.11 timers 5 15
.   neighbor 10.0.0.11 password 7 8Xbh/6JDYtBVMFZp5Rg2jw==
.   neighbor 10.0.0.11 send-community extended
.   neighbor 10.0.0.11 maximum-routes 12000
.   neighbor 10.11.22.11 remote-as 65011
.   neighbor 10.11.22.11 timers 5 15
.   neighbor 10.11.22.11 password 7 nUxMAXN9N7Rhha91NYr6LA==
.   neighbor 10.11.22.11 maximum-routes 12000
.   neighbor 10.22.33.33 remote-as 65001
.   neighbor 10.22.33.33 timers 5 15
.   neighbor 10.22.33.33 password 7 UGzc2IyRG9lbnB4yCZcWzg==
.   neighbor 10.22.33.33 maximum-routes 12000
.   neighbor 10.22.44.44 remote-as 65002
.   neighbor 10.22.44.44 timers 5 15
.   neighbor 10.22.44.44 password 7 EbNvHEPAdc7fwCWEz1jbxQ==
.   neighbor 10.22.44.44 maximum-routes 12000
.   !
.   address-family evpn
.      neighbor 10.0.0.11 activate
.      no neighbor 10.11.22.11 activate
.      no neighbor 10.22.33.33 activate
.      no neighbor 10.22.44.44 activate
.   !
.   address-family ipv4
.      no neighbor 10.0.0.11 activate
.      neighbor 10.11.22.11 activate
.      neighbor 10.22.33.33 activate
.      neighbor 10.22.44.44 activate
.      network 10.0.0.22/32
.!
.management ssh
.   vrf mgmt
.!
.end

Ansible playbook have been executed successfully, so our automation works!

Ansible roles and all other files we have created during this lab you will wind here: 122_lab.tar

Lessons learned

User guide for Arista EOS for Ansible has pointed me some possible solutions for problem with authentication in Cumulus Linux, which I have encountered and haven’t solves still. So, you never know which synergy effect we can achieve during troubleshooting of small things. It can be really big.

Another lesson learned is that we need to play with parameters of the Ansible modules. Used “eos_config” have failed many times, until I have added “match: none”. Though it was a best guess, it worked. So, try, try and try.

Conclusion

If you have seen some previous articles you might notice that I’m following more or less the same model all the time. I’m not saying it’s perfect, as I just don’t know that. But it’s actually the advantage of automation: we can just change the template from vendor A to vendor B and deploy new network element without changing the data model. For sure YANG gives even mode advantages (link), and we will try to step into really vendor-agnostic configuration model in the next article, when we’ll talk about OpenConfig. Take care and good bye!

Support us





P.S.

If you have further questions or you need help with your networks, I’m happy to assist you, just send me message. Also don’t forget to share the article on your social media, if you like it.

BR,

Anton Karneliuk