Ansible自动化管理和部署网络

首发于SDNLAB网站,链接:https://www.sdnlab.com/20798.html

今年NANOG大会上有个演讲Network Automation: Do I Need Expensive Tools To Do Meaningful Automation提起了如何去自动化管理网络的步骤。从管理网络设备到整个网络服务到阶段,需要一步一步完成,不能一下子跨越。公司需要构建DevOps文化,需要搞清楚当前自动化运维处于什么阶段,分清楚不同组织的角色,这样才能互相配合,从而实现自动化运维和开发。

下面会介绍演讲中提到的自动化开源工具Ansible。目前Ansible、SaltStack、Puppet、Chef都是比较受用户欢迎的自动化化运维工具,其中Ansible和SaltStack使用python编写,具有良好的可移植性。Ansible使用和部署简单(no databases,no daemons,no agents),控制节点上编译执行代码,然后通过SSH或者其他协议的方式将其命令发送至目标网络设备上执行。例如思科、Arista等公司的设备都支持Ansible模块去管理网络。

网络可编程不在于各种接口和各种规范,而在于对于网络的抽象。Ansible可以把对象的参数定义和执行层面进行解耦,从而实现定义一次,执行多次的效果。如下图所示:

Arista+Ansible

大概在2014年中旬的时候,Arista就已经开始使用Ansible去批量管理和部署网络设备了。以配置vlan和interface为例子,看看是如何建立数据模型的。

最上方标出的红框是Arista对vlan进行配置的命令。右边是抽象出vlan对象,属性有vlanid和name,这种字典式的模型是用YAML所描述的。同理,对于Ethernet interface有三个属性,分别是name, description和address。这样抽象有个好处,不仅可读性高,而且可以屏蔽厂商设备差异化。

根据数据模型,我们可以创建出Jinja template。如下图所示(vlan和interface所对应的模板):

那么如何使用这些模板(template)呢,前面提到过Ansible具有很强大的编排能力,可以使用playbook把角色(role),任务(task),inventory串起来。自动化和编排通过简化网络运营和管理,帮助实现这种敏捷性。无论是对单个设备还是服务进行管理或部署,网络运维人员需要使用模板编排,并且可以像代码一样进行版本控制、复制、更新模板。通过模板描述多个网络资源的依赖关系、配置等,并自动完成所有配置,以达到自动化部署、运维等目的。

如果不使用模板的形式,这会增加了开发人员和运维人员的沟通成本,而且当遇到问题时,运维人员或者QA对整个服务里面的实现逻辑并不了解。但如果使用模板去实现服务的话,网络运维人员可以根据不同设备及服务自行组装编排部署,可以填写参数。开发人员只需要保证对每个模块/任务功能正常即可。例如Ansible Playbook以YAML语言进行对任务、角色等进行编排,可读性高,能够跨越不同组织部门对其操作。

下图为利用Ansible Playbook对网络设备Vlan和Interface进行编排部署:

  • hosts file: 指定inventory,把目标设备放进来。在实际项目中可以通过自动化资产扫描从而实现动态的添加设备。
  • 由于对两个设备vlan的配置相同,所以把vlan对象放到全局变量文件中groupvars 对于差异性的配置模板放到hostvars中。
  • 运行playbook文件,会根据任务中的对应的模板生成配置进行下发,每个任务具有幂等性。

把运维一系列的手动执行的操作,用脚本串起来的思路做成工具去部署网络设备,做不到幂等性。在管理或部署网络设备时,一个请求除了成功和失败两种状态,还存在着超时状态。所以需要将对网络设备的操作设计为具有幂等性 ,即执行多次的结果与执行一次的结果相同。如果使用这种方式,当出现超时的时候,可以不断地重新请求直到成功。例如修改网络设备运行中的配置时,可能存在当前配置状态已经是理想的了,此时如果通过cli继续下发命令,有些命令操作会报错。正确的做法是实现所有function或者module对外接口实现幂等性。如下图所示,是Arista公司对部署设备配置时的方案,运行playbook文件,eos_config module首先会收集设备正在运行的配置,然后进行对比。如果目标设备已经处于目标状态中,该配置命令就不会被执行,从而实现了幂等性。

Ansible+Napalm

使用Vagrant和VirtualBox工具模拟网络设备环境,可以参考这篇博客Setting up the lab教程去搭建环境(思科和Arista的设备镜像需要到官网上注册后才能下载)。

$ git clone https://github.com/ios-xr/iosxrv-x64-vbox.git
$ brew install socat
$ git clone https://github.com/JNPRAutomate/vagrant-junos.git
$ vagrant plugin install vagrant-junos
$ vagrant up

# the most common and preferred way of installation
$ sudo pip install ansible

# install NAPALM
$ pip install napalm
$ pip install napalm-ansible
....

管理Inventory,例如需要管理两个DC。推荐的结构如下:

