From the Ansible GitHub page:

Ansible is a radically simple IT automation platform that makes your applications and systems easier to deploy. Avoid writing scripts or custom code to deploy and update your applications — automate in a language that approaches plain English, using SSH, with no agents to install on remote systems.

Design Principles

  • Have a dead simple setup process and a minimal learning curve
  • Manage machines very quickly and in parallel
  • Avoid custom-agents and additional open ports, be agentless by leveraging the existing SSH daemon
  • Describe infrastructure in a language that is both machine and human friendly
  • Focus on security and easy auditability/review/rewriting of content
  • Manage new remote machines instantly, without bootstrapping any software
  • Allow module development in any dynamic language, not just Python
  • Be usable as non-root
  • Be the easiest IT automation system to use, ever.

For further information see the documentation at http://docs.ansible.com/ansible/index.html.

To test ansible within GNS3 I’ve created a docker image (ehlers/ansible). My image includes the dependencies for the junos network modules. A lot of other images exist, e.g. the “Network Automation” appliance in GNS3 Marketplace / Appliances, that may work as well. I’ve created a small GNS3 project using ansible, three Cisco IOS router and a JunOS vSRX router.

GNS3 project

Network Device Configuration

Ansible uses exclusively SSH to communicate to the devices, it manages. Therefore you have to setup SSH access on the networking devices, see “SSH to Cisco and Juniper router” for details. Furthermore NETCONF has to be enabled on JunOS devices, configure “set system services netconf ssh” on them.

Ansible Base Configuration

Certain settings in ansible are adjustable via a configuration file, see http://docs.ansible.com/ansible/latest/intro_configuration.html for details. Normally you need to change only a few settings. You can change the global file /etc/ansible/ansible.cfg, create your own ansible.cfg in your current directory or use .ansible.cfg in your home directory.

[defaults]
hostfile = /root/hosts
host_key_checking=False
timeout = 30
#transport = paramiko

This sample config sets the path of the host inventory (hostfile), see next section. Furthermore the SSH host key checking is turned off during this test. I’m using a quite high SSH timeout, otherwise the JunOS config commit command may time out.

On default ansible uses OpenSSH, which is a bit picky with older IOS versions. You have to enable the Diffie–Hellman key exchange in ~/.ssh/config.

KexAlgorithms +diffie-hellman-group1-sha1

Alternatively, you might try paramiko for the transport.

Ansible Inventory

Ansible works against multiple systems at the same time. It does this by selecting portions of systems listed in ansible’s inventory, which defaults to /etc/ansible/hosts. You can specify a different inventory location in the configuration file ansible.cfg (hostfile setting).

[cisco]
R1
R2
R3

[juniper]
junOS-1

[core]
R1
junOS-1

[access]
R2
R3

The names in brackets are group names. You can (and should) group the systems in device groups, a node can be part of multiple groups. The special group ‘all’ contains all devices.

You can add additional variables like IP address, username and password to your hosts, see http://docs.ansible.com/ansible/latest/intro_inventory.html. But ansible allows to use a more flexible way for that. You can add host variables in the file ‘host_vars/<host name>’ and group variables in the file ‘group_vars/<group name>’.

For example, if the device IP of R1 is not configured in /etc/hosts or your DNS, you can set the IP address in host_vars/R1:

---
ansible_host: 10.1.1.1

Likewise you can set username/password for all cisco devices in group_vars/cisco:

---
ansible_user: auto
ansible_ssh_pass: mation

Of course using SSH keys is a better and more secure way doing authentication, but older cisco devices don’t support them.

Ad-Hoc Commands

An ad-hoc command is something that you might type in to do something really quick, but don’t want to save for later.

The basic syntax is ansible host_or_group -m MODULE_NAME -a MODULE_ARGS. As ansible originally was used to manage remote servers, most modules require a python installation on the remote machine. That leaves mainly the ‘raw’ module for network devices. So the typical use is ansible host_or_group -m raw -a "REMOTE_COMMAND". You must take care, that the remote command doesn’t want to read some input, otherwise the execution will block. For JunOS devices the ‘| no-more’ output filter is very useful.

Here some examples:

root@ansible-1:~# ansible cisco -m raw -a "show version | include IOS"
R1 | SUCCESS | rc=0 >>
Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)
Shared connection to 10.1.1.1 closed.

R3 | SUCCESS | rc=0 >>
Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)
Shared connection to r3 closed.

R2 | SUCCESS | rc=0 >>
Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)
Shared connection to r2 closed.

root@ansible-1:~# ansible junOS-1 -m raw -a "show version | no-more"
junOS-1 | SUCCESS | rc=0 >>
Hostname: junOS-1
Model: firefly-perimeter
JUNOS Software Release [12.1X47-D20.7]
Shared connection to junos-1 closed.

Playbooks

