如何用ansible备份网络设备配置

写完上一篇的几种执行命令行的分享后,一直在思考下一篇ansible写什么?当初说的最强入门,现在也变成了一个大坑,自己也在想如何填好这个坑,照着ansible的文档去讲变量?循环?其实这样写的ansible的教程已经非常多了,视频一大把,思来想去,自己想到了写这个系列的初心,是给网工的入门教程,而NetDevOps的一大特点就是落地场景,所以今天我们用ansible落地一个非常实用的场景,配置备份,在这个落地场景的过程中,我们带出一些概念来。

我在带出这些概念的时候,深浅不一,有时候会给大家讲的很深入,是希望大家明白ansible 网络实践这一块在这方面的坑,比如我之前讲过network_os,有时候,我也会浅浅一谈,讲讲基本应用,一来网工可能用的不是那么深,二来大家去看看官方文档或者网上的其他人的分享也很容易找到。我的还是聚焦在网络领域,聚焦在网络场景之中。在这个过程为大家带出ansible的全貌或者一个学习路线图。

废话不多讲,今天讲讲如何备份网络配置,以及对于网络配置备份的一些想法。

知识点有三个:注册变量、debug模块、备份配置到本地文件

注册变量

在把网络配置备份之前,我们首先show出来,然后打印到控制台上面,先看看我们执行的结果究竟长什么样子。这个时候我们用到了注册变量的动作register。就是把一次task模块的执行结果定义成为了一个变量,使用如下:

---
- name: 执行思科nxos show命令 # play的名称 选填,建议使用
 hosts: cisco_nxos  # filter网络设备
 gather_facts: no # 收集设备的一些基础信息。比如端口、版本、平台等等,耗费时间,大家都选择不调用
 tasks:
   - name: 执行show命令
     nxos_command:
       commands:
         - terminal length 0 # 去掉分页,这个非常关键
         - show run # 执行show 配置的命令,保证所有配置可以一次回显到stdout中
     register: show_text # 将结果注册到变量show_text中,你也可以起其他的名字

register就是把上述调用的模块执行结果(不仅仅是show的结果,而是整个task的结果,比如是否成功,是否发生变化,stdout、err等等)注册到它定义的变量中。

上文是注册到了show_text。

注册的这个变量名称不能用带有中横线的单词,咱们按照常规变量命名方法即可(字母数字下划线三者的组合,但是这里一定要用字母开头)。然后我们运行脚本的时候 最后加一个-v (调试模式,v的个数代表级别,什么都不写代表0,4个是上限,越多信息越详细,你可以简单这么理解,我们想看结果打一个v就可以,这个和是否注册变量没关系)

我们看看结果

利用ansible批量备份网络设备_.net

将变量值输出到对话框(debug模块)

我们注册变量,同时调用debug模块的时候把变量打印作为一个task

---
- name: 执行思科nxos show命令 # play的名称 选填,建议使用
 hosts: cisco_nxos  # filter网络设备
 gather_facts: no # 收集设备的一些基础信息。比如端口、版本、平台等等,耗费时间,大家都选择不调用
 tasks:
   - name: 执行show命令
     nxos_command:
       commands:
         - terminal length 0 # 去掉分页,这个非常关键
         - show version # 执行show 配置的命令,保证所有配置可以一次回显到stdout中
     register: show_text # 将结果注册到变量show_text中,你也可以起其他的名字
   
   - name: Dispaly show text in msg
     debug: msg="this is a debug message:{{ show_text }}"

结果:

在这个主机的后面,输出包含“msg”的一个字典,值就是输出结果,是一个字典:

changed:布尔型,代表是设备配置是否产生了变化,一方面和调用的module有关(有些会智能判断,有些是默认某个值);一方面和我们的playbook有关,我们可以设置,使用changed_when,将来会演示,大家也可以自己去查查。

stdout:这个是标准输出,是一个列表,每条命令的回显是一个字符串,show的文本是一个字符串,很长。

