1.. _network-best-practices:
2
3************************
4Ansible Network Examples
5************************
6
7This document describes some examples of using Ansible to manage your network infrastructure.
8
9.. contents::
10   :local:
11
12Prerequisites
13=============
14
15This example requires the following:
16
17* **Ansible 2.10** (or higher) installed. See :ref:`intro_installation_guide` for more information.
18* One or more network devices that are compatible with Ansible.
19* Basic understanding of YAML :ref:`yaml_syntax`.
20* Basic understanding of Jinja2 templates. See :ref:`playbooks_templating` for more information.
21* Basic Linux command line use.
22* Basic knowledge of network switch & router configurations.
23
24
25Groups and variables in an inventory file
26=========================================
27
28An ``inventory`` file is a YAML or INI-like configuration file that defines the mapping of hosts into groups.
29
30In our example, the inventory file defines the groups ``eos``, ``ios``, ``vyos`` and a "group of groups" called ``switches``. Further details about subgroups and inventory files can be found in the :ref:`Ansible inventory Group documentation <subgroups>`.
31
32Because Ansible is a flexible tool, there are a number of ways to specify connection information and credentials. We recommend using the ``[my_group:vars]`` capability in your inventory file.
33
34.. code-block:: ini
35
36   [all:vars]
37   # these defaults can be overridden for any group in the [group:vars] section
38   ansible_connection=ansible.netcommon.network_cli
39   ansible_user=ansible
40
41   [switches:children]
42   eos
43   ios
44   vyos
45
46   [eos]
47   veos01 ansible_host=veos-01.example.net
48   veos02 ansible_host=veos-02.example.net
49   veos03 ansible_host=veos-03.example.net
50   veos04 ansible_host=veos-04.example.net
51
52   [eos:vars]
53   ansible_become=yes
54   ansible_become_method=enable
55   ansible_network_os=arista.eos.eos
56   ansible_user=my_eos_user
57   ansible_password=my_eos_password
58
59   [ios]
60   ios01 ansible_host=ios-01.example.net
61   ios02 ansible_host=ios-02.example.net
62   ios03 ansible_host=ios-03.example.net
63
64   [ios:vars]
65   ansible_become=yes
66   ansible_become_method=enable
67   ansible_network_os=cisco.ios.ios
68   ansible_user=my_ios_user
69   ansible_password=my_ios_password
70
71   [vyos]
72   vyos01 ansible_host=vyos-01.example.net
73   vyos02 ansible_host=vyos-02.example.net
74   vyos03 ansible_host=vyos-03.example.net
75
76   [vyos:vars]
77   ansible_network_os=vyos.vyos.vyos
78   ansible_user=my_vyos_user
79   ansible_password=my_vyos_password
80
81If you use ssh-agent, you do not need the ``ansible_password`` lines. If you use ssh keys, but not ssh-agent, and you have multiple keys, specify the key to use for each connection in the ``[group:vars]`` section with ``ansible_ssh_private_key_file=/path/to/correct/key``. For more information on ``ansible_ssh_`` options see :ref:`behavioral_parameters`.
82
83.. FIXME FUTURE Gundalow - Link to network auth & proxy page (to be written)
84
85.. warning:: Never store passwords in plain text.
86
87Ansible vault for password encryption
88-------------------------------------
89
90The "Vault" feature of Ansible allows you to keep sensitive data such as passwords or keys in encrypted files, rather than as plain text in your playbooks or roles. These vault files can then be distributed or placed in source control. See :ref:`playbooks_vault` for more information.
91
92Here's what it would look like if you specified your SSH passwords (encrypted with Ansible Vault) among your variables:
93
94.. code-block:: yaml
95
96   ansible_connection: ansible.netcommon.network_cli
97   ansible_network_os: vyos.vyos.vyos
98   ansible_user: my_vyos_user
99   ansible_ssh_pass: !vault |
100                     $ANSIBLE_VAULT;1.1;AES256
101                     39336231636137663964343966653162353431333566633762393034646462353062633264303765
102                     6331643066663534383564343537343334633031656538370a333737656236393835383863306466
103                     62633364653238323333633337313163616566383836643030336631333431623631396364663533
104                     3665626431626532630a353564323566316162613432373738333064366130303637616239396438
105                     9853
106
107Common inventory variables
108--------------------------
109
110The following variables are common for all platforms in the inventory, though they can be overwritten for a particular inventory group or host.
111
112:ansible_connection:
113
114  Ansible uses the ansible-connection setting to determine how to connect to a remote device. When working with Ansible Networking, set this to an appropriate network connection option, such as``ansible.netcommon.network_cli``, so Ansible treats the remote node as a network device with a limited execution environment. Without this setting, Ansible would attempt to use ssh to connect to the remote and execute the Python script on the network device, which would fail because Python generally isn't available on network devices.
115:ansible_network_os:
116  Informs Ansible which Network platform this hosts corresponds to. This is required when using the ``ansible.netcommon.*`` connection options.
117:ansible_user: The user to connect to the remote device (switch) as. Without this the user that is running ``ansible-playbook`` would be used.
118  Specifies which user on the network device the connection
119:ansible_password:
120  The corresponding password for ``ansible_user`` to log in as. If not specified SSH key will be used.
121:ansible_become:
122  If enable mode (privilege mode) should be used, see the next section.
123:ansible_become_method:
124  Which type of `become` should be used, for ``network_cli`` the only valid choice is ``enable``.
125
126Privilege escalation
127--------------------
128
129Certain network platforms, such as Arista EOS and Cisco IOS, have the concept of different privilege modes. Certain network modules, such as those that modify system state including users, will only work in high privilege states. Ansible supports ``become`` when using ``connection: ansible.netcommon.network_cli``. This allows privileges to be raised for the specific tasks that need them. Adding ``become: yes`` and ``become_method: enable`` informs Ansible to go into privilege mode before executing the task, as shown here:
130
131.. code-block:: ini
132
133   [eos:vars]
134   ansible_connection=ansible.netcommon.network_cli
135   ansible_network_os=arista.eos.eos
136   ansible_become=yes
137   ansible_become_method=enable
138
139For more information, see the :ref:`using become with network modules<become_network>` guide.
140
141
142Jump hosts
143----------
144
145If the Ansible Controller does not have a direct route to the remote device and you need to use a Jump Host, please see the :ref:`Ansible Network Proxy Command <network_delegate_to_vs_ProxyCommand>` guide for details on how to achieve this.
146
147Example 1: collecting facts and creating backup files with a playbook
148=====================================================================
149
150Ansible facts modules gather system information 'facts' that are available to the rest of your playbook.
151
152Ansible Networking ships with a number of network-specific facts modules. In this example, we use the ``_facts`` modules :ref:`arista.eos.eos_facts <ansible_collections.arista.eos.eos_facts_module>`, :ref:`cisco.ios.ios_facts <ansible_collections.cisco.ios.ios_facts_module>` and :ref:`vyos.vyos.vyos_facts <ansible_collections.vyos.vyos.vyos_facts_module>` to connect to the remote networking device. As the credentials are not explicitly passed with module arguments, Ansible uses the username and password from the inventory file.
153
154Ansible's "Network Fact modules" gather information from the system and store the results in facts prefixed with ``ansible_net_``. The data collected by these modules is documented in the `Return Values` section of the module docs, in this case :ref:`arista.eos.eos_facts <ansible_collections.arista.eos.eos_facts_module>` and :ref:`vyos.vyos.vyos_facts <ansible_collections.vyos.vyos.vyos_facts_module>`. We can use the facts, such as ``ansible_net_version`` late on in the "Display some facts" task.
155
156To ensure we call the correct mode (``*_facts``) the task is conditionally run based on the group defined in the inventory file, for more information on the use of conditionals in Ansible Playbooks see :ref:`the_when_statement`.
157
158In this example, we will create an inventory file containing some network switches, then run a playbook to connect to the network devices and return some information about them.
159
160Step 1: Creating the inventory
161------------------------------
162
163First, create a file called ``inventory``, containing:
164
165.. code-block:: ini
166
167   [switches:children]
168   eos
169   ios
170   vyos
171
172   [eos]
173   eos01.example.net
174
175   [ios]
176   ios01.example.net
177
178   [vyos]
179   vyos01.example.net
180
181
182Step 2: Creating the playbook
183-----------------------------
184
185Next, create a playbook file called ``facts-demo.yml`` containing the following:
186
187.. code-block:: yaml
188
189   - name: "Demonstrate connecting to switches"
190     hosts: switches
191     gather_facts: no
192
193     tasks:
194       ###
195       # Collect data
196       #
197       - name: Gather facts (eos)
198         arista.eos.eos_facts:
199         when: ansible_network_os == 'arista.eos.eos'
200
201       - name: Gather facts (ios)
202         cisco.ios.ios_facts:
203         when: ansible_network_os == 'cisco.ios.ios'
204
205       - name: Gather facts (vyos)
206         vyos.vyos.vyos_facts:
207         when: ansible_network_os == 'vyos.vyos.vyos'
208
209       ###
210       # Demonstrate variables
211       #
212       - name: Display some facts
213         debug:
214           msg: "The hostname is {{ ansible_net_hostname }} and the OS is {{ ansible_net_version }}"
215
216       - name: Facts from a specific host
217         debug:
218           var: hostvars['vyos01.example.net']
219
220       - name: Write facts to disk using a template
221         copy:
222           content: |
223             #jinja2: lstrip_blocks: True
224             EOS device info:
225               {% for host in groups['eos'] %}
226               Hostname: {{ hostvars[host].ansible_net_hostname }}
227               Version: {{ hostvars[host].ansible_net_version }}
228               Model: {{ hostvars[host].ansible_net_model }}
229               Serial: {{ hostvars[host].ansible_net_serialnum }}
230               {% endfor %}
231
232             IOS device info:
233               {% for host in groups['ios'] %}
234               Hostname: {{ hostvars[host].ansible_net_hostname }}
235               Version: {{ hostvars[host].ansible_net_version }}
236               Model: {{ hostvars[host].ansible_net_model }}
237               Serial: {{ hostvars[host].ansible_net_serialnum }}
238               {% endfor %}
239
240             VyOS device info:
241               {% for host in groups['vyos'] %}
242               Hostname: {{ hostvars[host].ansible_net_hostname }}
243               Version: {{ hostvars[host].ansible_net_version }}
244               Model: {{ hostvars[host].ansible_net_model }}
245               Serial: {{ hostvars[host].ansible_net_serialnum }}
246               {% endfor %}
247           dest: /tmp/switch-facts
248         run_once: yes
249
250       ###
251       # Get running configuration
252       #
253
254       - name: Backup switch (eos)
255         arista.eos.eos_config:
256           backup: yes
257         register: backup_eos_location
258         when: ansible_network_os == 'arista.eos.eos'
259
260       - name: backup switch (vyos)
261         vyos.vyos.vyos_config:
262           backup: yes
263         register: backup_vyos_location
264         when: ansible_network_os == 'vyos.vyos.vyos'
265
266       - name: Create backup dir
267         file:
268           path: "/tmp/backups/{{ inventory_hostname }}"
269           state: directory
270           recurse: yes
271
272       - name: Copy backup files into /tmp/backups/ (eos)
273         copy:
274           src: "{{ backup_eos_location.backup_path }}"
275           dest: "/tmp/backups/{{ inventory_hostname }}/{{ inventory_hostname }}.bck"
276         when: ansible_network_os == 'arista.eos.eos'
277
278       - name: Copy backup files into /tmp/backups/ (vyos)
279         copy:
280           src: "{{ backup_vyos_location.backup_path }}"
281           dest: "/tmp/backups/{{ inventory_hostname }}/{{ inventory_hostname }}.bck"
282         when: ansible_network_os == 'vyos.vyos.vyos'
283
284Step 3: Running the playbook
285----------------------------
286
287To run the playbook, run the following from a console prompt:
288
289.. code-block:: console
290
291   ansible-playbook -i inventory facts-demo.yml
292
293This should return output similar to the following:
294
295.. code-block:: console
296
297   PLAY RECAP
298   eos01.example.net          : ok=7    changed=2    unreachable=0    failed=0
299   ios01.example.net          : ok=7    changed=2    unreachable=0    failed=0
300   vyos01.example.net         : ok=6    changed=2    unreachable=0    failed=0
301
302Step 4: Examining the playbook results
303--------------------------------------
304
305Next, look at the contents of the file we created containing the switch facts:
306
307.. code-block:: console
308
309   cat /tmp/switch-facts
310
311You can also look at the backup files:
312
313.. code-block:: console
314
315   find /tmp/backups
316
317
318If `ansible-playbook` fails, please follow the debug steps in :ref:`network_debug_troubleshooting`.
319
320
321.. _network-agnostic-examples:
322
323Example 2: simplifying playbooks with network agnostic modules
324==============================================================
325
326(This example originally appeared in the `Deep Dive on cli_command for Network Automation <https://www.ansible.com/blog/deep-dive-on-cli-command-for-network-automation>`_ blog post by Sean Cavanaugh -`@IPvSean <https://github.com/IPvSean>`_).
327
328If you have two or more network platforms in your environment, you can use the network agnostic modules to simplify your playbooks. You can use network agnostic modules such as ``ansible.netcommon.cli_command`` or ``ansible.netcommon.cli_config`` in place of the platform-specific modules such as ``arista.eos.eos_config``, ``cisco.ios.ios_config``, and ``junipernetworks.junos.junos_config``. This reduces the number of tasks and conditionals you need in your playbooks.
329
330.. note::
331  Network agnostic modules require the :ref:`ansible.netcommon.network_cli <ansible_collections.ansible.netcommon.network_cli_connection>` connection plugin.
332
333
334Sample playbook with platform-specific modules
335----------------------------------------------
336
337This example assumes three platforms, Arista EOS, Cisco NXOS, and Juniper JunOS.  Without the network agnostic modules, a sample playbook might contain the following three tasks with platform-specific commands:
338
339.. code-block:: yaml
340
341  ---
342  - name: Run Arista command
343    arista.eos.eos_command:
344      commands: show ip int br
345    when: ansible_network_os == 'arista.eos.eos'
346
347  - name: Run Cisco NXOS command
348    cisco.nxos.nxos_command:
349      commands: show ip int br
350    when: ansible_network_os == 'cisco.nxos.nxos'
351
352  - name: Run Vyos command
353    vyos.vyos.vyos_command:
354      commands: show interface
355    when: ansible_network_os == 'vyos.vyos.vyos'
356
357Simplified playbook with ``cli_command`` network agnostic module
358----------------------------------------------------------------
359
360You can replace these platform-specific modules with the network agnostic ``ansible.netcommon.cli_command`` module as follows:
361
362.. code-block:: yaml
363
364  ---
365  - hosts: network
366    gather_facts: false
367    connection: ansible.netcommon.network_cli
368
369    tasks:
370      - name: Run cli_command on Arista and display results
371        block:
372        - name: Run cli_command on Arista
373          ansible.netcommon.cli_command:
374            command: show ip int br
375          register: result
376
377        - name: Display result to terminal window
378          debug:
379            var: result.stdout_lines
380        when: ansible_network_os == 'arista.eos.eos'
381
382      - name: Run cli_command on Cisco IOS and display results
383        block:
384        - name: Run cli_command on Cisco IOS
385          ansible.netcommon.cli_command:
386            command: show ip int br
387          register: result
388
389        - name: Display result to terminal window
390          debug:
391            var: result.stdout_lines
392        when: ansible_network_os == 'cisco.ios.ios'
393
394      - name: Run cli_command on Vyos and display results
395        block:
396        - name: Run cli_command on Vyos
397          ansible.netcommon.cli_command:
398            command: show interfaces
399          register: result
400
401        - name: Display result to terminal window
402          debug:
403            var: result.stdout_lines
404        when: ansible_network_os == 'vyos.vyos.vyos'
405
406
407If you use groups and group_vars by platform type, this playbook can be further simplified to :
408
409.. code-block:: yaml
410
411  ---
412  - name: Run command and print to terminal window
413    hosts: routers
414    gather_facts: false
415
416    tasks:
417      - name: Run show command
418        ansible.netcommon.cli_command:
419          command: "{{show_interfaces}}"
420        register: command_output
421
422
423You can see a full example of this using group_vars and also a configuration backup example at `Network agnostic examples <https://github.com/network-automation/agnostic_example>`_.
424
425Using multiple prompts with the  ``ansible.netcommon.cli_command``
426-------------------------------------------------------------------
427
428The ``ansible.netcommon.cli_command`` also supports multiple prompts.
429
430.. code-block:: yaml
431
432  ---
433  - name: Change password to default
434    ansible.netcommon.cli_command:
435      command: "{{ item }}"
436      prompt:
437        - "New password"
438        - "Retype new password"
439      answer:
440        - "mypassword123"
441        - "mypassword123"
442      check_all: True
443    loop:
444      - "configure"
445      - "rollback"
446      - "set system root-authentication plain-text-password"
447      - "commit"
448
449See the :ref:`ansible.netcommon.cli_command <cli_command_module>` for full documentation on this command.
450
451
452Implementation Notes
453====================
454
455
456Demo variables
457--------------
458
459Although these tasks are not needed to write data to disk, they are used in this example to demonstrate some methods of accessing facts about the given devices or a named host.
460
461Ansible ``hostvars`` allows you to access variables from a named host. Without this we would return the details for the current host, rather than the named host.
462
463For more information, see :ref:`magic_variables_and_hostvars`.
464
465Get running configuration
466-------------------------
467
468The :ref:`arista.eos.eos_config <ansible_collections.arista.eos.eos_config_module>` and :ref:`vyos.vyos.vyos_config <ansible_collections.vyos.vyos.vyos_config_module>` modules have a ``backup:`` option that when set will cause the module to create a full backup of the current ``running-config`` from the remote device before any changes are made. The backup file is written to the ``backup`` folder in the playbook root directory. If the directory does not exist, it is created.
469
470To demonstrate how we can move the backup file to a different location, we register the result and move the file to the path stored in ``backup_path``.
471
472Note that when using variables from tasks in this way we use double quotes (``"``) and double curly-brackets (``{{...}}`` to tell Ansible that this is a variable.
473
474Troubleshooting
475===============
476
477If you receive an connection error please double check the inventory and playbook for typos or missing lines. If the issue still occurs follow the debug steps in :ref:`network_debug_troubleshooting`.
478
479.. seealso::
480
481  * :ref:`network_guide`
482  * :ref:`intro_inventory`
483  * :ref:`Keeping vaulted variables visible <tip_for_variables_and_vaults>`
484