首发于 网路行者
网络工程师的Python之路---Ansible篇

网络工程师的Python之路---Ansible篇

版权声明:我已加入“维权骑士”( rightknights.com )的版权保护计划,所有知乎专栏“网路行者”下的文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。


Nov.7 ---- 更新实验3

Nov.13 ---- 更新实验4, 实验5

Nov.20 ---- 更新实验6

感谢一直关注我《网络工程师的Python之路》系列文章的读者们,这次为大家带来最近在DevOps领域很受欢迎的Ansible的教程。

如果你是对Python感兴趣,希望学点网络运维自动化技术的网络工程师,并且是第一次到访我的专栏,可以关注我以往的《网络工程师的Python之路》系列专栏文章,包括《初级篇》,《进阶篇》以及《进阶篇(续)》,里面有大量实用的Python在网络运维中的教程和案例,全部为笔者自己的原创文章,付出了不少的心血,如果喜欢的话 ,收藏之余也请顺手点个赞 ,谢谢!


另外《Ansible篇》也将会和往常一样持续更新,每次更新会添加1-2个实验,最迟不超过一星期一更。好了废话不多说,下面进入正题。

什么是Ansible?

Ansible本身是基于Python开发(对,可以像paramiko那样用pip直接下载安装的),用来实现各种IT任务自动化的工具。比如自动化安装软件,自动化管理配置项,自动化管理服务器等等。换句话说,Ansible就是用来批量在多台远程设备上执行命令的工具。作为网工的你,是不是对“远程”两个字感到特别亲切?是的,Ansible的网络模块(Network Modules)支持Cisco, Juniper, Arista, HP等等所有主流网络设备的远程批量访问和管理,如下:




Ansible本身并没有应用到什么太高深的技术,只要你的主机能通过SSH连接上被管理的网络设备就行了,够简单吧?所以这篇文章不会讲太多理论,直接上实验拓扑实战讲解。

为了不浪费大家的时间,首先说一个Ansible最大的缺点:不支持Windows。如果你是对Linux一窍不通的Windows死忠,请考虑是否要继续读下去。

说完缺点,再说Ansible最大的优点:简单、多线程。如果你读过我之前的《初级篇》,《进阶篇》,《进阶篇(续)》就知道:Ansible能做的事,网工用Paramiko或者Netmiko来访问交换机、路由器后配合各种其他的Python库和Python代码也能做,但是为什么又要用Ansible呢?第一,Ansible简单易用(相较于Paramiko需要配合其他的库来写很复杂的代码),不需要你写太多代码。第二,Ansible默认是多线程,如果你有多个设备需要管理,Ansible可以达到同时访问多个设备,然后执行命令的目的,而不是像Paramiko那样配合for循环一个接一个的访问设备,从效率上来说,Ansible完胜Paramiko。


Ansible实验运行环境和拓扑:

本篇的实验运行环境以及网络实验拓扑和《初级篇》完全一样,这里简单回顾一下:

操作系统:Windows 8.1上跑CentOS 7(VMware虚拟机)

网络设备:GNS3运行的思科三层交换机

网络设备版本:思科IOS (vios_12-ADVENTERPRISEK9-M)

Python版本:2.7.5 (Python为使用Ansible之必备,关于Python的安装教程请参考 《初级篇》)

局域网IP地址段:192.168.2.0 /24

运行Ansible的客户端: 192.168.2.1

Layer3Switch-1: 192.168.2.11

Layer3Switch-2: 192.168.2.12

Layer3Switch-3: 192.168.2.13

Layer3Switch-4: 192.168.2.14

Layer3Switch-5: 192.168.2.15

所有的交换机已经预配好了SSH,用户名: python 密码:123



A. 安装Ansible

在CentOS上安装Ansible有两种方法,一个是pip install ansible (关于pip的安装,请参考 《初级篇》 ), 一个是yum install ansible,个人建议使用yum来安装,因为我用pip安装的Ansible出现了各种各样的bug,比如找不到自带的ansible.cfg和hosts文件,以及运行ad hoc命令时的各种报错。

用yum安装ansible


B. Ansible初始配置

Ansible安装好后,/etc目录下会多出ansible这个目录,该目录下又有ansible.cfg, hosts, roles这三个文件,其中 ansible.cfg hosts 是我们要重点关注的。

用vim打开ansible.cfg文件这个Ansible的配置文件,可以看见里面乱七八糟一大堆东西。


ansible.cfg是用来设置Ansible各种运行参数的文件,这么多东西对初学者来说似乎很不友善,不用担心,我们可以另外创建一个名为ansible.cfg的配置文件来替代这个东西。