stdout_lines: 标准输出的按行划分的模式,是列表的列表。stdout有所区别。每个列表的里的列表是按行分割好的。

failed:是否失败,可以人为判断修改,使用failed_when

ansible_facts: 是设备的一些基础信息,网络一般会关闭掉,为了节省开销(性能和时间),比如思科的就会显示有多少个物理口、多少虚拟口等等。

不同情况下,register的变量会有所区别,大家还是根据实际情况去操作(用debug打印出来后再使用),执行命令行,以上基本都是有的。

debug详解

在上面的playbook中,我们用到了debug这个module

debug有三个参数

Parameters

ParameterChoices/DefaultsComments
msgstringDefault: "Hello world!"The customized message that is printed. If omitted, prints a generic message.
varstring
A variable name to debug.Mutually exclusive with the msg option.Be aware that this option already runs in Jinja2 context and has an implicit {{ }} wrapping, so you should not be using Jinja2 delimiters unless you are looking for double interpolation.
verbosity integerDefault: 0A number that controls when the debug is run, if you set to 3 it will only run debug when -vvv or above

msg

我们用了msg,它是可以定制化输出的,基于可以使用jinja2的语法,ansible会自动把一些默认变量(主机名、ssh port等等)和注册的变量render进去,按需使用即可,我们在上面的playbook中就使用了"this is a debug message:{{ show_text }}"打印出了一个定制的消息。刚才看到了show_text的结构,我们就可以定制,比如使用"this is a debug message:{{ show_text.stdout_lines }}"

我们甚至可以写for循环

"this is a debug message:{% for lines in show_text.stdout_lines %} {% for line in lines %} {{ line }} \n {%endfor%} {%endfor%}"

var

var是直接输出变量用于debug,无法定制一些格式,它已经是经过jinja2的相关简化处理,它后面的变量隐式使用了{{}}包裹。什么意思呢,比如你写了var=A,ansible底层会这么处理var={{ A }},所以你再用jinja2 语法是有问题的。

它与msg是互斥的

使用的时候可以用jinja2相关的取值方法

        - name: Dispaly show text in var
     debug: var=show_text

   - name: Dispaly show text in var with python style
     debug: var=show_text['stdout_lines']

   - name: Dispaly show text in var with jinja2 style
     debug: var=show_text.stdout_lines

msg可以客制化,var的缩进更舒服,大家按需按习惯使用。

verbosity

显示等级,默认值是0 。就是-v 相当于1 -vvvv相当于4,不用v相当于0。默认的时候无需后缀v即可打印出来。

我们可以根据情况调整数字。

巧用变量进行配置备份

重点来了,输出到控制台只是方便我们调试,我们注册变量有多种用途,我们今天的一个场景就是把show出来的配置备份到文件。

因为show version简单,我们用这个演示,实际情况大家可以执行show run或者display run

配置备份,我们用了copy模块

这个模块,简单讲解一下就是把指定的内容(content)写入到dest的文件中,会覆盖。同时我们用了ansible自带的一些内置的变量。

       - name: copy file in method 1
     copy:
       content: "{% for lines in  show_text.stdout_lines %} {% for line in  lines %} {{ line }} \n {%endfor%} {%endfor%}"
       dest: "/tmp/{{ansible_host}}_{{lookup('pipe','date +%Y%m%d')}}_show_run_1.log"


   - name: copy file in method 2
     copy:
       content: "{{ show_text.stdout[1] }}"
       dest: "/tmp/{{ansible_host}}_{{lookup('pipe','date +%Y%m%d')}}_show_run_2.log"

同时时间我用了一些特殊的方法,你可以直接使用,说实话我也不是很熟这些东西。如果gather facts的话应该会获取一个时间,但是开始的时候我们false掉了所以那个时间我一直没法拿到,搜了半天用了这个方法。最后看看效果:


总结发散

通过以上我们实现了配置备份,早期的时候我都是用paramiko结合Python脚本去做的这些操作。ansible如果熟练掌握也是很方便的。

