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