再来看下hosts文件,是不是也觉得乱七八糟的?


hosts文件是Ansible最重要的文件之一,所有你要远程SSH访问的设备的主机名,IP地址或者URL必须全部预写在里面,否则Ansible无法登录你想登录的设备!而且后面你也会了解到该hosts文件所在的绝对路径也很重要,你必须在和hosts文件同样的目录下使用'ansible'这个命令,否则你必须用-i这个参数来指定hosts文件所在的绝对路径来运行Ansible命令,什么意思呢?往下看你就明白了。


因为ansible.cfg和hosts两个文件默认在/etc/ansible文件夹下,每次访问很麻烦,我们可以cd回到/root根目录,手动创建两个名为ansible.cfg和hosts的文件。首先我们来创建并配置ansible.cfg文件。

在ansible.cfg里写入如下内容

[defaults]
inventory = ./hosts
host_key_checking = False
timeout = 5

inventory用来指定hosts文件的所在位置,因为hosts文件和ansible.cfg处在同一个目录下,所以路径为./hosts, hosts_key_checking和SSH的RSA key fingerprint相关,这个我们不用操心,所以参数给False(注意第一个字母必须是大写的F,小写的无用),最后的timeout是Ansible用来尝试SSH远程连接设备的最大时限,如果5秒钟后远程设备依然不响应SSH请求,则切断连接。

接下来配置hosts文件

在hosts文件里写入我们实验拓扑里的5个交换机的管理IP地址然后保存。

一切准备就绪后,我们可以正式开始实验了。

实验1:

实验目的:通过Ansible的ad hoc命令查询单个交换机的配置

  • 首先用ansible --list-host all这个命令来检查host文件,确认里面已经有了5个交换机的管理IP
ansible --list-host all


  • 之后用 ansible 192.168.2.11 -m raw -a "show ip int brief" -u python -k 这个ad hoc命令向192.168.2.11发号施令,得到'show ip int brief'这个命令的输出结果
ansible 192.168.2.11 -m raw -a "show ip int brief" -u python -k 


看到上图中绿色部分的输出结果了吗?传统情况下,我们必须用putty或者SecurCRT,通过SSH或者Telnet登录,然后在命令行下输入命令来管理交换机。这里通过Ansible,在没有进入SW1 (192.168.2.11) 的CLI操作界面的情况下,我们就已经得到了show ip int brief这个命令在SW1上的输出结果。

这里讲解一下ansible 192.168.2.11 -m raw -a "show ip int brief" -u python -k 这条ad hoc命令里的各个参数:

-m

在Ansible中有用到模块(module)的概念,用-m参数来指定。默认是command模块。如果被ansible访问的设备本身已经安装并支持python, 那么就可以使用command或者shell这两个模块来管理该设备,如果被访问的设备没有安装python,比如老旧的思科2960,3750等交换机,这时就必须用raw这个模块来访问该设备。更多关于Ansible模块的信息可以访问 docs.ansible.com/ansibl

-a

-a即argument,代表登录设备后要输入的命令。后面接的命令用单引号,双引号都可以,比如"show clock", 'show version'等等。

-u

代表SSH用户名,后面的用户名不用接引号

-k

用来提示用户输入SSH密码


实验2:

实验目的:通过Ansible的ad hoc命令查询多个交换机的配置

  • 做完实验1后,你会觉得用Ansible的ad hoc命令查看单个交换机的配置并没省多少事,但是如果我们可以同时查看多个交换机的配置呢?要同时查看多个交换机配置,首先要将hosts文件里的交换机IP地址归纳进一个组,组名要写在[]里面,举例如下:

a. 用vim打开我们之前在/root下创建的hosts文件

b. 在交换机IP上面添加一个括号,在括号里面写入组名'switches'(组名可以任意取),然后保存退出vim。

c. 用cat命令验证hosts文件内容,确保组名[switches]在IP地址的上面。

d. 然后输入命令ansible switches -m raw -a "show clock" -u python -k,这时你可以看到Ansible已经自动登录了所有5个交换机,将'show clock'命令的输出结果显示出来了, 注意这里的switches即为我们刚才命名的组名。

ansible switches -m raw -a "show clock" -u python -k


e. 前面讲到了,如果你当前并不在hosts文件所在的目录下,则你需要用-i这个参数来指定hosts文件的位置,什么意思呢,举例如下:

首先随便换个目录,比如移动到/etc,然后再输入刚才的ad hoc命令ansible switches -m raw -a "show clock" -u python -k,