发散一下吧:

dest可以按主机名、时间(精确到日)去建立多级的文件件,美滋滋。

我们也可以按主机、日期的方式去备份一下我们的配置,这样目录结构更好。(我们演示用了一台 )

同时我也有一个很大的脑洞,我们完全可以结合git把配置push到gitlab(本地的)上去,去掉日期,通过git的版本管理,gitlab的web搜索,gitlab的diff比较等等,可视化管理起来我们的配置。这是多么神奇的一件事情啊!同时git只会记录配置的变化,不会全量存配置,理论上可以节省大量的空间。这个我脑洞我们下期实现。

我都把配置扔到了tmp,方便后期清理,大家使用时注意路径

我们用了nxos_command模块,实际上可以用cli_command ,会更好一些。可以兼容更多厂商,大家可以看看我之前的文章。

奉上我的playbook,同时奉上的gitee(上传到网上 yaml文件经常容易串行、缩进有问题,使用的时候建议大家看git),后续代码陆续上传。

https://gitee.com/feifeiflight/NetDevOpsShare

---
- name: 执行思科nxos show命令 # play的名称 选填,建议使用
 hosts: cisco_nxos  # filter网络设备
 gather_facts: no # 收集设备的一些基础信息。比如端口、版本、平台等等,耗费时间,大家都选择不调用
 tasks:
   - name: 执行show命令
     nxos_command:
       commands:
         - terminal length 0 # 去掉分页,这个非常关键
         - show version # 执行show 配置的命令,保证所有配置可以一次回显到stdout中
     register: show_text # 将结果注册到变量show_text中,你也可以起其他的名字
   
   - name: Dispaly show text in msg
     debug: msg="this is a debug message:{{ show_text }}"

   # msg隐藏模式,可以用列表操作
   - name: Dispaly show text in msg with loop
     debug:
       msg: "this is one command show {{ item }}"
     loop: "{{ show_text.stdout_lines[1] }}" # 注意下标

   - name: Dispaly show text in msg with loop
     debug: msg="this is a debug message: {% for lines in  show_text.stdout_lines %} {% for line in  lines %} {{ line }} \n {%endfor%} {%endfor%}"

   - name: Dispaly show text in var
     debug: var=show_text

   - name: Dispaly show text in var with python style
     debug: var=show_text['stdout_lines']

   - name: Dispaly show text in var with jinja2 style
     debug: var=show_text.stdout_lines


   - name: copy file in method 1
     copy:
       content: "{% for lines in  show_text.stdout_lines %} {% for line in  lines %} {{ line }} \n {%endfor%} {%endfor%}"
       dest: "/tmp/{{ansible_host}}_{{lookup('pipe','date +%Y%m%d')}}_show_run_1.log"


   - name: copy file in method 2
     copy:
       content: "{{ show_text.stdout[1] }}"
       dest: "/tmp/{{ansible_host}}_{{lookup('pipe','date +%Y%m%d')}}_show_run_2.log"

   - name: Create a directory if it does not exist
     file:
       path: "/tmp/{{ansible_host}}/{{lookup('pipe','date +%Y%m%d')}}"
       state: directory
       mode: '0755' # 文件夹设置权限
   
   - name: copy file with dir
     copy:
       content: "{{ show_text.stdout[1] }}"
       dest: "/tmp/{{ansible_host}}/{{lookup('pipe','date +%Y%m%d')}}/show_run_2.log" # 文件夹必须存在才可以

预告

最近萌生了写一个可拓展的cli_command模块,希望能支持或者通过简单文件修改适配国产设备如某3C甚至是其他国产设备,支持show命令,甚至支持config命令,到时候开源一下,估计500行代码差不多可以搞定,这样小伙伴们就能尽情的使用ansible了。

同时打算最近开个直播试试水

好了今天就到这里了,欢迎大家点赞、收藏、订阅、分享、喜欢、在看。

NetDevOps加油站,同名知乎专栏 、微信公众号!