使用 Ansible 配置 Juniper 路由器
背景
Juniper 是著名的网络设备提供商, 系统需要使用 Ansible 修改 Juniper 设备的配置,此外,对设备的操作需要 ACID 事务性支持。
Juniper 设备的三种配置模式
可以通过命令行对 Juniper 进行配置,配置的过程如下:
text
# 进入配置模式
edit / configure exclusive / configure private
# 修改配置
set xxxx
# 提交 / 回滚配置
commit / rollback
# 退出配置模式
exit
下面通过一些测试用例比较这三种模式的区别。
场景 1:已更改配置但未 commit
测试步骤:
- 进入配置模式。
- 更改配置。
- 直接退出。
- 重新进入配置模式。
- 执行
show | compare
命令查看差异。
edit
命令行操作记录
text
## step-1
user@hostname-re0> edit
Entering configuration mode
## step-2
{master}[edit]
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
## step-3
user@hostname-re0# exit
The configuration has been changed but not committed
Exit with uncommitted changes? [yes,no] (yes) yes
Exiting configuration mode
{master}
## step-4
user@hostname-re0> edit
Entering configuration mode
The configuration has been changed but not committed
## step-5
user@hostname-re0# show |compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
user@hostname-re0# show |compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
结论:未 commit 的改动 不会 被丢弃。
configure exclusive
命令行操作记录
text
## step-1
user@hostname-re0> configure exclusive
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
## step-3
user@hostname-re0# exit
The configuration has been changed but not committed
warning: Auto rollback on exiting 'configure exclusive'
Discard uncommitted changes? [yes,no] (yes) yes
warning: discarding uncommitted changes
Exiting configuration mode
{master}
## step-4
user@hostname-re0> configure exclusive
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-5
user@hostname-re0# show | compare
{master}[edit]
结论:未 commit 的改动 会 被丢弃。
configure private
命令行操作记录
text
## step-1
user@hostname-re0> configure private
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
## step-3
user@hostname-re0# exit
The configuration has been changed but not committed
Discard uncommitted changes? [yes,no] (yes) yes
Exiting configuration mode
{master}
## step-4
user@hostname-re0> configure private
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-5
user@hostname-re0# show | compare
{master}[edit]
结论:未 commit 的改动 会 被丢弃。
场景 2:不同连接间的隔离性
测试步骤:
- [connection-1] 进入配置模式
- [connection-1] 修改配置
- [connection-2] 进入配置模式
- [connection-2] 执行
show | compare
命令查看差异。
edit
命令行操作记录
text
# connection-1
## step-1
user@hostname-re0> edit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
# connection-2
## step-3
user@hostname-re0> edit
Entering configuration mode
Users currently editing the configuration:
naVer terminal pts/0 (pid 56253) on since 2023-08-08 11:33:18 UTC
{master}[edit]
The configuration has been changed but not committed
{master}[edit]
## step-4
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
结论:类似于 MySQL 的 read uncommitted,未 commit 的改动在多个连接间 共享。
configure exclusive
命令行操作记录
text
# connection-1
## step-1
user@hostname-re0> configure exclusive
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
user@hostname-re0#
# connection-2
## step-1
user@hostname-re0> configure exclusive
error: configuration database locked by:
naVer terminal pts/1 (pid 56260) on since 2023-08-08 11:36:56 UTC, idle 00:00:52
exclusive {master}[edit]
结论: 类似于 MySQL 的 serializable,不允许 多个连接同时改动配置。
configure private
命令行操作记录
text
# connection-1
## step-1
user@hostname-re0> configure private
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop ip-1/3/0.50;
+ next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
{master}[edit]
# connection-2
## step-3
user@hostname-re0> configure private
warning: uncommitted changes will be discarded on exit
Entering configuration mode
Users currently editing the configuration:
naVer terminal pts/1 (pid 56260) on since 2023-08-08 11:41:24 UTC, idle 00:00:42
private [edit]
{master}[edit]
## step-4
user@hostname-re0# show | compare
{master}[edit]
结论: 类似于 MySQL 的 read committed,无法 读到其他连接未 commit 的改动。
场景 3:使用不同的连接更改和回滚配置
测试步骤:
- [connection-1] 进入配置模式。
- [connection-1] 修改配置。
- [connection-1] commit。
- [connection-2] 进入配置模式
- [connection-2] 执行
rollback 1
回滚上一次的更改。 - [connection-2] 执行
show | compare
命令查看差异。
edit
命令行操作记录
text
# connection-1
## step-1
user@hostname-re0> edit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
## step-3
user@hostname-re0# commit
re0:
configuration check succeeds
re1:
commit complete
re0:
commit complete
{master}[edit]
user@hostname-re0# show | grep 202.179.183.102/32 | display set
set routing-options static route 202.179.183.102/32 next-hop ip-1/3/0.50
set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# exit
Exiting configuration mode
# connection-2
## step-1
user@hostname-re0> edit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# rollback 1
load complete
{master}[edit]
## step-3
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
+ next-hop ip-1/3/0.50;
{master}[edit]
结论:可以回滚。
configure exclusive
命令行操作记录
text
# connection-1
## step-1
user@hostname-re0> configure exclusive
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
## step-3
user@hostname-re0# commit
re0:
configuration check succeeds
re1:
commit complete
re0:
commit complete
{master}[edit]
user@hostname-re0# show | grep 202.179.183.102/32 | display set
set routing-options static route 202.179.183.102/32 next-hop ip-1/3/0.50
set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# exit
Exiting configuration mode
# connection-2
## step-4
user@hostname-re0> configure exclusive
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-5
user@hostname-re0# rollback 1
load complete
{master}[edit]
## step-6
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
+ next-hop ip-1/3/0.50;
{master}[edit]
结论:可以回滚。
configure private
命令行操作记录
text
# connection-1
## step-1
user@hostname-re0> configure private
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-2
user@hostname-re0# set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
## step-3
user@hostname-re0# commit
re0:
configuration check succeeds
re1:
commit complete
re0:
commit complete
{master}[edit]
user@hostname-re0# show | grep 202.179.183.102/32 | display set
set routing-options static route 202.179.183.102/32 next-hop ip-1/3/0.50
set routing-options static route 202.179.183.102/32 next-hop ip-0/3/0.51
{master}[edit]
user@hostname-re0# exit
Exiting configuration mode
# connection-2
## step-4
user@hostname-re0> configure private
warning: uncommitted changes will be discarded on exit
Entering configuration mode
{master}[edit]
## step-5
user@hostname-re0# rollback 1
load complete
{master}[edit]
## step-6
user@hostname-re0# show | compare
[edit routing-options static route 202.179.183.102/32]
- next-hop [ ip-1/3/0.50 ip-0/3/0.51 ];
+ next-hop ip-1/3/0.50;
{master}[edit]
结论:可以回滚。
总结
连接退出后未 commit 的改动是否保留 | 是否允许多个连接同时进入配置模式 | 是否可以读取到其他连接未 commit 的改动 | 是否可以回滚其他连接的改动 | |
---|---|---|---|---|
edit | √ | √ | √ | √ |
configure exclusive | × | × | × | √ |
configure private | × | √ | × | √ |
Juniper Ansible Collection
Ansible 提供了 Juniper 设备相关的 Collection,详细内容可参考 Junipernetworks.Junos 。
这里只介绍以下两个相关的 Modules。
junos_config
这个 module 基于 NETCONF(NetworkConfigurationProtocol, RFC6241)。
Module 文档:junos_config
这个模块具有如下特性:
- 使用 configure exclusive 模式。
- 由于使用 NETCONF rpc,无法显示像命令行那样的操作记录。
以下是部分关键的源码片段:
python
# enter exclusive mode by netconf lock rpc
with locked_config(module):
# modify configuration
diff = configure_device(module, warnings, candidate)
if diff:
if commit:
kwargs = {
"comment": module.params["comment"],
"check": module.params["check_commit"],
}
confirm = module.params["confirm"]
if confirm > 0:
kwargs.update(
{
"confirm": True,
"confirm_timeout": to_text(
confirm,
errors="surrogate_then_replace",
),
},
)
# commit if needed
commit_configuration(module, **kwargs)
else:
# if not need to commit(e.g. only check commit),
# discard uncommitted changes by netconf discard-changes rpc
discard_changes(module)
junos_command
Module 文档:junos_command
该模块有如下特性
- 使用时需要配置连接插件(connection plugin),有以下两种:
ansible.netcommon.network_cli
:- 可以执行任意命令。
- 不支持
wait_for
,match
,rpcs
选项。 - 如果进入配置模式(比如执行
edit
,configure
等命令),对命令执行的结果判断不准确,可能会出现执行失败但结果是成功的情况。
ansible.netcommon.netconf
:- 不能执行
edit
,configure
等命令。 - 支持
wait_for
,match
,rpcs
选项。
- 不能执行
- Playbook 的输出仅包含命令的响应,不能输出形如终端中“命令-响应”的格式。
Playbook 示例
以下是使用 Ansible 配置 Juniper 设备并支持失败回滚的 Playbook 示例:
yaml
---
- name: Update configs for juniper devices
hosts: all
gather_facts: false
vars:
ansible_connection: ansible.netcommon.netconf
ansible_network_os: junipernetworks.junos.junos
ansible_host_key_checking: no
tasks:
- name: 1. Update configs
block:
- name: 1.1 Update configs by NETCONF
junipernetworks.junos.junos_config:
lines:
"{{ router_configs }}"
comment: "{{ comment | default('configured by ansible') }}"
# trigger handler
notify:
- 2. Auto rollback
diff: true
# 标记失败状态
# rescue: 只针对失败的设备
rescue:
- name: 1.2 Mark failed status
vars:
ansible_connection: local
ansible.builtin.set_fact:
has_failed_routers: true
delegate_to: localhost
delegate_facts: true
# handlers: 若存在配置失败的设备则全部回滚
handlers:
- name: 2. Auto rollback
junipernetworks.junos.junos_config:
rollback: 1
comment: "{{ rollback_comment | default('rollback by ansible') }}"
when: hostvars['localhost']['has_failed_routers'] | default(false)