这时你会发现ansible给出WARNING,提示找不到host文件,自然也就找不到switches这个组名,这时就要用到-i参数来指定hosts文件所在的绝对路径,因为我的hosts这个文件是放在/root目录下面,所以-i参数放完整路径 /root/hosts

ansible switches -m raw -i /root/hosts -a "show clock" -u python -k

带上-i参数后再执行一次,成功。


f. 到这里也许你会问:我只需要SW1(192.168.2.11) 和SW2(192.168.2.12) 的输出结果,不需要剩下3个交换机的,可以吗?答案是肯定的,方法是在hosts文件里多分一个组出来,把SW1和SW2放进一个组,剩下的SW3,SW4,SW5放进另外一个组, 如下:

这里我们将SW1和SW2放进“sw-g1”这个组,把SW3,SW4,SW5放进"sw-g2"这个组,然后输入下面命令即可得到sw-g1组的"show clock"的输出结果,也就是SW1和SW2的结果。

ansible sw-g1 -m raw -a "show clock" -u python -k

g. 在分了多组的情况下又怎样一次性对所有组下面的交换机执行命令呢?很简单,在命令里用‘all’来表示组名就行了,如下:

ansible all -m raw -a "show clock" -u python -k


实验3:

实验目的:通过Ansible的Playbook查询多个交换机的配置

  • 在Ansible里有ad hoc(临时)和Playbook(剧本)两种方式来对设备进行管理,前者顾名思义是只能运行一次的临时命令,后者则如它名字一样,是一个一次写好以后,将来可以无数次反复使用的“剧本”(你也可以把它当成我们通常理解的脚本)。实验1和实验2我们已经用到了ad hoc命令,实验3里我们将用Playbook来查询多个交换机的配置。
  • Ansible的剧本是由yaml语言写成的,yaml语言本身是一种Markup Language(标记语言),有点类似于XML,可以简单表达清单、散列表、标量等数据结构,对于yaml语言不做过多介绍,学再多理论也不如做实验习得的知识来得快。

a. 首先用vim创建一个叫做‘arp’的剧本,注意扩展名为.yml,然后在剧本里面放入下面的yaml代码。

---
- name: Get ARP information
  hosts: all
  gather_facts: false
  tasks:
    - name: show arp
      raw: "show arp"
      register: print_output
    -  debug: var=print_output.stdout_lines

这里简单讲下这个剧本的内容:

  • name: 给剧本命名,我们要读取5个SW的ARP表的内容,故这里取名为Get ARP information
  • hosts: 指定被执行命令的主机,也就是我们的5个SW,实验2里已经讲到了all是什么意思。
  • gather_facts: 在ansible里面,facts是用来“发现”远程主机的各种已有的参数的,默认状态下是自动执行的,这个很耗时间,而且对我们的网络设备没什么用。gahter_facts代表一个布尔值,用来表示是否要启用facts这个模块,由于我们不会用到facts,所以这里将gather_facts设置成false。
  • tasks: 顾名思义,就是“剧情”,也就是我们要对要访问的远程设备做什么。
  • - name: 给剧情命名,这里取名show arp
  • raw: 实验1讲到了这是raw模块,后面的"show arp"则是我们真正要在SW1-SW5上执行的命令。
  • register: 将执行命令后输出的结果保存的意思,这里将它们保存给一个叫print_output的变量。
  • - debug: var=print_output.stoudt_lines, 表示将print_output的内容打印出来。

b. 创建好剧本后,输入下面的命令来执行剧本

ansible-playbook arp.yml -u python -k

这里你可以看到ansible已经登录了全部的交换机,并将每个交换机的'show arp'显示结果返回打印出来了。

C. 如果你嫌打印出来的内容太多,想过滤出其中某一个MAC地址,比如00e2.d54c.8001,则可以配合grep命令来搜索匹配你想要的文本,举例如下:

ansible-playbook arp.yml -u python -k | grep 'ok:\|8001'

注意这里的grep包含了ok:\,这是因为要显示每个交换机的IP地址,使结果更容易被理解,如果不加ok:\会怎样呢,看下面的效果就知道了。

是不是有点蒙?不知道那么多输出结果对应的是哪个交换机?


实验4:

