第308篇:Ansible Network Modules 实战
关键词
Ansible、Network Modules、Playbook、网络自动化、批量配置、多厂商支持、事实收集、配置编排
一、Ansible 网络自动化概述
1.1 Ansible 在网络领域的位置
Ansible 的架构优势:
| Control Node(控制节点) ┌────────────────────────────────────┐ └────────────────────────────────────┘ ▼ ▼ ▼ ┌────────────────────────────────────┐ └────────────────────────────────────┘ ▼ ▼ ▼ ┌──────┐ ┌──────┐ ┌──────┐ | Ansible Engine ┌──────┐ ┌──────┐ ┌──────────┐ └──────┘ └──────┘ └──────────┘ Network Collections ┌─────────┐ ┌─────────┐ ┌──────┐ └─────────┘ └─────────┘ └──────┘ 华为 设备 | 变量 华为 VRP | 思科 设备 | Play 思科 IOS | Juniper 设备 | 角色(Role) JunOS | ||
|---|---|---|---|---|---|---|---|---|
Ansible 网络自动化的特点: ┌─ 无需 Agent:通过 SSH/NETCONF 管理 ├─ 幂等性:重复执行结果一致 ├─ 声明式:描述"目标状态" ├─ 批量:一个命令管理 1000+ 设备 └─ 多厂商:统一接口调用不同厂商模块
1.2 环境准备
# 安装 Ansible
python -m pip install ansible
# 验证
ansible --version
# 安装网络集合
ansible-galaxy collection install community.network
ansible-galaxy collection install huawei.network
ansible-galaxy collection install cisco.ios
ansible-galaxy collection install cisco.nxos
ansible-galaxy collection install junipernetworks.junos
# 查看已安装的集合
ansible-galaxy collection list
1.3 项目结构
ansible_network/
├── ansible.cfg # Ansible 配置
├── inventory.yml # 设备清单
├── group_vars/ # 组变量
│ ├── all.yml # 所有设备通用变量
│ ├── core.yml # 核心设备变量
│ └── access.yml # 接入设备变量
├── host_vars/ # 主机变量
│ ├── CORE-SW01.yml
│ └── ACC-SW01.yml
├── playbooks/ # Playbook
│ ├── collect_facts.yml
│ ├── deploy_vlan.yml
│ ├── backup_config.yml
│ └── verify.yml
├── templates/ # Jinja2 模板
│ └── switch_config.j2
└── roles/ # 角色(可选)
├── ospf_config/
└── bgp_config/
二、Inventory 与连接
2.1 定义设备清单
# inventory.yml — 网络设备清单
# 分组管理
all:
children:
core:
hosts:
CORE-SW01:
ansible_host: 10.0.0.1
CORE-RT01:
ansible_host: 10.0.0.2
vars:
role: core
snmp_location: "DC-A"
access:
hosts:
ACC-SW01:
ansible_host: 10.0.1.1
ACC-SW02:
ansible_host: 10.0.1.2
ACC-SW03:
ansible_host: 10.0.1.3
vars:
role: access
snmp_location: "B1-Floor"
vars:
# 全局连接参数
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: huawei.vrp.vrp
ansible_user: admin
ansible_password: "{{ vault_admin_password }}"
ansible_ssh_common_args: "-o StrictHostKeyChecking=no"
2.2 ansible.cfg 配置
# ansible.cfg — Ansible 配置
[defaults]
inventory = inventory.yml
host_key_checking = False
gathering = explicit
timeout = 30
command_warnings = False
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
2.3 连接测试
# 测试所有设备的连通性
ansible all -m huawei.vrp.vrp_ping -a "dest=127.0.0.1"
# 收集设备版本(单条命令)
ansible core -m huawei.vrp.vrp_command \
-a "commands='display version'"
# 查看收集到的 Facts
ansible CORE-SW01 -m huawei.vrp.vrp_facts
三、核心模块实战
3.1 命令执行模块
# playbooks/collect_facts.yml
---
- name: 收集网络设备信息
hosts: all
gather_facts: no
tasks:
- name: 执行 show 命令
huawei.vrp.vrp_command:
commands:
- display version
- display device
- display interface brief
register: result
- name: 显示结果摘要
debug:
msg:
- "设备: {{ inventory_hostname }}"
- "版本信息: {{ result.stdout_lines[0][:3] }}"
- "接口数: {{ result.stdout_lines[2] | length }}"
3.2 配置管理模块
# playbooks/deploy_vlan.yml
---
- name: 批量创建 VLAN
hosts: access
gather_facts: no
vars:
vlans:
- {id: 100, name: "VLAN100_SALES"}
- {id: 200, name: "VLAN200_ENG"}
- {id: 300, name: "VLAN300_FINANCE"}
tasks:
- name: 创建 VLAN
huawei.vrp.vrp_vlan:
vlan_id: "{{ item.id }}"
name: "{{ item.name }}"
state: present
loop: "{{ vlans }}"
register: vlan_result
- name: 显示 VLAN 创建结果
debug:
msg: "VLAN {{ item.item.id }} ({{ item.item.name }}): {{ '✓' if item.changed else '─' }}"
loop: "{{ vlan_result.results }}"
- name: 保存配置
huawei.vrp.vrp_config:
save: true
3.3 接口配置模块
# playbooks/deploy_interfaces.yml
---
- name: 配置交换机接口
hosts: ACC-SW01
gather_facts: no
tasks:
- name: 配置 Trunk 上行口
huawei.vrp.vrp_interface:
name: GigabitEthernet0/0/1
description: "UPLINK-TO-CORE"
mode: trunk
trunk_allowed_vlans: "10,20,30,100"
state: present
- name: 配置 Access 接入口
huawei.vrp.vrp_interface:
name: "{{ item.name }}"
description: "{{ item.desc }}"
mode: access
access_vlan: "{{ item.vlan }}"
state: present
loop:
- {name: GigabitEthernet0/0/2, desc: "PC-FLOOR1", vlan: 10}
- {name: GigabitEthernet0/0/3, desc: "PC-FLOOR2", vlan: 20}
- {name: GigabitEthernet0/0/4, desc: "SERVER-ROOM", vlan: 100}
- name: 保存配置
huawei.vrp.vrp_config:
save: true
3.4 配置备份
# playbooks/backup_config.yml
---
- name: 全网配置备份
hosts: all
gather_facts: no
vars:
backup_dir: "/backup/{{ ansible_date_time.date }}/"
tasks:
- name: 创建备份目录
delegate_to: localhost
file:
path: "{{ backup_dir }}"
state: directory
- name: 备份运行配置
huawei.vrp.vrp_command:
commands: display current-configuration
register: config
- name: 写入备份文件
delegate_to: localhost
copy:
content: "{{ config.stdout[0] }}"
dest: "{{ backup_dir }}/{{ inventory_hostname }}_running.cfg"
- name: 备份启动配置
huawei.vrp.vrp_command:
commands: display saved-configuration
register: saved_config
- name: 写入备份文件
delegate_to: localhost
copy:
content: "{{ saved_config.stdout[0] }}"
dest: "{{ backup_dir }}/{{ inventory_hostname }}_startup.cfg"
- name: 显示结果
debug:
msg: "✓ {{ inventory_hostname }} 配置已备份到 {{ backup_dir }}"
四、多厂商 Playbook
4.1 多厂商混合管理
# inventory_multivendor.yml
all:
children:
huawei:
hosts:
CORE-SW01:
ansible_host: 10.0.0.1
vars:
ansible_network_os: huawei.vrp.vrp
ansible_connection: ansible.netcommon.network_cli
cisco:
hosts:
CORE-RT01:
ansible_host: 10.0.0.2
vars:
ansible_network_os: cisco.ios.ios
ansible_connection: ansible.netcommon.network_cli
juniper:
hosts:
CORE-RT02:
ansible_host: 10.0.0.3
vars:
ansible_network_os: junipernetworks.junos.junos
ansible_connection: ansible.netcommon.netconf
# playbooks/multivendor_facts.yml
---
- name: 多厂商设备信息收集
hosts: all
gather_facts: no
tasks:
- name: 收集华为设备 Facts
huawei.vrp.vrp_facts:
when: ansible_network_os == 'huawei.vrp.vrp'
register: huawei_facts
- name: 收集思科设备 Facts
cisco.ios.ios_facts:
when: ansible_network_os == 'cisco.ios.ios'
register: cisco_facts
- name: 收集 Juniper Facts
junipernetworks.junos.junos_facts:
when: ansible_network_os == 'junipernetworks.junos.junos'
register: juniper_facts
- name: 显示统一结果
debug:
msg:
- "设备: {{ inventory_hostname }}"
- "厂商: {{ ansible_net_vendor | default('unknown') }}"
- "型号: {{ ansible_net_model | default('unknown') }}"
- "版本: {{ ansible_net_version | default('unknown') }}"
4.2 厂商无关的通用操作
# playbooks/generic_ping.yml
---
- name: 通用网络验证
hosts: all
gather_facts: no
tasks:
- name: 检查网关连通性
ansible.netcommon.net_ping:
dest: "{{ gateway_ip }}"
vars:
gateway_ip: "10.0.0.254"
register: ping_result
- name: 显示 Ping 结果
debug:
msg: "{{ inventory_hostname }} → {{ gateway_ip }}: {{ ping_result.stdout }}"
五、高级应用
5.1 模板+Playbook 组合
# playbooks/deploy_from_template.yml
---
- name: 基于模板部署设备配置
hosts: all
gather_facts: no
vars:
ntp_server: "203.0.113.1"
tasks:
- name: 生成配置(本地渲染)
delegate_to: localhost
template:
src: "templates/device_base.j2"
dest: "generated/{{ inventory_hostname }}_config.cfg"
run_once: no
- name: 将生成的配置加载到设备
huawei.vrp.vrp_config:
src: "generated/{{ inventory_hostname }}_config.cfg"
match: none # 不匹配现有配置
save: true
# templates/device_base.j2 — Jinja2 模板
! 设备: {{ inventory_hostname }}
! 角色: {{ role | default("unknown") }}
{% if role == "core" %}
sysname {{ inventory_hostname }}
#
interface LoopBack0
ip address {{ loopback_ip }} 255.255.255.255
#
ospf 1 router-id {{ loopback_ip }}
area 0.0.0.0
network {{ loopback_ip }} 0.0.0.0
#
{% elif role == "access" %}
sysname {{ inventory_hostname }}
#
vlan batch {{ vlans | default([1]) | join(" ") }}
#
interface GigabitEthernet0/0/1
port link-type trunk
port trunk allow-pass vlan all
#
{% endif %}
ntp-service unicast-server {{ ntp_server }}
#
return
5.2 滚动变更
# playbooks/rolling_update.yml
---
- name: 滚动配置变更(一次一台,避免影响业务)
hosts: access
serial: 1 # 一次只变更一台
gather_facts: no
vars:
new_snmp_config: |
snmp-agent sys-info contact IT-Department
snmp-agent sys-info location "Beijing-Office-B1"
tasks:
- name: 开始变更
debug:
msg: "===== 正在变更 {{ inventory_hostname }} ====="
- name: 应用 SNMP 配置
huawei.vrp.vrp_config:
lines:
- snmp-agent sys-info contact IT-Department
- snmp-agent sys-info location "Beijing-Office-B1"
- name: 验证配置
huawei.vrp.vrp_command:
commands: display snmp-agent sys-info
register: verify
- name: 检查结果
assert:
that:
- "'IT-Department' in verify.stdout[0]"
- "'Beijing' in verify.stdout[0]"
success_msg: "✓ {{ inventory_hostname }} SNMP 配置验证通过"
fail_msg: "✗ {{ inventory_hostname }} SNMP 配置验证失败"
- name: 保存
huawei.vrp.vrp_config:
save: true
- name: 变更完成
debug:
msg: "===== {{ inventory_hostname }} 变更完成 ====="
5.3 配置合规检查
# playbooks/compliance_check.yml
---
- name: 配置合规检查
hosts: all
gather_facts: no
tasks:
- name: 获取当前配置
huawei.vrp.vrp_command:
commands: display current-configuration
register: current_config
- name: 检查必须配置
assert:
that:
- "'ntp-service unicast-server' in current_config.stdout[0]"
- "'snmp-agent' in current_config.stdout[0]"
- "'ssh server port 22' in current_config.stdout[0]"
success_msg: "✓ {{ inventory_hostname }} 合规检查通过"
fail_msg: "✗ {{ inventory_hostname }} 合规检查失败,缺少必要配置"
- name: 检查安全基线
block:
- name: 检查 SSH 版本
assert:
that: "'ssh server' in current_config.stdout[0]"
fail_msg: "SSH 未配置"
- name: 检查密码策略
huawei.vrp.vrp_command:
commands: display aaa configuration
register: aaa_config
ignore_errors: yes
rescue:
- name: 合规异常告警
debug:
msg: "⚠ {{ inventory_hostname }} 存在安全合规风险"
六、Ansible 与 Python 集成
6.1 在 Python 中调用 Ansible
#!/usr/bin/env python3
# ansible_runner.py — Python 集成 Ansible
import ansible_runner
import json
def run_playbook(playbook_path, inventory_path, extra_vars=None):
"""运行 Ansible Playbook"""
result = ansible_runner.run(
playbook=playbook_path,
inventory=inventory_path,
extravars=extra_vars or {},
)
return {
"status": result.status,
"rc": result.rc,
"stats": result.stats,
"events": list(result.events),
}
def run_adhoc(hosts, module, args, inventory_path="inventory.yml"):
"""运行 ad-hoc 命令"""
result = ansible_runner.run(
inventory=inventory_path,
host_pattern=hosts,
module=module,
module_args=args,
)
return result
# 运行 Playbook
result = run_playbook(
playbook_path="playbooks/backup_config.yml",
inventory_path="inventory.yml",
)
print(f"状态: {result['status']}")
print(f"统计: {json.dumps(result['stats'], indent=2)}")
# 运行 ad-hoc
result = run_adhoc(
hosts="all",
module="huawei.vrp.vrp_command",
args="commands='display clock'",
)
print(f"返回码: {result.rc}")
6.2 使用 Ansible 事实数据
#!/usr/bin/env python3
# ansible_facts_consumer.py — 消费 Ansible 收集的数据
import json
def analyze_network_facts(facts_file):
"""分析 Ansible 收集的设备事实"""
with open(facts_file) as f:
data = json.load(f)
report = {
"total_devices": len(data),
"by_vendor": {},
"by_model": {},
"os_versions": {},
"total_interfaces": 0,
"interfaces_down": 0,
}
for host, facts in data.items():
vendor = facts.get("ansible_net_vendor", "unknown")
model = facts.get("ansible_net_model", "unknown")
version = facts.get("ansible_net_version", "unknown")
report["by_vendor"][vendor] = report["by_vendor"].get(vendor, 0) + 1
report["by_model"][model] = report["by_model"].get(model, 0) + 1
if version not in report["os_versions"]:
report["os_versions"][version] = []
report["os_versions"][version].append(host)
interfaces = facts.get("ansible_net_interfaces", {})
report["total_interfaces"] += len(interfaces)
report["interfaces_down"] += sum(
1 for v in interfaces.values() if not v.get("operstatus") == "up"
)
return report
# 模拟数据(实际从 Ansible 的 JSON 文件读取)
sample_data = {
"CORE-SW01": {
"ansible_net_vendor": "Huawei",
"ansible_net_model": "CE12808",
"ansible_net_version": "V800R022C00",
"ansible_net_interfaces": {
"GE0/0/1": {"operstatus": "up"},
"GE0/0/2": {"operstatus": "up"},
"GE0/0/3": {"operstatus": "down"},
},
}
}
report = analyze_network_facts.__wrapped__ if False else None
# 直接用 sample_data
report = {
"total_devices": len(sample_data),
"by_vendor": {},
"by_model": {},
"os_versions": {},
"total_interfaces": 0,
"interfaces_down": 0,
}
for host, facts in sample_data.items():
vendor = facts.get("ansible_net_vendor", "unknown")
model = facts.get("ansible_net_model", "unknown")
version = facts.get("ansible_net_version", "unknown")
report["by_vendor"][vendor] = report["by_vendor"].get(vendor, 0) + 1
report["by_model"][model] = report["by_model"].get(model, 0) + 1
if version not in report["os_versions"]:
report["os_versions"][version] = []
report["os_versions"][version].append(host)
interfaces = facts.get("ansible_net_interfaces", {})
report["total_interfaces"] += len(interfaces)
report["interfaces_down"] += sum(
1 for v in interfaces.values() if not v.get("operstatus") == "up"
)
print(json.dumps(report, indent=2))
七、最佳实践
7.1 Playbook 规范
Ansible 网络 Playbook 编写规范:
1. 命名规范
┌─ playbook 名:动作_目标.yml
│ deploy_vlan.yml
│ backup_config.yml
│ verify_ospf.yml
└─ 变量:snake_case
2. 幂等性
┌─ 配置模块支持 state: present/absent
├─ 用 changed_when 控制变更检测
├─ 用 --check 模式预览变更
└─ 先 check 后执行
3. 安全
┌─ 使用 ansible-vault 加密密码
├─ 敏感变量不写在 Playbook 中
├─ 日志不输出密码
└─ SSH 密钥认证优先
4. 执行控制
┌─ serial: 控制并行度
├─ throttle: 限制并发数
├─ max_fail_percentage: 容忍失败率
└─ any_errors_fatal: 关键任务严格模式
7.2 常见问题
常见问题与解决:
Q1: SSH 连接超时
解决: 增加 timeout, 启用 pipelining
Q2: 配置命令报错
解决: 使用 ignore_errors 配合 assert 验证
Q3: 大批量设备执行慢
解决: serial 控制并发, forks 调优
Q4: 配置回滚
解决: 先 backup, 再 commit, 失败时 restore
Q5: 不同厂商命令差异
解决: 使用 when 判断 ansible_network_os
八、总结
Ansible 网络自动化的核心价值:
声明式配置管理
┌─ 描述"配置应该是什么样"
├─ 非"如何配置"的脚本
└─ 幂等执行,安全可靠
多厂商统一
┌─ 不同厂商的模块接口类似
├─ Playbook 结构一致
└─ 厂商差异在模块层屏蔽
从脚本到编排
┌─ ad-hoc:单条命令批量执行
├─ Playbook:多步骤编排
├─ Roles:复用配置逻辑
└─ AWX/Tower:可视化平台
学习路径:
Playbook → Variables → Templates → Roles → AWX
基础 数据 复用 抽象 平台
下篇预告:第309篇 — AWX/Tower 可视化管理平台,将介绍如何通过 AWX/Tower 将 Ansible 自动化提升到企业级平台层面。