.
├── ansible.cfg
├── inventory/            # Parent directory for our environment-specific directories
│   │
│   ├── DC1/              # Contains all files specific to Data Center 1
│   │   ├── host_vars/    # device specific vars files
│   │   │   ├── router1
│   │   │   │   ├── interfaces.yml  # Device specific interface config
│   │   │   │   └── ospf.yml        # Device specific OSPF config
│   │   │   └── switch1
│   │   │       ├── interfaces.yml  # Device specific interface config
│   │   │       └── vlans.yml       # Device specific vlan config
│   │   │
│   │   ├── group_vars/   # group specific vars files
│   │   │   ├── all
│   │   │   │   └── vlans.yml       # Global VLAN config
│   │   │   ├── routers
│   │   │   │   └── ospf.yml        # Global OSPF config
│   │   │   └── switches
│   │   │       └── stp.yml         # Global STP config
│   │   └── hosts         # Contains only the hosts in the dev environment
│   │
│   └── DC2/              # Contains all files specific to Data Center 1
│       ├── host_vars/    # device specific vars files
│       │   ├── router1
│       │   │   ├── interfaces.yml  # Device specific interface config
│       │   │   └── ospf.yml        # Device specific OSPF config
│       │   └── switch1
│       │       ├── interfaces.yml  # Device specific interface config
│       │       └── vlans.yml       # Device specific vlan config
│       │
│       ├── group_vars/   # group specific vars files
│       │   ├── all
│       │   │   └── vlans.yml       # Global VLAN config
│       │   ├── routers
│       │   │   └── ospf.yml        # Global OSPF config
│       │   └── switches
│       │       └── stp.yml         # Global STP config
│       └── hosts         # Contains only the hosts in the dev environment
│
├── playbook.yml
│
└── roles/            # Parent directory for our environment-specific directories
│   ├── common

使用ansible-playbook命令时可以带上-I参数指定执行哪一个inventory。host_vars目录里可以用inventory_hostname文件名描述一个设备变量(例如switch1.yml)或者用inventory_hostname目录里面包含多个变量文件(例如vlans.yml可以用于描述交换机vlan信息)。host_vars变量只能用于当前的设备使用,group_vars是本group的都可以使用。使用YAML格式是因为可读性高(json文件也是可以的)。如果host_vars中和group_vars中有相同变量,则以host_vars中的为准。template模板放在role目录下面。运行playbook后,变量会被加载到指定厂商的模板中,生成配置文件。下图展示了使用Ansible生成每个设备配置的框架图。

部署网络可以分成以下步骤:

  1. Design:对整个网络建数据模型,生成对应的设备数据模型。
  2. Transformation:通过JINJA2模板转译成对应的设备配置。
  3. Deploy:使用Ansible对应厂商开发的module或者通过Napalm工具下发配置到设备上。
  4. Retrieve facts: 获取设备当前状态。
  5. Validate:导入一个Data Model去验证网络设备状态是否和理想的一致。

NAPALM实现了对网络配置管理操作的抽象,屏蔽多厂商差异,并且可支持和集成到自定义脚本例如Ansible,实现自动化处理。下面是常用的Napalm模块:

  • napalmgetfacts:用于获取设备信息,返回统一的数据结构。
- name: get facts from device
  napalm_get_facts:
    hostname={{ inventory_hostname }}
    username={{ user }}
    dev_os={{ os }}
    password={{ passwd }}
    filter='facts,interfaces,bgp_neighbors'
  register: result

- name: print data
  debug: var=result
  • napalminstallconfig:下发配置到设备中。
  - assemble:
    src=../compiled/{{ inventory_hostname }}/
    dest=../compiled/{{ inventory_hostname }}/running.conf

  - napalm_install_config:
    hostname={{ inventory_hostname }}
    username={{ user }}
    dev_os={{ os }}
    password={{ passwd }}
    config_file=../compiled/{{ inventory_hostname }}/running.conf
    commit_changes={{ commit_changes }}
    replace_config={{ replace_config }}
    get_diffs=True
    diff_file=../compiled/{{ inventory_hostname }}/diff
  • napalm_validate:验证设备状态。不需要对设备的所有信息进行验证,只需要指出验证某一项的状态信息。例如通过get_interfaces_ip函数可以去验证某个端口的IP地址是否与理想的一致。另外还可以使用条件语句去判断参数是否符合理想状态的要求。例如判断cpu使用率是否小于15%。

validate.yml:

 - get_facts:
    os_version: 4.17
 - get_interfaces_ip:
    Management1:
        ipv4:
            10.0.2.14:
                prefix_length: 24
            _mode: strict
  - get_environment:
    memory:
      used_ram: '<15.0'
    cpu:
      0/RP0/CPU0
        '%usage': '<15.0'

playbook:

 - name: GET VALIDATION REPORT
  napalm_validate:
    username: "{{ un }}"
    password: "{{ pwd }}"
    hostname: "{{ inventory_hostname }}"
    dev_os: "{{ dev_os }}"
    validation_file: validate.yml
comments powered by Disqus