实验目的:使用Playbook对交换机输入多条命令,保存输出结果。

  • 虽然从实验3开始我们已经开始使用剧本来向交换机发号施令,但是我们仍然使用的是raw这个模块 (raw: "show ver"), raw模块作为ad hoc命令里常用的模块有个最大的缺点是不支持批量输入多条命令。
  • 开篇提到过,Ansible的Network modules支持目前市场上大部分主流网络设备的所有操作系统,以思科为例,Ansible支持思科的IOS, IOSXR, NXOS, Aireos(思科无线WLC的操作系统), Juniper的Junos, Arista的EOS, Dell的Dellos, Extreme的Ironware,Fortinet的Fortios等等等等。另外也支持Aruba, A10, Bigswitch, F5等主流厂商设备的操作系统。
  • 在本篇的实验环境里我们使用的是GNS3里的虚拟思科三层交换机,操作系统为IOS,因此我们可以在剧本里调用IOS这个network module来向交换机批量输入多条命令。IOS模块下面又含了很多子模块(如下图),这里主要讲下ios_command和ios_config这两个模块。
  • ios_command和ios_config模块最大的区别是前者不支持configure模式下的命令,而后者支持。因此如果你要配置交换机的话,必须使用ios_config这个子模块。了解了这点后下面正式进入实验部分。


a. 首先创建一个名为multiple_cmd.yml的剧本,然后在该剧本里面添加如下内容:

---
- name: run multiple commands on remote devices
  hosts: all
  gather_facts: false
  connection: local
  tasks:
   - name: show ver and show ip int brief
     ios_command:
       commands:
         - show clock
         - show ip int brief
     register: print_output
   - debug: var=print_output.stdout_lines
  • "connection: local"表示剧本将在本地,也就是本机执行,这样做的好处是将来可以把剧本放在crontab下面重复定时执行。
  • 在tasks下面可以看到,这里我们调用了"ios_command"这个子模块,调用"ios_command"后,接着在它下面使用commands这个参数(commands参数是必选项,没有它的话无法调用ios_command模块,关于该模块里其他的参数后文会讲到),之后再放入我们要对交换机输入的多条命令: show clock和show ip int brief.

b. 创建好剧本后,输入下面的命令来执行剧本

ansible-playbook multiple_cmd.yml -u python -k
  • 执行剧本之后,我们得到了每个交换机的show clock和show ip int b的输出内容。


实验5

实验目的:通过剧本在所有交换机上show run,并保存输出结果

  • 定期保存网络中各种设备的running config是网络运维中必不可少的一个环节,Ansible里有个模块叫做copy, 可以帮助我们达到这个目的。

a. 这里我们先用mkdir在/root下面创建一个文件夹,取名叫ansible_output,用来存放执行剧本后所得到的每台交换机的running config的文本文件。

b. 为了保存执行剧本后每天交换机的show run的输出内容,我们需要在tasks下面添加copy这个模块,添加的内容如下:

   - name: save output to a file
     copy: content="{{ print_output.stdout }}" dest="./ansible_output/{{ inventory_hostname }}.txt"

修改后的剧本内容如下:

---
- name: run multiple commands on remote devices
  hosts: all
  gather_facts: false
  connection: local
  tasks:
   - name: show ver and show ip int brief
     ios_command:
       commands:
         - show run
     register: print_output
   - debug: var=print_output.stdout_lines
   - name: save output to a file
     copy: content="{{ print_output.stdout[0] }}" dest="./ansible_output/{{ inventory_hostname }}.txt"
  • 这里的content="" 以及dest="" 都是copy模块的必填参数。
  • content=""代表需要复制的内容, 前面我们已经通过"register: print_output"将执行命令后输出的结果保存至print_output这个变量了, 所以这里用copy: content="{{ print_output.stdout }}"将print_output.stdout(标准输出的内容)当做要复制的内容赋值给content参数。
  • dest="./ansible_output/{{ inventory_hostname }}.txt",表示在我们刚才创建的/root/ansible_output这个文件夹下面创建txt文本文件,将content里的内容写进去,而txt文件的名字则是对应的交换机的IP地址,比如"192.168.2.11.txt", "192.168.2.12.txt",为什么?因为{{ inventory_hostname }}.txt里的inventory_hostname就是写在hosts文件下的交换机IP地址。
  • 这里重点讲下inventory_hostname这个变量,在Ansible里面有一个Magical Variable的概念,Magical Varaibale(后文简称MV)是Ansible预留的变量名, 用户不能使用 。比如inventory_hostname这个变量就是一个MV,它指向的就是hosts文件下的设备的hostname或IP地址。其他常用的MV还包括hostvar, group_names, groups, environment等等。

c. 理解了copy这个模块和MV的概念后,修改好剧本再执行一次