Playbooks are ansible’s configuration, deployment, and orchestration language. They can be used to manage configurations of and deployments to remote machines. Each playbook is composed of one or more tasks applied to hosts or a group of hosts. The documentation is available at http://docs.ansible.com/ansible/latest/playbooks.html.

For networking devices the “normal” modules don’t work, you should use the modules specially designed for its device type, see the documentation.

In the following I’m showing the basic use for some network device types.

Cisco

ios_command

With ios_command you can issue one or multiple commands to IOS router. Playbooks normally don’t show the output of modules, you have to ‘register’ a variable to fill it with the output and then show that variable with ‘debug’.

---
- name: Run cisco commands
  hosts: cisco
  connection: local
  gather_facts: false

  tasks:

    - name: get base information
      ios_command:
        commands:
          - show version | include IOS
          - show ip interface brief
      register: output

    - debug: var=output.stdout_lines

Here the output of a sample session:

root@ansible-1:~# ansible-playbook cisco_commands.yml

PLAY [Run cisco commands] ******************************************************

TASK [get base information] ****************************************************
ok: [R3]
ok: [R1]
ok: [R2]

TASK [debug] *******************************************************************
ok: [R1] => {
    "output.stdout_lines": [
        [
            "Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)"
        ],
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "FastEthernet0/0            10.1.1.1        YES NVRAM  up                    up      ",
            "FastEthernet0/1            unassigned      YES NVRAM  administratively down down"
        ]
    ]
}
ok: [R2] => {
    "output.stdout_lines": [
        [
            "Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)"
        ],
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "FastEthernet0/0            10.1.1.2        YES NVRAM  up                    up      ",
            "FastEthernet0/1            unassigned      YES NVRAM  administratively down down"
        ]
    ]
}
ok: [R3] => {
    "output.stdout_lines": [
        [
            "Cisco IOS Software, 3700 Software (C3725-ADVENTERPRISEK9-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)"
        ],
        [
            "Interface                  IP-Address      OK? Method Status                Protocol",
            "FastEthernet0/0            10.1.1.3        YES NVRAM  up                    up      ",
            "FastEthernet0/1            unassigned      YES NVRAM  administratively down down"
        ]
    ]
}

PLAY RECAP *********************************************************************
R1                         : ok=2    changed=0    unreachable=0    failed=0
R2                         : ok=2    changed=0    unreachable=0    failed=0
R3                         : ok=2    changed=0    unreachable=0    failed=0

ios_config

The configuration of IOS devices can be done with ios_config. The configuration statements can be embedded in the playbook with ‘lines’ or can be stored externally with ‘src’. In this playbook I’m using a host pattern to select only those cisco router that are member of the core group.

---
- name: Configure cisco core router
  hosts: cisco:&core
  connection: local
  gather_facts: false

  tasks:

  - name: load new acl into device
    ios_config:
      lines:
        - permit ip host 1.1.1.1 any log
        - permit ip host 2.2.2.2 any log
        - permit ip host 3.3.3.3 any log
        - permit ip host 4.4.4.4 any log
        - permit ip host 5.5.5.5 any log
      parents: ip access-list extended test
      before: no ip access-list extended test
      match: exact
      # save: yes

The ios_config configures only, when the configuration is not already active on the device. Therefore on the second run, the ‘changed’ counter is zero.

root@ansible-1:~# ansible-playbook cisco_core_config.yml

PLAY [Configure cisco core router] *********************************************

TASK [load new acl into device] ************************************************
changed: [R1]

PLAY RECAP *********************************************************************
R1                         : ok=1    changed=1    unreachable=0    failed=0

root@ansible-1:~# ansible-playbook cisco_core_config.yml

PLAY [Configure cisco core router] *********************************************

TASK [load new acl into device] ************************************************
ok: [R1]

PLAY RECAP *********************************************************************
R1                         : ok=1    changed=0    unreachable=0    failed=0

Juniper

junos_command

You can use junos_command to issue commands on JunOS devices, very similar to ios_command for cisco devices.

Older JunOS versions support only some ‘display’ settings in certain commands. For example in v12.x “show configuration” works only with ‘display: xml’.

---
- name: Run juniper commands
  hosts: juniper
  connection: local
  gather_facts: false

  tasks:

    - name: get base information
      junos_command:
        commands:
          - show version
          - show interfaces terse
        display: text
      register: output

    - debug: var=output.stdout_lines

junos_config

junos_config changes the configuration of JunOS devices. The ansible-playbook recap shows, if the router configuration is changed. As a commit can take quite some time, the timeout parameter in ansible.cfg should be increased.

---
- name: Configure juniper router
  hosts: juniper
  connection: local
  gather_facts: false

  tasks:

    - name: load configuration into device
      junos_config:
        lines:
          - set snmp community public authorization read-only
          - set snmp community private authorization read-write