Skip to content

使用 Ansible 配置 Juniper 路由器

背景

Juniper 是著名的网络设备提供商, 系统需要使用 Ansible 修改 Juniper 设备的配置,此外,对设备的操作需要 ACID 事务性支持。

Juniper 设备的三种配置模式

可以通过命令行对 Juniper 进行配置,配置的过程如下:

text
# 进入配置模式
edit / configure exclusive / configure private

# 修改配置
set xxxx

# 提交 / 回滚配置
commit / rollback

# 退出配置模式
exit

下面通过一些测试用例比较这三种模式的区别。

场景 1:已更改配置但未 commit

测试步骤:

  1. 进入配置模式。
  2. 更改配置。
  3. 直接退出。
  4. 重新进入配置模式。
  5. 执行 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:不同连接间的隔离性

测试步骤:

  1. [connection-1] 进入配置模式
  2. [connection-1] 修改配置
  3. [connection-2] 进入配置模式
  4. [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:使用不同的连接更改和回滚配置

测试步骤:

  1. [connection-1] 进入配置模式。
  2. [connection-1] 修改配置。
  3. [connection-1] commit。
  4. [connection-2] 进入配置模式
  5. [connection-2] 执行 rollback 1 回滚上一次的更改。
  6. [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_formatchrpcs 选项。
      • 如果进入配置模式(比如执行 editconfigure 等命令),对命令执行的结果判断不准确,可能会出现执行失败但结果是成功的情况。
    • ansible.netcommon.netconf
      • 不能执行 editconfigure 等命令。
      • 支持 wait_formatchrpcs 选项。
  • 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)

参考资料