ansible-playbook multiple_cmd.yml -u python -k
  • 这时在输出结果的底部你会看到多了一个TASK,提示我们以上所有输入内容已经保存完毕。
  • 进入ansible_output文件夹,可以发现已经多出了含有5个SW输出内容的TXT文件了。


实验6

实验目的:用剧本配置交换机,保存交换机配置。

  • 前面实验1-5都是通过ansible在交换机上执行各种show命令,还没有机会给交换机做配置,实验6就来讲下用ansible给网络设备做配置这一块。

a. 首先通过ios_config这个模块来给5个交换机配置,让他们都开启OSPF。剧本如下:

---
- name: enable ospf
  hosts: all
  gather_facts: false
  connection: local
  tasks:
    - name: enable ospf
      ios_config:
        authorize: yes
        parents: router ospf 2
        lines:
          - network 0.0.0.0 255.255.255.255 area 0
      register: print_output
    -  debug: var=print_output
  • authorize: yes表示使用特权模式(privileged mode),即输入enable后进入的"#"模式,默认是no, 也就是enable前进入的非特权模式">"。如果你不加这个authorize:yes,那你很大概率会在执行剧本后遇到下面的错误,提示你该配置必须在特权模式下完成(情理之中,不进特权模式,你怎么用configure terminal进配置模式 ?):
  • parents: 父配置,在思科IOS里面,很多配置是分父配置和子配置的,比如这里配置的OSPF,第一步便是开启父配置router ospf 1, 下面的network 0.0.0.0 255.255.255.255 area 0则为子配置,需要写在lines:的下面
parents: router ospf 2         
lines:
  - network 0.0.0.0 255.255.255.255 area 0
  • 有些配置还涉及到多重父配置,比如配置policy-map,通常policy-map要先匹配一个class-map,然后再针对该class-map做其他的配置,比如流量整形(policing),然后还要定义conform-action和exceed-action来对超出和未超出CIR的流量制定策略。举例如下:
- name: configure policer in Scavenger class
  ios_config:
    parents:
      - policy-map Foo
      - class Scavenger
      - police cir 64000
    lines:
      - conform-action transmit
      - exceed-action drop

b. 通过ios_vlan模块给交换机配置VLAN。

  • 首先解释下为什么不用ios_config模块给交换机配置vlan:用ios_config来配置vlan不是不可以,但是它只能“配置一次”,将来你想给已有的Vlan改名字,用ios_config就会报错,用下面的例子说明:

首先运行下面这个剧本,创建一个vlan 101,然后给它取名test,注意这里用的是ios_config模块:

---
- name: configure vlan
  hosts: 192.168.2.11
  gather_facts: false
  connection: local
  tasks:
    - name: configure vlan
      ios_config:
        lines:
          - vlan 101
          - name test
      register: print_output
     - debug: var=print_output

然后你会发现第一次运行这个剧本,没有任何问题, 剧本顺利执行,交换机192.168.2.11现在也多了一个名字叫test的vlan 101。但是当你再次运行该剧本(没有对剧本配置做任何修改),ansible就会报错:

具体原因不得而知,姑且当做是ansible想逼你用ios_vlan模块来配置vlan吧。

用ios_vlan配置vlan 101,并将端口Gi0/0划分在该vlan,剧本如下:

---
- name: configure vlan
  hosts: 192.168.2.11
  gather_facts: false
  connection: local
 tasks:
    - name: configure vlan
      ios_vlan:
        vlan_id: 101
        name: test
        state: present
      register: print_output
    - debug: var=print_output
    - name: add interface to vlan
      ios_vlan:
        vlan_id: 101
        interfaces:
          - GigabitEthernet0/0
  • 注意在ansible里面,"配置vlan"和“将端口划分在某个vlan”下是两个task,必须分开来写,也就是说"tasks:"下面必须看到两个"- name:"
  • 这里主要讲下state:这个参数,present表示创建vlan, 与它相对应的还有absent表示删除vlan,剧本举例如下:
- name: Delete vlan
  ios_vlan:
    vlan_id: 100
    state: absent

c. 保存交换机配置,为什么要把这点单独拿出来讲?因为在ansible里面我们不能用输入命令wr mem或copy run start的思维来保存配置,因为ansible有一个特殊的参数来做这个事情,举例如下:

---
- name: configure vlan
 hosts: 192.168.2.11
 gather_facts: false
 connection: local
  tasks:
    - name: configure vlan
      ios_vlan:
        vlan_id: 102
        name: vlan_102
        state: present
      register: print_output
    - debug: var=print_output
    - name: configure name server
      ios_config:
        lines: