十六、Python 网络自动化实验室:Ansible、pyATS、Docker 和 Twilio API
本章旨在向您介绍 Python virtualenv
和 Docker 概念,并向您展示探索 Python 外部模块和特性的其他方式。到目前为止,在本书中,我们重点介绍了如何使用 Python 的基本 Telnet 和 SSH 远程访问方法来探测、配置和管理实验室拓扑中的网络设备。在一个生产环境中,我们通常不会得到一个专用的 Linux 服务器来进行测试和实现;通常你会得到一个 Linux 服务器上的有限资源,可以和其他工程师共享。我们必须学习如何绕过这些障碍,并在这样的生产环境中实现我们的解决方案(或应用)。在本章结束时,你将能够在 Python 虚拟环境中进行测试,而不会影响现有的 Python 设置。您将快速入门 Ansible 和 pyATS (Genie ),并了解如何在 Linux 和 Python 上使用 Sendmail 发送电子邮件。最后,您将学习使用 Python 应用监控网络设备的 CPU 利用率,然后使用免费的 Twilio SMS 帐户通过 Twilio API 向您的智能手机发送 SMS。您将探索如何适应其他开源工具,并相应地编写 Python 程序。
Python 网络自动化开发实验室
我们已经接近这本书的结尾,在这一章中,你将探索一些 Python 网络自动化实验室。本章将向您介绍不同的网络自动化工具,帮助您踏上网络自动化之旅。有专门的网站和文档介绍 Ansible、pyATS 和 Docker。因此,仅仅通过阅读本书的一个章节,从技术上来说是不可能深入理解这些工具的。尽管如此,安装和学习这些工具是值得的,这样您就可以自己决定这些工具是否能帮助您实现网络自动化的梦想。不是每个网络工程师都想以写 Python 代码为生,但仍然想体验网络自动化的真正力量。在这种情况下,这些现成的工具将服务于最常见的网络自动化场景,您不必编写真正的 Python 代码。
但是,请注意,没有两个客户端网络环境是相同的,也没有两个组织的 IP 流量具有相同的优先级或特征。如果你读到这里,仍然认为网络自动化只包括自动化网络工程师的一般任务及其思维过程,那你就错了。这项工作的一半都是关于处理数据的,也就是说,你如何提取和使用这些数据为你的组织带来好处,用代码行代替可重复的任务。因此,每个场景中都不可避免地涉及到一些定制和编写代码。简而言之,本章将作为 Ansible、pyATS 和 Docker 的快速入门指南。
Red Hat 的 Ansible 在网络配置管理方面越来越受欢迎;它是一个开源工具,最初是作为操作系统的配置管理工具开发的。最近,同样的工具被用来管理网络设备。Ansible 的一大好处是,Ansible 用户不必编写真正的代码,如 Python 或 JavaScript 他们用叫做 YAML 的伪代码写代码。是的,编写 YAML 代码介于编写真正的代码和使用基于 GUI 的软糖代码之间。Ansible 只不过是一个基于 Python 的paramiko
、netmiko
和nornir
库的简化的 YAML 应用,但它比仅仅用 Python 编码要用户友好得多。与其他竞争对手一样,它使用无代理推送方法而非拉取方法,将 YAML 文件用于端点自动化。Ansible 可能是第一个网络自动化工具,许多网络工程师甚至在进入 Python 之前就会与之交互。或者,你可能是一个经验丰富的 Python 程序员,并爱上 Ansible 和 Ansible Tower 友好的用户界面和易于遵循和理解的 YAML 工作流设计。Ansible 使用 YAML,它比脚本编程语言更像应用;使用 Ansible,用户不需要知道任何编程语言。不是所有的网络工程师都想精通 Python 编程;他们想要一个预制的工具,所以 Ansible 可能是答案。我坚信 Ansible 对很多机构最大的吸引力就是它的低准入门槛(甚至比 Python 还低)。但同时,这从一个工程师的角度来看是一种失望,这可能会导致许多工程师过早地放弃他们的编程语言之旅。
在本章中,您将看到一些快速安装和启动实验。pyATS 和 Nornir 是思科认可和开发的工具;这里将向您介绍 pyATS,作为快速启动指南。pyATS 不是一种编程语言;相反,它是一个网络探测和信息收集工具,而不是配置工具。对于配置,可以使用 Nornir,但它只是一个具有更多用户友好功能的netmiko
的重写版本,其中netmiko
是paramiko
的改进版本,它明确专注于多供应商网络自动化。这些都是很棒的工具,值得花时间去探索它们。本章还将谈到 Docker 作为生产中的资源节约工具。Docker 是一个可以在 Linux 或 Windows 系统上运行的容器化解决方案。Docker 在企业网络自动化方面有很多用例。尽管如此,Docker 主要用作 Linux 服务器上运行和执行任务或应用的实例。它们占用的空间很小,可以在整个虚拟机不适合运行单个任务的地方运行,例如在专用虚拟机上运行夜间系统检查。我们将在各自的章节中进一步讨论这些工具。在这一章的后半部分,你将被引导建立两个 Docker 镜像的实验室,并学习如何使用 Docker。此外,作为实验的一部分,您将了解如何在 Python 中使用 REST API。
首先,你将安装 Python 的virtualenv
,并学习如何在 Python 虚拟环境中工作;然后使用virtualenv
向您介绍 Ansible 和 pyATS。在第三个实验中,您将安装 Docker 并从 Docker Hub 下载我专门构建的 Python 网络自动化服务器,并学习使用 Docker 容器来代替实际的虚拟机。在某些情况下,Docker 可能是运行独立 Python 应用而不浪费大量计算能力的最佳替代环境。
Ansible 快速入门指南:虚拟实验室 1
在开发 Python 应用时,有很多情况下您希望在不影响生产 Python 服务器的情况下测试新的 Python 模块。正如前面章节中所讨论的,Python 服务器通常是在 Linux 操作系统上实现的。随着你对 Linux 操作系统的了解越来越多,当一切都按设计运行时,操作 Linux 系统是一种乐趣。尽管如此,在引入新软件时,或者在这种情况下,在工作的 Python 环境中引入不同的 Python 库时,事情可能并且将会变得非常糟糕。您可能需要花费数小时来排除错误安装的程序,并尝试删除该软件,将操作系统恢复到良好的系统健康状态。virutalenv
是一个 Python 工具,用于创建隔离的环境,包含它们的 Python 副本、pip
和专用存储空间,以保存从 PyPl 安装的测试库。它允许用户在同一台机器上同时处理多个具有不同依赖关系的项目。您可以将virtualenv
视为 Python 库测试的快速沙箱。
在本实验中,您将快速了解如何在您的 Ubuntu 服务器上设置 Python virtualenv
,为 Ansible 创建virtualenv
并测试 Ansible 探测信息,以及在您的实验室中对 Cisco 设备进行快速配置更改。注意 Ansible 不是像 Python、Perl、JavaScript 那样的编程语言;这是一个使用 YAML 来协调 IP 设备的框架。在 Ansible 下面,它是用 Python 写的,而且,对于 SSH 连接,它依赖于paramiko
,正如我们在第七章中所学的。
很多人问我应该先学 Ansible 还是先学 Python?我认为答案取决于每个人对使用 Python 实现网络自动化的了解程度。既然你已经读到这里,你可能已经知道你的答案了:首先学习 Python,然后尝试 Ansible。因为来自 Red Hat 的 Ansible(以及某种程度上来自 Cisco 的 pyATS [Genie])是如此强大,不需要工程师去写真正的代码,它可以误导 Python 学习者认为你没有必要学习 Python;这是先学 Ansible 后学 Python 的最大弊端。
以下内容是在 Ubuntu 20.04 LTS 服务器环境下安装virtenv
模块的快速启动指南。按照这些简单的步骤设置您的环境,以完成下面的 Ansible 启动实验。如果您使用 CentOS(或任何 Red Hat)操作系统,这个过程会稍有变化,但基本功能是相同的。图 16-1 显示了本实验中使用的逻辑连接和设备。
图 16-1。
Ansible virtualenv 实验室使用的设备
安装 virtualenv 并开始使用 Ansible
让我们在你的 Ubuntu 20 LTS 服务器上安装virtualenv
模块,为本章的第一个实验做好准备。按照每个步骤完成virtualenv
的安装。
|
工作
|
| — | — |
| 1 | SSH 进入您的 Ubuntu 服务器,并执行以下步骤来启动并运行下一个实验。使用 Ubuntu 的高级打包工具将 Ubuntu 20.04 LTS 更新和升级至最新版本。pynetauto@ubuntu20s1:~$
sudo apt update``pynetauto@ubuntu20s1:~$
sudo apt -y upgrade
|
| 2 | 为了使您的 Python 环境更加健壮,安装一些额外的包和开发工具,以确保为编程环境设置了 Linux 服务器。最好以 root 用户身份安装这些软件包。pynetauto@ubuntu20s1:~$ su -``Password: ********``root@ubuntu20s1:~#
apt update``root@ubuntu20s1:~#
apt-get install -y``root@ubuntu20s1:~#
apt install -y build-essential python3-dev libssl-dev libffi-dev``root@ubuntu20s1:~#
apt install -y python3-pip``root@ubuntu20s1:~#
apt install -y python3-pip
|
| 3 | 您将使用venv
Python 模块来配置python3
虚拟环境,这是标准 Python 3 库的一部分。root@ubuntu20s1:~#
apt install -y python3-venv``root@ubuntu20s1:~#
apt install sshpass # For ansible ssh login``root@ubuntu20s1:~#
su - pynetauto
|
| 4 | 使用mkdir
来创建一个目录(环境),你的虚拟环境将在其中生存。使用cd
命令切换到新目录。pynetauto@ubuntu20s1:~$
mkdir venv1``pynetauto@ubuntu20s1:~$
cd venv1``pynetauto@ubuntu20s1:~/venv1$
|
| 5 | 进入目录后,创建您的测试环境。使用ls
Linux 命令查看环境目录中的项目,在本例中是ansible_venv
。pynetauto@ubuntu20s1:~/venv1$
python3 -m venv ansible_venv``pynetauto@ubuntu20s1:~/venv1$
ls ansible_venv``bin include lib lib64 pyvenv.cfg share
|
| 6 | 激活并启动新的虚拟环境。./
表示当前工作目录或您正在其中工作的目录。一旦您激活了您的虚拟环境,您将在 CLI 的开头看到(ansible_venv)
。pynetauto@ubuntu20s1:~/venv1$
source ./ansible_venv/bin/activate``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
|
| 7 | 如果您的 Linux 命令行前面有(ansible_venv)
,这意味着您处于虚拟环境中。运行Python –V
或python3 –V
确认 Python 版本。如果您只有 Python 版本 3,您可以使用Python
或Python3
在这个环境中运行您的脚本。(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
Python -V``Python 3.8.2``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
python3 -V``Python 3.8.2
|
| 8 | 要退出或停用环境,使用deactivate
命令退出您的virtualenv
。(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
deactivate``pynetauto@ubuntu20s1:~/venv1$
|
| 9 | 要重新激活相同的环境,如果您已经在netautovenv
目录中,使用相同的./ansible_venv/bin/activate
命令。pynetauto@ubuntu20s1:~/venv1$
source ./ansible_venv/bin/activate``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
|
| 10 | 要启动我们的 Ansible 实验室,首先安装paramiko
,然后使用pip3 install
命令安装 Ansible。Ansible 使用paramiko
作为它到网络设备的 SSH 连接,所以这是一个先决条件。pynetauto@ubuntu20s1:~/venv1$ source ./ansible_venv/bin/activate``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
pip3 install paramiko``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$``pip3 install ansible``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
ansible --version``ansible 2.9.12
|
| 11 | 在 Ansible 安装之后,再次在ubuntu20s1
服务器上生成 SSH 密钥。(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
ssh-keygen``Generating public/private rsa key pair.``Enter file in which to save the key (/home/pynetauto/.ssh/id_rsa):``Created directory '/home/pynetauto/.ssh'.``Enter passphrase (empty for no passphrase): **********``Enter same passphrase again: **********``Your identification has been saved in /home/pynetauto/.ssh/id_rsa``[... omitted for brevity]``+----[SHA256]-----+``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
|
| 12 | 使用ls ansible_venv/bin/ansible*
检查 Ansible 中可用的命令。(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ls ansible_venv/bin/ansible*``ansible_venv/bin/``ansible``ansible_venv/bin/``ansible-console``ansible_venv/bin/``ansible-inventory``ansible_venv/bin/``ansible-test ansible_venv``/bin/``ansible-config``ansible_venv/bin/``ansible-doc``ansible_venv/bin/``ansible-playbook``ansible_venv/bin/``ansible-vault``ansible_venv/bin/``ansible-connection``ansible_venv/bin/``ansible-galaxy``ansible_venv/bin/``ansible-pull
|
| 13 | 接下来,使用sudo /home/pynetauto/.ssh/config
命令打开 SSH 配置文件,并添加 Cisco 设备支持的安全密钥交换。更新后,您的文件应该如下所示:(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
nano /home/pynetauto/.ssh/config``Update the ~/.ssh/config file for the correct security key exchange.``GNU nano 4.8
/home/pynetauto/.ssh/config``Host 192.168.183.10``KexAlgorithms +diffie-hellman-group1-sha1``Host 192.168.183.20``KexAlgorithms +diffie-hellman-group1-sha1``Host 192.168.183.101``KexAlgorithms +diffie-hellman-group1-sha1``Host 192.168.183.102``KexAlgorithms +diffie-hellman-group1-sha1``Host 192.168.183.153``KexAlgorithms +diffie-hellman-group1-sha1``Host 192.168.183.244``KexAlgorithms +diffie-hellman-group1-sha1``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
现在,尝试使用以下命令 SSH 到每台设备。确保您可以成功登录到每台设备。另外,R1
使用旧的 AES 类型的密钥交换,并且与 Ubuntu 服务器有问题,因此不在本实验中。对于 CML 或更新的 Cisco 设备,您还可以强制服务器使用diffie-hellman-group1-sha1
进行密钥交换。(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ssh pynetauto@192.168.183.10``(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ssh pynetauto@192.168.183.20``(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ssh pynetauto@192.168.183.101``(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ssh pynetauto@192.168.183.102``(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$``ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 pynetauto@192.168.183.153``(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 pynetauto@192.168.183.244
使用 SSH 登录每台路由器和交换机非常重要。在第一次提示时,接受新的 RSA 密钥指纹,以允许从 Python 服务器到每个设备的 SSH 连接。(ansible_venv) pynetauto@ubuntu20s1:~/venv1/ansible$
ssh pynetauto@192.168.183.244
主机‘192 . 168 . 183 . 244(192 . 168 . 183 . 244)’的真实性无法成立。RSA key fingerprint is sha 256:1 type 2 xgth HANA 9ee+nvbqemnnw 6 eha 5i/xygj 8 zu 1 P4。您确定要继续连接吗(是/否/[指纹])?是警告:已将“192 . 168 . 183 . 244”(RSA)永久添加到已知主机列表中。如果您在通过 SSH 登录时遇到问题,请打开 known_hosts 文件并清除条目,然后再次尝试 SSH 登录。(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
sudo nano /home/pynetauto/.ssh/known_hosts
|
| 14 | 现在,使用 Ansible 命令首次以交互方式登录到您的一个网络设备。对网络中的每台设备运行以下命令。以下示例是使用 Ansible ad hoc 命令登录到LAB-R1
(192.168.183.10)的 SSH。(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ansible all -i 192.168.183.10, -c network_cli -u pynetauto -k -m ios_facts -e ansible_network_os=ios``SSH password:********``[WARNING]: default value for
gather_subsetwill be changed to
minfrom
!config v2.11 onwards``192.168.183.10 | SUCCESS => {``"ansible_facts": {``"ansible_net_all_ipv4_addresses": [``"192.168.183.10",``"172.168.1.1"``],``"ansible_net_all_ipv6_addresses": [],``"ansible_net_api": "cliconf",``"ansible_net_filesystems": [``"flash0:"``[...omitted for brevity]``"ansible_net_iostype": "IOS",``"ansible_net_memfree_mb": 246866.65625,``"ansible_net_memtotal_mb": 314934.875,``"ansible_net_model": "IOSv",``"ansible_net_neighbors": {},``"ansible_net_python_version": "3.8.2",``"ansible_net_serialnum": "9M39W7I3ODBU6XCMNZ5GB",``"ansible_net_system": "ios",``"ansible_net_version": "15.6(2)T",``"ansible_network_resources": {},``"discovered_interpreter_python": "/usr/bin/python3"``},``"changed": false``}``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
|
| 15 | 我们可以去 Ansible 的官方网站获取有用的信息和技术文档。Red Hat 提供了大量关于如何开始使用 Ansible 以及如何使用 Ansible 实现网络自动化的文档。URL: https://docs.ansible.com/ansible/latest/network/getting_started/first_playbook.html
URL: https://docs.ansible.com/ansible/latest/modules/ios_facts_module.html
让我们转到第一个 playbook HTML URL,快速测试 Ansible 能为您做什么。一旦你上了网站,进入first_playbook.yml
,在你的virtualenv
中,用相同的名字创建一个新的 YAML 文件。将其修改为用 Cisco 替换 Vyatta,第一个剧本将类似于此。该行动手册将获取您设备的主机名和操作系统。(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
pwd``/home/pynetauto/venv1``(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
nano first_playbook.yml``GNU nano 4.8
first_playbook.yml``--- # indicates that this is a YAML file``- name: Network Getting Started First Playbook``connection: network_cli``gather_facts: false``hosts: all``tasks:``- name: Get config for Cisco devices``ios_facts:``gather_subset: all``- name: Display the config``debug:``msg: "The hostname is {{ ansible_net_hostname }} and the OS is {{ ansible_net_version }}"``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
来源: https://docs.ansible.com/ansible/latest/network/getting_started/first_playbook.html
|
| 16 | 按照文档运行first_playbook.yml
。下面的例子使用了LAB-R1
,但是您可以使用另一个设备的 IP 地址来运行ansible-playbook
命令。您现在已经完成了您的第一次ansible-playbook``(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ansible-playbook -i 192.168.183.10, -u pynetauto -k -e ansible_network_os=ios first_playbook.yml``SSH password: ********``PLAY [Network Getting Started First Playbook] **************************************``TASK [Get config for Cisco IOS devices] ********************************************``[WARNING]: default value for
gather_subsetwill be changed to
minfrom
!config v2.11 onwards``ok: [192.168.183.10]``TASK [Display the config] **********************************************************``ok: [192.168.183.10] => {``"msg": "The hostname is LAB-R1 and the OS is 15.6(2)T"``}``PLAY RECAP *************************************************************************``192.168.183.10 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
| 17 | 现在创建一个简单的show_clock.yml
来获取设备上的时间。由于我们不想重新发明轮子,我们将借用旧的 Ansible playbook 在您的设备上执行show
时钟。源代码位置在代码后引用。(ansible_venv) pynetauto@ubuntu20s1:~/venv1$
nano show_clock.yml``GNU nano 4.8
show_clock.yml``---``- hosts: all``gather_facts: no``connection: local``vars_prompt:``- name: "mgmt_username"``prompt: "Username"``private: no``- name: "mgmt_password"``prompt: "Password"``tasks:``- name: SYS | Define provider``set_fact:``provider:``host: "{{ inventory_hostname }}"``username: "{{ mgmt_username }}"``password: "{{ mgmt_password }}"``- name: IOS | Show clock``ios_command:``provider: "{{ provider }}"``commands:``- show clock``register: clock``- debug: msg="{{ clock.stdout }}"``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
来源: https://github.com/brona/ansible-cisco-ios-example/blob/master/show_clock.yml
|
| 18 | 现在要检查设备上的时间,运行下面的ansible-playbook
命令。请注意,这个虚拟环境不会影响安装在虚拟机上的实际主机 Linux 操作系统或其他软件。Ansible 将只在这个虚拟环境中工作,一旦ansible_venv
被停用,你将不再能够访问 Ansible,直到你重新激活ansible_venv
。以下是在lab-sw2
(192.168.183.102)上运行剧本的示例。(ansible_venv) pynetauto@ubuntu20s1:~/netautovenv$
ansible-playbook -i 192.168.183.102, -u pynetauto -k -e ansible_network_os=ios show_clock.yml``SSH password
:********``Username:
pynetauto``Password:
********``PLAY [show clock] ******************************************************************``TASK [SYS | Define provider] *******************************************************``ok: [192.168.183.102]``TASK [IOS | Show clock] ************************************************************``[WARNING]: provider is unnecessary when using network_cli and will be ignored``ok: [192.168.183.102]``TASK [debug] ***********************************************************************``ok: [192.168.183.102] => {``"msg": [``"*19:17:03.351 UTC Wed Jan 13 2021"``]``}``PLAY RECAP *************************************************************************``192.168.183.102 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
| 19 | 现在,停用测试 Python 虚拟环境,并检查您的本地服务器上是否安装了 Ansible。(ansible_venv) pynetauto@ubuntu20s1:~/venv1/ansible$
deactivate``pynetauto@ubuntu20s1:~/venv1/ansible$ python3``Python 3.8.2 (default, Jul 16 2020, 14:00:26)``[GCC 9.3.0] on linux``Type "help", "copyright", "credits" or "license" for more information.``>>> import ansible``Traceback (most recent call last):``File "<stdin>", line 1, in <module>``ModuleNotFoundError: No module named 'ansible'
|
你刚刚学会如何使用virtualenv
。您在 Python 虚拟环境中安装了 Ansible,并简要介绍了 Ansible 作为一种网络工具。Ansible 是一个很棒的配置管理工具,也是必备的工具集。但是,这不是一本关于 Ansible 或使用 Ansible 的网络自动化的书。这是本书中关于可行性讨论的结尾。如果你想了解更多关于 Ansible 的知识,你应该访问 Red Hat 的官方 Ansible 网站或者购买一本单独的 Ansible 书籍来扩展你的知识。
接下来,让我们在 Python virtualenv
中安装 pyATS,并找出 pyATS 与其他工具的不同之处。
pyATS (Genie)快速入门指南:虚拟实验室 2
pyATS 是思科系统内部支持的核心框架,最初是为思科内部工程团队开发的。它使用 Genie 库作为 pyATS 标准库,使用 XPRESSO 作为 pyATS web 仪表板。目前,pyATS 是思科测试自动化解决方案的核心。这个工具对于网络社区来说是一个相对较新的网络自动化工具。它更像是一个信息收集和网络系统监控工具,而不是像 Red Hat 的 Ansible 那样的真正的配置管理工具。
pyATS 更多的是一个辅助工具,而不是主要的自动化工具。一些受支持的功能包括:
-
思科为跨不同平台/功能的内部思科工程师提供的默认测试框架,运行数百万 CI/CD、健全性、回归、规模、HA、解决方案
-
被数百万工程师和开发人员使用
-
一个标准的开源、平台/厂商无关的库系统(Genie)
-
管理您的测试套件、测试平台、测试结果及其见解的 web 仪表板(XPRESSO)
-
以库、扩展、插件和 API 的形式集成支持其他集成工具
-
支持在基础设施堆栈之上构建客户的业务逻辑和解决方案
-
主要关注思科设备
以下是思科官方 pyATS 文档和入门指南的链接。如果您打算留在思科网络领域,那么 pyATS 和 Nonir 值得一读。但是如果你对你的网络自动化用例有更大的计划,那么坚持使用网络自动化工具,它更倾向于厂商中立。见图 16-2 。
网址: https://developer.cisco.com/docs/pyats/
(pyATS 简介)
网址: https://developer.cisco.com/docs/pyats-getting-started/
(pyATS 入门)
图 16-2。
pyATS virtualenv 实验室使用的设备
与之前的virtualenv
实验一样,您将创建一个新目录,并在新目录中运行virtualenv
。按照这些步骤开始你的新的virtualenv
吧。
|
工作
|
| — | — |
| 1 | 创建一个名为mygenie
的新目录,并将cd
放入新目录。进入目录后,创建一个新的虚拟环境来测试 pyATS。要创建并激活 pyATS 测试环境,请键入以下命令:pynetauto@ubuntu20s1:~/venv1$
mkdir mygenie``pynetauto@ubuntu20s1:~/venv1$
cd mygenie``pynetauto@ubuntu20s1:~/venv1/mygenie$
python3 -m venv genie_venv``pynetauto@ubuntu20s1:~/venv1/mygenie$
ls genie_venv``bin include lib lib64 pyvenv.cfg share``pynetauto@ubuntu20s1:~/venv1/mygenie$
source ./genie_venv/bin/activate``(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
|
| 2 | 用库安装 pyATS。您可能会收到一些与bdist_wheel-
相关的消息,但是您可以忽略这些消息并继续。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
pip3 install pyATS[library]
|
| 3 | 运行git clone
命令从 GitHub 下载示例。com 。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$``git clone``Cloning into 'examples'...``remote: Enumerating objects: 142, done.``remote: Counting objects: 100% (142/142), done.``remote: Compressing objects: 100% (103/103), done.``remote: Total 765 (delta 58), reused 94 (delta 32), pack-reused 623``Receiving objects: 100% (765/765), 1.03 MiB | 1.05 MiB/s, done.``Resolving deltas: 100% (385/385), done.
|
| 4 | 然后运行pyats run job
命令来检查 pyATS 的运行状态。如果basic_example_job.py
运行成功,那么您的安装是好的,您可以开始了。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
cd examples``(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie/examples$
pyats run job basic/basic_example_job.py
|
| 5 | 在继续之前,您必须为 Excel 安装额外的库,这是 pyATS 以后正常工作的先决条件。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie/examples$``cd``(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
pip3 install xlrd xlwt xlsxwriter
|
| 6 | Genie 使用 YAML testbed JSON 文件进行设备连接和认证。安装pyats.contrib
,这是一个要求。(genie) pynetauto@ubuntu20s:~/genie$
pip3 install pyats.contrib
|
| 7 | 接下来,创建一个用于认证的testbed.yml
文件。使用同样的pyats create
命令来帮助你创建一个testbed.yml
文件。以下示例为交换机 3 和交换机 4 创建了一个testbed.yml
文件:(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
genie create testbed interactive --output testbed.yml --encode-password``Start creating Testbed yaml file ...``Do all of the devices have the same username? [y/n] y``Common Username:
pynetauto``Do all of the devices have the same default password? [y/n] y``Common Default Password (leave blank if you want to enter on demand):``Do all of the devices have the same enable password? [y/n] y``Common Enable Password (leave blank if you want to enter on demand):``Device hostname:
Lab-sw3``IP (ip, or ip:port):
192.168.183.153
协议(ssh,Telnet,…):sshOS (iosxr, iosxe, ios, nxos, linux, ...):
iosxe``More devices to add ? [y/n]
y``Device hostname:
Lab-SW4``IP (ip, or ip:port):
192.168.183.244
协议(ssh,telnet,…):sshOS (iosxr, iosxe, ios, nxos, linux, ...):
iosxe``More devices to add ? [y/n]
n``Testbed file generated:``testbed.yml
注根据安装的 pyATS 版本,需要用pyats create testbed interactive --output testbed.yml --encode-password
命令替换之前的命令genie create testbed interactive --output testbed.yml --encode-password
。 |
| 8 | 快速查看新创建的testbed.yml
文件。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
cat testbed.yml``devices:``Lab-SW4:``connections:``cli:``ip: 192.168.183.244``protocol: ssh``credentials:``default:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``username: pynetauto``enable:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``os: iosxe``type: iosxe``Lab-sw3:``connections:``cli:``ip: 192.168.183.153``protocol: ssh``credentials:``default:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``username: pynetauto``enable:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``os: iosxe``type: iosxe
|
| 9 | 首先,在两个交换机上使用genie parse
命令,然后是show version
命令。您可以使用设备名称来运行命令,如下所示。以下示例显示了交换机 4 的结果。对于Lab-sw3
:(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
genie parse "show version" --testbed-file testbed.yml --devices Lab-sw3``0%| | 0/1 [00:00<?, ?it/s]{``"version": {``"chassis_sn": "93RJPGR4I08",``"compiled_by": "mmen",``"compiled_date": "Wed 22-Mar-17 08:38",``"curr_config_register": "0x101",``"hostname": "Lab-sw3",``"image_id": "vios_l2-ADVENTERPRISEK9-M",``"image_type": "developer image",``"last_reload_reason": "Unknown reason",``"mem_size": {``"non-volatile configuration": "256"``},``"number_of_intfs": {``"Gigabit Ethernet": "16",``"Virtual Ethernet": "1"``},``"os": "IOS",``"platform": "vios_l2",``"processor_board_flash": "0K",``"returned_to_rom_by": "reload",``"rom": "Bootstrap program is IOSv",``"system_image": "flash0:/vios_l2-adventerprisek9-m",``"uptime": "1 hour, 47 minutes",``"version": "15.2(20170321:233949)",``"version_short": "15.2"``}``}``100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:01<00:00, 1.17s/it]
对于Lab-SW4
:(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
genie parse "show version" --testbed-file testbed.yml --devices Lab-SW4``0%| | 0/1 [00:00<?, ?it/s]{``"version": {``"chassis_sn": "9B66XQMVHID",``"compiled_by": "mmen",``"compiled_date": "Wed 22-Mar-17 08:38",``"curr_config_register": "0x101",``"hostname": "Lab-SW4",``"image_id": "vios_l2-ADVENTERPRISEK9-M",``"image_type": "developer image",``"last_reload_reason": "Unknown reason",``"mem_size": {``"non-volatile configuration": "256"``},``"number_of_intfs": {``"Gigabit Ethernet": "16",``"Virtual Ethernet": "1"``},``"os": "IOS",``"platform": "vios_l2",``"processor_board_flash": "0K",``"returned_to_rom_by": "reload",``"rom": "Bootstrap program is IOSv",``"system_image": "flash0:/vios_l2-adventerprisek9-m",``"uptime": "1 hour, 51 minutes",``"version": "15.2(20170321:233949)",``"version_short": "15.2"``}``}``100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1.38it/s]
|
| 10 | 使用以下genie parse
命令检索您的设备信息:(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
genie parse "show ip int brief" --testbed-file testbed.yml --devices Lab-sw3``0%| | 0/1 [00:00<?, ?it/s]{``"interface": {``"GigabitEthernet0/0": {``"interface_is_ok": "YES",``"ip_address": "unassigned",``"method": "unset",``"protocol": "up",``"status": "up"``},``[...omitted for brevity]``"Vlan1": {``"interface_is_ok": "YES",``"ip_address": "192.168.183.153",``"method": "NVRAM",``"protocol": "up",``"status": "up"``}``}``}``100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1.08it/s]
|
| 11 | 在 nano 文本编辑器中修改testbed.yml
以包含所有 CML 路由器和交换机。让SW3
和SW4
使用 Telnet,并配置其他人使用 SSH 进行测试。或者,您可以重新运行pyats create testbed interactive --output testbed.yml --encode-password
命令来重新创建该文件。你可以从本章的下载页面找到testbed.yml
文件。由于页面限制,部分内容被省略。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
pyats create testbed interactive --output testbed.yml --encode-password``(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
cat testbed.yml``devices:``LAB-R1:``connections:``cli:``ip: 192.168.183.10``protocol: ssh``credentials:``default:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``username: pynetauto``enable:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``os: iosxe``type: iosxe``[... omitted for brevity]``lab-sw2:``connections:``cli:``ip: 192.168.183.102``protocol: ssh``credentials:``default:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``username: pynetauto``enable:``password: '%ENC{w5PDosOUw5fDosKQwpbCmA==}'``os: iosxe``type: iosxe
|
| 12 | 使用以下命令测试 Genie:(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
genie parse "show clock" --testbed-file testbed.yml --devices[hostname]``0%| | 0/1 [00:00<?, ?it/s]{``"day": "13",``"day_of_week": "Wed",``"month": "Jan",``"time": "20:02:59.396",``"timezone": "UTC",``"year": "2021"``}``100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1.50it/s]``[...omitted for brevity]``0%| | 0/1 [00:00<?, ?it/s]{``"day": "13",``"day_of_week": "Wed",``"month": "Jan",``"time": "19:59:35.229",``"timezone": "UTC",``"year": "2021"``}
现在更改show
命令,并在多个设备上运行show cdp neigh
命令。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
genie parse "show cdp neigh" --testbed-file testbed.yml --devices[hostname]``0%| | 0/1 [00:00<?, ?it/s]Parsed command 'show cdp neigh' but it returned empty``100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 1.28it/s]``0%| | 0/1 [00:00<?, ?it/s]{``"cdp": {``"index": {``"1": {``"capability": "S I",``"device_id": "Lab-sw3.pynetauto.local",``"hold_time": 141,``"local_interface": "GigabitEthernet0/0",``"platform": "Gig",``"port_id": "0/0"``},``"2": {``"capability": "S I",``"device_id": "Lab-SW4.pynetauto.local",``"hold_time": 146,``"local_interface": "GigabitEthernet0/0",``"platform": "Gig",``"port_id": "0/0"``},``[...omitted for brevity]
如果连接的接口禁用了 CDP,结果将返回错误:Parsed command 'show cdp neigh' but it returned empty
。您可能需要在受影响的接口上启用cdp enable
命令。 |
| 13 | 让我们快速安装将在本实验中使用的pandas
库。如果已经安装了pandas
,可以跳过这一步。让我们安装pandas
模块进行数据分析,并将数据存储到 Excel 中。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
pip3 install pandas
|
| 14 | pyATS 是思科及其内部工程团队认可的优秀工具,但它的真正威力在哪里?如果 pyATS 的数据收集能力与 Python 的数据处理能力相结合,这将使我们能够轻松地收集和存储数据。下面是 pyATS 与 Python re
(正则表达式模块)在交互式会话中结合的一个例子。您可以将 pyATS 与 Python 正则表达式结合使用,并将任何值用作变量或将它们存储到 Excel 文件中。这是一个交互式的例子,您可以跟着做,但是您输入的内容也可以保存为扩展名为.py
的脚本文件。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
python3``Python 3.8.2 (default, Jul 16 2020, 14:00:26)``[GCC 9.3.0] on linux``Type "help", "copyright", "credits" or "license" for more information.``>>>
import os``>>>
show_ver = os.popen('genie parse "show version" --testbed-file testbed.yml --devices[hostname]')``100%|████████████████████ 1/1 [00:00<00:00, 1.54it/s]``100%|████████████████████| 1/1 [00:00<00:00, 2.84it/s]``100%|████████████████████| 1/1 [00:00<00:00, 2.80it/s]``100%|████████████████████| 1/1 [00:00<00:00, 2.07it/s]``100%|████████████████████| 1/1 [00:00<00:00, 3.48it/s]``100%|████████████████████| 1/1 [00:00<00:00, 2.41it/s]``# Press [Enter] key once.``>>>
output = show_ver.read()``>>>
print(output)``{``"version": {``"chassis": "IOSv",``"chassis_sn": "9M39W7I3ODBU6XCMNZ5GB",``"compiled_by": "prod_rel_team",``"compiled_date": "Tue 22-Mar-16 16:19",``"curr_config_register": "0x0",``"hostname": "LAB-R1",``"image_id": "VIOS-ADVENTERPRISEK9-M",``"image_type": "production image",``"last_reload_reason": "Unknown reason",``"main_mem": "460017",``"mem_size": {``"non-volatile configuration": "256"``},``[...omitted for brevity]``{``"version": {``"chassis_sn": "93GM5T0AAL2",``"compiled_by": "mmen",``"compiled_date": "Wed 22-Mar-17 08:38",``"curr_config_register": "0x101",``"hostname": "lab-sw2",``"image_id": "vios_l2-ADVENTERPRISEK9-M",``"image_type": "developer image",``"last_reload_reason": "Unknown reason",``"mem_size": {``"non-volatile configuration": "256"
},"number_of_intfs": {``"Gigabit Ethernet": "16",``"Virtual Ethernet": "1"``},``"os": "IOS",``"platform": "vios_l2",``"processor_board_flash": "0K",``"returned_to_rom_by": "reload",``"rom": "Bootstrap program is IOSv",``"system_image": "flash0:/vios_l2-adventerprisek9-m",``"uptime": "2 hours, 25 minutes",``"version": "15.2(20170321:233949)",``"version_short": "15.2"``}``}
|
| 15 | 正如在交互式会话中所确认的,输出数据类型是保存到变量 output 的字符串类型。>>>
type(output)``<class 'str'>
导入re
模块,使用你在第五章中学到的一个很酷的正则表达式来捕捉你想要的特定信息。这里,示例使用了 lookahead ( ?=
)和 look ahead(?<=
)正则表达式示例。在下面的代码中,使用正向后视和正向前视从上一步的输出中获取每个设备的主机名:>>>
import re``>>>
p1 = re.compile(r'(?<=\"hostname\": \").+(?=\")')``>>>
m1 = p1.findall(output)``>>>
m1``['LAB-R1', 'LAB-SW1', 'Lab-SW4', 'Lab-sw3', 'lab-r2', 'lab-sw2']
在下面几行中,再次使用 lookbehind 和 lookahead 从输出中获取每个设备的正常运行时间:>>>
p2 = re.compile(r'(?<=\"uptime\": \").+(?=\")')``>>>
m2 = p2.findall(output)``>>>
m2``['6 hours, 52 minutes', '6 hours, 52 minutes', '12 hours, 14 minutes', '10 hours, 13 minutes', '12 hours, 12 minutes', '10 hours, 39 minutes']
我们希望使用 Python 将正常运行时间信息转换成条形图,因此您必须首先将小时和分钟转换成正确的十进制格式。看一下下面的转换,把时间转换成两位小数。只有当您的所有设备都已经启动并运行了一个多小时,以下 Python 代码才会起作用,因此正常运行时间的输出是“x 小时 y 分钟”>>> uptime = []
# create empty list called uptime``>>>``for x in m2:``...``y = [int(s) for s in x.split() if s.isdigit()]``...``z = (y[1]/60)``...``a = round(y[0] + z, 2)``...``uptime.append(a)``...``>>>``print(uptime)``[6.87, 6.87, 12.23, 10.22, 12.2, 10.65]
|
| 如果您设备的正常运行时间少于 60 分钟,请使用以下 Python 代码:
>>> uptime = []``>>> for x in m2
:``... y = [int(s) for s in x.split() if s.isdigit()]``... z = (y[0]/60)``... a = round(z, 2)``... uptime.append(a)``...``>>> print(uptime)``[0.25, 0.22, 0.24, 0.19, 0.18, 0.21]
|
| Fifteen | 现在,使用 dictionary zip
特性将这两个列表转换成一个 Python 字典。我们需要使用dict(zip(m1, uptime))
函数将十进制的设备名称列表和正常运行时间列表结合起来。结果应该如下所示:>>>``device_uptime = dict(zip(m1,uptime))``>>>``print(device_uptime)``{'LAB-R1': 6.87, 'LAB-SW1': 6.87, 'Lab-SW4': 12.23, 'Lab-sw3': 10.22, 'lab-r2': 12.2, 'lab-sw2': 10.65}
让我们使用pandas
模块将字典转换成pandas
数据帧,并保存为 Excel 电子表格,以供报告之用。在将字典转换成pandas
数据帧时,您将添加头:host
表示设备名,uptime
表示运行时间。Python 网络自动化的关键成功因素是如何处理和处理这些关键数据,以满足您和您公司的需求。>>>``type(device_uptime)``<class 'dict'>``>>>``import pandas as pd``>>>``df = pd.DataFrame(list(device_uptime.items()),columns = ['host','uptime'])``>>>``df``host uptime``0 LAB-R1 6.87``1 LAB-SW1 6.87``2 Lab-SW4 12.23``3 Lab-sw3 10.22``4 lab-r2 12.20``5 lab-sw2 10.65``>>>``df.to_excel('device_uptime.xlsx')
|
| 16 | 检查文件是否存在于mygenie
目录中。使用 WinSCP 通过 SCP 登录到ubuntu20s1
服务器,将device_uptime.xlsx
文件的副本下载到您的 Windows 主机 PC 上,并在 Excel 中打开它,以确认数据已经以 Excel 格式正确保存。参见图 16-3 和图 16-4 。pynetauto@ubuntu20s1:~$
cd venv1``pynetauto@ubuntu20s1:~/venv1$
cd mygenie``pynetauto@ubuntu20s1:~/venv1/mygenie$
ls``device_uptime.xlsx examples genie_venv testbed.yml
图 16-3。WinSCP,从 ubuntu20s1 服务器检索 device_uptime.xlsx
图 16-4。主机,在 Excel 中打开 device_uptime.xlsx |
| 17 | 现在,在您的 Windows 主机 PC(或 Ubuntu 服务器的桌面)上,您可以编写一些简单的 Python 代码来转换步骤 15 中的字典,并将其转换为图形。您也可以使用pandas
读取 Excel 文件,并将其转换成数据帧,以达到相同的结果。(这对于 Linux 命令行来说效果不好,因为它没有直接的图形支持。)在从 Windows 主机的命令提示符下编写脚本之前,使用下面显示的pip3
命令安装pandas
和matplotlib
:C:\Users\brendan>
cd Desktop``C:\Users\brendan\Desktop>
pip3 install pandas``C:\Users\brendan\Desktop>
pip3 install matplotlib
编写以下代码,并将脚本保存在 Windows 桌面上。在你的 Windows 桌面上创建一个 Python 文件,另存为device_uptime_graph.py
。import matplotlib.pyplot as plt
# import matplotlib library``import pandas as pd
# import pandas library``device_uptime = {'LAB-R1': 6.87, 'LAB-SW1': 6.87, 'Lab-SW4': 12.23, 'Lab-sw3': 10.22, 'lab-r2': 12.2, 'lab-sw2': 10.65}``# Resulting dictionary from step 15``df = pd.DataFrame(list(device_uptime.items()),columns = ['host','uptime'])
# Convert dictionary into pandas dataframe with column titles 'host' & 'uptime'``#print(df)``df.plot(kind='bar',x='host',y='uptime')
# Plot a bar graph with host as x-axis and uptime(float) as y-axis``plt.show()
# Display graph
现在,从 PowerShell 或 Windows 命令提示符运行该脚本,或者双击该脚本。如果一切正常,您的数据将显示为条形图,如下所示。或者,您可以将它们转换为不同类型的图形,并应用相同的方法将数据转换为数据帧,并根据需要创建图形。您可以从 Excel 中创建相同的图表,但是您现在知道了使用 Python 创建用于报告的图表的另一种方法。见图 16-5 。C:\Users\brendan\Desktop>``python device_uptime_graph.py
图 16-5。设备正常运行时间示例的 matplotlib 图 |
| 18 | 在本实验结束时,确保使用deactivate
命令并停止虚拟环境。(genie_venv) pynetauto@ubuntu20s1:~/venv1/mygenie$
deactivate``pynetauto@ubuntu20s1:~/venv1/mygenie$
|
已经向您介绍了思科自己的 pyATS,以帮助您实现网络自动化之旅。您已经使用 Python 的 re 模块收集设备的系统正常运行时间数据,然后使用pandas
模块将数据转换成 pandas 数据帧。作为最后一步,您已经使用matplotlib
库将数据转换成一个简单的条形图。将数据转换为图形的任务称为数据可视化。
如果您想从 Ubuntu Server 的桌面运行之前的实验,请先尝试安装 VMware Workstation 工具,以便在您的 Windows 主机 PC 和 Linux 虚拟机之间进行复制和粘贴。遵循以下 URL 中的说明,并使用“方法 2:从 VMware 主机安装”
使用导入的 Docker 映像的 Sendmail Lab
Docker 是一个面向开发者和系统管理员的分布式应用开放平台,它的座右铭是“构建、发布、运行”这是一个可扩展的容器管理服务。它是另一种虚拟技术,可以帮助您成功地将 Linux 网络自动化服务器部署到生产环境中,并且只利用服务器资源的一小部分。Docker 于 2013 年 3 月首次推出,是一款基于平台即服务软件应用的容器。Docker 使用虚拟化技术为软件和工具提供隔离的容器。这些容器使用定义明确的通道在主机和 Docker 容器之间进行通信。它还提供了一个框架来隔离一个应用及其对称为容器的自包含单元的依赖。Docker 容器类似于虚拟机,但 Docker 是一个没有内核和操作系统的虚拟机,容器必须依赖托管服务器的内核进行操作。容器化已经成为 IT 界的流行语,因为公司可以用更少的服务器资源做更多的事情,特别是在敏捷和基于 DevOps 的项目中。换句话说,通过 Kubernetes 编排的 Docker 解决方案可以通过提高 IT 资源的利用率和降低每个节点的运营成本,为公司节省成百上千美元。
Docker 的著名口号是“开发,运输,在任何地方运行。”Docker 是一个开发人员的工具,可以帮助他们快速开发应用,并将其装入容器中,然后可以部署在网络中的任何地方。Docker 占用空间很小,因为它依赖于主机服务器的本地资源,并且它不是真正意义上的虚拟机,因为它没有自己的操作系统。Docker 容器映像只是快照映像的可运行实例或进程。您可以创建、启动、停止、移动或删除容器。尽管容器没有自己的操作系统,但它运行时有自己的文件系统、自己的网络和自己的独立于主机的进程树。对于不同部门的团队来说,比如开发、QA 和操作,使用容器在应用之间无缝地工作变得更加容易。您可以在任何地方部署 Docker 容器,在任何物理或虚拟机上,甚至在云上。
如果我们必须用不到 20 页的篇幅来介绍 Docker,那么最好用真实的图像来弄脏你的手。除了这本书之外,你可以按照自己的进度了解 Docker。在这里,为了加快您的进度,您将从 Docker Hub 下载一个预留的 Python 网络自动化 Docker 映像,并使用下载的映像来测试 Python 实验室,就像您一直在使用ubuntu20s1
和centos8s1
服务器一样。见图 16-6 。
图 16-6。
Sendmail Docker 实验室,二手设备
Docker 组件和帐户注册
Docker 就像一个专门构建的虚拟机,没有携带完整操作系统的负担,因为它依赖于主机的 Linux 内核来运行,占用空间很小。您可以在一个平台上运行许多 Docker 容器,而不会有系统资源争用的问题。让我们快速回顾一下 Docker 解决方案由哪些组件组成;我们不会涉及太多的细节,因为我们想把重点放在 Docker 的实用方面。
Docker 有以下组件:
-
Docker 有跨平台支持;它可以作为服务安装在 Linux、macOS 和 Windows 上。
-
Docker 引擎用于构建 Docker 映像和创建 Docker 容器。
-
Docker Hub 是用于托管各种 Docker 映像的注册表。
-
Docker Compose 用于定义使用多个 Docker 容器的应用。
需要注册 Docker 帐户才能在线使用 Docker 的全部功能。注册后,我们可以轻松访问数百个专门构建的 Docker 图像,并立即启动和运行我们的应用。要从 Docker Hub 登录并下载 Docker 映像,您需要一个 Docker Hub 帐户。为了让您为本次实验做好准备,请访问以下 URL 并注册您的 Docker Hub 帐户。这是重要的一步,因为您将需要您的帐户遵循这一部分。
URL: https://hub.docker.com/
一旦你完成了注册,并有一些空闲时间阅读,请访问 Docker Hub 页面,熟悉 Docker 的工作方式。
URL: https://www.docker.com/get-started
码头设备
对于本实验,所有任务都将从 Ubuntu 服务器执行。按照以下步骤在您的ubuntu20s1
服务器上安装 Docker。幸运的是,Docker 在 Ubuntu 上的安装非常容易,不到 15 分钟你就可以启动并运行了。
|
工作
|
| — | — |
| 1 | 像往常一样,让我们从一个apt update
命令开始,升级你的 Ubuntu 20 LTS 服务器。如果您有一段时间没有更新您的服务器软件包,这将需要几分钟的时间。pynetauto@ubuntu20s1:~$
sudo apt update``[sudo] password for pynetauto: ***********``pynetauto@ubuntu20s1:~$
sudo apt upgrade -y
|
| 2 | 输入以下命令下载并安装 Docker 包:pynetauto@ubuntus20s1:~$
sudo apt install docker.io -y
|
| 3 | 要启用 Docker,输入enable
命令,并使用docker –version
命令检查安装的版本。pynetauto@ubuntus20s1:~$
sudo systemctl enable --now Docker``pynetauto@ubuntu20s1:~$
sudo systemctl status docker``pynetauto@ubuntu20s1:~$
sudo docker --version``Docker version 19.03.8, build afacb8b7f0
|
| 4 | 将您自己添加到 Docker 组来运行sudo
命令。用您的用户 ID 替换pynetauto
。运行最后一个命令以使更改生效。pynetauto@ubuntus20s1:~$
sudo usermod -aG docker pynetauto``pynetauto@ubuntu20s1:~$
sudo gpasswd -a $USER docker``pynetauto@ubuntu20s1:~$ newgrp docker
|
| 5 | 通过运行hello-world
命令来测试 Docker,这将打开一个容器来运行hello-world
命令。pynetauto@ubuntu20s1:~$
docker run hello-world``Unable to find image 'hello-world:latest' locally``latest: Pulling from library/hello-world``0e03bdcc26d7: Pull complete``Digest: sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc``Status: Downloaded newer image for hello-world:latest``Hello from Docker!``This message shows that your installation appears to be working correctly.``[... omitted for brevity]``For more examples and ideas, visit:``https://docs.docker.com/get-started/
|
| 6 | 运行docker ps –a
检查hello-world
命令是否成功运行。此外,运行docker images
命令来检查下载的 Docker 图像。pynetauto@ubuntu20s1:~$
docker ps -a``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``ef48902f0801 hello-world "/hello" 2 minutes ago Exited (0) 2 minutes ago modest_goldberg``pynetauto@ubuntu20s1:~$
docker images``REPOSITORY TAG IMAGE ID CREATED SIZE``hello-world latest bf756fb1ae65 8 months ago 13.3kB
|
试驾码头
为了节省时间,本书的读者已经使用 Dockerfile 方法创建了一个 Python 网络自动化 Docker 映像。您将要下载到您的 Ubuntu20s1 服务器的映像也是一个 Ubuntu 20.04 LTS 服务器,在 Docker 映像中预装了许多 IP 服务和网络自动化库。从 Docker 文件创建模板 Docker 图像涉及几个步骤,但我们不会在本书中详细讨论,因为这是另一个可能需要单独一章讨论的主题。请去 YouTube 定位观看 Docker 基础训练。在本实验中,您将下载预安装的 Python network automation Docker 映像,并从您的 Ubuntu 服务器上运行它。
||
工作
|
| — | — |
| 6 | 检查 Docker 镜像和安装在pynetauto_ubuntu20
Docker 容器上的 Python 版本。大多数命令与 Linux 标准命令相同,应该像另一台 Linux 机器一样运行。但是,Docker 容器实例中没有基本服务或systemctl
或任何 Linux 标准软件;换句话说,在 Docker 映像构建过程中,您必须精确地指定您想要安装的 Linux 软件。如果您想在现有 Docker 映像上安装更多软件,您可以进行修改。然而,Docker 基本图像修改主题超出了本书的范围。root@2f81da41426b:/#
cat /etc/*release``DISTRIB_ID=Ubuntu``DISTRIB_RELEASE=20.04``DISTRIB_CODENAME=focal``DISTRIB_DESCRIPTION="Ubuntu 20.04 LTS"``NAME="Ubuntu"``VERSION="20.04 LTS (Focal Fossa)"``ID=ubuntu``ID_LIKE=debian``PRETTY_NAME="Ubuntu 20.04 LTS"``VERSION_ID="20.04"``HOME_URL="https://www.ubuntu.com/"``SUPPORT_URL="https://help.ubuntu.com/"``BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"``PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"``VERSION_CODENAME=focal``UBUNTU_CODENAME=focal``root@2f81da41426b:/#
python3 --version``Python 3.8.2
|
| 7 | 在 Docker bash shell 上创建一个testfile999.txt
文件。root@2f81da41426b:/#
touch home/testfile999.txt
|
| 8 | 分离并检查主机上新创建的文件。使用 Ctrl+P 和 Ctrl+Q 从 Docker 容器中分离。这将使您回到 Linux 主机操作系统。检查/home/pynetauto/mnt
文件夹。您应该在 Linux 服务器的目录中找到testfile999.txt
文件。pynetauto@ubuntu20s1:~$
pwd``/home/pynetauto``pynetauto@ubuntu20s1:~$
ls /home/pynetauto/mnt``testfil999.txt
|
| 9 | 现在,从您的 Linux 主机,在共享目录下创建一个新文件。然后,重新附加到 Docker 实例,从 Docker 容器实例中检查文件。当您重新附加到正在运行的 Docker 实例时,您可以使用容器 ID 或名称。pynetauto@ubuntu20s1:~$
touch /home/pynetauto/mnt/testfile123.txt``pynetauto@ubuntu20s1:~$
docker ps``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``2f81da41426b pynetauto/pynetauto_ubuntu20 "/bin/bash" 6 minutes ago Up 6 minutes20-22/tcp, 25/tcp, 12020-12025/tcp pynetauto_ubuntu20``pynetauto@ubuntu20s1:~$
docker attach pynetauto_ubuntu20``root@2f81da41426b:/#
ls``bin boot dev etc ftp home lib lib32 lib64 libx32 media mnt opt proc pynetauto root run sbin srv sys tmp usr var``root@2f81da41426b:/#
ls /home/``testfile999.txt testfile123.txt
|
| 10 | 要停止并退出 Docker,请按 Ctrl+D 或键入exit
。这将停止并退出当前登录的 Docker 容器。root@2f81da41426b:/#
exit``pynetauto@ubuntu20s1:~$
docker ps -a``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``2f81da41426b pynetauto/pynetauto_ubuntu20 "/bin/bash" 9 minutes ago Exited (130) 11 seconds ago pynetauto_ubuntu20``ef48902f0801 hello-world "/hello" 42 minutes ago Exited (0) 42 minutes ago modest_goldberg
|
| 11 | 要重新启动一个停止的 Docker 实例,可以使用docker start instance_name
命令。pynetauto@ubuntu20s1:~$
docker start pynetauto_ubuntu20``pynetauto_ubuntu20``pynetauto@ubuntu20s1:~$
docker ps -a``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``2f81da41426b pynetauto/pynetauto_ubuntu20 "/bin/bash" 11 minutes ago Up 4 seconds 20-22/tcp, 25/tcp, 12020-12025/tcp pynetauto_ubuntu20``ef48902f0801 hello-world "/hello" 44 minutes ago Exited (0) 44 minutes agomodest_goldberg
|
| 12 | 要删除不运行的 Docker 实例并保持环境整洁,请运行docker system prune
命令。pynetauto@ubuntu20s1:~$
docker system prune``WARNING! This will remove:``- all stopped containers``- all networks not used by at least one container``- all dangling images``- all dangling build cache``Are you sure you want to continue? [y/N]
Y``Deleted Containers:``ef48902f0801b692e963c317873075d0152a3c09fd992f8b2f5e3f8d55b71f01``Total reclaimed space: 0B``pynetauto@ubuntu20s1:~$
docker ps -a``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``2f81da41426b pynetauto/pynetauto_ubuntu20 "/bin/bash" 13 minutes ago Up 2 minutes 20-22/tcp, 25/tcp, 12020-12025/tcp pynetauto_ubuntu20
|
| 13 | 要删除 Docker 图像,请使用docker rmi image_name:version
。pynetauto@ubuntu20s1:~$
docker images``REPOSITORY TAG IMAGE ID CREATED SIZE``pynetauto/pynetauto_ubuntu20 latest 39ea52cc1e39 12 days ago 1.69GB``hello-world latest bf756fb1ae65 8 months ago 13.3kB``pynetauto@ubuntu20s1:~$
docker rmi hello-world:latest``Untagged: hello-world:latest``Untagged: hello-world@sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc``Deleted: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b``Deleted: sha256:9c27e219663c25e0f28493790cc0b88bc973ba3b1686355f221c38a36978ac63``pynetauto@ubuntu20s1:~$
docker images``REPOSITORY TAG IMAGE ID CREATED SIZE``pynetauto/pynetauto_ubuntu20 latest 39ea52cc1e39 12 days ago 1.69GB
|
-
如需完整的 Docker 运行参考,请访问以下网站。
URL:
https://docs.docker.com/engine/reference/run/
表 16-1。
前面命令的分解和解释
|命令中断
|
解释
|
| — | — |
| docker run -it --entrypoint=/bin/bash
| 使用–i
和–t
选项运行 Docker,其中入口点是 bash shell。-i
表示交互模式,而–t
表示分配一个pseudo-tty
。 |
| -v /home/pynetauto/mnt/:/home
| -v
与一个卷(共享文件系统)相关;在这种情况下,unbuntu20s1
的/home/pynetauto/mnt
目录链接到新 Docker 容器的/home
目录。 |
| --name pynetauto_ubuntu20
| --name
选项允许您给容器的这个实例起一个更有意义的名字。如果不使用–name
,那么 Docker 会自动分配一个随机名称。 |
| pynetauto/pynetauto_ubuntu20
| 这是本地映像池中的实际 Docker 映像名称。 |
|
工作
|
| — | — |
| 1 | 创建 Docker Hub ID 后,请访问以下 Docker Hub 网站查看您将下载的图像。URL: https://hub.docker.com/u/pynetauto
|
| 2 | 在 Linux 命令行中,使用docker login
登录 Docker Hub。用您的用户名替换用户名,并输入您的密码登录。您需要在底部得到一个“登录成功”的消息。当您创建帐户并登录 Docker Hub 时,您可以将 Docker 映像推送到 Docker Hub,将映像保存为模板,并与社区中的其他人共享。pynetauto@ubuntu20s1:~$
docker login
使用您的 Docker ID 登录,从 Docker Hub 推送和提取图像。如果你没有 Docker ID,请访问 https:// hub。码头工人。com 来创建一个。Username:
pynetauto``Password: ***********``WARNING! Your password will be stored unencrypted in /home/pynetauto/.docker/config.json.``Configure a csredential helper to remove this warning. See``https://docs.docker.com/engine/reference/commandline/login/#credentials-store``Login Succeeded
|
| 3 | 使用下面的docker pull
命令将该映像下载到虚拟机的 Docker 映像库。建议您通过家庭网络连接到互联网,而不是 4G 或 5G 网络,因为这将耗尽您的移动数据。pynetauto@ubuntu20s1:~$
docker pull pynetauto/pynetauto_ubuntu20:latest``latest: Pulling from pynetauto/pynetauto_ubuntu20``d51af753c3d3: Pull complete``[... omitted for brevity]``f92a9a96eae3: Pull complete``Digest: sha256:86f2178825cf09a1b7f7c370a460b34b109f62ce4471d944ef108d0a29162ed4``Status: Downloaded newer image for pynetauto/pynetauto_ubuntu20:latest``docker.io/pynetauto/pynetauto_ubuntu20:latest
|
| 4 | 使用docker images
命令,检查具有正确细节的新图像,如下所示:pynetauto@ubuntu20s1:~$
docker images``REPOSITORY TAG IMAGE ID CREATED SIZE``pynetauto/pynetauto_ubuntu20 latest 39ea52cc1e39 12 days ago 1.69GB``hello-world latest bf756fb1ae65 8 months ago 13.3kB
|
| 5 | 要使用容器的本地服务器目录挂载点在 bash shell 中启动 Docker 容器实例,请使用这里显示的命令。命令解释参见表 16-1 。pynetauto@ubuntu20s1:~$
docker run -it --entrypoint=/bin/bash -v /home/pynetauto/mnt/:/home --name pynetauto_ubuntu20 pynetauto/pynetauto_ubuntu20``root@2f81da41426b:/#
|
Sendmail Python Lab 停靠器
现在我们已经熟悉了 Docker,让我们快速了解如何从 Docker 中获益并运行我们的 Python 脚本。在本实验中,您将使用您的pynetauto/pynetauto_ubuntu20:latest
Docker 容器上预装的 Sendmail,并从您的 Python 脚本发送一封测试电子邮件。然后,将指导您编写一个 Python 脚本来监控拓扑中某个设备的 CPU 利用率。当利用率超过黄色水印时,您的 Python 脚本将向您的电子邮件收件箱触发电子邮件警报。首先,让我们在您的 Docker 映像上设置 Sendmail。为了让 Sendmail 在 Docker 容器上工作,需要预先安装和配置 Sendmail,并且必须为 SMTP 打开端口 25。此外,要接收测试电子邮件,您的电子邮件帐户的安全性必须降低。在本例中,Gmail 帐户用于演示目的,您可以对您的测试 Gmail 帐户进行同样的操作。
|
工作
|
| — | — |
| 1 | 如果您遵循了前面步骤中的 Docker 安装过程,那么您应该拥有名为pynetauto_ubuntu20
的 Docker 映像。pynetauto@ubuntu20s1:~$
docker images``REPOSITORY TAG IMAGE ID CREATED SIZE``pynetauto/pynetauto_ubuntu20 latest 39ea52cc1e39 2 weeks ago 1.69GB
|
| 2 | 现在,让我们通过重新运行以下命令来启动一个新的 Docker 实例。如果您已经有一个现有的实例,那么您可以启动该实例并将其附加到 Docker 实例。在本例中,我们将启动一个新实例。如果您的 Docker 实例启动了,您将登录到新 Docker 实例的 bash shell 中,并且应该准备好了。pynetauto@ubuntu20s1:~$
docker run -it --entrypoint=/bin/bash -v /home/pynetauto/mnt/:/home --name pynetauto_ubuntu20sendmail pynetauto/pynetauto_ubuntu20``root@062fc2a30243:/#
|
| 3 | 首先,让我们检查端口 25 是否处于监听模式。使用netstat –tuna
命令检查打开的端口。没有返回任何结果,所以看起来您必须配置 Sendmail 并在这个 Docker 实例或机器上允许端口 25。另外,通过快速运行apt install Sendmail
来检查 Sendmail 的安装状态;应该已经安装了。root@062fc2a30243:/#
netstat -tuna``Active Internet connections (servers and established)``Proto Recv-Q Send-Q Local Address Foreign Address State``root@062fc2a30243:/#
apt install sendmail``Reading package lists... Done``Building dependency tree``Reading state information... Done``sendmail is already the newest version (8.15.2-18).``0 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.
|
| 4 | 现在让我们在 Docker 容器实例上快速配置 Sendmail。运行sendmailconfig
命令。当提示输入 Y 时,按 Y,然后按三次回车键。在第二个 Y 之后,文件更新可能需要一点时间,所以请耐心完成并重新加载 Sendmail 服务。root@062fc2a30243:/#
sendmailconfig``Configure sendmail with the existing /etc/mail/sendmail.conf? [Y] Y``Reading configuration from /etc/mail/sendmail.conf.``Validating configuration.``Writing configuration to /etc/mail/sendmail.conf.``Writing /etc/cron.d/sendmail.``Configure sendmail with the existing /etc/mail/sendmail.mc? [Y] Y <<< may take a few minutes``Updating sendmail environment ...``Reading configuration from /etc/mail/sendmail.conf.``[... omitted for brevity]``Updating /etc/mail/aliases...``WARNING: local host name (062fc2a30243) is not qualified; see cf/README: WHO AM I?``/etc/mail/aliases: 0 aliases, longest 0 bytes, 0 bytes total``Reload the running sendmail now with the new configuration? [Y] Y``Reloading sendmail ...
|
| 5 | 使用以下命令检查是否已经在 Docker 上成功创建了与 Sendmail 相关的目录:root@062fc2a30243:/#``ls /usr/sbin/send``/usr/sbin/sendmail /usr/sbin/sendmail-msp /usr/sbin/sendmail-mta /usr/sbin/sendmailconfig
|
| 6 | 再次运行netstat –tuna
命令,确认端口 25 处于监听模式。root@062fc2a30243:/#
netstat -tuna``Active Internet connections (servers and established)``Proto Recv-Q Send-Q Local Address Foreign Address State``tcp 0 0 127.0.0.1:587 0.0.0.0:* LISTEN``tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
|
| 7 | 从您的 Windows 主机连接到互联网。接下来,进入你的测试 Gmail 账户,作为邮箱用户打开“不太安全的应用”设置。默认情况下它应该是关闭的,你必须打开它来允许电子邮件通过。尝试为此类测试创建一个测试帐户;请勿使用您的私人 Gmail 帐户进行测试。见图 16-7 。1.转到 Google 帐户安全部分。2.在左侧导航面板中,单击安全性。3. At the bottom of the page, in the “Less secure app access” panel, click “Turn on” access.图 16-7。Gmail,将不太安全的应用访问切换到打开 After you have turned on this feature, your security setting should look like Figure 16-8.
图 16-8。Gmail,允许安全性较低的应用访问 |
| 8 | 回到您的 Docker 实例;您将使用两种不同的电子邮件发送方法,使用简单的 Python 脚本,向自己发送两封测试电子邮件。首先,创建一个 Python 文件并复制以下脚本的内容,该脚本使用了email.mime.text
和 Linux 子进程。root@062fc2a30243:/#
nano sendmail_test01.py``GNU nano 4.8
sendmail_test01.py``from email.mime.text import MIMEText``from subprocess import Popen, PIPE``msg = MIMEText("Python & sendmail email test 01.")``msg["From"] = "brendanchoi@italchemy.com" # Update to your test email address``msg["To"] = "pynetauto@gmail.com" # Update to your test email address``msg["Subject"] = "Python & sendmail email test 01"``p = Popen(["/usr/sbin/sendmail", "-t", "-oi"], stdin=PIPE, universal_newlines=True)``p.communicate(msg.as_string())``print("Email sent!")``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
将测试电子邮件地址更新为您的电子邮件地址。 |
| 9 | 完成脚本后,从 Docker 实例运行它。发送电子邮件会有一点延迟。大约需要两到五分钟的时间来发送电子邮件。root@062fc2a30243:/#
cat sendmail_test01.py
|
| 10 | 另一种发送邮件的方法是使用 Python 的标准smtplib
。让我们创建第二个脚本并发送另一封测试邮件,这次使用smtplib
。root@062fc2a30243:/#
nano sendmail_test02.py``GNU nano 4.8
sendmail_test02.py``import smtplib``sender = 'no_reply@italchemy.com'``receivers = ['pynetauto@gmail.com']``message = """``From: No Reply <no_reply@italchemy.com>``To: Python Network Automation <pynetauto@gmail.com>``Subject: Sendmail SMTP Email test 01``This is a Sendmail SMTP Email test 01``"""``try:``smtpObj = smtplib.SMTP('localhost')``smtpObj.sendmail(sender, receivers, message)``print("Successfully sent email")``except SMTPException:``print("Error: unable to send email")``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 11 | 使用以下命令发送第二封测试电子邮件:root@062fc2a30243:/#
python3 sendmail_test02.py``Successfully sent email
|
| 12 | Wait for a couple of minutes and check your email’s Spam folder. If both scripts worked correctly, you should receive your first and second test emails in your Gmail Spam folder. See Figure 16-9.图 16-9。Python Sendmail 测试电子邮件,检查垃圾邮件文件夹 |
实验:Sendmail 电子邮件通知脚本开发
在本实验中,让我们创建一个简单的 Python 监控脚本,该脚本监控一个套接字LAN-SW1
(为了方便起见,在端口 22 上)并在交换机在网络上变得不可达时触发电子邮件通知。在现实世界中,此类电子邮件通知将被定向到 24/7 服务台团队或 SNMP 服务器,这些操作将触发 IT 企业票证系统(如 ITSM、ServiceNow 和 Remedy)上的自动案例记录。建立实验室的美妙之处在于,你可以自由地测试任何你想研究的东西,验证各种概念。本书所展示的只是使用 Python 和其他基于 Python 的自动化工具在网络中实现自动化的冰山一角。作为个体,每个人都来自不同的背景。我们有不同的背景和教养,所以我们的想法和行为不同,这就是在编写代码时需要你的创造力的地方。你在某些方面会比你旁边的人更有创造力,而她在其他方面也会更有创造力。当你编码时,没有正确或错误的答案,只有建议。打开你的思维,让你的想象力保持开放;想想你想在工作或学习中用 Python 实现什么。我们知道有更好的工具可用,它们是专门为 SNMP 监控、电子邮件通知工具和案例记录工具而设计的。尽管如此,通过编写代码来模拟这种工具,我们将更深入地了解这种工具如何在 IT 生态系统中工作。
本实验中的所有任务都将在 Docker 环境中完成,因此您也可以在工作中接触到 Docker 的强大功能。借用前面的实验,让我们创建一个套接字监视工具,每三秒检查一次套接字的可用性。如果套接字连续十次不可用,它将离线 30 秒,脚本将使用 Sendmail 向您的测试电子邮件收件箱发送电子邮件通知。脚本将持续监控套接字,直到您停止应用。假设您有一台需要监控的带链路挡板的设备,要求您每隔几分钟监控一次链路。你不希望整天坐在电脑前,盯着控制台信息。您将创建一个简单的脚本,将日志保存到一个文件中,并让您的脚本通知需要通知的适当人员,在本例中是通过电子邮件。让我们看看如何轻松实现这一点。
||
工作
|
| — | — |
| 1 | 您将继续使用上一节中的 Docker 实例。如果您已经停止 Docker 实例或从 Docker 实例分离,请将其重新附加到 Docker 实例。以下示例显示了启动并重新附加到 Docker 实例的示例:pynetauto@ubuntu20s1:~$``docker ps –a``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``062fc2a30243 pynetauto/pynetauto_ubuntu20 "/bin/bash" 45 hours ago Exited (130) 43 hours ago pynetauto_ubuntu20sendmail``pynetauto@ubuntu20s1:~$``docker start pynetauto_ubuntu20sendmail``pynetauto_ubuntu20sendmail``pynetauto@ubuntu20s1:~$``docker ps``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``062fc2a30243 pynetauto/pynetauto_ubuntu20 "/bin/bash" 45 hours ago Up 5 seconds 20-22/tcp, 25/tcp, 12020-12025/tcp pynetauto_ubuntu20sendmail``pynetauto@ubuntu20s1:~$``docker attach pynetauto_ubuntu20sendmail``root@062fc2a30243:/#
|
| 2 | 转到映射到主机的/home
目录,创建一个新目录;为了简单起见,这个目录被命名为monitoring
。将工作目录更改为新目录。root@062fc2a30243:/#
ls /home``testfil999.txt testfile123.txt``root@062fc2a30243:/#
cd /home``root@062fc2a30243:/home#
mkdir monitoring``root@062fc2a30243:/home#
cd monitoring``root@062fc2a30243:/home/monitoring#
|
| 3 | 现在,创建两个空 Python 文件,一个用于脚本,另一个用于电子邮件。root@062fc2a30243:/home/monitoring#
touch monitor_sw1.py send_email.py``root@062fc2a30243:/home/monitoring#
ls``monitor_sw1.py send_email.py
|
| 4 | 下面的脚本是一个套接字检查脚本,我经常用它来检查 SSH 端口,端口 22。如果您的设备上打开了另一个端口,如端口 23 (Telnet)、69 (TFTP)或 80 (HTTP),它们也可以在本例中替换,但您已经知道端口 22 是拓扑中所有 Cisco 设备上的开放端口;因此,这个脚本将检查端口 22。使用这个基本的端口检查脚本和我们从前面的练习中学到的知识,您将重写代码并应用它来解决我们的问题。GNU nano 4.8
check_port_22.py``import socket``ip = '192.168.183.101'``def check_port_22():
# Define a function``for port in range (22, 23):
# port 22``dest = (ip, port)
# new tuple variable``try
: # using try & except, we can avoid check process being stuck in a loop``with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:``s.settimeout(3)``connection = s.connect(dest)
# Connect to socket``print(f"On {ip}, port {port} is open!")
# Informational``print("OK - This device is on the network.")
# Informational``except:``print(f"On {ip}, port {port} is closed. Exiting!")
# Informational``print("! FAILED - to reach the device. Check the connectivity to this device")
# Informational``exit()
# Exit application``check_port_22()
# Run the function``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 5 | 现在在 nano 文本编辑器中打开monitor_sw1.py
,开始输入以下脚本。是的,您可以下载该脚本,剪切和粘贴代码会节省时间。但是,在现实世界中,这绝不是剪切和粘贴那么简单。大多数时候,你必须输入所有的代码,除非你的公司花钱请人编写一个完整的、可以工作的应用。所以,试着按照脚本写每一行代码。root@062fc2a30243:/home/monitoring#
nano monitor_sw1.py``GNU nano 4.8
monitor_sw1.py``import socket``import time``from datetime import datetime``# Custom module``from send_email import send_email
# import send_email module from email_send.py``starttime = time.time()``# Variables``ip = "192.168.183.101"``port = 22``dest = (ip, port)``def check_sw1():
# Define check_sw1 function``counter = 0
# Set counter's original value to 0``while (counter < 10):
# Run the script until counter 10 is reached``f = open('./monitoring_logs.txt', 'a')
# Open monitoring logs file for record in appending mode``try:
# This is basically the same code as check_port_22.py``with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:``s.settimeout(3)``connection = s.connect(dest)``counter = 0
# Reset the counter to 0 on successful check``counter1 = str(counter)
# convert counter to string``f.write(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
# Write time to file``print(f" {counter1} port {port} is open")
# Informations for console user``f.write(f" {counter1} port {port} is open\n")
# Write to log file for task completion``f.close()
# close file``time.sleep(3)
# Wait for 3 seconds before another check``except:
# If port 22 is closed``counter += 1
# Adds 1 to counter every loop``counter2 = str(counter)
# convert counter to string``f.write(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
# Write time to file``print(f" {counter2} port {port} is closed")
# Informations for console user``f.write(f" {counter2} port {port} is closed\n")
# Write to log file for task completion``time.sleep(3)
# Wait for 3 seconds before another check``if counter == 10:
# Check if counter is 10``counter=0
# Only resets the counter to 0 on 10th time``print(“send failed email here”)
# Informations for console user``send_email()
# Send an email notification, calling send_email() from send_email.py script``f.close()
# close file``check_sw1()
# Run check_sw1 function``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 6 | 现在让我们编写一个smtplib
Python 文件,以便前面的主脚本可以导入它并发送电子邮件。这是脚本的电子邮件部分。通过将脚本分解成多个功能部分,您可以保持代码整洁。root@062fc2a30243:/home/monitoring#
nano send_email.py``GNU nano 4.8
send_email.py``import smtplib``sender = 'no_reply@italchemy.com'
# A variable, sender``receivers = ['pynetauto@gmail.com']
# A variable, receivers. Add more emails using comma separator``# This is the main message which will be sent to the email recipient(s). Anything between the triple quotes``message = """``From: No Reply <no_reply@italchemy.com>``To: Python Network Automation <pynetauto@gmail.com>``Subject: SW1 not reachable for more than 30 seconds``SW1 is not reachable. Please investigate.``"""``def send_email():
# Define send_email function``try:``smtpObj = smtplib.SMTP('localhost')
# Define smtpObj``smtpObj.sendmail(sender, receivers, message)
# Send an email using smtpObj and variables``print("Successfully sent email")
# Informational``except SMTPException:
# SMTP Exception``print("Error: unable to send email")
# Informational``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 7 | 一旦创建了主脚本和辅助脚本,就可以使用下面的命令在 Docker 环境中运行代码。一旦在屏幕上看到消息,就可以让脚本继续运行。root@062fc2a30243:/home/monitoring#
python3 monitor_sw1.py
登录LAN-SW1
(192.168.183.101)并关闭端口 Gi0/0,等待大约 30 秒,发送no shut
命令。然后再等 30 秒;这将模拟 link flap 场景,并将触发脚本更改其状态。LAB-SW1(config)#
interface Gi0/0``LAB-SW1(config-if)#``shut``LAB-SW1(config-if)#``no shut
如果您让端口关闭(不可达)超过 30 秒,那么脚本将触发一封电子邮件,您将在收件箱中收到一封电子邮件。在您调出界面之前,脚本应该每 30 秒发送一次通知电子邮件并继续运行。root@062fc2a30243:/home/monitoring#
python3 monitor_sw1.py``...``0 port 22 is open``0 port 22 is open``0 port 22 is open``1 port 22 is closed``2 port 22 is closed``3 port 22 is closed``4 port 22 is closed``5 port 22 is closed``6 port 22 is closed``7 port 22 is closed``8 port 22 is closed``9 port 22 is closed``10 port 22 is closed``send failed email here``Successfully sent email``...
|
| 8 | Check the Spam folder of your email account. If you keep the interface shut down for three minutes, you should receive six email notifications, as shown in Figure 16-10.图 16-10。检查垃圾邮件文件夹中收到的测试电子邮件测试电子邮件的以下内容将与此类似:From:没有回复<??【no_reply@italchemy.com】??****到:Python 网络自动化<pynetauto@gmail.com****主题:超过 30 秒无法联系到 SW1****SW1 不可达。请调查。 |
| 9 | 按 Ctrl+P,然后按 Ctrl+Q 键从 Docker 实例中分离。这将使脚本持续运行。要停止脚本和 Docker 实例,请按 Ctrl+Z。您也可以使用docker stop <container>
正常关闭容器实例。 |
假设现在是星期五下午 5 点,你的老板让你监控一台连接不可靠的设备(路由器、交换机或防火墙)。如果设备失去连接,立即通知所有利益相关方。Docker 实例上运行的脚本可以为您检查与网络的通信,当事件发生时,它会向您发送电子邮件通知。你可以随时从智能手机上查看邮件,甚至是在回家的路上或晚饭后。下午 5 点,当您离开办公室时,使用cron
安排运行脚本,让您的 Python 应用为您工作。
CPU 利用率监控实验:使用 Twilio 发送短信
在本实验中,您将快速开发一个 REST API 短信工具;然后,您将编写 Python 代码来监控路由器的 CPU 利用率。当达到某个阈值时,该脚本将使用 REST API 调用向您的智能手机触发一条 SMS 消息。随着较新的网络设备平台和大多数虚拟和云网络平台开始支持 REST API,理解 REST API 并将理论应用于实际用例将是网络自动化中的必备技能之一。这本书不会讨论如何开始 REST API 研究,也不会试图广泛地涵盖这个主题;这是一个简单的例子,可以让你体会一下。不过你可以从 YouTube 或者 LinkedIn 视频教程开始学习 REST API 大多数教程都会让你安装一个 REST 客户端,比如 POSTMAN 或者 REST for Visual Studio。如果您有时间,建议您在完成本实验之前访问以下网站并做一些初步阅读。参见图 16-11 。
网址: https://www.postman.com/
(邮差休息客户端)
网址: https://marketplace.visualstudio.com/items?itemName=humao.rest-client
(微软 Visual Studio 的 REST 客户端)
图 16-11。
CPU 利用率监控实验室,使用的设备
创建 TWILIO 帐户,安装 Twilio Python 模块和 SMS 消息设置
在本节中,您将设置一个 Twilio 测试帐户来免费发送短信。根据维基百科的说法,Twilio 是一家作为服务公司的云通信平台,允许软件开发人员通过编程方式拨打和接听电话,发送和接收短信,以及使用 Twilio 的 web 服务 API 执行其他通信功能。在我们的使用中,我们需要在系统监控拓扑中的网络设备时,在满足特定条件时发送 SMS 通知消息。首先,创建一个帐户来接收美国测试号、帐户 SID 和授权码;然后在 Python 上安装 Twilio 模块,并编写一条简单的 SMS 消息发送到您的智能手机号码。此后,您将编写一个 CPU 利用率监控脚本,模拟安全攻击下的高 CPU 利用率场景,并触发一个 SMS 通知。首先,让我们快速设置帐户,并向您的智能手机号码发送第一条测试消息。
||
工作
|
| — | — |
| 1 | 去 Twilio 创建一个试用账户。参见图 16-12 。URL: https://www.twilio.com/try-twilio
图 16-12。Twilio 帐户注册 |
| 2 | 注册后,您必须登录您的电子邮件帐户,并确认您的电子邮件。Enter your smartphone number, and once the verification code is sent to your smartphone, enter the code into the Twilio code verification site. Follow the prompts to complete the account registration. See Figure 16-13.图 16-13。Twilio 项目仪表板 |
| 3 | A trial number will be offered from an available number range. Choose the first number. See Figure 16-14.图 16-14。Twilio 美国试用电话号码 |
| 4 | You will get the free trial balance and account SID and authorization token along with your new phone number. An account SID and authorization token will be used in the code to send an SMS message. See Figure 16-15.图 16-15。带有试用编号的项目仪表板 |
| 5 | 要了解更多关于如何开始使用 Twilio 的信息,请访问以下网站并阅读官方文档。URL: https://www.twilio.com/docs/sms/quickstart/python
|
| 6 | 在本实验中,我们将在 Docker 实例中隔离该实例,使其不会直接影响您的实验设置。使用 Docker,您可以根据需要删除实例并重新创建任意数量的实例。让我们继续创建另一个 Docker 实例来运行 Twilio 实验室。pynetauto@ubuntu20s1:~$
|
| 7 | 为了准备 Twilio SMS 消息,使用pip3
命令在 Docker 实例上安装 Twilio。因为您是在 Docker 环境中测试的,所以您不必担心破坏真正的 Linux 主机的软件兼容性问题。pynetauto@ubuntu20s1:~/monitor_cpu$
pip3 install Twilio``[...omitted for brevity]``Successfully installed twilio-6.45.3
如果您得到一个bash: snmpwalk: command not found
错误消息,请在您的 Linux 服务器上安装snmp
。pynetauto@ubuntu20s1:~$
apt-get install snmp –y
|
| 8 | 您将创建两个文件来从 Python 脚本发送测试 SMS 消息。创建一个名为credentials.py
的凭证馈送器脚本,然后创建一个 SMS 脚本来发送名为twilio_sms.py
的 SMS。请用您的凭据和号码更新和替换凭据和号码。pynetauto@ubuntu20s1:~$
mkdir monitor_cpu``pynetauto@ubuntu20s1:~$
cd monitor_cpu``pynetauto@ubuntu20s1:~/monitor_cpu$
nano credentials.py``GNU nano 4.8
/home/pynetauto/monitor_cpu/credentials.py``account_sid = " AC42d8ee3a2ee22e8684e6f3d4eca2b8c7"``auth_token = "5233e8502a3dd3a2d006913c937a6f26"``my_smartphone = "+61498765432"``twilio_trial = "+16076003338"``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
这是将向您的智能手机发送my_message
的短信脚本。pynetauto@ubuntu20s1:~/monitor_cpu$
nano twilio_sms.py``GNU nano 4.8 /
home/pynetauto/monitor_cpu/twilio_sms.py``from twilio.rest import Client``from credentials import account_sid, auth_token, my_smartphone, twilio_trial``client = Client(account_sid, auth_token)``my_message = f"High CPU utilization notice, LAB-R1 has reached 99% CPU utilization."``message = client.messages.create(body=my_message, from_=twilio_trial, to=my_smartphone)``print(message.sid)``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 9 | 从 Docker 容器命令行,运行python3
命令来运行 SMS 脚本以发送您的第一条 Twilio SMS。如果一切都设置正确,它将发送短信到您的智能手机。pynetauto@ubuntu20s1:~/monitor_cpu$
python3 twilio_sms.py``SMf1c0d46e60b245b08e56a58563a434aa
|
| 10 | On your smartphone, check the SMS message, and you should receive an SMS message similar to Figure 16-16.图 16-16。测试收到的 SMS 消息示例 |
如果您在智能手机上成功收到了 SMS 消息,您就可以开始下一个实验了。
使用 SMS 消息监控 CPU 利用率实验室
既然您已经知道如何向智能手机发送 SMS 消息,那么是时候编写一些代码并集成前面的代码片段,以便在现实世界的模拟中使用。让我们使用 SNMP v3 为网络设备编写一个简单的 CPU 监控应用,该应用可以监控 R1 实验室 5 分钟间隔的 CPU 利用率水平。我们可以使用第三方流量生成器来使我们的路由器非常繁忙,并将 CPU 利用率提高到 90%以上,但这意味着您必须学习另一种工具来完成本实验。为了简单起见,我们将使用debug all
命令和多个ping
包来提高LAB-R1
的 CPU 利用率。是的,您将在LAB-R1
上启用debug all
命令,这将把 CPU 利用率提高到 50%左右。您将发送大量 ping 数据包,将其它路由器和交换机的 CPU 利用率推至 90%以上。在生产环境中小心使用debug all
命令,并让它在生产环境中的 Cisco 设备上运行;您可能会关闭您的网络并导致中断。
首先,让我们编写一个简单的 CPU 监控脚本,并将 SMS 脚本集成到其中,这样,当 CPU 利用率超过 90%超过 5 分钟时,我们的脚本将向您的智能手机发送一条 SMS 警报消息,说明 CPU 利用率超过 90%超过 5 分钟。
为此,我们需要找出准确的 OID 参考号,如表 16-2 所示。前往 https://oidref.com/1.3.6.1.4.1.9.9.109.1.1.1.1
查看各种思科 IOS 设备 CPU 相关的 OID 值。
表 16-2。
思科设备 CPU 利用率 OID id
|OID 标识
|
描述
|
| — | — |
| 1.3.6.1.4.1.9.9.109.1.1.1.1.3 | 过去五秒钟内整体 CPU 繁忙百分比 |
| 1.3.6.1.4.1.9.9.109.1.1.1.1.4 | 过去一分钟内整体 CPU 繁忙百分比 |
| 1.3.6.1.4.1.9.9.109.1.1.1.1.5 | 过去五分钟内整体 CPU 繁忙百分比 |
来源: https://oidref.com/
对于 CPU 利用率,您将使用 OID ID 1 . 3 . 6 . 1 . 4 . 1 . 9 . 109 . 1 . 1 . 1 . 1 . 5,并每五分钟检查一次。您将在 Ubuntu 服务器上使用crontab
每五分钟运行一次脚本。当过去五分钟的 CPU 利用率超过 90%时,该脚本将使用上一个 lab+中的 Twilio API 帐户发送 SMS 警报。
|
工作
|
| — | — |
| 1 | 让我们首先编写一个简单的snmpwalk
脚本,它向LAB-R1
发送一个snmpwalk
命令并检索 CPU 利用率信息。以下命令应该返回路由器在过去五分钟内的 CPU 利用率。在正常运行情况下,路由器的 CPU 利用率应该低于 80 %,但是当进行一些更改或更新时,在更改窗口期间看到高 CPU 利用率是正常的。pynetauto@ubuntu20s1:~/monitor_cpu$
python3 cpu_util_5min.py``iso.3.6.1.4.1.9.9.109.1.1.1.1.5.1 = Gauge32: 3
这是原剧本。创建以下文件,并检查它是否仍然有效。如果您还没有阅读 SNMP 部分,请回到上一章继续阅读。cpu_util_5min.py``import os``stream = os.popen('snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.4.1.9.9.109.1.1.1.1.5')``output = stream.read()``print(output)
|
| 2 | 现在让我们使用之前练习中的 Twilio SMS 脚本并更新信息。pynetauto@ubuntu20s1:~/monitor_cpu$
nano credentials.py``credentials.py``account_sid = "2d8ee3a2ee22e8684e6f3d4eca2b8"
# Twilio Account SID``auth_token = "3e8502a3dd3a2d006913c937a6"
# Twilio Authorization Token``my_smartphone = "+61498765432"
# Your country code and smartphone number``twilio_trial = "+16076003338"
# Your Twilio trial number``Create another python file called, 'twilio_sms.py'.``pynetauto@ubuntu20s1:~/monitor_cpu$
nano twilio_sms.py``twilio_sms1.py``from twilio.rest import Client
# Import required twilio module``from credentials import account_sid, auth_token, my_smartphone, twilio_trial
# import information from credentials.py file``client = Client(account_sid, auth_token)
# Create a variable``my_message = f" High CPU utilization notice, LAB-R1 has reached 90% CPU utilization
. # Write a SMS message to send``message = client.messages.create \``(body=my_message, from=twilio_trial, to=my_smartphone)``# Send``print(message.sid)
# Print sent message SID
|
| 3 | 现在,让我们看看如何从输出中获得我们想要的确切值,并再次使用优秀的旧正则表达式提取 CPU 利用率值。创建新代码并针对LAB-R1
运行它。cpu_util_5min1.pyimport os
# Import os and re modules``import re``stream = os.popen('snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.4.1.9.9.109.1.1.1.1.5')
# SNMPwalk from previous chapter modified``output = stream.read()
# Read output``print(output)``p1 = re.compile(r"(?:Gauge32: )(\d+)")
# Use re positive lookbehind and match the digit``m1 = p1.findall(output)
# Match the CPU utilization value (digit) in output``cpu_util_value = int(m1[0])
# Index item 0 in the list and convert to an integer``print(pu_util_value)
# print the integer
|
| 4 | 当您从服务器上运行代码时,您应该得到 1 到 100 之间的 CPU 利用率值。在下面的示例中,返回了 5%或 5%的利用率。pynetauto@ubuntu20s1:~/monitor_cpu$
python3 cpu_util_5min.py``iso.3.6.1.4.1.9.9.109.1.1.1.1.5.1 = Gauge32: 5``5
|
| 5 | 结合前面显示的脚本,是时候完成我们的脚本了。为了简化实验,所有三个脚本,credentials.py
、twilio_sms1.py
和cpu_util_5min1.py
,将被重写为cpu_util_5min_monitor.py
。重复和集成是开发这样的工作应用的关键,这种方法将被扩展到最终的 IOS 升级实验室。pynetauto@ubuntu20s1:~/monitor_cpu$
nano cpu_util_5min_monitor.py
在组合所有三个脚本的最后,您的工作脚本应该类似于以下内容:GNU nano 4.8
/home/pynetauto/monitor_cpu/cpu_util_5min_monitor.py``from twilio.rest import Client
# import required libraries and modules``import os``import re``import time``from time import strftime``current_time = strftime("%a, %d %b %Y %H:%M:%S")
# Create your own time string format``account_sid = "AC42d8ee3a2ee22e8684e6f3d4eca2b8c7"
# Replace this with your own Twilio SID``auth_token = "5233e8502a3dd3a2d006913c937a6f26"
# Replace this with your own Twilio token``my_smartphone = "+61490417510"
# Replace this your own country code and Smartphone number``twilio_trial = "+16076003338"
# Twilio trial number``def send_sms():
# Define send_sms function``client = Client(account_sid, auth_token)``my_message = f"High CPU utilization notice, LAB-R1 has reached 90% CPU utilization."``message = client.messages.create (body=my_message, from_=twilio_trial, to=my_smartphone)``print(message.sid)``stream = os.popen('snmpwalk -v3 -l authPriv -u SNMPUser1 -a SHA -A "AUTHPass1" -x AES -X "PRIVPass1" 192.168.183.10 1.3.6.1.4.1.9.9.109.1.1.1.1.5')
# SNMPwalk command``output = stream.read()
# Read and capture output``time.sleep(3)
# Pause script for 3 seconds``print("-"*80)``print(current_time, output)
# Informational``with open('./cpu_oid_log.txt', 'a+') as f
: # When manually run, writes to this file``if "Gauge32:" in output:
# Check if 'Gauge32' is in the output``p1 = re.compile(r"(?:Gauge32: )(\d+)")
# Positive look behind to locate CPU digit value``m1 = p1.findall(output)
# Find and match the digit``cpu_util_value = m1[0]
# Re findall returns value as a list, so index it to get it as an item``if int(cpu_util_value) < 90
: # If CPU utilization value is less than 90 ( 90%)``f.write(f”{current_time} {cpu_util_value}%, OK\n’”)
# Write to file``print(“OK”)
# Write to cron.log``elif int(cpu_util_value)>= 90:
# If CPU utilization value is more than 90 ( 90%)``f.write(f”{current_time} {cpu_util_value}%, High CPU\n’”)``print(“High CPU”)
# Write to cron.log``send_sms()``elif "Timeout:" in output:
# if 'Timeout' is in output, send an SMS``f.write(f"{current_time} High CPU, Timeout: No Response\n'")``print("Timeout: No Response")
# Write to cron.log``send_sms()``elif "snmpwalk:" in output:
# if 'snmpwalk' is in output, send an SMS``f.write(f"{current_time} High CPU, snmpwalk: Timeout\n'")``print("No Response")
# Write to cron.log``send_sms()``else
: # Everything else``f.write(f"{current_time} High CPU utilization, IndexError\n")``print("IndexError occured due to High CPU Utilization")
# Write to cron.log``send_sms()``print("Finished")
# This should print when the script runs successfully.``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 6 | 要在LAB-R1
上获得高 CPU 利用率,请执行以下操作。在这里,我们希望提高该路由器的 CPU 利用率,以便我们的脚本可以在满足特定条件时运行并发送 SMS。下面是如何打开LAB-R1
上的所有调试:LAB-R1#``debug all
这可能会严重影响网络性能。继续吗?(是/[否]): 是 #实验室环境,一个大大的是永久变量调试全部启用从其它交换机和路由器,连续发送大尺寸数据报。我们想要的是在LAB-R1
达到 90%或更高的 CPU 利用率。LAB-SW1#``ping``Protocol [ip]:``Target IP address:``192.168.183.10``Repeat count [5]:``100000``Datagram size [100]:``1500``Timeout in seconds [2]:``Extended commands [n]:``Sweep range of sizes [n]:
重复并启用来自lab-sw2
和lab-r2
的相同ping
命令。等待大约两到五分钟,让LAB-R1
的 CPU 利用率达到 90%或以上。 |
| 7 | 在正常负载下,CPU 利用率将保持相对较低和稳定。当调试重大变化或奇怪的问题时,CPU 利用率可能会达到 90%以上。假设 CPU 利用率长时间持续运行在 90%或以上,这可能会对该设备上运行的服务的性能产生重大影响,如果该设备是核心路由器或边缘路由器,该问题将变得非常严重。以下是正常负载条件下的日志示例:pynetauto@ubuntu20s1:~/monitor_cpu$
cat cpu_oid_log.txt``'Thu, 24 Sep 2020 12:22:44 16% OK``'Thu, 24 Sep 2020 12:22:47 16% OK``'Thu, 24 Sep 2020 12:23:56 24% OK``'Thu, 24 Sep 2020 12:24:13 26% OK``'Thu, 24 Sep 2020 12:24:39 31% OK
以下是高 CPU 利用率下的日志示例:pynetauto@ubuntu20s1:~/monitor_cpu$
cat cpu_oid_log.txt``'Thu, 24 Sep 2020 13:06:25 96%, High CPU``'Thu, 24 Sep 2020 13:10:53 97%, High CPU``'Thu, 24 Sep 2020 13:29:56 High CPU utilization, IndexError``Thu, 24 Sep 2020 13:31:12 98%, High CPU``Thu, 24 Sep 2020 13:50:24 High CPU utilization, IndexError
|
| 8 | 如果在过去的五分钟内 CPU 的使用率超过了 90 %,这个脚本将会给你的手机发送一条短信。使用此处显示的 Python 命令运行完整的应用。这个实验可能有一点难以模拟和正确计时,但是如果您严格按照步骤操作,您的应用将触发 Twilio 服务器(在云中的某个地方)向您的智能手机发送 SMS 消息。pynetauto@ubuntu20s1:~/monitor_cpu$
python3 cpu_util_5min_monitor2.py``iso.3.6.1.4.1.9.9.109.1.1.1.1.5.1 = Gauge32: 97``97``SM9bc812d18dd74a6d95cb8c4cc4c8d758``Finished
|
| 9 | 如果从路由器收到了snmpwalk timeout
或Timeout: No Response
消息,脚本也会发送一条 SMS。Example of "snmpwalk: Timeout"``pynetauto@ubuntu20s1:~/monitor_cpu$
python3 cpu_util_5min_monitor2.py``snmpwalk: Timeout``SMfe169235c757467cb16fd621ee58e457``Finished``Example of "Timeout: No Response"``pynetauto@ubuntu20s1:~/monitor_cpu$
python3 cpu_util_5min_monitor2.py``Timeout: No Response from 192.168.183.10``SMbd36cc1b70ec4e61a988b8a51b524ee6``Finished
|
| 10 | If you followed the steps correctly, then when the router meets the high CPU utilization conditions we have set, it should send an SMS message similar to Figure 16-17. Now that everything is working as tested, let’s use cron
to run the script every five minutes to check the CPU utilization of LAB-R1
. Remember, if this was in a real production environment, the ultimate goal would be to make you spend less time in front of your computer physically monitoring the high CPU utilization in use.图 16-17。从 Python 脚本收到的 SMS 消息 |
| 11 | 从您的ubuntu20s1
服务器,使用crontab –e
命令打开cron
,并安排脚本每五分钟运行一次,以检查路由器的 CPU 利用率。pynetauto@ubuntu20s1:~/monitor_cpu$
pwd``/home/pynetauto/monitor_cpu``pynetauto@ubuntu20s1:~/monitor_cpu$
ls``cpu_util_5min_monitor.py cpu_oid_log.txt credentials.py nano.save __pycache__ twilio_sms.py
添加到crontab
的最后一行如下所示。一旦在crontab
中安排了以下任务,它将开始每五分钟运行一次。pynetauto@ubuntu20s1:~/monitor_cpu$
crontab -e``GNU nano 4.8
/tmp/crontab.DbnMHK/crontab``[...omitted for brevity]``# For more information see the manual pages of crontab(5) and cron(8)``#``# m h dom mon dow command``*/5 * * * * /usr/bin/python3.8 /home/pynetauto/monitor_cpu/cpu_util_5min_monitor.py >> /home/pynetauto/monitor_cpu/cron.log``^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos``^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| 12 | 让脚本运行并使用ls
命令检查是否已经创建了cron.log
。pynetauto@ubuntu20s1:~/monitor_cpu$
ls``cpu_util_5min_monitor.py credentials.py nano.save twilio_sms.py``cpu_oid_log.txt cron.log __pycache__
|
| 13 | 检查记录在工作目录下的cron.log
文件中的日志。当脚本运行并将记录写入日志文件时,您应该可以找到带有时间戳的 CPU 利用率日志。pynetauto@ubuntu20s1:~/monitor_cpu$
cat cron.log``--------------------------------------------------------------------------------``Thu, 24 Sep 2020 14:25:01 iso.3.6.1.4.1.9.9.109.1.1.1.1.5.1 = Gauge32: 97``97``High CPU``SM5d062edec4e844bda63ab372a568c883``Finished``--------------------------------------------------------------------------------``Thu, 24 Sep 2020 14:30:02 iso.3.6.1.4.1.9.9.109.1.1.1.1.5.1 = Gauge32: 98``High CPU``SMde58b1ebfc554ea1b94637ab26db256e``Finished``--------------------------------------------------------------------------------``Thu, 24 Sep 2020 14:35:02 iso.3.6.1.4.1.9.9.109.1.1.1.1.5.1 = Gauge32: 97``High CPU``SMb5365d20960848459d87e0b14570596d``Finished
|
| 14 | Check SMS alerts on your smartphone. If the cron
job properly runs every five minutes, it should check for high CPU utilization, and if high CPU utilization has been detected, then send an SMS message. If the CPU for the last five minutes is less than 90 percent, it just writes the cron
logs and should not send the SMS utilization. See Figure 16-18.图 16-18。CPU 利用率达到或超过 90%时收到的 SMS 消息 |
| 15 | 实验结束时,使用所有 pingss 设备上的 Ctrl+^键停止 ICMP 对LAB-R1
的 ping。 |
| 16 | 最后,通过发出u all
、un all
或undebug all
命令停止LAB-R1
上的调试。LAB-R1#
un all``All possible debugging has been turned off
|
您已经完成了 CPU 监控实验,并向您的智能手机发送了事件通知短信。这是在个人笔记本电脑/PC 上的简单集成;然而,这些想法延伸到更大规模的生产基础设施。
摘要
很好地完成了本章中的所有任务!本章旨在让您跳出框框思考,探索管理网络基础设施的各种 Python 用例。在这个阶段,你应该开始思考你在工作中遇到的自动化挑战,并开始研究你将如何解决公司的问题。在本章中,您已经在 Python 上安装了virtualenv
,并简单测试了 Ansible 和 pyATS。然后,您从 Docker 概念开始,并完成了在 Docker 环境中运行的电子邮件发送应用。最后,你在 Twilio 上注册了一个免费账户。您编写了一个 Python 应用来检查路由器的 CPU 利用率,当满足特定条件时,您会向智能手机发送一条 SMS 警告消息。我希望您可以开始思考工作中的其他 Python 应用用例,并尝试通过将各种可用的开源和专有源代码工具放在一起来解决这个问题。在第十七章中,我们将详细讨论思科 IOS XE 升级,作为开发我们应用的第一阶段。只有当任务被文档化时,它们才能以正确的顺序使用编程语言被自动化。
十七、升级多台 Cisco IOS XE 路由器
本章通过一个真实的生产实例来解释面向对象编程(OOP)。然后,本章将讨论使用用户名和密码应用示例的 Python 应用流控制。最后,我们将讨论规划 IOS 升级所涉及的逻辑思维过程。
Cisco 设备上的 IOS 升级是网络运行的一个重要组成部分,可以提高所有 Cisco 设备的可靠性和安全性。定期的 IOS 补丁管理也是网络团队关键绩效指标的一部分,因此是业务的关键部分。将指导您安装两台 Cisco CRS1000v IOS XE 路由器,为接下来的两章做准备。最后,我们将讨论由网络工程师执行的手动 Cisco IOS 升级流程和任务,并将手动任务和工程师的想法转化为 Python 脚本。在编写 Python 代码之前,必须在流程图中记录所有任务和流程。如果你想自动化一些东西,你必须写下来,记录每一步。
实用 Python 网络自动化实验室:IOS XE 升级实验室
我希望您最初对 Python 网络自动化之旅的热情没有随着时间的推移而减弱。如果你喜欢这一章之前的每一章,那就太棒了。然而,你可能遇到了一些障碍,需要在这些方面做更多的工作。每一章都旨在教你一些新的 IT 技能来增强你的能力。准备好这本书最精彩的三章吧!
纵观这本书,你学到了各种 IT 系统管理技巧,最后你来了。像许多其他流行的编程语言一样,Python 被归类为面向对象的编程语言。我一直没有讨论 OOP,因为讨论 OOP 很容易吓跑 Python 新手。在这一章中,我将给出一个关于 OOP 如何应用于实际工作场景的工作示例,这样你就可以立即将 OOP 与你的工作联系起来。接下来,您将回顾我们如何在脚本中使用用户 ID 和密码输入来使用基本的流控制。在许多环境中,您可能没有安全的密码库;通常,您会编写交互式代码,要求用户向您开发的应用提供用户 ID 和密码。在最后的实验中,您将在 VMware Workstation 15 Pro 上构建两个 Cisco CSR 1000v 虚拟路由器,为第十八章中的迷你工具开发做好准备。我们将在本章末尾详细讨论 Cisco IOS 升级过程。
将 OOP 概念应用到您的网络中
到目前为止,我们还没有讨论 Python 中的面向对象编程。即使对 OOP 了解不多,我们的 Python 脚本也工作得很好。但是当中高级 Python 用户开始谈论优化 Python 脚本时,他们总是强调 Python 是一种 OOP 语言。因此,我们必须充分利用面向对象的概念。就像另一种流行的 OOP 语言 Java 一样,我们需要知道四个基本概念:封装、抽象、继承和多态。OOP 的最终目标是更好地绑定数据,从而消除大量重复,并且相同的代码可以在相同的程序或应用中重用。
让我们快速介绍一下基础知识,然后我们将通过编写一个使用路由器和交换机的 OOP 示例来看看 OOP 的实际应用。我们将理论保持在最低限度,并进入实际的例子,以帮助您更好地理解 OOP 的概念。
-
面向对象编程(Object-oriented programming):从 OOP 的角度来看,一切都被认为是对象。C 语言被称为面向过程的编程语言,因为它基于按功能顺序运行的进程运行。另一方面,在面向对象的程序设计中,对象之间是相互关联的,并被连接起来以运行程序。OOP 把任何事物都当作一个对象,认为它们通过某种关系相互联系。
-
对象:顾名思义,OOP 中的对象就是一个事物或对象。例如,一个人或一个机器人可以被认为是一个对象,一本书或一个路由器也是一个对象。由于相同的路由器(或交换机)模型看起来相同,每个路由器都可以被称为一个对象,因为如果您在其中一个路由器上留下凹痕,它不一定会在其他路由器上留下凹痕。具有相似特性或特征的对象在同一对象组中,这意味着它们可以被分组。同一组中相同对象之间的某些特征是相同或相似的。基于 Python OOP 的概念,甚至我们人类也可以被视为对象。
-
类:人一般都有几乎相同的属性,比如两只眼睛、一个鼻子、一张嘴、手、脚和其他身体部位。书籍也有相同的属性,如书名、作者、出版商和出版日期。不同供应商的交换机仍然具有相同的交换机型号、序列号、机架单元大小和以太网端口。类是由对象(如人、书和开关)共有的公共属性集合定义的。
-
抽象:这是指只显示本质属性,对用户隐藏不必要的对象。例如,类中的复杂函数对用户隐藏(抽象)了详细的信息,因此她/他可以在抽象的基础上实现逻辑,而无需理解实际的函数,甚至无需考虑该类所有隐藏的复杂性。
-
封装:这是指将数据和操作数据的方法绑定到一个单元中的想法。同样,类是封装的一个很好的例子,因为类中的对象保持它们的私有状态,不可直接访问;相反,对象的状态是通过调用一系列公共函数的方法来访问的。
-
多态:这是制作一个运算符,并在很多方面使用它的过程;它有一种形式,但有许多用例。一个很好的类比是,不同种类的笔都被归类为钢笔,但用途却千差万别。比如有记笔记用的笔,有画画创作艺术品用的笔。
-
继承:这是从一个现有的类创建一个类的过程。考虑父母对孩子,孩子对父母;如果你出生在一个家庭,你的父母是你的亲生父母,你和你的兄弟姐妹作为孩子会继承或继承父母的特征。如果创建一个从现有类派生并与之关联的新类,则新类将继承旧类的特征。
现在我们已经有了基本的 OOP 理论,让我们使用路由器和交换机的例子来编写类,以更好地理解 OOP 风格的编码。随着您的应用变得越来越复杂,OOP 的威力在它绑定数据时真正大放异彩。让我们来看一个真实的例子。阅读并跟随您的 Python 解释器。
||
工作
|
| — | — |
| 1 | 让我们为路由器和交换机写一个类。在大多数情况下,路由器和交换机具有相同的属性;区别在于功能或特征。在这个例子中,使用继承,我们可以将def __init__
折叠或移动到父类。让我们快速编写我们的第一个类。如您所见,路由器和交换机类都有serialnumber
、hostname
、ipaddress
(管理)、modelnumber
和devicetype
属性。__init__
是类中的保留方法,在 OOP 中称为构造函数。程序员将__init__
读作“dunder init dunder ”,它允许类初始化一个类的属性。类中的单词self
用来表示类的实例;使用self
关键字,您可以访问该类的属性和方法。self
用于将带有给定参数的属性绑定到 Java 中使用的@
语法。它向类、方法和变量添加了一个特殊的属性。 |
| | class Router:``def __init__(self,``serialnumber, hostname, ipaddress, modelnumber, devicetype``self.serialnumber = serialnumber # 12 Hexadecimal numbers
|
| | self.hostname = hostname
|
| | self.ipaddress = ipaddress # format 1.0.0.X - 254.254.254.XXX
|
| | self.modelnumber = modelnumber
|
| | self.devicetype = devicetype
|
| | def process(self):
|
| | print("Packet routing")
|
| | class Switch:
|
| | def __init__(self,``serialnumber, hostname, ipaddress, modelnumber, devicetype
|
| | self.serialnumber = serialnumber # 12 Hexadecimal numbers
|
| | self.hostname = hostname
|
| | self.ipaddress = ipaddress # format 1.0.0.X - 254.254.254.XXX
|
| | self.modelnumber = modelnumber
|
| | self.devicetype = devicetype
|
| | def process(self):
|
| | print("Packet switching")
|
| | 路由器和交换机都处理数据包;一个负责分组路由,另一个负责分组交换,因此创建了一个流程来描述它们的功能。您已经成功地为网络设备创建了第一组类。 |
| 2 | 创建一个名为Devices
的父类,如下所示。Devices
类也采用了与您在任务 1 中创建的Router
和Switch
类相同的属性。所以,用同样的方式创建Devices
类。您还想添加另一个名为show
的函数,让该函数在被调用时显示信息。 |
| | >>>
class Devices:
|
| | ...
def __init__(self, serialnumber, hostname, ipaddress, modelnumber, devicetype):
|
| | ... self.serialnumber = serialnumber
# 12 Hexadecimal numbers
|
| | ...
self.hostname = hostname
|
| | ...``self.ipaddress = ipaddress
|
| | ...
self.modelnumber = modelnumber
|
| | ...
self.devicetype = devicetype
|
| | ...``def show(self):
|
| | ...
print(f"{self.serialnumber},{self.hostname},{self.ipaddress},{self.modelnumber},{self.devicetype}")
|
| | ...
|
| | >>>
|
| | 现在,分别创建三个名为Router
、Switch
和Firewall
的类,并将类Devices
传递给每个设备类型类。现在这三个类都将继承父类Devices
的特征。您已经可以看到类和 OOP 是如何简化您的代码的,因为您不必三次创建相同的属性。我们刚刚为父类创建了一个,并将其应用于所有三个子类。 |
| | >>> class Router(Devices):
|
| | ... def process(self):
|
| | ... print("Packet routing")
|
| | ...
|
| | >>> class Switch(Devices):
|
| | ... def process(self):
|
| | ... print("Packet switching")
|
| | ...
|
| | >>> class Firewall(Devices):
|
| | ... def process(self):
|
| | ... print("Packet filtering")
|
| | ...
|
| | 现在创建三个变量,每个子类一个,如下所示: |
| | >>> rt1 = Switch("001122AABBCC", "RT01", "1.1.1.1", "C4431-K9", "RT")
|
| | >>> sw1 = Router("001122DDEEFF", "SW02", "2.2.2.2", "WS-3850-48T", "SW")
|
| | >>> fw1 = Firewall("003344ABCDEF", "FW02", "3.3.3.3", "PA-5280", "FW")
|
| | 从 Python 解释器中,运行以下命令来检索设备信息。show()
方法是从父类Devices
中自动继承的。这个例子显示在解释器上,但是你也可以把它写进代码或者下载oop1.py
并作为脚本运行。你刚刚学习完 OOP 类的继承。 |
| | >>> rt1.show()
|
| | 001122AABBCC,RT01,1.1.1.1,C4431-K9,RT
|
| | >>> sw1.show()
|
| | 001122DDEEFF,SW02,2.2.2.2,WS-3850-48T,SW
|
| | >>> fw1.show()
|
| | 003344ABCDEF,FW02,3.3.3.3,PA-5280,FW
|
| 3 | 接下来,让我们构建层次化的类,这样我们对 OOP 的理解会更加巩固,我们可以在现实生活中使用它。下面的例子有一个名为Pynetauto_co
的顶层类,它代表您的公司。下面是名为Cisco
的子类,代表贵公司网络设备的厂商。接下来是子类Devices
,然后在Devices
下,有两个子类叫做Router
和Switch
。传承就像瀑布,所以是自上而下的。您可以在记事本中或直接在 nano 文本编辑器中编写此内容。 |
| | oop_task1.py |
| | class Pynetauto_co:
# parent class of Cisco class
|
| | "Parent class of Cisco, HP, Juniper, Arista"
|
| | def __init__(self, companyname):
|
| | self.companyname = companyname
|
| | def PrintInfo_P1(self):
|
| | print("Pynetauto Company")
|
| | #print("www.xyzptyltd.com")
|
| | class Cisco(Pynetauto_co):
# parent class of Devices class
|
| | "Parent class of Switch, Router and Firewall"
|
| | def __init__(self, vendor):
|
| | self.Vendor = vendor
|
| | def PrintInfo_P2(self):
|
| | print("Cisco")
|
| | #print("www.cisco.com")
|
| | class Devices (Cisco):
# parent class of Router and Switch classes
|
| | "Parent class of Switch, Router and Firewall"
|
| | def __init__(self, serialnumber, hostname, ipaddress, modelnumber, devicetype):
|
| | self.serialnumber = serialnumber # 12 Hexadecimal numbers
|
| | self.hostname = hostname
|
| | self.ipaddress = ipaddress # format 1.0.0.X - 254.254.254.XXX
|
| | self.modelnumber = modelnumber
|
| | self.devicetype = devicetype
|
| | def show(self): # show all the attributes
|
| | print(f"{self.serialnumber},{self.hostname},{self.ipaddress},\
|
| | {self.modelnumber},{self.devicetype}")
|
| | class Router(Devices):
|
| | def process(self):
|
| | print("Packet switching")
|
| | class Switch(Devices)
:
|
| | def process(self):
|
| | print("Packet routing")
|
| | rt3 = Router("000111222222", "RT03", "2.2.2.2", "C4351/K9", "RT")
|
| | sw3 = Switch("000111222111", "SW03", "1.1.1.1", "WS-3850-48T", "SW")
|
| | rt3.PrintInfo_P1()
|
| | rt3.PrintInfo_P2()
|
| | rt3.show()
|
| | sw3.PrintInfo_P1()
|
| | sw3.PrintInfo_P2()
|
| | sw3.show()
|
| | 当您保存前面的 OOP 任务脚本并从 Python 中运行它时,它将返回以下输出: |
| | Pynetauto Company
|
| | Cisco
|
| | 000111222222,RT03,2.2.2.2,C4351/K9,RT
|
| | Pynetauto Company
|
| | Cisco
|
| | 000111222111,SW03,1.1.1.1,WS-3850-48T,SW
|
OOP 将把数据绑定在一起,让你能够编写干净的代码。当你不得不处理许多具有相似属性的对象,并且不能创建每个对象的属性时,OOP 将会拯救你。此外,当我们必须管理数据帧或任何 SQL 类型中的数据时,OOP 结构可以帮助我们更好地管理和处理 Python 脚本中的数据。现在让我们快速进入下一个话题。
流量控制和控制用户输入:UID、PWD 和信息收集器
在这一部分,你会学到一些有趣又实用的东西。在开发交互式或非交互式 Python 应用时,您通常希望控制交互式用户的数据输入。您希望确保用户输入与脚本中定义的完全一致。反过来,这可以用于更改 Python 脚本的流程。一个简单的例子是要求用户输入是或否。有四种可能的用户输入场景。在这种情况下,首先,键入 yes 或 y;第二,键入 no 或 n;第三,键入是或否以外的内容;第四,盯着屏幕,什么也不输入。此外,通过在用户输入中引入正则表达式,您可以接受您想要的信息并拒绝任何其他变量值。您将在下一个示例中看到这一点,但是首先,让我们从 yes/no 用户输入场景中获得一些乐趣;然后,我们将重申脚本,使其更有用,并研究它如何改变您的 Python 脚本的流程,以及重复如何增强您的简单脚本。
您将开发的第一个脚本是使用是/否脚本的用户名和密码输入应用。
||
工作
|
| — | — |
| 1 | 下面是一个使用if ~ else
语句的简单的是/否用户输入示例。创建一个名为yes_or_no1.py
的新 Python 脚本,并运行该脚本。这个脚本有一些缺陷,但是我们将增强这个第一个输入脚本,使它在我们真正的 Python 应用中有用。 |
| | 在您的服务器上创建一个单独的目录,并创建您的第一个脚本,如下所示: |
| | pynetauto@ubuntu20s1:~$
mkdir user_input
|
| | pynetauto@ubuntu20s1:~$
cd user_input
|
| | pynetauto@ubuntu20s1:~/user_input$
nano yes_or_no1.py
|
| | GNU nano 4.8 /home/pynetauto/user_input
/yes_or_no1.py
|
| | def yes_or_no():
# Create function called yes_or_no
|
| | yes_or_no = input("Enter yes or no : ")
# Ask for user input
|
| | yes_or_no = yes_or_no.lower()
# change all input casing to lower casing
|
| | if yes_or_no == "yes":
# if answer is 'yes' take the following action
|
| | print("Oh Yes!")
|
| | else:
# if answer is 'no' or other input, take the following action
|
| | print("Oh No!")
|
| | yes_or_no()
# Run the function
|
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
|
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| | 当您运行前面的脚本并给出输入时,每个响应的输出应该是这样的。 |
| | 这段 Python 代码非常简单。想想我们可以在这个脚本的哪些地方进行改进。 |
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no1.py
|
| | Enter yes or no :
Yes
|
| | Oh Yes!
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no1.py
|
| | Enter yes or no :
no
|
| | Oh No!
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no1.py
|
| | Enter yes or no : maybe
|
| | Oh No!
|
| 2 | 第一个例子有以下缺陷: |
| | a.不考虑用户的简短响应,y
表示是,n
表示否。 |
| | b.没有响应和不正确的响应被视为相同的响应。 |
| | 让我们快速纠正前面的错误,看看我们是否可以重申这个简单的代码,并使它变得更好。 |
| | 将您的代码文件命名为yes_or_no2.py
。 |
| | pynetauto@ubuntu20s1:~/user_input$
nano yes_or_no2.py
|
| | GNU nano 4.8 /home/pynetauto/user_input/
yes_or_no2.py
|
| | def yes_or_no():
|
| | yes_or_no = input("Enter yes or no : ")
|
| | yes_or_no = yes_or_no.lower()
|
| | if yes_or_no == "yes" or yes_or_no == "y":
# Takes abbreviated response
|
| | print("Oh Yes!")
|
| | elif yes_or_no == "no" or yes_or_no == "n":
# Takes abbreviated response
|
| | print("Oh No!")
|
| | else:
# Any other responses, print the following statement
|
| | print("You have not entered the correct response.")
|
| | yes_or_no()
|
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
|
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| | 预期的输出示例如下所示,但我们仍可以进一步改进: |
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no2.py
|
| | Enter yes or no : y
|
| | Oh Yes!
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no2.py
|
| | Enter yes or no : no
|
| | Oh No!
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no2.py
|
| | Enter yes or no : maybe
|
| | You have not entered the correct response
.
|
| 3 | 您只希望允许yes
或no
作为响应,并希望提示用户直到提供yes
、y
、no
或n
响应。通过限制用户的响应,您现在可以支配和控制脚本的流程。你将如何实现这个目标?为了让回答者给出一个yes
或no
,你需要修改脚本的哪一部分?有几种可能的方法可以实现这一点,而且和往常一样,Python 编码没有对错之分,只有最佳建议。让我们看下一个例子来实现我们的目标。 |
| | 在下面的示例中,您将使用一个包含预期答案的简单列表,并且您将只允许您的脚本在那些值作为响应被接收时运行。直到您得到正确的响应,您将提示用户您期待一个yes
或no
响应。 |
| | pynetauto@ubuntu20s1:~/user_input$
nano yes_or_no3.py
|
| | GNU nano 4.8 /home/pynetauto/user_input/
yes_or_no3.py
|
| | def yes_or_no():
|
| | yes_or_no = input("Enter yes or no : ")
|
| | yes_or_no = yes_or_no.lower()
|
| | expected_response = ['yes', 'y', 'no', 'n']
# Expected responses
|
| | while yes_or_no not in expected_response:
# Prompt until 'yes' or 'no' response is given
|
| | yes_or_no = input("Expecting yes or no : ")
|
| | if yes_or_no == "yes" or yes_or_no == "y":
# 'yes' or 'y' action
|
| | print("Oh Yes!")
|
| | else:
# 'no' or 'n' action
|
| | print("Oh No!")
|
| | yes_or_no()
|
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
|
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| | 预期的输出应该如下所示,并且响应必须满足通过while
循环的标准。否则,用户会被反复询问正确的答案。现在,这看起来很有希望,但是你能在哪里使用这段代码呢?在下一个例子中,让我们看看真实的例子以及它是如何使用的。 |
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no3.py
|
| | Enter yes or no : Y
|
| | Oh Yes!
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no3.py
|
| | Enter yes or no : NO
|
| | Oh No!
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no3.py
|
| | Enter yes or no : maybe
|
| | Expecting yes or no : maybe
|
| | Expecting yes or no : yes
|
| | Oh Yes!
|
| 4 | 以下脚本收集两组网络管理员 ID 和密码。首先,获取第一个管理员的凭据,然后询问第二个管理员的凭据是否相同。如果响应为yes
或y
,则用户 ID 和密码相同。如果响应为no
或n
,则收集第二组用户 ID 和密码。 |
| | pynetauto@ubuntu20s1:~/user_input$
nano yes_or_no4.py
|
| | GNU nano 4.8 /home/pynetauto/user_input/
yes_or_no4.py
|
| | from getpass import getpass
|
| | def get_credentials():
|
| | #Prompts for, and returns a username1 and password1
|
| | username1 = input("Enter Network Admin1 ID: ")
# Request for username 1
|
| | password1 = getpass("Enter Network Admin1 PWD: ")
# Request for password 1
|
| | print("Username1 :", username1, "Password1 :", password1)
# Print username and password
|
| | #Prompts for username2 and password2
|
| | yes_or_no = input("Network Admin2 credentials same as Network Admin1 credentials? (Yes/No): ").lower()
# Ask if Network Admin 2 has the same credentials as Admin 1
|
| | expected_response = ['yes', 'y', 'no', 'n']
# Expect any of these four responses
|
| | while yes_or_no not in expected_response:
# Prompt until 'yes' or 'no' response is given
|
| | yes_or_no = input("Expecting yes or no : ")
|
| | if yes_or_no == "yes" or yes_or_no == "y":
# If 'yes' or 'y', credentials ate the same as Admin1
|
| | username2 = username1
|
| | password2 = password1
|
| | print("Username2 :", username2, "Password2: ", password2)
# Print username and password
|
| | else:
# If 'no' or 'n', request for Admin2 username and password
|
| | username2 = input("Enter Network Admin2 ID : ")
|
| | password2 = getpass("Enter Network Admin2 Password : ")
|
| | print("Username2 :", username2, "Password2 :", password2)
# Print username and password
|
| | get_credentials()
|
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
|
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| | 运行脚本来测试用户 ID 和密码收集器脚本。预期输出应该类似于以下输出。你觉得可以重申一下这个剧本,改进一下这个剧本吗?我们来看最后一个例子。 |
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no4.py
|
| | Enter Network Admin1 ID:
hugh
|
| | Enter Network Admin1 PWD:*********
|
| | Username1 : john Password1 :
password1
|
| | Network Admin2 credentials same as Network Admin1 credentials? (Yes/No):
yes
|
| | Username2 : john Password2: password1
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no4.py
|
| | Enter Network Admin1 ID:
hugh
|
| | Enter Network Admin1 PWD: *********
|
| | Username1 : hugh Password1 : password1
|
| | Network Admin2 credentials same as Network Admin1 credentials? (Yes/No):
no
|
| | Enter Network Admin2 ID :
john
|
| | Enter Network Admin2 Password : ***********
|
| | Username2 : john Password2 : password777
|
| 5 | 在最后重复的yes
或no
示例中,您将添加一个密码验证特性,以确保用户的第一个密码与第二个密码进行了验证。两个密码必须匹配;getpass
库中的getpass
模块在用户输入密码时隐藏密码。通过添加密码验证功能,您可以最大限度地降低密码不正确的可能性。 |
| | pynetauto@ubuntu20s1:~/user_input$
nano yes_or_no5.py
|
| | GNU nano 4.8 /home/pynetauto/user_input/
yes_or_no5.py
|
| | from getpass import getpass
|
| | def get_credentials():
|
| | #Prompts for, and returns a username1 and password1
|
| | username1 = input("Enter Network Admin1 ID: ")
# Request for username 1
|
| | password1 = None
# Set password1 to None (initial value to None)
|
| | while not password1
: # Until password1 is given
|
| | password1 = getpass("Enter Network Admin1 PWD : ")
# Get password1
|
| | password1_verify = getpass("Confirm Network Admin1 PWD : ")
# Request for validation
|
| | if password1 != password1_verify
: # If the password1 and verification password does not match
|
| | print("Passwords do not match. Please try again.")
# Print this information
|
| | password1 = None
# Set the password to None and ask for password1 again
|
| | print("Username1 :", username1, "Password1 :", password1) # Print username and password
|
| | #Prompts for username2 and password2
|
| | yes_or_no = input("Network Admin2 credentials same as Network Admin1 credentials? (Yes/No): ").lower()
# Ask if Network Admin 2 has the same credentials as Admin 1
|
| | expected_response = ['yes', 'y', 'no', 'n']
# Expect any of these four responses
|
| | while yes_or_no not in expected_response:
# Prompt until 'yes' or 'no' response is given
|
| | yes_or_no = input("Expecting yes or no : ")
|
| | if yes_or_no == "yes" or yes_or_no == "y":
# If 'yes' or 'y', credentials ate the same as Admin1
|
| | username2 = username1
|
| | password2 = password1
|
| | print("Username2 :", username2, "Password2 :", password2)
# Print username and password
|
| | else:
# If 'no' or 'n', request for Admin2 username and password
|
| | username2 = input(“Enter Network Admin2 ID: “)
# Request for username 2
|
| | password2 = None
# Explanation same as above
|
| | while not password2:
# Explanation same as above
|
| | password2 = getpass(“Enter Network Admin2 PWD : “)
# Explanation same as above
|
| | password2_verify = getpass(“Confirm Network Admin2 PWD : “)
|
| | if password2 != password2_verify:
# Explanation same as above
|
| | print(“Passwords do not match. Please try again.”)
|
| | password2 = None
# Explanation same as above
|
| | print(“Username2 :”, username2, “Password2 :”, password2) # Print username and password
|
| | get_credentials()
|
| | ^G Get Help ^O Write Out ^W Where Is ^K Cut Text ^J Justify ^C Cur Pos
|
| | ^X Exit ^R Read File ^\ Replace ^U Paste Text ^T To Spell ^_ Go To Line
|
| | 可以学习迭代代码的过程来完善原代码。一旦您对代码的质量感到满意,您就可以将它保存到您的代码库中并记录下来,或者您也可以选择与团队中的其他人或在线社区共享它。事实上,Python 编码并不总是有趣的,因为你必须对你编码的所有东西进行分析。你经常需要调整你的代码和其他代码的重复。学会从一个基本的脚本开始,然后通过反复修改来改进。 |
| | 以下是预期的输出: |
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no5.py
|
| | Enter Network Admin1 ID:
hugh
|
| | Enter Network Admin1 PWD :***********
|
| | Confirm Network Admin1 PWD : *********
# incorrect password typed
|
| | Passwords do not match. Please try again.
|
| | Enter Network Admin1 PWD : ***********
|
| | Confirm Network Admin1 PWD : ***********
# correct password typed
|
| | Username1 : hugh Password1 : password123
|
| | Network Admin2 credentials same as Network Admin1 credentials? (Yes/No): yes
|
| | Username2 : hugh Password2 : password123
|
| | pynetauto@ubuntu20s1:~/user_input$
python3 yes_or_no5.py
|
| | Enter Network Admin1 ID:
john
|
| | Enter Network Admin1 PWD : ***********
|
| | Confirm Network Admin1 PWD : ***********
|
| | Username1 : john Password1 : password777
|
| | Network Admin2 credentials same as Network Admin1 credentials? (Yes/No): no
|
| | Enter Network Admin2 ID: bill
|
| | Enter Network Admin2 PWD : ***********
|
| | Confirm Network Admin2 PWD : ***********
|
| | Username2 : bill Password2 : password888
|
您已经完成了一个练习,编写了一个基本的 Python 应用,然后通过反复添加新的特性和想法来改进代码。你的第一个代码永远不会完美;为您的工作制作最简单的 Python 程序也需要时间和想象力。接下来,让我们准备最后的实验,并通过讨论实际的 IOS 升级任务和流程来规划 IOS 升级应用开发。
实验室准备
GNS3 上的 IOS 升级测试的一个问题是,它不完全支持 GNS3 内的重新加载功能。与真正的 IOS 软件相比,这是仿真软件的缺点之一。我们可以通过两种可能的方式开始开发和测试 IOS 升级场景。第一种方法是购买真实的硬件并进行测试,第二种方法是找到一个替代的或虚拟的 IOS 映像,该映像支持在虚拟环境中升级 IOS。从这本书的第一章开始,我就已经向你承诺,我们在这本书里所做的一切都可以在没有昂贵的思科设备的情况下被模仿和实践。此外,所有学习都将在一台 PC 或笔记本电脑上完成。然而,在开发升级 Cisco IOS 的应用时,我不得不承认实际的硬件设备是无法替代的。虚拟化的 Cisco 路由器或交换机不同于物理路由器或交换机。虚拟路由器和交换机具有文件中的逻辑部件,但没有真正的主板、可插拔端口、耗电电源和噪音大的冷却风扇。此外,交换的某些部分可以模拟,正如您在 IOSvL2 交换机的使用中所看到的那样。这些交换机不完全像生产设备,因为它们缺少对局域网交换的真正 ASCII 支持。我们可以在一台笔记本电脑上使用最接近的 Cisco IOS 设备,通过虚拟云路由器 Cisco CSR 1000v 来模拟 IOS 升级。只要您能够获得 Cisco CSR 1000v 的副本,您仍然可以使用 Python 脚本来练习 IOS 升级场景。不幸的是,我不能与每个读者分享这个软件,如果你能得到你自己的拷贝,你将能从头到尾跟踪整个实验室。如果您无法获得 CSR 1000v IOS XE 映像或这里使用的类似映像,您可能需要在易贝上以每台 10 美元的价格购买几台非常过时的 Cisco 2621XM/2651XM 路由器,并模仿代码的重载部分。
CSR 1000v IOS XE 软件和下载
为了准备这个实验,我们必须下载一组 Cisco CSR 1000v IOS XE 映像。请密切注意并记下我们将为最终实验下载的文件集的不同文件扩展名。对于虚拟机(路由器)的创建,我们首先需要下载以文件扩展名.ova
结尾的文件。VMware ESXi6.5 虚拟机模板文件和 IOS 升级练习需要最新的 IOS XE 文件,文件扩展名为.bin
。你可以在表 17-1 中找到这些文件的更多细节。
表 17-1。
本章中使用的 CSR 1000v IOS XE 文件
|项目
|
来自 IOS XE 详细信息
|
至 IOS XE 详细信息
|
| — | — | — |
| 出厂日期 | 2018 年 11 月 30 日 | 2020 年 9 月 4 日 |
| 释放;排放;发布 | 富士-16.7.3 | 富士-16.9.6 |
| 文件名 | CSR 1000v–通用 k9.16.07.03.ova | csr1000v 通用型 9.16.09.06.SPA.bin |
| 出厂日期 | 2018 年 11 月 30 日 | 2020 年 9 月 4 日 |
| 最小内存 | DRAM 4096 闪存 8192 | DRAM 4096 闪存 8192 |
| 大小 | 397.99 兆字节 | 416.35 兆字节 |
| 讯息摘要 5 | aa 6 cba 7 ff 85 afb 3 和 7ca29831a28aefb4 | 778 AE 6db 8 和 34 90 E2 和 3 以及 83741bf39 |
正如您已经注意到的,在 GNS3 上运行的最低内存和闪存大小要求非常苛刻,GNS3 将难以在 GNS3 环境中运行 Cisco CRS 1000v 路由器。由于只有当同一应用可以在多台设备上运行时,网络自动化的强大功能才能实现,因此您将在您的计算机上创建两台虚拟 CSR 1000v 路由器。这意味着仅这两个虚拟路由器的内存消耗就将达到 8 GB。此外,如前所述,在基本 GNS3 配置中,重启仿真不具备您在真实生产环境中所期望的行为,因此您将导入.ova
文件并在 VMware Workstation 15 Pro 上创建两个 Cisco IOS XE 路由器,而不是在 GNS3 上构建 IOS XE 路由器。然后,你可以通过上传同一 IOS XE 火车的最新.bin
文件来跟随 IOS 升级。由于您不需要测试任何严肃的路由概念,而只需要模拟 IOS 升级,因此本实验设置足以帮助您开发一个真正适用于端到端 IOS 升级场景的 Python 应用。
如果您拥有有效的 Cisco 服务合同,并且能够访问 CCO,请登录并下载以下或类似的文件集,用于您的实验练习。不一定是富士牌的。如果您喜欢 CSR 1000v 的另一个 IOS XE 版本,您应该为此实验下载一组文件。见图 17-1 。
图 17-1。
从思科软件下载网站下载 CSR 1000v IOS XE
在 VMware 工作站上安装 Cisco CSR 1000v
在 VMware Workstation 15 上安装 CSR 1000v 与安装之前安装的 GNS3 VM .ova
文件没有什么不同。但是这一次,您将创建两个虚拟机,而不是一个。此外,对于本实验,您可以保持 GNS3 完全关闭,但您仍然需要打开 Python 服务器ubuntu20s1
。一旦我们构建了虚拟路由器,我们将使用最低配置配置两台路由器,并继续进行 IOS 升级应用开发。见图 17-2 。
图 17-2。
第十章实验室设备
现在按照这些任务安装csr1000v-1
和csr1000v-2
虚拟路由器。csr100-v 的安装非常简单。
|
工作
|
| — | — |
| 1 | To create the virtual machine, first open the VMware Workstation main window and go to File ➤ Open. Then go to the Downloads folder and select the csr1000v-universalk9.16.07.03.ova
file. See Figure 17-3.图 17-3。VMware 工作站,选择 csr1000v。ova 文件 |
| 2 | For the first virtual router, give csr1000v-1
as the name of this device. When you create the second virtual router, you just need to change the last digit of the router name to 2, so it would be csr1000v-2
. See Figure 17-4.图 17-4。csr1000v-1,给出新的路由器名称 |
| 3 | Leave the Deployment Options setting as Small and click the Next button. See Figure 17-5.图 17-5。csr1000v-1,部署选项 |
| 4 | 现在要导入虚拟路由器,您需要填写以下属性。在 GUI 上配置属性与通过 CLI 配置这些设置是一样的。 |
| | For 1. Bootstrap Properties , fill in the Router Name, Login Username, and Login Password fields. See Figure 17-6.图 17-6。csr1000v-1,1。引导属性 |
| | For 2. Features , change False to True for both Enable SCP Server and Enable SSH and Disable Telnet Login. See Figure 17-7.图 17-7。csr1000v-1,2。特征 |
| | 三个。附加配置属性,填写启用密码和测试域名。见图 17-8 。 |
| | No configuration is required for 4. Intercloud Configuration Properties; leave the settings at the defaults.图 17-8。csr1000v-1,3。其他配置属性 |
| 5 | 双击屏幕(或按 Ctrl+G)并按任意键继续导入路由器。按 Ctrl+Alt 键返回到 Windows 主机。当您在屏幕上看到以下消息时,按任意键继续: |
| | LoLoading stage2 ……
|
| | Press any key to continue
|
| | 第二阶段…… |
| 6 | 您可能需要等待三到五分钟才能将虚拟机导入到工作站。如果有足够的时间,路由器导入将会完成,如下所示。导入完成后,立即登录并检查接口状态。 |
| | Ecsr1000v-1>enable
|
| | Password :
|
| | csr1000v-1#show ip interface brief
|
| | Interface IP-Address OK? Method Status Protocol
|
| | GigabitEthernet1 unassigned YES unset administratively down down
|
| | GigabitEthernet2 unassigned YES unset administratively down down
|
| | GigabitEthernet3 unassigned YES unset administratively down down
|
| | S unset administratively down down
|
| 7 | Right-click the new virtual router, csr1000v-1
, and select Settings . The network adapter type needs to be changed. See Figure 17-9.图 17-9。csr1000v-1,打开设置… |
| 8 | Under Hardware, change the Network Adapter configuration from Bridged (Automatic) to “NAT: Used to share the host’s IP address.” See Figure 17-10.图 17-10。csr1000v-1,将网络适配器(千兆以太网 1)更改为 NAT |
| 9 | 虚拟路由器构建完成后,接口将像所有其它 Cisco 路由器一样处于管理关闭/关闭状态。在显示接口之前,让我们快速为 GigabitEthernet1 接口分配正确的 IP 地址 192.168.183.111/24。因为我们的 IOS 升级工具开发只需要一个接口,所以您不必配置其他接口。 |
| | csr1000v-1#``config termina
|
| | csr1000v-1(config)#
interface GigabitEthernet1
|
| | csr1000v-1(config-if)#
ip address 192.168.183.111 255.255.255.0
|
| | csr1000v-1(config-if)#
no shut
|
| | csr1000v-1(config-if)#
end
|
| | 请注意,一旦创建了第二台虚拟路由器,就应该使用 192.168.183.222/24 配置 GigabitEthernet1 接口。 |
| | csr1000v-2#``config termina
|
| | csr1000v-2(config)#
interface GigabitEthernet1
|
| | csr1000v-2(config-if)#
ip address 192.168.183.222 255.255.255.0
|
| | csr1000v-2(config-if)#
no shut
|
| | csr1000v-2(config-if)#
end
|
| 10 | On your Windows host, open the PuTTY SSH client and log into csr1000v-1
(192.168.183.111). When you are prompted with PuTTY security alert, make sure you click the Yes button to accept the device’s host key. Also, don’t forget to send some ICMP commands to the ubuntu20s1
server to test the connectivity. See Figure 17-11.图 17-11。csr1000v-1,第一次 PuTTY 登录 |
| 11 | 为了完成基本配置,让我们检查域并为安全密钥交换创建 RSA 密钥。此外,禁用 VTY 线路的超时,以停止 SSH 会话超时;仅在实验室环境中使用此功能。通过运行write memory
保存配置。 |
| | csr1000v-1#
show run | in ip domain
|
| | ip domain name pynetauto.local
|
| | csr1000v-1#
conf terminal
|
| | Enter configuration commands, one per line. End with CNTL/Z.
|
| | csr1000v-1(config)#
crypto key generate rsa
|
| | The name for the keys will be: csr1000v-1.pynetauto.local
|
| | Choose the size of the key modulus in the range of 360 to 4096 for your
|
| | General Purpose Keys. Choosing a key modulus greater than 512 may take
|
| | a few minutes.
|
| | How many bits in the modulus [512]:
1024
|
| | % Generating 1024 bit RSA keys, keys will be non-exportable...
|
| | [OK] (elapsed time was 0 seconds)
|
| | csr1000v-1(config)#
line vty 0 15
|
| | csr1000v-1(config-line)#
exec-timeout 0
|
| | csr1000v-1(config-line)#
end
|
| | csr1000v-1#
write memory
|
| | Building configuration...
|
| | [OK]
|
| 12 | Once you are happy with the basic configuration , take a snapshot of the virtual router, as shown in Figure 17-12.图 17-12。csr1000v-1,拍摄快照 |
| 13 | 现在,重复步骤 1 到 12,创建第二个虚拟路由器(csr1000v-2
),详细信息如下: |
| | Second router name:
csr1000v-2
|
| | Second router IP:
192.168.183.222 255.255.255.0
|
| 14 | Once you have two csr1000v
routers (csr1000v-1
and csr1000v-2
) installed, then you are ready to move on to the Cisco IOS upgrade processes . See Figure 17-13.图 17-13。启动并运行 csr1000v-1 和 csr1000v-2 |
您现在已经完成了两台用于 IOS 升级应用开发的 Cisco CSR 1000v 路由器的安装。让我们快速回顾一下 Cisco IOS 设备的升级过程。
讨论如何在 Cisco 设备上升级 IOS (IOS-XE/XR)
在 Cisco 路由器和交换机上升级 Cisco IOS (IOS/IOS-XE/IOS-XR)不适合胆小的工程师,因为成百上千的关键业务应用在工作时间和非工作时间都在运行。即使系统和 DevOps 工程师回家了,企业路由器和交换机也必须为最终用户提供 IP 连接,并为其他系统提供 IP 服务,从而允许备份服务在半夜运行。企业网络设备几乎全天候运行,这就是为什么您必须为企业网络购买声誉良好的供应商产品(如思科)。如果任何服务失败,那么第一个指责就会指向 IP 网关(路由器)设备,即使是公司电子邮件、云或系统备份问题。在一个 ITIL 驱动的组织中,要执行 IOS 升级,你总是不得不通过一系列繁文缛节的变更控制。升级 IOS 设备的原因有很多。最常见的是过时的 IOS、bug、Cisco TAC 的建议或第三方协议。IOS 升级承诺写入支持合同,IOS 升级作为网络补丁管理的一部分。
Cisco IOS 升级涉及的任务
让我们快速看一下一个经验丰富的网络工程师如何将一个过时的 IOS 升级到最新的 IOS 版本。IOS 升级流程涉及一组预定义的任务,但工程师之间的预检查和后检查会有所不同。然而,在大多数情况下,IOS 升级过程相对标准化,但需要大量关注,并且在工作时间之外进行,以避免重大停机。在极端情况下,您只允许在午夜后执行 IOS 升级,并有一个小的更改窗口。这种变化通常发生在奇数时间,这导致长期网络工程师失眠,并从长远来看给婚姻带来很大压力。无论如何,网络自动化对网络工程师和他们的家庭来说都是好消息。
让我们研究一个普通的 IOS 升级工作流程,工程师需要遵循该流程来完成 Cisco 路由器或交换机的 IOS 升级。图 17-14 展示了许多常见平台的思科 IOS 升级工作流程。
图 17-14。
常规 Cisco IOS 升级工作流程
如图 17-14 所示,思科 IOS 升级过程相当复杂;换句话说,这是劳动密集型的。如果有一个以上的设备需要升级,那么整个过程立即变得重复。那么,为什么不将工程师的任务自动化呢?在最后两章的最后实验中,你将被指导开发小的 Python 模块来代替图 17-14 中描述的大部分任务。
让我们将图 17-14 分解成三个重要阶段和许多线性步骤,以便更好地理解哪些任务必须被翻译并写入 Python 代码。在这里,你必须戴上应用开发人员/程序员的帽子,同时还要戴上网络工程师的帽子。表 17-2 提供了手动任务驱动的思科 IOS 升级明细;这一过程在不同代和不同平台之间可能略有不同。总的来说,原理几乎相同。
表 17-2。
IOS 升级任务细分
|第一阶段:预检
|
| — |
| one | 检查服务器和网络设备之间的网络连接。 |
| Two | 收集用户的登录凭证以及作为变量的用户输入。 |
| three | 检查作为变量的新 IOS 的 MD5 值。 |
| | 将用户的 MD5 值与服务器检查的 MD5 值进行比较。 |
| four | 作为变量检查 Cisco 设备上的闪存大小。 |
| | 让用户选择在flash:/
删除旧的 IOS 文件。 |
| | 提供查看 Cisco 交换机目录内部并删除旧 IOS 文件的选项。 |
| five | 制作running-config
的备份。 |
| 第二阶段:IOS 上传和预上传检查 |
| six | 将新的 IOS 文件从文件服务器上传到路由器的闪存。 |
| seven | 检查 Cisco 设备闪存上的新 IOS MD5 值。 |
| | 比较服务器的 MD5 值和交换机的 MD5 值。 |
| eight | 给用户一个结果回顾。 |
| | 如果需要,让用户选择在延迟时间重新加载设备。 |
| nine | 设备重新加载前: |
| | 更改引导系统并将当前的running-config
保存到start-up
配置。 |
| | 在本地/TFTP/FTP 存储上备份运行配置(根据需要)。 |
| | 选择性地获取任何其他重要信息。 |
| | 对于开关,运行show ip interface brief
。 |
| | 对于路由器,运行show ip routing
,它是路由表。 |
| 阶段 3:重新加载和升级后检查 |
| Ten | 设备重新加载期间(重新加载状态): |
| | 检查设备是否在网络上。 |
| Eleven | 重新加载后(设备重新接入网络): |
| | 重新登录设备,使用前后配置执行升级后验证。 |
| | 或者,在升级完成后向工程师发送电子邮件通知。 |
我们已经回顾了前面显示的手动 IOS 升级过程,但是我们如何将每一项都翻译成 Python 代码呢?我们从哪里开始,甚至有可能在生产中创建如此复杂的 IOS 升级应用吗?与当前市场上的其他工具相比,有许多任务(和工程师的逻辑思维)必须转化为代码行(程序)。接下来,我们来学习图 17-15 ,这是我把表 17-2 和图 17-14 翻译成可自动化的任务。
图 17-15。
Cisco IOS/IOS XE 自动升级,建议的工作流程示例
图 17-15 几乎是表 17-2 中讨论的任务的直接翻译,数字与表 17-2 中的数字相对应。在进入下一章之前,你应该仔细研究一下。在第十八章中,我们将把升级过程中的每一步都变成更小的 Python 代码片段。在第十八章中,为每个过程开发的工具本身将成为单独的工具。然后,在第十九章中,你将把它们组合成一个单一的工具(应用)来从头到尾取代思科 IOS 升级任务,即预检查、IOS 升级和后检查过程。
摘要
本章中讨论的过程仍然有点笼统,但是如果你想更详细,你可以将每个任务分解成更小的任务。没有基本网络背景的程序员对有经验的网络工程师通常执行的过程知之甚少或一无所知。只有能够编写代码的网络工程师才会将每个任务串成有序的任务,并涵盖将他们的行动转化为可操作代码行所需的所有基本任务。因此,在这种情况下,网络(包括安全)工程师可预见的未来是光明的,因为他们无法被纯粹的应用开发人员或未经训练的人工智能(AI)所取代。尽管如此,组织还是更喜欢有编码技能的工程师,而不是没有编码技能的传统工程师。也许大多数网络工程师都会同意,很快,人工智能将取代我们今天所做的大部分工作。至少对于这些 AI 平台的最初设计和开发来说是如此,因为它们必须由经验丰富的智能网络工程师发起,而不是应用开发人员。
十八、Python 网络自动化实验室:Cisco IOS 升级迷你工具开发
这一章是这本书的倒数第二章,你将完成十个应用。在下一章中,您将把它们变成一个单一的、功能性的 IOS 升级应用。本章中开发的工具包括以下内容:连接性验证工具、用户名和密码交互式收集工具、文件信息读取工具、用于 Linux 服务器上文件完整性的 MD5 检查工具、网络设备的配置备份工具、IOS 文件上传工具、路由器闪存 IOS MD5 检查工具、用于更改应用流的用户输入工具以及带有后检查工具的路由器重新加载工具。在本章中,您还将测试这些工具并验证它们的功能。
思科 IOS 升级应用开发
现在是时候开发一系列小的 Python 工具(应用)来使用了,就像小的乐高积木一样,在下一章中,创建一个更复杂和完整的网络自动化应用。
为了开始开发第一个应用(工具),让我们讨论一下如何将信息输入到脚本中,以便转换成 Python 变量和参数。有三种方法可以将用户和设备信息输入到我们的 Cisco IOS XE 升级应用中。首先,我们可以创建一个交互式信息收集器工具,从用户那里交互式地收集用户和设备信息。其次,我们可以交互地收集用户登录信息,但从文本文件、Excel 文件、CSV 文件或数据库等文件中读取设备信息。第三,我们可以从文件(文本、Excel 或 CSV)或数据库中获取所有信息。从不安全的文本文件中读取用户名和密码的最大问题是,它可能是生产环境中的一个重大安全威胁。您必须考虑使用加密的密码库,这将保护您在网络上的凭证。这个话题超出了本书的主题,留给你去研究;我们将用一个更简单的用户交互工具来代替它。
为了利用我们在第 1 到 17 章节中学到的一切,让我们使用第二种方法向应用提供信息。在本例中,我们将从交互式用户会话中获取用户凭证,并使用pandas
模块从 CSV 文件中获取设备信息。
A 部分:预检查工具开发连通性验证工具
网络工程师不断努力保持各种 IP 设备、应用和用户连接到他们的网络,尽可能避免或减少网络中断。甚至在网络可编程性这个词出现之前,每个人都在谈论代码基础设施或网络自动化。每个组织都希望其托管网络安全稳定。当我们编写脚本化的网络自动化应用时,我们必须将网络稳定性和安全性放在第一位。我们总是需要参考网络和安全方面的最佳实践。当你接触到网络和编程方面的最佳实践时,你就可以编写良好的网络自动化应用。
我们在这里要开发的第一个迷你工具是网络连接工具。在前面的章节中已经介绍了一个类似的工具,但是我们将重构代码并使它变得更好。在编写了一个独立的网络连通性检查工具后,您将能够测试服务器(ubuntu20s1
)和两台路由器(csr1000v-1
和csr1000v-2
)之间的通信。每个网络工程师都在工作中使用 ICMP (ping)和 socket (port)扫描,您的应用将使用 Python 代码行代替终端控制台上的手动任务。这里,我们将通过一遍又一遍地重复(代码重构)相同的代码来开发 ICMP 应用。本章中开发的所有应用将直接整合到最后一章中的最终 Cisco IOS 升级应用中。
请注意,在现实世界中没有人会为你写这段代码,所以你必须习惯于准确快捷地自己写每一行代码。与本书中的其他代码一样,对重要代码行的解释出现在每一行的
#
标记之后。
|
工作
|
| — | — |
| 1 | 使用 PuTTY SSH 到您的 Python automation server ( ubuntu20s1
)服务器,并编写以下代码。您将编写一个 ICMP 脚本,然后编写一个套接字脚本来测试服务器和路由器之间的网络连通性。然后运行并验证脚本,并修改它们以改变脚本的流程。这里我们将只使用本地的os
和socket
模块;虽然scapy
模块是一个优秀的外部工具,但是本地工具可以实现我们想要的,所以您将使用开箱即用的东西。 |
| | pynetauto@ubuntu20s1:~$
mkdir my_tools
|
| | pynetauto@ubuntu20s1:~$
cd my_tools
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool1_ping
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool1_ping
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool1.py
|
| | ping_tool1.py
|
| | import os
|
| | device_list = ['192.168.183.111', '192.168.183.222']
|
| | for ip in device_list:
# for loop for IP address
|
| | if len(ip) != 0:
# Only run this part of script if the list is not empty
|
| | print(f'Sending icmp packets to {ip}')
# Informational
|
| | resp = os.system(f'ping -c 3 {ip}')
# send ICMP packets 4 times
|
| | if resp == 0:
# If on the network (pingable), run this script
|
| | print(f'{ip} is on the network.')
# Informational
|
| | print('-'*80)
# Line divider
|
| | else:
|
| | print(f'{ip} is unreachable.')
# Informational
|
| | print('-'*80)
# Line divider
|
| | else:
|
| | exit()
# If not on the network, exit application
|
| 2 | 如果您的服务器可以与两台路由器通信并运行前面的脚本,您应该在 SSH 控制台上看到类似的结果。运行python3 ping_tool1.py
,结果将如下所示: |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 ping_tool1.py
|
| | Sending icmp packets to 192.168.183.111
|
| | PING 192.168.183.111 (192.168.183.111) 56(84) bytes of data.
|
| | 64 bytes from 192.168.183.111: icmp_seq=2 ttl=255 time=1.63 ms
|
| | 64 bytes from 192.168.183.111: icmp_seq=3 ttl=255 time=1.60 ms
|
| | 64 bytes from 192.168.183.111: icmp_seq=4 ttl=255 time=1.58 ms
|
| | --- 192.168.183.111 ping statistics ---
|
| | 4 packets transmitted, 3 received, 25% packet loss, time 3032ms
|
| | rtt min/avg/max/mdev = 1.581/1.603/1.628/0.019 ms
|
| | 192.168.183.111 is on the network.
|
| | --------------------------------------------------------------------------------
|
| | [...omitted for brevity]
|
| 3 | 现在,让我们将device_list = ['192.168.183.111', '192.168.183.222']
修改为device_list = ['10.10.10.1', '192.168.183.111', '192.172.1.33', '192.168.183.222']
。添加到列表中的两个虚拟 IP 地址并不存在,您将使用这个新列表来迭代和开发您的脚本。我们将添加一个特性,所以如果设备是可达的(pingable),脚本将把 IP 地址附加到一个新的reachable_ips
列表中。如果 IP 地址不可达,则将 IP 地址添加到一个名为unreachable_ips
的新列表中。生产网络中正在发生许多事情,我们必须应对这些情况。假设您一次只在一台设备上工作。在这种情况下,您只关心一台设备,因此如果无法远程访问该设备,您就无法将该设备升级到较新的 IOS 版本,直到您对连接问题进行故障排除。但是,当您同时处理许多设备时,如果一个或两个设备离线,您会想知道哪些设备不可访问,但同时您会想继续对其余设备进行 IOS 升级。我们将使用 ICMP 工具来区分可到达的 IP 地址和不可到达的 IP 地址,并将它们保存到两个单独的列表中。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
cp ping_tool1.py ping_tool2.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool2.py
|
| | 修改代码: |
| | ping_tool2.py
|
| | import os
|
| | device_list = ['10.10.10.1', '192.168.183.111', '192.172.1.33', '192.168.183.222']
|
| | reachable_ips = []
# Define a blank list for on the network ips
|
| | unreachable_ips = []
# Define a blank list for off the network ips
|
| | for ip in device_list:
|
| | if len(ip) != 0:
|
| | print(f'Sending icmp packets to {ip}')
|
| | resp = os.system(f'ping -c 3 {ip}')
|
| | if resp == 0:
|
| | reachable_ips.append(ip)
# Append ip to reachable_ips list
|
| | print('-'*80)
|
| | else:
|
| | unreachable_ips.append(ip)
# Append ip to unreachable_ips list
|
| | print('-'*80)
|
| | else:
|
| | exit()
|
| | print("Reachable IPs: ", reachable_ips)
# Print result 1
|
| | print("Unreachable IPs: ", unreachable_ips)
# Print result 2
|
| 4 | 现在运行python3 ping_tool2.py
命令,您应该会得到与此类似的结果: |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 ping_tool2.py
|
| | Sending icmp packets to 10.10.10.1
|
| | PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
|
| | --- 10.10.10.1 ping statistics ---
|
| | 3 packets transmitted, 0 received, 100% packet loss, time 2035ms
|
| | --------------------------------------------------------------------------------
|
| | Sending icmp packets to 192.168.183.111
|
| | PING 192.168.183.111 (192.168.183.111) 56(84) bytes of data.
|
| | 64 bytes from 192.168.183.111: icmp_seq=1 ttl=255 time=1.85 ms
|
| | 64 bytes from 192.168.183.111: icmp_seq=2 ttl=255 time=1.58 ms
|
| | 64 bytes from 192.168.183.111: icmp_seq=3 ttl=255 time=1.66 ms
|
| | --- 192.168.183.111 ping statistics ---
|
| | 3 packets transmitted, 3 received, 0% packet loss, time 2004ms
|
| | rtt min/avg/max/mdev = 1.583/1.694/1.845/0.110 ms
|
| | --------------------------------------------------------------------------------
|
| | [...omitted for brevity]
|
| | --------------------------------------------------------------------------------
|
| | Reachable IPs: ['192.168.183.111', '192.168.183.222']
|
| | Unreachable IPs: ['10.10.10.1', '192.172.1.33']
|
| | 因此,正如所料,可到达的 IP 地址是 192.168.183.111 和 192.168.183.222。该列表可用于继续运行脚本的其余部分。此外,无法到达的 IP 地址是 10.10.10.1 和 192.172.1.33。这是一个预期的结果。但是,在生产中,如果这些设备应该在网络上,那么您将需要排除连接问题,并在连接问题解决后执行更改。 |
| 5 | 作为变量的列表是有用的,但是这些信息只有在脚本运行并且您坐在控制台前观看这些信息时才会显示。注意,服务器的随机访问内存只是临时的,Python 的print
语句只是为了方便用户使用。实际的 Python 脚本并不使用屏幕上显示的内容。如果我们想在任务调度器(如cron
)成功运行脚本时访问这些信息,您必须将这些信息写入并保存到一个文件中。让我们将两个列表写入两个单独的文件中,以备后用。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
cp ping_tool2.py ping_tool3.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool3.py
|
| | ping_tool3.py
|
| | import os
|
| | device_list = ['10.10.10.1', '192.168.183.111', '192.172.1.33', '192.168.183.222']
|
| | # reachable_ips = []
# Add '#' to make this line inactive
|
| | f1 = open('reachable_ips.txt', 'w+')
# Create and open reachable_ips.txt file to write on
|
| | # unreachable_ips = []
# Add '#' to make this line inactive
|
| | f2 = open('unreachable_ips.txt', 'w+')
# Create and open unreachable_ips.txt file to write on
|
| | for ip in device_list:
|
| | if len(ip) != 0:
|
| | print(f'Sending icmp packets to {ip}')
|
| | resp = os.system('ping -c 3 ' + ip)
|
| | if resp == 0:
|
| | #reachable_ips.append(ip)
# Add '#' to make this line inactive
|
| | f1.write(f'{ip}\n')
# write ip address to each line of reachable_ips.txt file
|
| | print('-'*80)
|
| | else:
|
| | #unreachable_ips.append(ip)
# Add '#' to make this line inactive
|
| | f2.write(f'{ip}\n')
# write ip address to each line of unreachable_ips.txt file
|
| | print('-'*80)
|
| | else:
|
| | exit()
|
| | f1.close()
# Close f1 file
|
| | f2.close()
# Close f2 file
|
| 6 | 运行原始代码的第三次迭代,应该会产生两个文本文件,一个包含可到达的 IP 地址,另一个包含不可到达的 IP 地址。注意,为了节省空间,省略了输出。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
ls
|
| | ping_tool1.py ping_tool2.py ping_tool3.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 _ping_tool3.py
|
| | [... output omitted for brevity]
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
ls
|
| | ping_tool1.py ping_tool2.py ping_tool3.py reachable_ips.txt unreachable_ips.txt
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
more reachable_ips.txt
|
| | 192.168.183.111
|
| | 192.168.183.222
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
more unreachable_ips.txt
|
| | 10.10.10.1
|
| | 192.172.1.33
|
| | 因此,您的工具可以测试网络连接,并在此阶段对在线和离线设备之间的 IP 地址进行分类。此外,IP 地址可以保存(记录)在各自的文件中。 |
| 7 | 我们现在将把前面脚本中使用的 IP 地址列表转换成一个文件ip_addresses.txt
。这将允许您更有效地添加和管理大量 IP 地址,而不是读取和输入列表中的每个 IP 地址。我们从文本文件中读取 IP 地址,但我们也可以直接从 Excel 或 CSV 文件中读取 IP 地址。当我们从 Excel 或 CSV 中读取信息时,我们可以将数据转换为二维数据。信息同时变得更有意义和更有力量。 |
| | 注意最后一个 IP 地址 192.168.183.133 是 GNS3 R1
的路由器 IP 地址,所以你需要从 GNS3 的cmllab-devops
项目启动R1
。 |
| 8 | 给 GNS3 加电,启动 IOS 路由器,R1
。创建一个访问列表来阻止任何到此设备的入站 SSH 流量。创建一个访问列表,并将其应用于R1
的 FastEthernet 0/0。另外,您可以在vty 0 15
线上启用 Telnet 和 SSH。 |
| | R1#
ping 192.168.183.132
|
| | R1#
configure terminal
|
| | R1(config)#
access-list 100 deny tcp any any eq 22
|
| | R1(config)#
access-list 100 permit ip any any
|
| | R1(config)#
interface f0/0
|
| | R1(config-if)#
ip access-group 100 in
|
| | R1(config)#
line vty 0 15
|
| | R1(config-line)#
transport input telnet ssh
|
| | R1(config-line)#
do write memory
|
| 9 | 现在创建并编写一个套接字工具,如下所示,并运行脚本。结果应该类似于这里显示的内容。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano socket_tool.py
|
| | socket_tool.py
|
| | import socket
|
| | device_list = ['10.10.10.1', '192.168.183.111', '192.172.1.33', '192.168.183.222', '192.168.183.133']
|
| | for ip in device_list:
|
| | print("-"*80)
|
| | for port in range (22, 24):
|
| | destination = (ip, port)
|
| | try:
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| | s.settimeout(3)
|
| | connection = s.connect(destination)
|
| | print(f"On {ip}, SSH port {port} is open!")
|
| | except:
|
| | print(f"On {ip}, SSH port {port} is closed.")
|
| | 当您运行前面的脚本时,您应该会得到与这里所示类似的响应。如果是的话,那么是时候将这个脚本集成到ping_tool3.py
脚本中了。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 socket_tool.py
|
| | --------------------------------------------------------------------------------
|
| | On 10.10.10.1, SSH port 22 is closed.
|
| | On 10.10.10.1, SSH port 23 is closed.
|
| | --------------------------------------------------------------------------------
|
| | On 192.168.183.111, SSH port 22 is open!
|
| | On 192.168.183.111, SSH port 23 is closed.
|
| | --------------------------------------------------------------------------------
|
| | On 192.172.1.33, SSH port 22 is closed.
|
| | On 192.172.1.33, SSH port 23 is closed.
|
| | --------------------------------------------------------------------------------
|
| | On 192.168.183.222, SSH port 22 is open!
|
| | On 192.168.183.222, SSH port 23 is closed.
|
| | --------------------------------------------------------------------------------
|
| | On 192.168.183.133, SSH port 22 is closed.
|
| | On 192.168.183.133, SSH port 23 is open!
|
| 10 | 现在让我们将这两个工具结合起来,增强ping
工具来发送 ICMP 消息,然后检查 Telnet 或 SSH 连接的开放端口。完成此工具后,您可以将其用作独立工具来检查连接和端口状态。 |
| | 我们首选的连接方法是 SSH 连接。当脚本运行时,它将创建三个文件,第一个包含 SSH 连接的 IP 地址,第二个包含 Telnet 连接的 IP 地址,第三个包含无法到达的 IP 地址和关闭端口的日志。 |
| | 我们将使用从ip_addresses.txt
文件中读取的信息替换device_list
,因此继续使用 IP 地址创建文件。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ip_addresses.txt
|
| | ip_addresses.txt
|
| | 10.10.10.1
|
| | 192.168.183.111
|
| | 192.172.1.33
|
| | 192.168.183.222
|
| | 192.168.183.133
|
| | 复制ping_tool3.py
,保存为ping_tool4.py
,然后将上一步的socket_tool.py
合并为ping_tool4.py
。一旦你将两个脚本合并成一个文件,它应该看起来像ping_tool4.py
。这里,我们打破了编写更多 Pythonic 代码的规则,因为我们的代码使用了多个for
循环,缩进由八个空格组成。为了代码的可读性,推荐的缩进是 4;我们很快就会看到这一点。注意,如果在这个脚本中打开了端口 22,它不会检查端口 23,因为我们感兴趣的只是端口 22 是否打开。还要注意,我们添加了time
模块来检查运行应用的时间;第 2 和第十四章介绍了time
模块。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
cp ping_tool3.py ping_tool4.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool4.py
|
| | ping_tool4.py
|
| | import os
|
| | import socket
|
| | import time
|
| | t = time.mktime(time.localtime())
# Timer start to measure script running time
|
| | # device_list = ['10.10.10.1', '192.168.183.111', '192.172.1.33', '192.168.183.222']
# Hashed out line
|
| | ip_add_file = './ip_addresses.txt'
# IP address location variable, "./" denotes pwd
|
| | # reachable_ips = []
|
| | f1 = open('reachable_ips_ssh.txt', 'w+'
) # Open f1 file
|
| | f2 = open('reachable_ips_telnet.txt', 'w+')
# Open f2 file
|
| | # unreachable_ips = []
|
| | f3 = open('unreachable_ips.txt', 'w+')
# Open f3 file
|
| | with open(ip_add_file, 'r') as ip_addresses
: # Use with file open method
|
| | for ip in ip_addresses
: # Loop through and read IP address from each line
|
| | ip = ip.strip()
# Remove any white spaces
|
| | resp = os.system('ping -c 3 ' + ip)
# Send four ICMP packets
|
| | if resp == 0
: # If 0, in other words, the device is on the network
|
| | for port in range (22, 23):
# Check port 22 (SSH)
|
| | destination = (ip, port)
# Create arrayed tuple variable with ip and port as items
|
| | try:
# First try
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# socket command
|
| | s.settimeout(3)
# Set socket timeout to 3 seconds
|
| | connection = s.connect(destination)
# Send socket connection using destination variable
|
| | print(f"{ip} {port} opened")
#Informational, all print statements are informational
|
| | f1.write(f"{ip}\n")
# write the IP to reachable_ips_ssh.txt
|
| | except:
|
| | print(f"{ip} {port} closed")
|
| | f3.write(f"{ip} {port} closed\n")
# write the IP to unreachable_ips.txt
|
| | for port in range (23, 24)
: # Check port 23 (telnet)
|
| | destination = (ip, port)
|
| | try:
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| | s.settimeout(3)
|
| | connection = s.connect(destination)
|
| | print(f"{ip} {port} opened")
|
| | f2.write(f"{ip}\n")
# write the IP to reachable_ips_telnet.txt
|
| | except:
|
| | print(f"{ip} {port} closed")
|
| | f3.write(f"{ip} {port} closed\n")
# write the IP to unreachable_ips.txt
|
| | else:
|
| | print(f"{ip} unreachable")
|
| | f3.write(f"{ip} unreachable\n")
# write the IP to unreachable_ips.txt
|
| | f1.close()
# Close f1 file
|
| | f2.close()
# Close f2 file
|
| | f3.close()
# Close f3 file
|
| | tt = time.mktime(time.localtime()) - t
# Timer finish to measure script running time
|
| | print("Total wait time : {0} seconds".format(tt))
#Informational
|
| 11 | 运行组合脚本,它应该创建三个文件,根据 ICMP 和端口验证对 IP 地址进行排序。想象一下,在成百上千的设备上运行这样的连通性检查,以便进行故障排除或进行更改;这种类型的工具可以为您节省大量时间,并且可以从另一个应用中读取这里创建的文件,以执行进一步的编程任务。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 ping_tool4.py
|
| | [... output omitted for brevity]
|
| | --- 192.168.183.133 ping statistics ---
|
| | 3 packets transmitted, 3 received, 0% packet loss, time 2004ms
|
| | rtt min/avg/max/mdev = 4.046/7.700/12.147/3.354 ms
|
| | 192.168.183.133 22 closed
|
| | 192.168.183.133 23 opened
|
| | Total wait time : 31.0 seconds
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
ls
|
| | ip_addresses.txt ping_tool4.py socket_tool.py
|
| | ping_tool1.py reachable_ips_ssh.txt unreachable_ips.txt
|
| | ping_tool2.py reachable_ips_telnet.txt
|
| | ping_tool3.py reachable_ips.txt
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
more reachable_ips_ssh.txt
|
| | 192.168.183.111
|
| | 192.168.183.222
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
more reachable_ips_telnet.txt
|
| | 192.168.183.133
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
more unreachable_ips.txt
|
| | 10.10.10.1 unreachable
|
| | 192.172.1.33 unreachable
|
| | 192.168.183.133 22 closed
|
| 12 | 现在创建一个check_port()
函数,并使这个工作函数成为一个单独的函数。当您运行下面的代码时,它将以一种更 Pythonic 化的方式产生相同的结果。最佳实践是分离函数,以另一个文件名保存为模块,然后作为工具导入,这样主脚本就不那么拥挤,更容易理解。但目前来看,这应该是可以接受的。重写ping_tool4.py
中的代码,使其看起来像ping_tool5.py
。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$ cp ping_tool4.py ping_tool5.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool5.py
|
| | ping_tool5.py
|
| | import os
|
| | import socket
|
| | import time
|
| | t = time.mktime(time.localtime())
|
| | def check_port(ip):
|
| | for port in range (22, 23):
|
| | destination = (ip, port)
|
| | try:
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| | s.settimeout(3)
|
| | connection = s.connect(destination)
|
| | print(f"{ip} {port} opened")
|
| | f1.write(f"{ip}\n")
|
| | except:
|
| | print(f"{ip} {port} closed")
|
| | f3.write(f"{ip} {port} closed\n")
|
| | for port in range (23, 24):
|
| | destination = (ip, port)
|
| | try:
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| | s.settimeout(3)
|
| | connection = s.connect(destination)
|
| | print(f"{ip} {port} opened")
|
| | f2.write(f"{ip}\n")
|
| | except:
|
| | print(f"{ip} {port} closed")
|
| | f3.write(f"{ip} {port} closed\n")
|
| | ip_add_file = './ip_addresses.txt'
|
| | f1 = open('reachable_ips_ssh.txt', 'w+')
|
| | f2 = open('reachable_ips_telnet.txt', 'w+')
|
| | f3 = open('unreachable_ips.txt', 'w+')
|
| | with open(ip_add_file, 'r') as ip_addresses:
|
| | for ip in ip_addresses:
|
| | ip = ip.strip()
|
| | resp = os.system('ping -c 3 ' + ip)
# there is a whitespace after the digit 3, be careful.
|
| | if resp == 0:
|
| | check_port(ip)
|
| | else:
|
| | print(f"{ip} unreachable")
|
| | f3.write(f"{ip} unreachable\n")
|
| | f1.close()
|
| | f2.close()
|
| | f3.close()
|
| | tt = time.mktime(time.localtime()) - t
|
| | print("Total wait time : {0} seconds".format(tt))
|
| | 现在运行应用并检查结果。我们期待看到与ping_tool4.py
相同的结果。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 ping_tool5.py
|
| | [... output omitted for brevity]
|
| | 64 bytes from 192.168.183.133: icmp_seq=2 ttl=255 time=2.43 ms
|
| | 64 bytes from 192.168.183.133: icmp_seq=3 ttl=255 time=12.7 ms
|
| | --- 192.168.183.133 ping statistics ---
|
| | 3 packets transmitted, 3 received, 0% packet loss, time 2002ms
|
| | rtt min/avg/max/mdev = 2.429/6.314/12.670/4.531 ms
|
| | 192.168.183.133 22 closed
|
| | 192.168.183.133 23 opened
|
| | Total wait time : 30.0 seconds
|
| 13 | 让我们通过将端口检查工具分离到一个单独的模块(一个单独的文件)来简化主脚本。当你运行ping_tool6.py
时,你会得到同样的结果,但是我们可以说我们的代码更 Pythonic 化一点。 |
| | 将check_port
函数作为一个单独的工具分离出来,所以创建并保存为ping_tool6_tools.py
。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool6_tools.py
|
| | ping_tool6_tools.py
|
| | # ping_tool6_tools.py for ping_tool6.py
|
| | import socket
|
| | def check_port(ip, f1, f2, f3):
|
| | for port in range (22, 23):
|
| | destination = (ip, port)
|
| | try:
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| | s.settimeout(3)
|
| | connection = s.connect(destination)
|
| | print(f"{ip} {port} open")
|
| | f1.write(f"{ip}\n")
|
| | except:
|
| | print(f"{ip} {port} closed")
|
| | f3.write(f"{ip} {port} closed\n")
|
| | for port in range (23, 24):
|
| | destination = (ip, port)
|
| | try:
|
| | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
| | s.settimeout(3)
|
| | connection = s.connect(destination)
|
| | print(f"{ip} {port} open")
|
| | f2.write(f"{ip}\n")
|
| | except:
|
| | print(f"{ip} {port} closed")
|
| | f3.write(f"{ip} {port} closed\n")
|
| | 复制ping_tool5.py
并创建ping_tool6.py
;然后创建如下所示的主脚本: |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
cp ping_tool5.py ping_tool6.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
nano ping_tool6.py
|
| | 确保您移除了check_port()
功能,并修改了文件,如下所示: |
| | ping_tool6.py
|
| | import os
|
| | import time
|
| | from ping_tool6_tools import check_port
# importing check_port tool from ping_tool6_tools.py
|
| | t = time.mktime(time.localtime())
|
| | ip_add_file = './ip_addresses.txt'
|
| | f1 = open('reachable_ips_ssh.txt', 'w+')
|
| | f2 = open('reachable_ips_telnet.txt', 'w+')
|
| | f3 = open('unreachable_ips.txt', 'w+')
|
| | with open(ip_add_file, 'r') as ip_addresses:
|
| | for ip in ip_addresses:
|
| | ip = ip.strip()
|
| | resp = os.system('ping -c 3 ' + ip)
|
| | if resp == 0:
|
| | check_port(ip, f1, f2, f3)
# parsing arguments ip, f1, f2, f3
|
| | else:
|
| | print(f"{ip} unreachable")
|
| | f3.write(f"{ip} unreachable\n")
|
| | f1.close()
|
| | f2.close()
|
| | f3.close()
|
| | tt = time.mktime(time.localtime()) - t
|
| | print("Total wait time : {0} seconds".format(tt))
|
| | 现在,运行最后一个工具ping_tool6.py
,你应该会看到与ping_tool5.py
和ping_tool4.py
迭代相同的结果。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool1_ping$
python3 ping_tool6.py
|
| | [... output omitted for brevity]
|
| | 64 bytes from 192.168.183.133: icmp_seq=3 ttl=255 time=13.8 ms
|
| | --- 192.168.183.133 ping statistics ---
|
| | 3 packets transmitted, 3 received, 0% packet loss, time 2004ms
|
| | rtt min/avg/max/mdev = 5.775/9.237/13.752/3.340 ms
|
| | 192.168.183.133 22 closed
|
| | 192.168.183.133 23 open
|
| | Total wait time : 30.0 seconds
|
我们的 ping 工具的最后一次迭代已准备就绪,可以立即用于生产,并且足够好地集成到我们的 Cisco IOS 升级应用中。这是第一个工具开发的结束;现在让我们看看第二个工具。
收集用户的登录凭据和用户输入
当网络工程师在 Cisco 路由器和交换机上执行 IOS 升级时,他必须输入具有正确用户权限的网络管理员凭据:15 级管理员用户 ID 和密码。设备访问级别和安全性可能使用本地配置的用户凭证,也可能位于 TACACS 服务器上。在本书中,我们使用本地登录来简化事情。成功认证和登录后,网络管理员必须手动运行一些命令来检查设备的可升级性。也就是说,设备需要有足够的闪存(存储)来保存新的 IOS 映像。然后,网络管理员必须使用新的 IOS 映像文件名和 FTP/TFTP 服务器信息运行命令。在 TFTP 的情况下,不需要管理员用户名或密码。不过,在大多数情况下,您必须提供另一个用户名和密码组合,以便对 FTP 服务器进行进一步的身份验证。一些信息是一次一条一行地输入的。但是通过脚本,我们可以简化并在脚本开始运行时使用一种数据收集方法收集管理员的数据。最简单的方法是使用一个文件一次性收集所有需要的信息。最麻烦的方法是让用户一次一条地手工输入信息。出于演示的目的,我们可以编写一些代码,使用这两者来获取用户 ID、密码和秘密密码。我们将使用交互式数据收集工具来收集 IOS 名称和 MD5 值。该信息将从 CSV 文件中读取。
||
工作
|
| — | — |
| 1 | 要开发一个获取凭证工具,您将从input
函数和getpass
模块开始;getpass
模块隐藏输入控制台的密码。由于我们正在开发该工具,为了方便起见,我们将使用print
语句打印密码。 |
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool2_login
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool2_login
|
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
nano get_cred1.py
|
| | 要收集用户 ID 和网络密码并启用加密密码,最基本的工具如下所示: |
| | get_cred1.py
|
| | from getpass import getpass
|
| | uid = input("Enter Network Admin ID : ")
|
| | pwd = getpass("Enter Network Admin PWD : ")
|
| | secret = getpass("Enter secret password : ")
|
| | print(uid, pwd, secret)
|
| | 运行初始脚本进行快速测试。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
python3 get_cred1.py
|
| | Enter Network Admin ID :
pynetauto
|
| | Enter Network Admin PWD : ********
|
| | Enter secret password : *********
|
| | pynetauto cisco123 secret123
|
| | 对于前面显示的简单用户 ID 和密码收集工具,有两个问题。首先,您可以输入任何信息或不输入任何信息,然后继续下一步,因此脚本至少需要在继续下一步之前从用户那里获得一个有效的输入。第二,对于getpass
模块,它不显示输入的密码,因此在 SSH 或 Telnet 认证过程中脚本运行并出错之前,您无法判断是否输入了正确的密码。因此,我们必须通过添加验证步骤来改进这个脚本。 |
| 2 | 让我们对这个工具做一些改进,使它达到可接受的标准。在这个迭代中,我们将通过添加密码和秘密的验证脚本来解决第二个问题,即getpass
模块。您必须输入密码和秘密两次,以确保输入的密码匹配且正确。我们还会提示用户密码和密码是否与生产环境中的相同。密码和秘密都是一样的,因此通过回答y
或yes
,用户不必再次键入秘密。在第一次迭代之后,您的代码将类似于下一次编写的代码。请注意,我已经将秘密集合移到了一个单独的函数中,以使这个脚本更加简洁,此外,如果用户回答了除y
、yes
、n
或no
之外的问题,将会提示用户提供正确的答案。该功能使用户只提供期望的响应:任何一个y
、yes
、n,
或no
响应。 |
| | 参考get_cred1.py
,开始拼起来吧。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
nano get_cred2.py
|
| | get_cred2.py
|
| | from getpass import getpass
|
| | def get_secret():
|
| | global secret
|
| | resp = input("Is secret the same as password? (y/n) : ")
|
| | resp = resp.lower()
|
| | if resp == "yes" or resp == "y":
|
| | secret = pwd
|
| | elif resp == "no" or resp == "n":
|
| | secret = None
|
| | while not secret:
|
| | secret = getpass("Enter the secret : ")
|
| | secret_verify = getpass("Confirm the secret : ")
|
| | if secret != secret_verify:
|
| | print("! Secrets do not match. Please try again.")
|
| | secret = None
|
| | else:
|
| | get_secret()
|
| | def get_credentials():
|
| | global uid
|
| | uid = input("Enter Network Admin ID : ")
|
| | global pwd
|
| | pwd = None
|
| | while not pwd:
|
| | pwd = getpass("Enter Network Admin PWD : ")
|
| | pwd_verify = getpass("Confirm Network Admin PWD : ")
|
| | if pwd != pwd_verify:
|
| | print("! Network Admin Passwords do not match. Please try again.")
|
| | pwd = None
|
| | get_secret()
|
| | return uid, pwd, secret
|
| | get_credentials()
|
| | print(uid, pwd,secret)
|
| | 一旦您完成了前面脚本的编写,请测试您的应用。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
python3 get_cred2.py
|
| | Enter Network Admin ID :
pynetauto
|
| | Enter Network Admin PWD : ********
|
| | Confirm Network Admin PWD : ******* # Enter mismatched password
|
| | ! Network Admin Passwords do not match. Please try again.
|
| | Enter Network Admin PWD : ********
|
| | Confirm Network Admin PWD : ********
|
| | Is secret the same as password? (y/n) :
n
|
| | Enter the secret : ********
|
| | Confirm the secret : ******** # Enter mismatched password
|
| | ! secret do not match. Please try again.
|
| | Enter the secret : ********
|
| | Confirm the secret : ********
|
| | pynetauto cisco123 secret123
|
| | 之前的用户 ID、密码和秘密收集工具看起来比任务 1 中的原始工具好得多。尽管如此,如前所述,它还有另一个缺陷,用户可以输入任意长度的用户名、密码或秘密,所以这是我们必须解决的另一个问题,以使该工具更加真实。请看下面的例子,了解这意味着什么: |
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
python3 get_cred_b_test.py
|
| | Enter Network Admin ID :
a
|
| | Enter Network Admin PWD : *
|
| | Confirm Network Admin PWD : *
|
| | Is secret the same as password? (y/n) :
n
|
| | Enter the secret : *
|
| | Confirm the secret : *
|
| 3 | 我们可以使用几个正则表达式来控制用户输入,这将解决前面的问题。在管理良好的 IT 环境中,管理员总是对用户名和密码执行约定。因此,对于我们的脚本,我们将遵循相同的实践,只允许可接受的输入到我们的标准中。对于用户名,惯例是它需要 5 到 30 个字符长,必须以字母开头,并且在用户名中除了_
和-
之外不能使用任何特殊字符。对于密码约定,密码必须以小写或大写字母开头,并且密码必须超过 8 个字符,但等于或少于 50 个字符。第 2 个和第 50 个之间的字符可以包含特殊字符。让我们看看如何在用户名和密码上实施这些约定。在编写 Python 代码时,您必须灵活并创造性地使用您的正则表达式,以使您的代码做您想要的事情,这就是其中的一个例子。 |
| | 参考get_cred2.py
,开始组合代码。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
nano get_cred3.py
|
| | get_cred3.py
|
| | import re
|
| | from getpass import getpass
|
| | p1 = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_-]{3,28}[a-zA-Z0-9]$') # You must comprehend Chapter 9!
|
| | p2 = re.compile(r'^[a-zA-Z].{7,49}') # You must read through and complete all exercise in Chapter 9.
|
| | def get_secret():
|
| | global secret
|
| | resp = input("Is secret the same as password? (y/n) : ")
|
| | resp = resp.lower()
|
| | if resp == "yes" or resp == "y":
|
| | secret = pwd
|
| | elif resp == "no" or resp == "n":
|
| | secret = None
|
| | while not secret:
|
| | secret = getpass("Enter the secret : ")
|
| | while not p2.match(secret): # apply the re pattern 2 secret
|
| | secret = getpass(r"*Enter the secret : ")
|
| | secret_verify = getpass("Confirm the secret : ")
|
| | if secret != secret_verify:
|
| | print("!!! secret do not match. Please try again.")
|
| | secret = None
|
| | else:
|
| | get_secret()
|
| | def get_credentials():
|
| | global uid
|
| | uid = input("Enter Network Admin ID : ")
|
| | while not p1.match(uid): # apply the re pattern 1 to uid
|
| | uid = input(r"*Enter Network Admin ID : ")
|
| | global pwd
|
| | pwd = None
|
| | while not pwd:
|
| | pwd = getpass("Enter Network Admin PWD : ")
|
| | while not p2.match(pwd): # apply the re pattern 2 password
|
| | pwd = getpass(r"*Enter Network Admin PWD : ")
|
| | pwd_verify = getpass("Confirm Network Admin PWD : ")
|
| | if pwd != pwd_verify:
|
| | print("!!! Network Admin Passwords do not match. Please try again.")
|
| | pwd = None
|
| | get_secret()
|
| | return uid, pwd, secret
|
| | get_credentials()
|
| | print(uid, pwd,secret)
|
| | 运行最终应用并验证功能;您的测试运行应该类似于下面的结果。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool2_login$
python3 get_cred3.py
|
| | Enter Network Admin ID : jdoe # Entered only four characters. Minimum of five characters required
|
| | *Enter Network Admin ID :
pynetauto
|
| | Enter Network Admin PWD : ******* # Entered only seven characters. Minimum eight characters long
|
| | *Enter Network Admin PWD : ********
|
| | Confirm Network Admin PWD : ********
|
| | Is secret the same as password? (y/n) :
n
|
| | Enter the secret : ******* # Entered only seven characters, minimum of 8 characters long
|
| | *Enter the secret : *********
|
| | Confirm the secret : *********
|
| | pynetauto cisco123 secret123
|
| | 在三次重复原始代码之后,它看起来几乎已经完成,可以在升级脚本中使用了。接下来,让我们看看如何从 CSV 文件中读取 IOS 名称和 MD5 值,并将它们转换为 Python 变量。 |
现在,您已经从应用用户那里收集了用户 ID 和密码,让我们看看如何通过读取文件来收集新的 IOS 文件名和 MD5 值。如果您有多个设备和多个值,通过命令行输入这些信息将会非常麻烦。它也容易出错,所以理想情况下,这些值以二维数组形式输入,如 Excel 或 CSV 文件,让您的 Python 脚本读取这些信息。我们希望在命令行控制台的剪切和粘贴操作中节省时间并减少人为错误。
从 CSV 文件中收集新的 IOS 文件名和 MD5 值
收集用户 ID 和密码后,我们希望收集 Cisco IOS 升级所需的更多信息,但这次是通过读取 CSV 文件的内容。额外的信息包括新的 IOS 名称及其各自的 MD5 值。我们还可以包括主机名、设备类型、IP 地址和其他信息,以便为我们的脚本提供二维数据。
如果您有有效的服务合同或为思科合作伙伴工作,可以从思科下载网站下载 IOS 以升级到最新的 IOS 版本。执行 IOS 升级的工程师通常会下载文件,并从供应商的下载网站获取 MD5 值。将 IOS 下载到工程师的计算机后,工程师会确认该 IOS 副本的 MD5 值,这样他们就知道所有软件在下载过程中都是完整的,没有损坏。这是 IOS 升级准备过程中的第一批验证步骤之一。想象一下,不检查 MD5 值并使用该文件来升级 Cisco 设备的 IOS 软件!这可能会变成一个糟糕的情况,而不是简单的 IOS 升级。
当我们升级任何供应商产品上的 IOS 或任何操作系统时,您必须确保供应商下载的软件版本是正确的。您需要确保下载过程没有由于糟糕的互联网连接或其他问题而损坏 IOS。在 IOS 升级工具中有两个地方可以验证新的 IOS MD5 值。首先,用户提供的新 IOS 的 MD5 值将根据服务器检查的 MD5 值进行验证。其次,根据 Cisco 路由器检查的 MD5 值验证服务器检查的 MD5 值。在路由器检查新 IOS 文件的 MD5 之前,必须使用 TFTP/FTP/SFTP/SCP 协议将新 IOS 传输到路由器的闪存中。当然,我们希望每次都检查所有的 MD5 测试,以避免不可思议的事情。在我们将 Cisco 网站上的 MD5 值与服务器端的 MD5 值进行比较之前,我们必须为我们的脚本输入正确的 MD5 值。提供这些信息的一个很好的方法是通过 Excel 或 CSV 文件。与使用文本文件不同,我们可以在 Python 脚本中使用二维值(带有一个头),它们可以由pandas
模块处理。
让我们看看如何从一个文件(如.xlsx
或.csv
文件)中导入新的 IOS 名称和 MD5 值。虽然从基于 Windows 的计算机创建二维文件更方便,但将文件保存为.csv
文件更有利。它为您在 Linux 服务器的命令行文本编辑器中修改内容提供了更多的灵活性。在本例中,您将使用 Excel 处理文件,然后将其保存为 CSV 文件。一旦pandas
模块将二维值读入我们的脚本,您就可以访问它们并将其转换成您想要的任何变量形式。
|
工作
|
| — | — |
| 1 | 让我们打开一个新的 Excel 文件,输入以下详细信息,标题在第一行,路由器信息在第二和第三行。在这个阶段,您必须从 Cisco 下载站点获得新 IOS .bin
文件的副本和正确的 MD5 值。此时,我们可以输入每个设备的更多信息,如devicename
(路由器名称)、device
(类型)、devicetype
(用于netmiko
字典)、host
(IP 地址或 DNS 主机名)、newios
和newiosmd5
值,如图 18-1 所示。 |
| | If you are using another IOS version, then you will have to update the information accordingly.图 18-1。在 Microsoft Excel 中创建 CSV 文件 |
| | If you don’t have Microsoft Excel on your host computer, then you can work in any text editor using commas as separators, as shown in Figure 18-2. Alternatively, if you have a couple of devices like our example, you can directly enter this information in the Linux server’s vi or nano text editor. If you have hundreds of devices, use a spreadsheet program such as Microsoft Excel or Google Sheets.图 18-2。使用文本编辑器创建 CSV 文件 |
| 2 | Once you have finished entering the information in Microsoft Excel, save the file as device_info.csv
. Make sure you select “Save as type” and select “CSV (Comma delimited).” See Figure 18-3.图 18-3。在 Microsoft Excel 中将文件保存为 CSV 文件 |
| 3 | 在前面的章节中,您使用 WinSCP 将文件上传到 Python 自动化服务器。如果您想将文件上传到您的 Linux 服务器的工作目录,您现在就可以这样做。由于您试图在此上传的 CSV 文件是一个具有不同文件扩展名(.csv
)的文本(.txt
)文件,因此可以在 Linux 的 vi 或 nano 文本编辑器中将信息复制并粘贴到一个新文件中,然后将该文件另存为.csv
文件。 |
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool3_read_csv
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool3_read_csv
|
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
pwd
|
| | /home/pynetauto/my_tools/tool3_read_csv
|
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano devices_info.csv
|
| | devices_info.csv
|
| | devices_info.csv
|
| | devicename,device,devicetype,host,newios,newiosmd5
|
| | csr1000v-1,RT,cisco_xe,192.168.183.111,csr1000v-universalk9.16.09.06.SPA.bin,77878ae6db8e34de90e2e3e83741bf39
|
| | csr1000v-2,RT,cisco_xe,192.168.183.222,csr1000v-universalk9.16.09.06.SPA.bin,77878ae6db8e34de90e2e3e83741bf39
|
| | 编写基本脚本来读取您的 CSV 文件。如果您使用的是.xlsx
文件,用.xlsx
替换文件扩展名类型,pandas
将以与.csv
文件相同的方式读取该文件。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info1.py
|
| | read_info1.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | print(df)
|
| | 运行脚本并打印出数据帧(df
)。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info1.py
|
| | devicename device ... newios newiosmd5
|
| | 0 csr1000v-1 RT ... csr1000v-universalk9.16.09.06.SPA.bin 77878ae6db8e34de90e2e3e83741bf39
|
| | 1 csr1000v-2 RT ... csr1000v-universalk9.16.09.06.SPA.bin 77878ae6db8e34de90e2e3e83741bf39
|
| | [2 rows x 6 columns]
|
| | 现在重复脚本并添加两行代码来读取行数;行数也可以用作变量来控制我们的应用的流程。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
cp read_info1.py read_info2.py
|
| | ynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info2.py
|
| | read_info2.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | print(df)
|
| | number_of_rows = len(df.index)
|
| | print(number_of_rows)
|
| | 运行前面的脚本以获取行数;我们期望 2,因为默认情况下pandas
将第一行作为标题行读取。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info2.py
|
| | devicename device ... newios newiosmd5
|
| | 0 csr1000v-1 RT ... csr1000v-universalk9.16.09.06.SPA.bin 77878ae6db8e34de90e2e3e83741bf39
|
| | 1 csr1000v-2 RT ... csr1000v-universalk9.16.09.06.SPA.bin 77878ae6db8e34de90e2e3e83741bf39
|
| | [2 rows x 6 columns]
|
| | 2
|
| 4 | 我们试图读取每个值并将它们用作变量,所以我们在脚本中使用特定的信息。我们希望读取每一行,并将其转换为一种类型的数组,如列表或元组。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info3.py
|
| | read_info3.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | print(devicename)
|
| | print(device)
|
| | print(devicetype)
|
| | print(ip)
|
| | print(newios)
|
| | print(newiosmd5)
|
| | 当您运行前面的脚本时,现在可以轻松地访问数据,并以 Python 列表的形式进行检索。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info3.py
|
| | ['csr1000v-1', 'csr1000v-2']
|
| | ['RT', 'RT']
|
| | ['cisco_xe', 'cisco_xe']
|
| | ['192.168.183.111', '192.168.183.222']
|
| | ['csr1000v-universalk9.16.09.06.SPA.bin', 'csr1000v-universalk9.16.09.06.SPA.bin']
|
| | ['77878ae6db8e34de90e2e3e83741bf39', '77878ae6db8e34de90e2e3e83741bf39']
|
| 5 | 让我们稍微处理一下数据,并将其放入包含列表项的单个列表中。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info4.py
|
| | read_info4.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | # Convert the list into a device_list
|
| | device_list = []
|
| | for index, rows in df.iterrows():
|
| | device_append = [rows.devicename, rows.device, \
|
| | rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
|
| | device_list.append(device_append)
|
| | print(device_list)
|
| | 运行脚本,您会注意到读取的设备信息现在已经变成了一个列表列表。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info4.py
|
| | [['csr1000v-1', 'RT', 'cisco_xe', '192.168.183.111', 'csr1000v-universalk9.16.09.06.SPA.bin', '77878ae6db8e34de90e2e3e83741bf39'], ['csr1000v-2', 'RT', 'cisco_xe', '192.168.183.222', 'csr1000v-universalk9.16.09.06.SPA.bin', '77878ae6db8e34de90e2e3e83741bf39']]
|
| 6 | 如果我们只需要通过读取 CSV 文件创建的来自device_list
的特定信息,比如newios
和newiosmd5
,我们可以使用一个简单的循环来调用这些数字。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
cp read_info4.py read_info5.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info5.py
|
| | read_info5.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | # Convert the list into a device_list
|
| | device_list = []
|
| | for index, rows in df.iterrows():
|
| | device_append = [rows.devicename, rows.device, \
|
| | rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
|
| | device_list.append(device_append)
|
| | for x in device_list:
|
| | newios, newiosmd5 = x[4], x[5].lower()
|
| | print(newios, newiosmd5)
|
| | 现在运行脚本,您将获得每个设备的新 IOS 文件名和 MD5 值。由于我们有相同的设备类型,新的 IOS 名称和 MD5 值将是相同的。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info5.py
|
| | csr1000v-universalk9.16.09.06.SPA.bin 77878ae6db8e34de90e2e3e83741bf39
|
| | csr1000v-universalk9.16.09.06.SPA.bin 77878ae6db8e34de90e2e3e83741bf39
|
| 7 | 最初,当我们创建 CSV 文件时,我们使用了比所需更多的信息,这是有原因的。使用pandas
文件读取方法,我们还想创建一个包含netmiko
字典格式的列表。我们可以在登录路由器时将字典的值传递给用于 SSH 连接的netmiko ConnectHandler
。让我们研究一下简单的netmiko
字典格式,并使用 IP 地址和来自device_list
的devicetype
信息为 SSH 登录创建另一个字典。设备字典如下例所示。您还可以添加logging
和delay factors
选项,但我们将保持简单,以便在实验室使用。 |
| | device1 = {
|
| | 'device_type': 'cisco_ios',
|
| | 'host': '10.10.10.1',
|
| | 'username': 'username',
|
| | 'password': 'password',
|
| | 'secret': 'secret',
|
| | }
|
| | 因此,从 10.5.2 开始,我们已经可以通过交互式输入会话从用户那里收集用户名、密码和密码。现在我们已经从一个 CSV 文件中读取了device_type
和host
(IP 地址)值。我们可以使用收集的信息将它转换成 SSH 连接的netmiko
兼容的字典格式。请注意,我们添加了用户名和密码收集工具,以教您如何以交互方式收集它们。如果在极其安全的环境中运行 Python 脚本,可以通过文件读取方法提供所有信息,包括用户名、密码和密码。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
cp read_info5.py read_info6.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info6.py
|
| | read_info6.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | # Convert the list into a device_list
|
| | device_list = []
|
| | for index, rows in df.iterrows():
|
| | device_append = [rows.devicename, rows.device, \
|
| | rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
|
| | device_list.append(device_append)
|
| | i = 0
|
| | for x in device_list:
|
| | if len(x) !=0:
|
| | i += 1
|
| | name = f'device{str(i)}'
|
| | devicetype, host = x[2], x[3]
|
| | device = {
|
| | 'device_type': devicetype,
|
| | 'host': host,
|
| | 'username': 'username',
|
| | 'password': 'password',
|
| | 'secret': 'secret',
|
| | }
|
| | print(name, "=" ,device)
|
| | 现在,当您运行脚本时,您将看到信息被格式化为一个字典,我们可以将字典分配给变量,在本例中是device1
和device2
。想象一下,你有 20 或 200 台设备将阅读的信息转换成netmiko
友好的词典。有人说自动化完全是在命令的循环中。如果一个任务是重复性的,那就是自动化的潜在目标。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info6.py
|
| | device1 = {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'username', 'password': 'password', 'secret': 'secret'}
|
| | device2 = {'device_type': 'cisco_xe', 'host': '192.168.183.222', 'username': 'username', 'password': 'password', 'secret': 'secret'}
|
| 8 | 复制之前的脚本,创建一个名为read_info7.py
的新脚本。您将修改它,因此字典现在存储在一个列表中。换句话说,您将创建一个包含多个字典的列表。这样,我们可以在 SSH 连接到您的设备期间调用它们和字典。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
cp read_info6.py read_info7.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info7.py
|
| | 请注意,出于测试目的,我已经手动将用户名、密码和密码添加到该脚本中。但是,当我们集成前面开发的用户 ID 和密码收集工具时,变量将被替换。当您在生产环境中工作时,强烈建议您删除用户名和密码信息,或者完全删除包含敏感信息的文件。 |
| | read_info7.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | # Convert the list into a device_list
|
| | device_list = []
|
| | for index, rows in df.iterrows():
|
| | device_append = [rows.devicename, rows.device, \
|
| | rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
|
| | device_list.append(device_append)
|
| | device_list_netmiko = []
|
| | i = 0
|
| | for x in device_list:
|
| | if len(x) !=0:
|
| | i += 1
|
| | name = f'device{str(i)}'
|
| | devicetype, host = x[2], x[3]
|
| | device = {
|
| | 'device_type': devicetype,
|
| | 'host': host,
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | }
|
| | device_list_netmiko.append(device)
|
| | print(device_list_netmiko)
|
| | 当您运行这个脚本时,您应该得到一个包含两个字典作为条目的列表。如果您想将数据解析成 Python 脚本,以便从任何网络设备访问任何信息,那么您必须熟悉处理数据。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info7.py
|
| | [{'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}, {'device_type': 'cisco_xe', 'host': '192.168.183.222', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}]
|
| 9 | 为了测试这个脚本是否能按预期工作,添加一个netmiko ConnectHandler
,并运行 Cisco router
命令。注意,只有一个import
语句(来自netmiko import ConnectHandler
)和最后四行代码被添加到前面的脚本中。这个 Python 脚本可以从我的 GitHub 站点下载。 |
| | 下载 URL: |
| | 创建最终脚本以使用读取的数据,然后执行一个简单的任务;在这种情况下,运行show clock
命令来显示 CSR 路由器的时间。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
cp read_info7.py read_info8.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
nano read_info8.py
|
| | read_info8.py
|
| | import pandas as pd
|
| | from netmiko import ConnectHandler
|
| | [... omitted for brevity. Same as read_info7.py]
|
| | [... See the source code for full details.]
|
| | for device in device_list_netmiko:
|
| | net_connect = ConnectHandler(**device)
|
| | show_clock = net_connect.send_command("show clock")
|
| | print(show_clock)
|
| | 运行该脚本时,您应该会看到每台路由器的时间。show clock
命令是最简单的命令之一,您可以从脚本中运行它来检查您的 SSH 连接是否工作正常。检查端口 22 并不测试您的凭证,因此值得编写这样的代码来运行一个简单的show clock
命令。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool3_read_csv$
python3 read_info8.py
|
| | *13:37:19.262 UTC Fri Jan 15 2021
|
| | *13:37:20.403 UTC Fri Jan 15 2021
|
检查服务器上新 IOS 的 MD5 值
如前所述,认证新的 IOS 文件是成功升级 IOS 的关键。该 MD5 值可以从思科的网站上获得,或者你可以在新的 IOS 下载后,使用WinMD5.exe
等工具甚至命令行来检查文件的 MD5 值。在上传文件之前,我们要检查这个 MD5 值是否正确,以再次检查您将要上传的文件的完整性。当您手动将新的 IOS 文件上传到 TFTP 或 FTP 服务器时,您将手动验证这一点,但是在我们的 IOS 升级工具中,我们将 TFTP/FTP 文件传输方法替换为安全复制协议(SCP)文件传输。在文件传输发生之前,我们希望对照良好的 MD5 值检查 SCP 文件夹中 IOS 文件的 MD5 值,这样可以保证文件的完整性,从而避免意外的结果。
|
工作
|
| — | — |
| one | 首先,使用 WinSCP 或 FileZilla 创建一个目录来放置新的 IOS 文件。见图 18-4 。 |
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir new_ios
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd new_ios
|
| | pynetauto@ubuntu20s1:~/my_tools/new_ios$
pwd
|
| | /home/pynetauto/my_tools/new_ios
|
| | pynetauto@ubuntu20s1:~/my_tools/new_ios$
ls
|
| | csr1000v-universalk9.16.09.06.SPA.bin
图 18-4。ubuntu20s1,将新的 IOS 复制到 new_ios 目录 |
| Two | 创建tool4_md5_linux
目录;然后复制read_info5.py
文件作为md5_validate1.py
脚本的基础。还有,为了帮助我们的开发,复制devices_info.csv
文件。read_info5.py
和devices_info.csv
文件都来自于tool3
开发。 |
| | pynetauto@ubuntu20s1:~/my_tools/new_ios$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
pwd
|
| | /home/pynetauto/my_tools
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool4_md5_linux
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool4_md5_linux
|
| | pynetauto@ubuntu20s1:~/my_tools/tool4_md5_linux$
cp /home/pynetauto/my_tools/tool3_read_csv/read_info5.py ./md5_validate1.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool4_md5_linux$
cp /home/pynetauto/my_tools/tool3_read_csv/devices_info.csv ./devices_info.csv
|
| | 我们想要修改来自read_info5.py
的代码的最后一个for
循环,所以我们的任务是修改这个for
循环,并添加一个函数来检查刚刚复制的 IOS 文件的 MD5 值,并检查稍后在闪存大小检查期间要使用的文件大小。 |
| | md5_validate1.py
|
| | import pandas as pd
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | # Convert the list into a device_list
|
| | device_list = []
|
| | for index, rows in df.iterrows():
|
| | device_append = [rows.devicename, rows.device, \
|
| | rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
|
| | device_list.append(device_append)
|
| | for x in device_list:
|
| | newios, newiosmd5 = x[4], x[5].lower()
|
| | print(newios, newiosmd5)
|
| 3 | 现在为内置的os.path
和hashlib
模块添加导入代码。hashlib
将用于检查 Linux 服务器上文件的 MD5,而os.path
将用于计算实际的文件大小。脚本的中间部分与复制的脚本相同。尽管如此,您仍将更改device_list:
中的for x
,以便脚本返回在服务器端计算的 MD5 值和newios
大小,稍后将使用它们来检查路由器闪存上的空闲大小是否能容纳新的 IOS 大小。更改后,md5_validate1.py
看起来应该类似于这样: |
| | pynetauto@ubuntu20s1:~/my_tools/tool4_md5_linux$
nano md5_validate2.py
|
| | md5_validate2.py
|
| | import pandas as pd
|
| | import os.path
|
| | import hashlib
|
| | [... omitted for brevity, same as read_info5.py]
|
| | for x in device_list:
|
| | print(x[0])
|
| | newios = x[4]
|
| | newiosmd5 = x[5].lower()
|
| | newiosmd5hash = hashlib.md5()
|
| | file = open(f'/home/pynetauto/my_tools/new_ios/{newios}', 'rb')
# change path to your own path
|
| | content = file.read()
|
| | newiosmd5hash.update(content)
|
| | newiosmd5server = newiosmd5hash.hexdigest()
|
| | print(newiosmd5server)
|
| | newiossize = round(os.path.getsize(f'/home/pynetauto/my_tools/new_ios/{newios}')/1000000, 2)
|
| | print(newiossize, "MB")
|
| | 运行该脚本,它应该返回服务器端的 IOS MD5 值和实际的 IOS 大小(以兆字节为单位)。您还将学习如何检查路由器的已用、空闲和总大小,以确保闪存有足够的空闲空间来容纳新的 IOS。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool4_md5_linux$
python3 md5_validate2.py
|
| | csr1000v-1
|
| | 77878ae6db8e34de90e2e3e83741bf39
|
| | 436.57 MB
|
| | csr1000v-2
|
| | 77878ae6db8e34de90e2e3e83741bf39
|
| | 436.57 MB
|
| four | 一旦您对步骤 3 中的结果感到满意,就在最后一行的末尾添加一个if-else
语句,看看如何通过测试两个 MD5 值来控制脚本流。 |
| | md5_validate3.py
|
| | import pandas as pd
|
| | import os.path
|
| | import hashlib
|
| | df = pd.read_csv(r'./devices_info.csv')
|
| | number_of_rows = len(df.index)
|
| | # Read the values and save as a list, read column as df and save it as a list
|
| | devicename = list(df['devicename'])
|
| | device = list(df['device'])
|
| | devicetype = list(df['devicetype'])
|
| | ip = list(df['host'])
|
| | newios = list(df['newios'])
|
| | newiosmd5 = list(df['newiosmd5'])
|
| | # Convert the list into a device_list
|
| | device_list = []
|
| | for index, rows in df.iterrows():
|
| | device_append = [rows.devicename, rows.device, \
|
| | rows.devicetype, rows.host, rows.newios, rows.newiosmd5]
|
| | device_list.append(device_append)
|
| | for x in device_list:
|
| | print(x[0])
|
| | newios = x[4]
|
| | newiosmd5 = str(x[5].lower()).strip()
|
| | print(newiosmd5)
|
| | newiosmd5hash = hashlib.md5()
|
| | file = open(f'/home/pynetauto/my_tools/new_ios/{newios}', 'rb')
|
| | content = file.read()
|
| | newiosmd5hash.update(content)
|
| | newiosmd5server = newiosmd5hash.hexdigest()
|
| | print(newiosmd5server.strip())
|
| | newiossize = round(os.path.getsize(f'/home/pynetauto/my_tools/new_ios/{newios}')/1000000, 2)
|
| | print(newiossize, "MB")
|
| | if newiosmd5server == newiosmd5:
|
| | print("MD5 values matched!")
|
| | else:
|
| | print("Mismatched MD5 values. Exit")
|
| | exit()
|
| | 当您运行该脚本时,您将看到类似于以下输出的结果。现在,您已经确认了所读取的信息已经被转换成 Python 变量,以便在我们的脚本中使用。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool4_md5_linux$
python3 md5_validate3.py
|
| | csr1000v-1
|
| | 77878ae6db8e34de90e2e3e83741bf39
|
| | 77878ae6db8e34de90e2e3e83741bf39
|
| | 436.57 MB
|
| | MD5 values matched!
|
| | csr1000v-2
|
| | 77878ae6db8e34de90e2e3e83741bf39
|
| | 77878ae6db8e34de90e2e3e83741bf39
|
| | 436.57 MB
|
| | MD5 values matched!
|
检查 Cisco 路由器上的闪存大小
您在前面的脚本中学习了如何获取服务器上的 IOS 大小,新的 IOS 文件大小为 436.57 MB。在将这个新的 IOS 文件上传到路由器的闪存之前,您必须检查闪存中是否有足够的可用空间。如果闪存有足够的空闲空间,上传可以立即进行。不过,如果闪存大小不够大,您将不得不删除旧的或冗余的文件,或者对于较旧的 IOS 设备,您将需要从闪存中删除当前运行的 IOS 文件。在引导过程中,IOS 从闪存复制并解压缩到随机存取存储器(RAM)中。在一些最新的思科平台上,文件已经解压缩,以节省开机自检(POST)过程中的时间。无论哪种方式,您都需要为新的 IOS 文件腾出足够的空间。要获得 Cisco 设备上的空闲闪存大小,您可以使用show flash:
或dir
命令,然后使用正则表达式解析信息。如果您知道要运行什么命令以及需要什么信息,就很容易得到这些信息。尽管如此,许多供应商网络设备不提供 API 支持。此外,SNMP 在处理这类信息时也有其局限性。虽然这不是收集我们想要的数据的最聪明或最复杂的方式,但这可能是一些设备的唯一选择。
计算出空闲闪存大小后,如果 IOS 大小超过了空闲闪存大小,我们希望给用户(或脚本)一个选项来定位旧的 IOS 文件并删除它。有时,路由器的闪存上可能有一个很大的文件,所以在这种情况下,我们还必须给用户(或脚本)一个选项来搜索文件和删除一个大文件。让我们看看这是如何实现的。
||
工作
|
| — | — |
| 1 | 创建另一个名为tool5_fsize_cisco
的工作目录,然后创建一个新的脚本来运行和捕获dir
或show flash:
命令。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool4_md5_linux$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool5_fsize_cisco
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool5_fsize_cisco
|
| | pynetauto@ubuntu20s1:~/my_tools/tool5_fsize_cisco$
nano check_flash1.py
|
| | 编写以下脚本来捕获 Cisco 路由器上的dir
命令的输出。在 IOS XE 路由器上,使用dir
命令更容易捕获信息,但是在其他设备上,show flash:
命令在大多数设备上都有效。在这个练习中,我们将使用dir
命令,因为输出更短。 |
| | check_flash1.py
|
| | import time
|
| | from netmiko import ConnectHandler
|
| | # Borrowed from read_info7.py result.
|
| | devices_list =[
|
| | {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | },
|
| | {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | }]
|
| | for device in devices_list:
|
| | net_connect = ConnectHandler(**device)
|
| | net_connect.send_command("terminal length 0")
|
| | showdir = net_connect.send_command("dir")
|
| | #showflash = net_connect.send_command("show flash:") # Alternatively use 'show flash:'
|
| | print(showdir)
|
| | print("-"*80)
|
| | time.sleep(2)
|
| | 当您再次运行 check_flash1.py 脚本时,输出会显示文件详细信息和闪存使用情况。我们对路由器闪存中的空闲空间感兴趣。这里,我们感兴趣的是输出的最后一行中的“总共 7897796608 个字节(5230376448 个空闲字节)”。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool5_fsize_cisco$
python3 check_flash1.py
|
| | Directory of bootflash:/
|
| | 11 drwx 16384 Sep 28 2020 22:15:16 +00:00 lost+found
|
| | 32513 drwx 4096 Jan 15 2021 08:38:08 +00:00 .installer
|
| | 12 -rw- 377500632 Sep 28 2020 22:15:59 +00:00 csr1000v-mono-universalk9.16.07.03.SPA.pkg
|
| | 13 -rw- 38978681 Sep 28 2020 22:15:59 +00:00 csr1000v-rpboot.16.07.03.SPA.pkg
|
| | [...omitted for brevity]
|
| | 430785 drwx 4096 Dec 3 2020 10:21:10 +00:00 iox
|
| | 65025 drwx 4096 Dec 3 2020 10:21:25 +00:00 .dbpersist
|
| | 89409 drwx 4096 Dec 3 2020 10:22:16 +00:00 onep
|
| | 7897796608 bytes total (6230376448 bytes free)
|
| | --------------------------------------------------------------------------------
|
| 2 | 这是第一个路由器的dir
输出。让我们看看如何使用正则表达式来获得“空闲字节” |
| | Directory of bootflash:/
|
| | 11 drwx 16384 Sep 28 2020 22:15:16 +00:00 lost+found
|
| | 32513 drwx 4096 Oct 5 2020 11:50:08 +00:00 .installer
|
| | 12 -rw- 377500632 Sep 28 2020 22:15:59 +00:00 csr1000v-mono-universalk9.16.07.03.SPA.pkg
|
| | 13 -rw- 38978681 Sep 28 2020 22:15:59 +00:00 csr1000v-rpboot.16.07.03.SPA.pkg
|
| | 14 -rw- 1941 Sep 28 2020 22:15:59 +00:00 packages.conf
|
| | 365761 drwx 4096 Sep 28 2020 22:16:36 +00:00 core
|
| | 390145 drwx 4096 Sep 28 2020 22:16:29 +00:00 .prst_sync
|
| | 81281 drwx 4096 Sep 28 2020 22:16:36 +00:00 .rollback_timer
|
| | 455169 drwx 12288 Oct 5 2020 12:00:45 +00:00 tracelogs
|
| | 48769 drwx 4096 Sep 28 2020 22:16:55 +00:00 virtual-instance
|
| | 15 -rw- 30 Oct 5 2020 11:51:27 +00:00 throughput_monitor_params
|
| | 16 -rw- 848 Oct 5 2020 11:51:35 +00:00 cvac.log
|
| | 447041 drwx 4096 Sep 28 2020 22:17:39 +00:00 CRDU
|
| | 17 -rw- 16 Sep 28 2020 22:17:43 +00:00 ovf-env.xml.md5
|
| | 18 -rw- 157 Oct 5 2020 11:51:35 +00:00 csrlxc-cfg.log
|
| | 19 -rw- 35 Sep 28 2020 22:22:44 +00:00 pnp-tech-time
|
| | 20 -rw- 51746 Sep 28 2020 22:22:45 +00:00 pnp-tech-discovery-summary
|
| | 7897796608 bytes total (``7060598784
|
| | 由于有许多数字,我们必须精确地定位数字,这可以通过使用积极的前瞻方法来实现。由于数字在bytes free
的前面,我们可以用它作为搜索句柄,简单地查找这个特定字符串前面的数字串。因此,所需的正则表达式如下所示: |
| | \d+(?=\sbytes\sfree\))
|
| | 所以,前面的正则表达式应该匹配出现在bytes free
前面的任何数字,在我们的例子中是 7060598784。 |
| 3 | 复制第一个脚本,并创建以下脚本来应用我们的正则表达式: |
| | pynetauto@ubuntu20s1:~/my_tools/tool5_fsize_cisco$
cp check_flash1.py check_flash2.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool5_fsize_cisco$
nano check_flash2.py
|
| | nano check_flash2.py
|
| | import time
|
| | from netmiko import ConnectHandler
|
| | import re
# Import re module
|
| | # This is borrowed from read_info7.py result
|
| | devices_list =[
|
| | {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | },
|
| | {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | }]
|
| | for device in devices_list:
|
| | net_connect = ConnectHandler(**device)
|
| | net_connect.send_command("terminal length 0")
|
| | showdir = net_connect.send_command("dir")
|
| | #showflash = net_connect.send_command("show flash:")
|
| | #print(showdir) # Hash out the line
|
| | print("-"*80)
|
| | time.sleep(2)
|
| | p1 = re.compile("\d+(?=\sbytes\sfree\))")
# Compiled Regular expression
|
| | m1 = p1.findall(showdir)
# Match regular expression
|
| | flashfree = ((int(m1[0])/1000000))
# convert bytes into MB
|
| | print(flashfree)
|
| | 运行前面的脚本,现在您有了以兆字节为单位的空闲字节。第一个值来自csr1000v-1
,第二个值来自csr1000v-2
。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool5_fsize_cisco$
python3 check_flash2.py
|
| | --------------------------------------------------------------------------------
|
| | 7060.598784
|
| | --------------------------------------------------------------------------------
|
| | 7060.578304
|
| | 当我们将之前的脚本与之前练习中的md5_validate2.py
结合起来时,我们可以检查闪存的空闲大小是否大于新的 IOS 文件大小。可以使用比较的结果来改变应用流程。路由器上几乎有 7 GB 的空闲闪存,我们的 IOS 大小为 436.57 MB,因此在将文件上传到闪存之前,无需删除任何文件。但是,如上所述,在较旧的 Cisco 设备上,闪存的大小通常很紧,作为脚本的一部分,您可能需要为用户提供一个选项,从闪存中删除一些文件,以便为新的 IOS 上传腾出空间。通常,我们会在路由器或交换机等关键设备上升级 IOS。工程师投入超过 80%的时间来准备这种类型的更改,这意味着在准备工作开始时就可以检测到闪存空间的不足。大多数情况下,问题需要在实际升级之前解决。但是有时工程师会在变更准备过程中试图偷工减料。如今,大多数声誉卓著的 IT 驱动型公司都采用了 ITIL 流程,而 ITIL 则负责变革管理。这种变更案例的所有者甚至在变更被涉众批准之前就执行端到端的检查,只有这样变更才能开始。出于这个原因,为了节省本章的篇幅,我排除了让用户选择删除文件或在目录中查找文件的脚本。我会把这个作为你的家庭作业,让你知道如何把这个特性添加到你的脚本中。 |
备份运行配置、接口状态和路由表
假设我们的路由器有足够的闪存来容纳路由器的新 IOS,您现在想要备份运行配置。在大多数生产环境中,路由器的配置将在配置备份服务器上每天或每周备份一次。在这种情况下,您必须确保正在升级的设备的最新备份配置不超过几个小时。换句话说,最新的路由器备份是您在路由器重新加载之前捕获的。使用 Python 脚本,有两种方法可以备份路由器的running-config
。第一种方法是将配置保存在 Python 服务器的本地磁盘上,第二种方法是运行copy running-config tftp/ftp:
命令。如果备份将存储一段时间,第二种方法将是首选的running-config
备份方法。尽管如此,如果这个备份是为了恢复过程,如果系统由于某种看不见的原因丢失了配置,在您的服务器上做一个备份就足够了。因此,您将学习如何通过 SSH 连接使用show
命令保存运行配置。
在进行任何更改之前,请先备份运行配置,然后再重新加载路由器。
||
工作
|
| — | — |
| 1 | 同样,创建一个单独的目录来处理新工具。因为本节的内容已经在前面的章节中介绍过了,所以您应该对文件处理非常熟悉。您将在一次尝试中编写这个工具,不需要重复,所以请在这里键入每个单词,并参考代码行中的解释。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool5_fsize_cisco$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool6_make_backup
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool6_make_backup
|
| | pynetauto@ubuntu20s1:~/my_tools/tool6_make_backup$
nano make_backup1.py
|
| | 这个脚本将使每个设备的字典和它们的变量变得更加简单。您将调出每个设备的 IP 地址,以形成每个文件的名称。这样,每台设备都将有自己唯一的备份文件名,以后在对问题进行故障排除时,您将不会有一个包含多台设备配置的文件。您可以运行任何show
命令并备份路由器的运行配置。然而,思科路由器值得捕捉的一些基本的show
命令有show running-config
、show ip route
和show ip interface brief
。如果您正在使用 Cisco 交换机,show running-config
、show ip interface brief
和show switch
命令可能会有所帮助。现在,让我们编写并运行这段代码。 |
| | make_backup1.py
|
| | import time
|
| | from netmiko import ConnectHandler
|
| | # Converted the list of dictionaries back to each variables
|
| | device1
= {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | }
|
| | device2
= {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | }
|
| | devices_list = [device1, device2]
# Make a list of devices
|
| | for device in devices_list:
# Read each device from device_list
|
| | print(device)
# Print out each device information during development
|
| | ip = str(device['host'])
# To make unique file names, assign IP address of device as variable, ip.
|
| | f1 = open(ip + '_showrun_1.txt', 'w+')
# Create and open f1
|
| | net_connect = ConnectHandler(**device)
#Connect to device, ** denotes parsing of a dictionary
|
| | net_connect.send_command("terminal length 0")
# Change terminal length to 0
|
| | showrun = net_connect.send_command("show running-config")
# Run show run and save to showrun variable
|
| | f1.write(showrun)
# Write showrun to f1\. showrun is in the memory.
|
| | time.sleep(1)
# Pause for 1 second
|
| | f1.close()
# Close f1
|
| | # The remaining codes are same as above except the file name and actual command
|
| | f2 = open(ip + '_show_ip_route_1.txt', 'w')
# Crate and open f2
|
| | #net_connect.enable()
|
| | showiproute = net_connect.send_command("show ip route")
|
| | #net_connect.exit_enable_mode()
|
| | f2.write(showiproute)
|
| | time.sleep(1)
|
| | f2.close()
|
| | f3 = open(ip + '_show_ip_int_bri_1.txt', 'w')
|
| | showiproute = net_connect.send_command("show ip interface brief")
|
| | f3.write(showiproute)
|
| | time.sleep(1)
|
| | f3.close()
|
| | print("All tasks completed successfully")
# Information to let you know that all tasks completed
|
| 2 | 现在运行脚本。它将通过运行您选择的show
命令进行备份。这里我们将三个show
命令输出捕获到不同的文件中。稍后,升级后检查将重新运行相同的命令,并创建重新加载后文件。然后,脚本将执行升级后检查,以确保升级前后的配置保持相同,并且 IOS 升级不会导致任何故障。 |
| | 一旦你完成了编写make_backup1.py
Python 代码,运行它来备份show
命令。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool6_make_backup$
python3 make_backup1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.222', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | All tasks completed successfully
|
| | 使用ls –lh
Linux 命令检查show
命令的备份和运行配置。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool6_make_backup$
ls -lh
|
| | total 28K
|
| | -rw-rw-r-- 1 pynetauto pynetauto 323 Jan 15 15:33 192.168.183.111_show_ip_int_bri_1.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 842 Jan 15 15:33 192.168.183.111_show_ip_route_1.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 4.0K Jan 15 15:33 192.168.183.111_showrun_1.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 323 Jan 15 15:33 192.168.183.222_show_ip_int_bri_1.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 842 Jan 15 15:33 192.168.183.222_show_ip_route_1.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 1.8K Jan 15 15:33 192.168.183.222_showrun_1.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 1.8K Jan 15 15:33 make_backup1.py
|
您已经成功创建了一个方便的工具,可以在进行任何配置更改之前备份您的设备。现在,在将新的 IOS 上传到 Cisco 路由器之前,我们将完成初始预检查工具。让我们看看如何将新的 IOS 上传到路由器的闪存中。
B 部分:IOS 上传和更多预检工具开发
让我们进入下一个话题。
IOS 上传工具
在整个预检查通过并且备份了每台设备的当前运行配置之后,我们需要将新的 IOS 从服务器上传到路由器的闪存中。如前所述,文件上传过程使用 SCP,因为netmiko
库有一个内置模块,支持不同文件大小的计时。作为一个netmiko
库用户,我们不用担心文件有多大才能上传文件;netmiko
已经有一个内置模块来处理这个问题。再次感谢 Kirk Byers 编写并分享了这样一个方便的库。我已经用 FTP 和 TFTP 两种方法测试了文件传输,虽然它们工作正常,但不如netmiko
的 SCPConn 文件传输方法可靠。
我们已经知道,我们试图上传的实际 IOS 文件大小为 436.57 MB,在生产网络上,将这样的文件上传到网络设备的闪存大约需要 8 到 15 分钟。即使在您计算机上的实验室拓扑中,也需要几分钟。所以,当你开发一个测试工具时,你应该使用一个更小的文件,这样可以节省你的时间。在这个例子中,我使用完整的 436.57 MB 文件进行演示。在生产中,一个网络的速度会不同于另一个网络;因此,文件传输时间也会因网络不同而有很大差异。
||
工作
|
| — | — |
| 1 | 为 IOS 上传工具创建另一个工作目录。这一次,您将复制上一节中创建的脚本,并重写代码来测试文件上传。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool6_make_backup$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool7_upload_ios
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool7_upload_ios
|
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
pwd
|
| | /home/pynetauto/my_tools/tool7_upload_ios
|
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
cp /home/pynetauto/my_tools/tool6_make_backup/make_backup1.py ./upload_ios1.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
ls
|
| | upload_ios1.py
|
| 2 | 在 IOS 上传部分,我们将新的 IOS 文件复制到了/home/pynetauto/my_tools/new_ios/
目录。对于 SCP 文件上传测试,我们可以将文件位置指向这个文件夹,或者复制当前工作目录。所以,s_newios
(源文件位置)指向新的 IOS 文件:/home/pynetauto/my_tools/new_ios/csr1000v-universalk9.16.09.06.SPA.bin
。对于d_newios
(对于目标文件名,使用新的 IOS 文件名,以.bin
结尾)。文件将被保存到路由器的flash:/
目录中。 |
| | Cisco 路由器(或交换机)上需要三项配置,SCP 文件传输才能启动并成功地将文件复制到路由器。首先,用户必须拥有 15 级权限,其次,必须配置aaa
身份验证登录,第三,必须预先配置授权exec
。如果设备访问由生产环境中的 TACACS 服务器控制,您应该对以下脚本稍作修改。登录将以同样的方式工作,只是身份验证是通过服务器进行的。此外,在许多环境中,不使用 TACACS 服务器。在这种情况下,我们可以利用本地aaa
授权和认证,这就是您在本例中将要使用的。该脚本将首先检查您的用户名是否具有 15 级权限,然后检查是否配置了aaa
认证和授权。 |
| | SCP 文件传输的最后一个要求是路由器必须扮演 SCP 服务器的角色,所以我们会检查是否已经配置了ip scp server
命令,如果没有配置,脚本会在路由器上启用 SCP 服务,然后启动 IOS 文件传输过程。文件传输结束时,此 SCP 服务将被禁用。 |
| | 现在修改 Python 文件的内容,当您完成 SCP IOS 上传应用时,它应该类似于下面的内容。如果您想添加一些其他功能,您可以这样做,并探索其他选项。免费 Python 脚本的美妙之处在于没有像 Ansible 中的 YAML 代码那样的束缚。使用 Python 脚本,你是汽车的设计师、机械师和司机,而使用 Ansible,你只是一个司机。您可以继续使用 Python 脚本编写您的工具。和以前一样,你会在重要的代码行旁边找到解释。 |
| | upload_ios1.py
|
| | import time
|
| | from netmiko import ConnectHandler, SCPConn
# Import SCPConn
|
| | # From section 10.5.3
|
| | s_newios = "/home/pynetauto/my_tools/new_ios/csr1000v-universalk9.16.09.06.SPA.bin"
# Source file, ensure you have specified the correct directory path
|
| | d_newios = "csr1000v-universalk9.16.09.06.SPA.bin"
# Destination file name
|
| | # newiosmd5 = "77878ae6db8e34de90e2e3e83741bf39"
# MD5 value of new IOS file
|
| | device1 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | }
|
| | device2 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123'
|
| | }
|
| | devices_list = [device1, device2]
|
| | for device in devices_list
:
|
| | print(device)
# Print dictionary item for confirmation only
|
| | ip = str(device['host'])
# Assign a variable, ip to IP Address of device
|
| | username = str(device['username'])
# Assign a variable, username to username of device
|
| | net_connect = ConnectHandler(**device)
# Parse netmiko dictionary to ConnectHandler
|
| | net_connect.send_command("terminal length 0")
# Set terminal to display without any breaks
|
| | showrun = net_connect.send_command("show running-config")
# Run show running-config
|
| | check_priv15 = (f'username {username} privilege 15')
# Assign a variable to username config
|
| | aaa_authenication = "aaa authentication login default local enable"
# Assign a variable to authentication config
|
| | aaa_authorization = "aaa authorization exec default local"
# Assign a variable to authorization config
|
| | if check_priv15 in showrun
: # Check if showrun contains (meets) basic configuration requirements
|
| | print(f"{username} has level 15 privilege - OK")
|
| | if aaa_authenication in showrun
:
|
| | print("check_aaa_authentication - OK")
|
| | if aaa_authorization in showrun:
|
| | print("check_aaa_authorization - OK")
# All three conditions are met, then continue
|
| | else:
|
| | print("aaa_authorization - FAILED ")
|
| | exit()
# exit application for a review
|
| | else:
|
| | print("aaa_authentication - FAILED ")
|
| | exit()
# exit application for a review
|
| | else:
|
| | print(f"{username} has not enough privilege - FAILED")
|
| | exit()
# exit application for a review
|
| | net_connect.enable(cmd='enable 15')
# Enable level 15 privilage
|
| | net_connect.config_mode()
# Enter configuration mode
|
| | net_connect.send_command('ip scp server enable')
# Enable SCP service on the router
|
| | net_connect.exit_config_mode()
# Exit configuration mode
|
| | time.sleep(1)
# Pause for 1 second
|
| | print("New IOS uploading in progress! Please wait...")
|
| | scp_conn = SCPConn(net_connect)
# Create object for netmiko SCPConn
|
| | scp_conn.scp_transfer_file(s_newios, d_newios)
# Start file transfer from source to destination
|
| | scp_conn.close()
# close scp_conn session
|
| | time.sleep(1)
# Pause for 1 second
|
| | net_connect.config_mode()
# Enter configuration mode
|
| | net_connect.send_command('no ip scp server enable')
# Disable SCP service on the router
|
| | net_connect.exit_config_mode()
# Exit configurator mode
|
| | print("-"*80)
# Line divider
|
| 3 | 我们知道用户pynetauto
被正确地配置了 15 级管理员权限,但是aaa
新模型在csr1000v-1
路由器上没有启用。让我们运行前面的脚本来测试返回了哪个错误消息。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
python3 upload_ios1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | pynetauto has level 15 privilege - OK
|
| | aaa_authentication - FAILED
|
| | 现在,继续向第一台路由器添加以下aaa
配置: |
| | csr1000v-1(config)#
aaa new-model
|
| | csr1000v-1(config)#
aaa authentication login default local enable
|
| 4 | 让我们再运行一次 IOS 上传应用,观察脚本在哪里停止。尽管 admin 用户拥有 15 级权限,但脚本抱怨用户没有正确的权限,因为它现在已经转移到了aaa
本地认证。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
python3 upload_ios1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | pynetauto has not enough privilege - FAILED
|
| | 为了解决这个问题,将最后一个aaa
配置添加到第一个路由器。 |
| | csr1000v-1(config)#
aaa authorization exec default local
|
| 5 | 停下来!暂时不要运行脚本。如果您再次运行该应用,您应该会在控制台上看到以下消息,应用将进入下一阶段,检查路由器上的 SCP 配置。 |
| | 为了简单起见,我们可以在启动新的 IOS 上传到第一个设备之前检查这一点。该检查可以在脚本开始时在所有设备上执行,因此我们可以在上传开始之前检测所有设备上的任何配置问题。在最终脚本中,我们将尝试稍微更改这一部分,以便它成为预检查脚本的一部分。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
python3 upload_ios1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | pynetauto has level 15 privilege - OK
|
| | check_aaa_authentication - OK
|
| | check_aaa_authorization - OK
|
| 6 | 继续在第二台路由器csr1000v-2
上启用aaa
配置。 |
| | csr1000v-2(config)#
aaa new-model
|
| | csr1000v-2(config)#
aaa authentication login default local enable
|
| | csr1000v-2(config)#
aaa authorization exec default local
|
| 7 | 然后,在两台路由器上启用以下调试命令: |
| | csr1000v-2#
debug ip scp
|
| | Incoming SCP debugging is on
|
| | csr1000v-2#
terminal monitor
|
| | csr1000v-1#
debug ip scp
|
| | Incoming SCP debugging is on
|
| | csr1000v-1#
ter mon
|
| 8 | 现在重新运行 Python 脚本。如果所有的配置验证都通过了,新的 IOS 文件传输将在第一台路由器上开始。当文件传输在csr1000v-1
完成时,相同的脚本将在第二台路由器csr1000v-2
上运行。这里,我只演示了在两台路由器上上传 IOS,但是想象一下,如果我们有 20 或 200 台路由器来上传 IOS 映像。另外,请注意,现在我们可以将该脚本安排在工作时间之外运行,这样您就不必在获准回家之前盯着屏幕看几个小时了。这可能是自动化枯燥和重复的任务的最好的部分。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$
python3 upload_ios1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | pynetauto has level 15 privilege - OK
|
| | check_aaa_authentication - OK
|
| | check_aaa_authorization – OK
|
| | New IOS uploading in progress! Please wait...
|
| | --------------------------------------------------------------------------------
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.222', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123'}
|
| | pynetauto has level 15 privilege - OK
|
| | check_aaa_authentication - OK
|
| | check_aaa_authorization – OK
|
| | New IOS uploading in progress! Please wait...
|
| | --------------------------------------------------------------------------------
|
| | 在开发过程中,用户名、密码和密码被打印在屏幕上,以检查正在开发的应用是否如设计的那样工作,但是在生产实现中,您必须禁用任何冗余的print()
语句。本书开头提到过,print()
函数是给我们的;计算机不必把信息输出到屏幕上。 |
| 9 | 当脚本运行时,检查两台路由器的终端屏幕。如果您观察到以下屏幕日志,您就知道新的 IOS 文件传输已成功完成。 |
| | csr1000v-1#
|
| | *Jan 15 03:21:00.687: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty1 (192.168.183.132)
|
| | *Jan 15 03:21:02.228: SCP: [22 -> 192.168.183.132:46546] send <OK>
|
| | *Jan 15 03:21:02.229: SCP: [22 <- 192.168.183.132:46546] recv C0644 436573677 csr1000v-universalk9.16.09.06.SPA.bin
|
| | *Jan 15 03:21:02.230: SCP: [22 -> 192.168.183.132:46546] send <OK>
|
| | *Jan 15 03:25:34.917: SCP: [22 <- 192.168.183.132:46546] recv 436573677 bytes
|
| | *Jan 15 03:25:34.918: SCP: [22 <- 192.168.183.132:46546] recv <OK>
|
| | *Jan 15 03:25:34.918: SCP: [22 -> 192.168.183.132:46546] send <OK>
|
| | *Jan 15 03:25:34.922: SCP: [22 <- 192.168.183.132:46546] recv <EOF>
|
| | *Jan 15 03:25:36.192: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty1 (192.168.183.132)
|
| | csr1000v-2#
|
| | *Jan 15 03:25:37.522: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty1 (192.168.183.132)
|
| | *Jan 15 03:25:39.072: SCP: [22 -> 192.168.183.132:36148] send <OK>
|
| | *Jan 15 03:25:39.073: SCP: [22 <- 192.168.183.132:36148] recv C0644 436573677 csr1000v-universalk9.16.09.06.SPA.bin
|
| | *Jan 15 03:25:39.074: SCP: [22 -> 192.168.183.132:36148] send <OK>
|
| | *Jan 15 03:30:10.935: SCP: [22 <- 192.168.183.132:36148] recv 436573677 bytes
|
| | *Jan 15 03:30:10.935: SCP: [22 <- 192.168.183.132:36148] recv <OK>
|
| | *Jan 15 03:30:10.935: SCP: [22 -> 192.168.183.132:36148] send <OK>
|
| | *Jan 15 03:30:10.937: SCP: [22 <- 192.168.183.132:36148] recv <EOF>
|
| | *Jan 15 03:30:12.230: %SYS-5-CONFIG_I: Configured from console by pynetauto on vty1 (192.168.183.132)
|
| 10 | 检查两台路由器上上传的文件,保存配置以完成任务。 |
| | csr1000v-1#
show flash: | in csr1000v-universalk9.16.09.06.SPA.bin
|
| | 215 436573677 Oct 07 2020 03:25:34.0000000000 +00:00 /bootflash/csr1000v-universalk9.16.09.06.SPA.bin
|
| | csr1000v-1#
write memory
|
| | Building configuration...
|
| | [OK]
|
| | csr1000v-2#
show flash: | in csr1000v-universalk9.16.09.06.SPA.bin
|
| | 215 436573677 Oct 07 2020 03:30:10.0000000000 +00:00 /bootflash/csr1000v-universalk9.16.09.06.SPA.bin
|
| | csr1000v-2#
write memory
|
| | Building configuration...
|
| | [OK]
|
检查 Cisco 设备闪存上的新 IOS MD5 值
在之前的 IOS 上传工具开发中,您已经将新的 IOS 版本成功上传到了csr1000v-1
和csr1000v-2
路由器。我们需要编写一个脚本来验证 verify IOS 命令,并验证路由器闪存上 IOS 副本的 MD5 值。这一次,我们将只允许脚本运行,如果实际的 IOS 文件被发现在flash:/
根。然后运行verify
命令,检查在一个成功的 IOS 验证命令之后,脚本将在输出中期望“Verified ”,因此将使用一个正则表达式来验证 IOS 验证结果。如果成功,脚本将打印出原始值的 MD5 值和闪存上新 IOS 文件的 MD5 值。我们还想继续运行这个应用,即使第一个路由器在网络上不可达,所以我们必须包含一个netmiko
超时异常来解决这个问题。通常,即使您的脚本遇到异常,您也希望继续运行脚本,直到列表中的最后一个设备。
让我们快速编写这个应用,并在我们的实验室中测试它。
||
工作
|
| — | — |
| 1 | 和往常一样,让我们从创建一个新目录开始,然后创建一个新的 Python 脚本。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool7_upload_ios$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool8_md5_cisco
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool8_md5_cisco
|
| | pynetauto@ubuntu20s1:~/my_tools/tool8_md5_cisco$
nano md5_verify1.py
|
| 2 | 这个脚本的大部分内容与前面的脚本相同。让我们快速重写代码来验证命令,并比较用户提供的 MD5 和路由器上计算的 MD5。应用流将基于两个 MD5 值比较的结果。 |
| | 在完成用于 Cisco IOS 路由器的 IOS MD5 检查器应用后,您的脚本将类似于以下内容。和往常一样,你的脚本不需要看起来和这个一样,只要它被优化并且运行良好。尝试一次写一行代码,因为这是一个很好的实践,可以让你感受到以写代码为生是什么感觉。如果你喜欢什么都自己做,那么你会喜欢写代码。 |
| | md5_verify1.py
|
| | from netmiko import ConnectHandler
|
| | from netmiko.ssh_exception import NetMikoTimeoutException
# To handle Timeout/Network exception
|
| | import re
# For match specific characters from the verified output
|
| | d_newios = "csr1000v-universalk9.16.09.06.SPA.bin"
|
| | newiosmd5 = "77878ae6db8e34de90e2e3e83741bf39"
|
| | device1 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2 # Run netmiko commands twice slower. If you are getting errors due to slow router/switch response, use these attributes to slow down the script
|
| | }
|
| | device2 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | devices_list = [device1, device2]
|
| | for device in devices_list:
|
| | print(device)
|
| | ip = str(device['host'])
|
| | try:
|
| | net_connect = ConnectHandler(**device)
|
| | net_connect.send_command("terminal length 0")
|
| | locate_newios = net_connect.send_command(f"show flash: | in {d_newios}")
# Check router's flash for new IOS image file
|
| | if d_newios in locate_newios:
# If new IOS is found on the router's flash, run this script
|
| | result = net_connect.send_command("verify /md5 flash:{} {}".format(d_newios,newiosmd5))
# Cisco IOS/IOS XE verify command, run and assign variable result to the output
|
| | print(result)
# Print the result, informational for user
|
| | net_connect.disconnect()
# Disconnect session
|
| | p1 = re.compile(r'Verified')
# Regular Expression (re) compiler for word 'Verified'
|
| | p2 = re.compile(r'[a-fA-F0-9]{31}[a-fA-F0-9]')
# re compiler for MD5 value
|
| | verified = p1.findall(result)
# Find 'Verified' in result
|
| | newiosmd5flash = p2.findall(result)
# Find the MD5 value from result
|
| | if verified:
# If 'Verified' was found in result, run this part of the script
|
| | result = True
|
| | print("-"*80)
|
| | print("MD5 values MATCH! Continue")
|
| | print("MD5 of new IOS on Server : ",newiosmd5)
|
| | print("MD5 of new IOS on flash : ",newiosmd5flash[0])
|
| | print("-"*80)
|
| | else:
# If 'Verified' was not found in result, print and exit the application
|
| | result = False
|
| | print("-"*80)
|
| | print("MD5 values DO NOT MATCH! Exiting.")
|
| | print("-"*80)
|
| | exit()
|
| | else:
# If no new IOS file was found on the router's flash:/. Print the statement
|
| | print("No new IOS found on router's flash. Continue to next device...")
|
| | print("-"*80)
|
| | except (NetMikoTimeoutException):
# Handle Timeout error due to network issue
|
| | print (f'Timeout error to : {ip}')
|
| | print("-"*80)
|
| | continue
# Continue to next device
|
| | except unknown_error:
# Handle other errors as exception
|
| | print ('Unknown error occured : ' + str(unknown_error))
|
| | print("-"*80)
|
| | continue
# Continue to next device
|
| | print("Completed new IOS verification.")
# Informational
|
| 3 | 确保两台路由器都已通电并处于正常工作模式。运行应用检查新上传的 IOS 映像文件的 MD5 值。如果一切正常,您应该会得到如下所示的输出。为了节省空间,省略了一些输出。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool8_md5_cisco$
python3 md5_verify1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123', 'global_delay_factor': 2}
|
| | ...........................................................
[omitted for brevity]
|
| | ...........................................................................Done!
|
| | Verified (bootflash:csr1000v-universalk9.16.09.06.SPA.bin) = 77878ae6db8e34de90e2e3e83741bf39
|
| | --------------------------------------------------------------------------------
|
| | MD5 values MATCH! Continue
|
| | MD5 of new IOS on Server : 77878ae6db8e34de90e2e3e83741bf39
|
| | MD5 of new IOS on flash : 77878ae6db8e34de90e2e3e83741bf39
|
| | --------------------------------------------------------------------------------
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.222', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123', 'global_delay_factor': 2}
|
| | ..........................................................
[...omitted for brevity]
|
| | ...........................................................................Done!
|
| | Verified (bootflash:csr1000v-universalk9.16.09.06.SPA.bin) = 77878ae6db8e34de90e2e3e83741bf39
|
| | --------------------------------------------------------------------------------
|
| | MD5 values MATCH! Continue
|
| | MD5 of new IOS on Server : 77878ae6db8e34de90e2e3e83741bf39
|
| | MD5 of new IOS on flash : 77878ae6db8e34de90e2e3e83741bf39
|
| | --------------------------------------------------------------------------------
|
| | Completed new verification.
|
| 4 | 要模拟csr1000v-1
不在网络上(不可达)的场景,禁用千兆以太网 1。您必须通过 VMware 工作站的主控制台禁用该端口。 |
| | csr1000v-1#
config terminal
|
| | csr1000v-1(config)#
interface GigabitEthernet1
|
| | csr1000v-1(config-if)#
shutdown
|
| | 现在重新运行 Python 脚本来检查结果。因为我们已经添加了一个异常来解决这个问题,所以脚本将继续运行直到结束。如果没有添加异常处理程序,您将看到以下错误: |
| | pynetauto@ubuntu20s1:~/my_tools/tool8_md5_cisco$
python3 md5_verify1.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123', 'global_delay_factor': 2}
|
| | Traceback (most recent call last):
|
| | File "/usr/local/lib/python3.8/dist-packages/netmiko/base_connection.py", line 920, in establish_connection
|
| | self.remote_conn_pre.connect(**ssh_connect_params)
|
| | File "/usr/lib/python3/dist-packages/paramiko/client.py", line 368, in connect
|
| | raise NoValidConnectionsError(errors)
|
| | paramiko.ssh_exception.NoValidConnectionsError: [Errno None] Unable to connect to port 22 on 192.168.183.111
|
| | 在处理上一个异常的过程中,发生了另一个异常: |
| | Traceback (most recent call last):
|
| | File``" md5_verify1.py"
|
| | net_connect = ConnectHandler(**device)
|
| | File "/usr/local/lib/python3.8/dist-packages/netmiko/ssh_dispatcher.py", line 312, in ConnectHandler
|
| | return ConnectionClass(*args, **kwargs)
|
| | File "/usr/local/lib/python3.8/dist-packages/netmiko/cisco/cisco_ios.py", line 17, in __init__
|
| | return super().__init__(*args, **kwargs)
|
| | File "/usr/local/lib/python3.8/dist-packages/netmiko/base_connection.py", line 346, in __init__
|
| | self._open()
|
| | File "/usr/local/lib/python3.8/dist-packages/netmiko/base_connection.py", line 351, in _open
|
| | self.establish_connection()
|
| | File "/usr/local/lib/python3.8/dist-packages/netmiko/base_connection.py", line 942, in establish_connection
|
| | raise NetmikoTimeoutException(msg)
|
| | netmiko.ssh_exception.NetmikoTimeoutException: TCP connection to device failed.
|
| | Common causes of this problem are:
|
| | 1\. Incorrect hostname or IP address.
|
| | 2\. Wrong TCP port.
|
| | 3\. Intermediate firewall blocking access.
|
| | Device settings: cisco_xe 192.168.183.111:22
|
| 5 | 现在从第一台路由器上删除新的 IOS 版本,并检查您的脚本是否成功运行。您必须从 VMware 工作站控制台启用csr1000v-1
的千兆以太网 1 端口,因为您将失去通过 SSH 与该设备的连接。 |
| | csr1000v-1#
delete flash:/csr1000v-universalk9.16.09.06.SPA.bin
|
| | Delete filename [csr1000v-universalk9.16.09.06.SPA.bin]?
|
| | Delete bootflash:/csr1000v-universalk9.16.09.06.SPA.bin?
[confirm]
|
| | 该脚本应该成功运行,并继续运行,直到结束,这也不应该是我们的脚本中的一个节目停止。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool8_md5_cisco$
python3 md5_verify2.py
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.111', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123', 'global_delay_factor': 2}
|
| | No new IOS found on router's flash. Continue to the next device...
|
| | --------------------------------------------------------------------------------
|
| | {'device_type': 'cisco_xe', 'host': '192.168.183.222', 'username': 'pynetauto', 'password': 'cisco123', 'secret': 'cisco123', 'global_delay_factor': 2}
|
| | ...........................................................[omitted for brevity]
|
| | ...........................................................................Done!
|
| | Verified (bootflash:csr1000v-universalk9.16.09.06.SPA.bin) = 77878ae6db8e34de90e2e3e83741bf39
|
| | --------------------------------------------------------------------------------
|
| | MD5 values MATCH! Continue
|
| | MD5 of new IOS on Server : 77878ae6db8e34de90e2e3e83741bf39
|
| | MD5 of new IOS on flash : 77878ae6db8e34de90e2e3e83741bf39
|
| | --------------------------------------------------------------------------------
|
| | Completed new IOS verification.
|
| 6 | 您已经从csr1000v-1
的 flash 中删除了新的 IOS 版本来验证我们的脚本。为了准备下一个实验,我们必须再次运行 IOS 上传工具。再次运行新的 IOS 上传脚本,以便在csr1000v-1
的闪存上安装新的 IOS。 |
| | 如果您只想在第一台路由器上上传文件,请删除或注释掉此处显示的device2
或csr1000v-2
信息;这将为你节省大约五分钟。 |
| | # device2 = {
|
| | # 'device_type': 'cisco_xe',
|
| | # 'host': '192.168.183.222',
|
| | # 'username': 'pynetauto',
|
| | # 'password': 'cisco123',
|
| | # 'secret': 'cisco123',
|
| | # 'global_delay_factor': 2
|
| | # }
|
| | 在开发 Python 工具时,您必须多次测试脚本,以确保自动化任务可重复执行,并获得相同的预期结果。 |
停止或重新加载路由器的选项
这里,您将编写一个脚本,为用户提供两个选项:一个选项是更改引导系统的备份运行配置并重新加载,另一个选项是退出应用以便稍后重新加载。对于第一个选项,您必须编写代码来更改引导系统,保存更改,然后有选择地备份running-config
以供以后更改验证。
|
工作
|
| — | — |
| 1 | 更改目录,为这个部分创建一个新的目录,并创建一个新的基础脚本。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool8_md5_cisco$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool9_yes_no
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool9_yes_no
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
nano yesno.py
|
| 2 | 这次您将编写一个快速的是/否函数,因此期望的输入是yes
/ y
或no
/ n
。如果用户输入的是别的东西,那么yes_or_no
功能将再次运行,直到输入正确的输入。这样,您可以控制输入,并且基于输入,脚本流将会改变。如果答案是肯定的,我们可以更改路由器的配置并重新加载。如果响应为否,请退出应用,稍后重新加载路由器。 |
| | yesno.py
|
| | yes = ['yes', 'y']
|
| | no = ['no', 'n']
|
| | def yes_or_no():
|
| | resp = input("Would you like to reload your devices? (y/n)? ").lower()
|
| | if resp in yes:
|
| | print("YES")
|
| | elif resp in no:
|
| | print("NO")
|
| | else:
|
| | yes_or_no()
|
| | yes_or_no()
|
| | print("All tasks completed.")
|
| | 完成前面的脚本后,运行该脚本并进行测试,如下所示: |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 yesno.py
|
| | Would you like to reload your devices? (y/n)?
y
|
| | YES
|
| | All tasks completed.
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 yesno.py
|
| | Would you like to reload your devices? (y/n)?
yes
|
| | YES
|
| | All tasks completed.
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 yesno.py
|
| | Would you like to reload your devices? (y/n)?
N
|
| | NO
|
| | All tasks completed.
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 yesno.py
|
| | Would you like to reload your devices? (y/n)?
NO
|
| | NO
|
| | All tasks completed.
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 yesno.py
|
| | Would you like to reload your devices? (y/n)?
sure
|
| | Would you like to reload your devices? (y/n)?
Why not?
|
| | Would you like to reload your devices? (y/n)?
12345
|
| | Would you like to reload your devices? (y/n)?
OK
|
| | Would you like to reload your devices? (y/n)?
YES
|
| | YES
|
| | All tasks completed.
|
| 3 | 现在是时候使用这个基本脚本并将其扩展到我们的场景中了。制作副本,修改脚本,如reload_yesno1.py
所示。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
cp yesno.py reload_yesno1.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
nano reload_yesno1.py
|
| | 以下脚本将更改设备上的引导系统语句,保存配置,并执行一些show
命令来捕获预加载操作状态。最后,它将重新加载路由器。 |
| | reload_yesno1.py
|
| | from netmiko import ConnectHandler
|
| | import time
|
| | d_newios = "csr1000v-universalk9.16.09.06.SPA.bin"
|
| | newiosmd5 = "77878ae6db8e34de90e2e3e83741bf39"
|
| | device1 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2 # Used to slow down the script
|
| | }
|
| | device2 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | devices_list = [device1, device2]
|
| | yes_list = ['yes', 'y']
|
| | no_list = ['no', 'n']
|
| | def yes_or_no():
|
| | resp = input("Would you like to reload your devices? (y/n)? ").lower()
|
| | if resp in yes_list:
|
| | print("Reloading devices")
|
| | for device in devices_list:
|
| | ip = str(device['host'])
|
| | net_connect = ConnectHandler(**device)
|
| | net_connect.enable(cmd='enable 15')
|
| | config_commands1 = ['no boot system', 'boot system flash:/' + d_newios, 'do write memory']
|
| | output = net_connect.send_config_set(config_commands1)
|
| | print (output)
|
| | net_connect.send_command('terminal length 0\n')
|
| | show_boot = net_connect.send_command('show boot\n')
|
| | show_dir = net_connect.send_command('dir\n')
|
| | if d_newios not in show_dir:
|
| | print('Unable to locate new IOS on the flash:/. Exiting.')
|
| | print("-"*80)
|
| | exit()
|
| | elif d_newios not in show_boot:
|
| | print('Boot system was not correctly configured. Exiting.')
|
| | print("-"*80)
|
| | exit()
|
| | elif d_newios in show_boot and d_newios in show_dir:
|
| | print(f'Found {d_newios} in show boot')
|
| | print("-"*80)
|
| | net_connect.send_command("terminal length 0")
|
| | time.sleep(1)
|
| | with open(f'{ip}_showver_pre.txt', 'w+') as f1:
|
| | print("Capturing pre-reload 'show version'")
|
| | showver_pre = net_connect.send_command("show version")
|
| | f1.write(showver_pre)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showrun_pre.txt', 'w+') as f2:
|
| | print("Capturing pre-reload 'show running-config'")
|
| | showrun_pre = net_connect.send_command("show running-config")
|
| | f2.write(showrun_pre)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showint_pre.txt', 'w+') as f3:
|
| | print("Capturing pre-reload 'show ip interface brief'")
|
| | showint_pre = net_connect.send_command("show ip interface brief")
|
| | f3.write(showint_pre)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showroute_pre.txt', 'w+') as f4:
|
| | print("Capturing pre-reload 'show ip route'")
|
| | showroute_pre = net_connect.send_command("show ip route")
|
| | f4.write(showroute_pre)
|
| | time.sleep(1)
|
| | print("-"*80)
|
| | # Trigger the device reload
|
| | print("Your device is now reloading.")
|
| | net_connect.send_command('reload', expect_string="[confirm]")
|
| | net_connect.send_command('yes\n')
|
| | net_connect.send_command('\n')
|
| | net_connect.disconnect()
|
| | print("-"*80)
|
| | elif resp in no_list:
|
| | print("You have chosen to reload the devices later. Exiting the application.")
|
| | else:
|
| | yes_or_no()
|
| | yes_or_no()
|
| | print("All tasks completed.")
|
| 4 | 当您运行前一个脚本时,它将在脚本中间挂起,超时,并继续运行,直到脚本结束。但是脚本不会启动路由器重新加载。 |
| | 当您尝试手动重新加载路由器时,它会抱怨最终用户许可协议,并揭示实际问题。我们忘记接受最终用户许可协议,并且没有设置路由器平台的正确功能集。 |
| | csr1000v-1#
reload
|
| | % Unfortunately EULA is not detected for following feature/features:
|
| | % ax
|
| | % Please configure 'license accept end user agreement' and
|
| | % use 'write' command to ensure license configurations take effect
|
| | % Continue reload will cause functionality loss for above feature/features.
|
| | Continue to reload? (yes/[no]):
|
| | 我们将按照说明添加 ax(企业)许可证级别,然后接受 EULA 并保存配置。在 csr1000v-1 上接受许可后,在 csr1000v-2 上重复该过程。 |
| | csr1000v-1#
configure terminal
|
| | csr1000v-1(config)#
license boot level ax
|
| | % use 'write' command to make license boot config take effect on next boot
|
| | csr1000v-1(config)#
license accept end user agreement
|
| | [omitted for brevity]
|
| | Activation of the software command line interface will be evidence of
|
| | your acceptance of this agreement.
|
| | ACCEPT? (yes/[no]): yes
|
| | csr1000v-1(config)#
exit
|
| | csr1000v-1#
write memory
|
| | Building configuration...
|
| | [OK]
|
| | 当您发出一个reload
命令时,我们希望看到来自路由器的[confirm]
。 |
| | csr1000v-1#
reload
|
| | Proceed with reload? [confirm]
|
| | 您还必须接受第二台路由器csr1000v-2
上的最终用户许可协议。重复前面显示的过程。 |
| 5 | After accepting the end-user license agreement (EULA) and saving the configuration, this is the right time to take a snapshot of your routers. You will be running your script against these devices multiple times and do not want to reverse the change every time you want to test this feature. So, on VMware Workstation, go to the Snapshot Manager feature and take another snapshot of both routers. See Figure 18-5.图 18-5。快照管理器,拍摄 csr1000v-1 的快照 |
| | 也给csr1000v-2
路由器拍一张快照。VMware Workstation Pro 允许您拍摄 Cisco 路由器的快照,如果您想重新运行相同的测试,您可以随时返回到该快照。请特别注意,在生产环境中,由于时间戳和数据库相关问题,Cisco 通常不允许管理员拍摄 Cisco 虚拟机的快照。 |
| 6 | 现在再次运行脚本,这次第一个路由器重新加载 OK,但是返回“OSError: Socket is closed”错误并断开我们的netmiko ConnectHandler
连接。如果我们只进行单个设备升级,并希望完成任务,这是很好的。我们希望脚本继续在第二台路由器上运行这组命令。只有当我们能够在多种设备上应用该解决方案并获得一致的结果时,才能实现网络自动化的真正威力。 |
| | 看起来我们必须为这个错误做一个例外,以便它运行并重新加载第二台路由器。这可以通过使用try ~ except
方法来实现。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 reload_yesno1.py
|
| | Would you like to reload your devices? (y/n)? y
|
| | Reloading devices
|
| | configure terminal
|
| | Enter configuration commands, one per line. End with CNTL/Z.
|
| | csr1000v-1(config)#no boot system
|
| | csr1000v-1(config)#boot system flash:/csr1000v-universalk9.16.09.06.SPA.bin
|
| | csr1000v-1(config)#do write memory
|
| | Building configuration...
|
| | [OK]
|
| | csr1000v-1(config)#end
|
| | csr1000v-1#
|
| | Found csr1000v-universalk9.16.09.06.SPA.bin in show boot
|
| | --------------------------------------------------------------------------------
|
| | Capturing pre-reload 'show version'
|
| | Capturing pre-reload 'show running-config'
|
| | Capturing pre-reload 'show ip interface brief'
|
| | Capturing pre-reload 'show ip route'
|
| | --------------------------------------------------------------------------------
|
| | Your device is now reloading.
|
| | Traceback (most recent call last):
|
| | File "reload_yesno1.py", line 88, in <module>
|
| | yes_or_no()
|
| | File "reload_yesno1.py", line 79, in yes_or_no
|
| | [...omitted for brevity]
|
| | File "/usr/lib/python3/dist-packages/paramiko/channel.py", line 1198, in _send
|
| | raise socket.error("Socket is closed")
|
| | OSError: Socket is closed
|
| 7 | 让我们复制并重构我们的脚本,使其包含try ~ except
语句来捕捉OSError
,这样脚本就可以继续运行。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
cp reload_yesno1.py reload_yesno2.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
nano reload_yesno2.py
|
| | reload_yesno2.py
|
| | from netmiko import ConnectHandler
|
| | import time
|
| | d_newios = "csr1000v-universalk9.16.09.06.SPA.bin"
|
| | newiosmd5 = "77878ae6db8e34de90e2e3e83741bf39"
|
| | device1 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | device2 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | devices_list = [device1, device2]
|
| | yes_list = ['yes', 'y']
|
| | no_list = ['no', 'n']
|
| | def yes_or_no():
|
| | resp = input("Would you like to reload your devices? (y/n)? ").lower()
|
| | if resp in yes_list:
|
| | print("Reloading devices")
|
| | for device in devices_list:
|
| | ip = str(device['host'])
|
| | net_connect = ConnectHandler(**device)
|
| | net_connect.enable(cmd='enable 15')
|
| | config_commands1 = ['no boot system', 'boot system flash:/' + d_newios, 'do write memory']
|
| | output = net_connect.send_config_set(config_commands1)
|
| | print (output)
|
| | net_connect.send_command('terminal length 0\n')
|
| | show_boot = net_connect.send_command('show boot\n')
|
| | show_dir = net_connect.send_command('dir\n')
|
| | if d_newios not in show_dir:
|
| | print('Unable to locate new IOS on the flash:/. Exiting.')
|
| | print("-"*80)
|
| | exit()
|
| | elif d_newios not in show_boot:
|
| | print('Boot system was not correctly configured. Exiting.')
|
| | print("-"*80)
|
| | exit()
|
| | elif d_newios in show_boot and d_newios in show_dir:
|
| | try:
|
| | print(f'Found {d_newios} in show boot')
|
| | print("-"*80)
|
| | net_connect.send_command("terminal length 0")
|
| | time.sleep(1)
|
| | with open(f'{ip}_showver_pre.txt', 'w+') as f1:
|
| | print("Capturing pre-reload 'show version'")
|
| | showver_pre = net_connect.send_command("show version")
|
| | f1.write(showver_pre)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showrun_pre.txt', 'w+') as f2:
|
| | print("Capturing pre-reload 'show running-config'")
|
| | showrun_pre = net_connect.send_command("show running-config")
|
| | f2.write(showrun_pre)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showint_pre.txt', 'w+') as f3:
|
| | print("Capturing pre-reload 'show ip interface brief'")
|
| | showint_pre = net_connect.send_command("show ip interface brief")
|
| | 6 f3.write(showint_pre)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showroute_pre.txt', 'w+') as f4:
|
| | print("Capturing pre-reload 'show ip route'")
|
| | showroute_pre = net_connect.send_command("show ip route")
|
| | f4.write(showroute_pre)
|
| | time.sleep(1)
|
| | print("-"*80)
|
| | # Trigger the device reload
|
| | print("Your device is now reloading.")
|
| | net_connect.send_command('reload', expect_string="[confirm]")
|
| | net_connect.send_command('yes')
|
| | net_connect.send_command('\n')
|
| | net_connect.disconnect()
|
| | print("-"*80)
|
| | except OSError:
|
| | print("Device is now reloading. This may take 2-5 minutes.")
|
| | time.sleep(10)
|
| | print("-"*80)
|
| | elif resp in no_list:
|
| | print("You have chosen to reload the devices later. Exiting the application.")
|
| | else:
|
| | yes_or_no()
|
| | yes_or_no()
|
| | print("All tasks completed.")
|
| | 现在,重新运行脚本,您将看到脚本成功地到达了最后一行代码。此外,csr1000v-1
和csr1000v-2
都应该被重新加载并提交到新的 IOS XE 镜像中,csr1000v-universalk9.16.09.06.SPA.bin
。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$
python3 reload_yesno2.py
|
| | Would you like to reload your devices? (y/n)? yes
|
| | Reloading devices
|
| | configure terminal
|
| | Enter configuration commands, one per line. End with CNTL/Z.
|
| | csr1000v-1(config)#no boot system
|
| | csr1000v-1(config)#boot system flash:/csr1000v-universalk9.16.09.06.SPA.bin
|
| | csr1000v-1(config)#do write memory
|
| | Building configuration...
|
| | [OK]
|
| | [...omitted for brevity]
|
| | Your device is now reloading.
|
| | Device is now reloading. This may take 2-5 minutes.
|
| | --------------------------------------------------------------------------------
|
| | All tasks completed.
|
| 8 | While the script runs, keep an eye on the first router’s console, and you will see that the system is booting into the .bin
file of the new IOS XE image. See Figure 18-6.图 18-6。csr1000v-1,引导至新的 IOS XE 映像 |
| | 路由器经过 POST 过程后,通过运行show version
命令检查 IOS 版本,现在路由器运行在新的 IOS 映像上。脚本继续在第二台路由器上运行,在csr1000v-2
路由器上将观察到相同的结果。 |
| | csr1000v-1#show version
|
| | Cisco IOS XE Software, Version 16.09.06
|
| | Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.6, RELEASE SOFTWARE (fc2)
|
| | Technical Support: http://www.cisco.com/techsupport
|
| | Copyright (c) 1986-2020 by Cisco Systems, Inc.
|
| | Compiled Thu 27-Aug-20 02:35 by mcpre
|
| | Cisco IOS-XE software, Copyright (c) 2005-2020 by cisco Systems, Inc.
|
| | All rights reserved. Certain components of Cisco IOS-XE software are
|
| | licensed under the GNU General Public License
("GPL") Version 2.0. The
|
| | software code licensed under GPL Version 2.0 is free software that comes
|
| | with ABSOLUTELY NO WARRANTY. You can redistribute and/or modify such
|
| | GPL code under the terms of GPL Version 2.0. For more details, see the
|
| | documentation or "License Notice" file accompanying the IOS-XE software,
|
| | or the applicable URL provided on the flyer accompanying the IOS-XE
|
| | software.
|
| | ROM: IOS-XE ROMMON
|
| | csr1000v-1 uptime is 1 minute
|
| | Uptime for this control processor is 2 minutes
|
| | System returned to ROM by reload
|
| | System image file is "bootflash:/csr1000v-universalk9.16.09.06.SPA.bin"
|
| | Last reload reason: Reload Command
|
| 9 | 在前一个应用成功运行时,您还将获得每个路由器的四个show
命令的副本。这些文件将在 IOS 升级后与同一组show
命令结果进行比较。这充分验证了 IOS 升级成功运行,并且没有对通过这些设备服务的网络造成任何中断。这只是一个开发实验室,但是如果您在生产环境中运行该脚本,该文件可能包含数百行。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$ ls -lh
|
| | total 44K
|
| | -rw-rw-r-- 1 pynetauto pynetauto 323 Jan 15 16:57 192.168.183.111_showint_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 842 Jan 15 16:57 192.168.183.111_showroute_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 4.0K Jan 15 16:57 192.168.183.111_showrun_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 2.3K Jan 15 16:57 192.168.183.111_showver_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 323 Jan 15 16:57 192.168.183.222_showint_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 842 Jan 15 16:57 192.168.183.222_showroute_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 1.8K Jan 15 16:57 192.168.183.222_showrun_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 2.3K Jan 15 16:57 192.168.183.222_showver_pre.txt
|
| | -rw-rw-r-- 1 pynetauto pynetauto 3.6K Jan 15 16:31 reload_yesno1.py
|
| | -rw-rw-r-- 1 pynetauto pynetauto 3.9K Jan 15 16:56 reload_yesno2.py
|
| | -rw-rw-r-- 1 pynetauto pynetauto 285 Jan 15 16:30 yesno.py
|
现在,我们已经重新加载了两台路由器,它们正在运行最新的 IOS XE 映像,我们必须通过重新登录设备并使用捕获的信息执行升级后检查来锦上添花。继续下一节。
检查重新加载设备,并执行重新加载后配置验证
您已经到达了我们将开发的最终工具。您将学习如何创建一个工具,在我们的脚本启动路由器重新加载后,扫描并检查端口 22 是否处于打开状态。当脚本检测到打开的端口 22 时,应用将 SSH 到设备中,然后执行运行配置的重新加载后捕获。预加载捕获可以比作使用 Python 的difflib
库。让我们按照一步一步的过程来开发最终的工具。
|
工作
|
| — | — |
| 1 | 为了简单起见,您可以复制前面的脚本并对其进行修改,以创建本章的最后一个迷你脚本。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool9_yes_no$``cd
|
| | pynetauto@ubuntu20s1:~/my_tools$
mkdir tool10_post_check
|
| | pynetauto@ubuntu20s1:~/my_tools$
cd tool10_post_check
|
| | pynetauto@ubuntu20s1:~/my_tools/ tool10_post_check $
touch post_check1.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
nano post_check1.py
|
| 2 | 这里,我们将创建一个检查端口 22 的套接字应用。当它检测到端口 22 打开时,它将登录到该设备并运行show clock
命令。否则,它将休眠 10 秒钟,并重新检查端口 22。该检查将重复 60 次,这意味着检查将运行 10 分钟,这是我们让路由器重新启动并启动到新的 IOS XE 映像的时间。在生产中,这个值必须根据网络速度和实际的硬件 CPU 处理器速度而有所不同,并且您必须估计出等待时间。本实验假设,如果设备的端口 22 在执行reload
命令后没有恢复服务,则可能需要进行故障排除。 |
| | post_check1.py
|
| | import socket
|
| | import time
|
| | from netmiko import ConnectHandler
|
| | t1 = time.mktime(time.localtime()) # Timer start to measure script running time
|
| | device1 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | device2 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | devices_list = [device1, device2]
|
| | for device in devices_list:
|
| | ip = str(device['host'])
|
| | port = 22
|
| | retry = 60
|
| | delay = 10
|
| | def isOpen(ip, port):
|
| | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| | s.settimeout(3)
|
| | try:
|
| | s.connect((ip, int(port)))
|
| | s.shutdown(socket.SHUT_RDWR)
|
| | return True
|
| | except:
|
| | return False
|
| | finally:
|
| | s.close()
|
| | t1 = time.mktime(time.localtime())
|
| | ipup = False
|
| | for i in range(retry):
|
| | if isOpen(ip, port):
|
| | ipup = True
|
| | print(f"{ip} is online. Logging into device to perform post reload check")
|
| | net_connect = ConnectHandler(**device)
|
| | print(net_connect.send_command("show clock"))
|
| | break
|
| | else:
|
| | print("Device is still reloading. Please wait...")
|
| | time.sleep(delay)
|
| | t2 = time.mktime(time.localtime()) - t1
|
| | print("Total wait time : {0} seconds".format(t2))
|
| | 当您运行前面的脚本时,它将检查端口 22,如果端口 22 在目标设备上打开,它应该 SSH 到路由器并运行show clock
命令。然后它将跳出for
循环,移动到下一个设备,所以您的结果应该类似于这个输出。现在您知道端口 22 后检查器工作正常,我们准备编写完整的后检查应用。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
python3 post_check1.py
|
| | 192.168.183.111 is online. Logging into device to perform post reload check
|
| | *16:13:37.425 UTC Fri Jan 15 2021
|
| | 192.168.183.222 is online. Logging into device to perform post reload check
|
| | *16:13:42.200 UTC Fri Jan 15 2021
|
| | Total wait time : 4.0 seconds
|
| 3 | 现在,从 VMware Workstation 用户界面,转到csr1000v-1
的控制台,启用访问列表以阻止端口 22 来测试应用。启用access-list 100
来阻塞csr1000v-1
上端口 22 的流量。见图 18-7 。 |
| | Username: pynetauto
|
| | Password: ********
|
| | csr1000v-1> enable
|
| | csr1000v-1#``conf termina
|
| | csr1000v-1(config)#
access-list 100 deny tcp any any eq 22
|
| | csr1000v-1(config)#
access-list 100 permit ip any any
|
| | csr1000v-1(config)#
interface Gi1
|
| | csr1000v-1(config-if)#
ip access-group 100 in
|
| | csr1000v-1(config-if)#
图 18-7。VMware 控制台,csr1000v-1,使访问列表 100 能够阻止端口 22 上的流量 |
| 4 | 使用以下 Python 命令运行基本后检查脚本。由于我们阻塞了端口 22 来模拟一个不可到达的设备,它将返回“设备仍在重新加载”。请稍候…”消息每隔 10 秒出现一次。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
python3 post_check1.py
|
| | Device is still reloading. Please wait...
|
| | Device is still reloading. Please wait...
|
| | 在 VMware Workstation 的主控制台上,登录csr1000v-1
并从 GigabitEthernet1 接口中删除访问组 100;这将允许脚本检测端口 22 何时对 SSH 开放。 |
| | csr1000v-1(config-if)#
no ip access-group 100 in
|
| | 一旦访问列表从接口 GigabitEthernet1 中删除,您的脚本将检测到一个开放的端口 22,登录到路由器,并运行show clock
命令。然后继续到第二台路由器,完成环路。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
python3 post_check1.py
|
| | Device is still reloading. Please wait...
|
| | Device is still reloading. Please wait...
|
| | Device is still reloading. Please wait...
|
| | Device is still reloading. Please wait...
|
| | 192.168.183.111 is online. Logging into device to perform post reload check
|
| | *16:19:26.299 UTC Fri Jan 15 2021
|
| | 192.168.183.222 is online. Logging into device to perform post reload check
|
| | *16:19:29.063 UTC Fri Jan 15 2021
|
| | Total wait time : 2.0 seconds
|
| 5 | 为了测试以前的应用,您将需要从以前的开发中创建的文件。让我们从tool9
开发中复制所有预加载的show
文件。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
pwd
|
| | /home/pynetauto/my_tools/tool10_post_check
|
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
ls
|
| | post_check1.py
|
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
ls /home/pynetauto/my_tools/tool9_yes_no/
|
| | reload_yesno1.py 192.168.183.111_showroute_pre.txt 192.168.183.222_showroute_pre.txt
|
| | reload_yesno2.py 192.168.183.111_showrun_pre.txt 192.168.183.222_showrun_pre.txt
|
| | yesno.py 192.168.183.111_showver_pre.txt 192.168.183.222_showver_pre.txt
|
| | 192.168.183.111_showint_pre.txt 192.168.183.222_showint_pre.txt
|
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
cp /home/pynetauto/my_tools/tool9_yes_no/192.* ./
|
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
ls
|
| | 192.168.183.111_showint_pre.txt 192.168.183.111_showver_pre.txt 192.168.183.222_showrun_pre.txt
|
| | 192.168.183.111_showroute_pre.txt 192.168.183.222_showint_pre.txt 192.168.183.222_showver_pre.txt
|
| | 192.168.183.111_showrun_pre.txt 192.168.183.222_showroute_pre.txt post_check1.py
|
| 6 | 复制第一个文件,并将其命名为post_check2.py
。以此作为我们最终剧本的基础。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
cp post_check1.py post_check2.py
|
| | 编写以下代码并完成应用。该应用将检查端口 22,然后登录到每个路由器,并从每个路由器捕获四个show
命令。然后,使用 Python 的difflib
库,我们逐行比较捕获前后的配置。接下来,我们将每个比较集保存为 HTML 格式,以便于查看。仔细阅读和研究每一行代码;下面的代码中嵌入了解释,供您参考: |
| | post_check2.py
|
| | from netmiko import ConnectHandler
|
| | import socket
|
| | import time
|
| | import difflib
|
| | d_newios = "csr1000v-universalk9.16.09.06.SPA.bin"
|
| | device1 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.111',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | device2 = {
|
| | 'device_type': 'cisco_xe',
|
| | 'host': '192.168.183.222',
|
| | 'username': 'pynetauto',
|
| | 'password': 'cisco123',
|
| | 'secret': 'cisco123',
|
| | 'global_delay_factor': 2
|
| | }
|
| | devices_list = [device1, device2]
|
| | # Checks SSH port and then logs back in to complete the post-upgrade check.
|
| | def post_check():
|
| | for device in devices_list:
|
| | ip = str(device['host'])
|
| | port = 22
|
| | retry = 120
|
| | delay = 10
|
| | def isOpen(ip, port):
|
| | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| | s.settimeout(3)
|
| | try:
|
| | s.connect((ip, int(port)))
|
| | s.shutdown(socket.SHUT_RDWR)
|
| | return True
|
| | except:
|
| | return False
|
| | finally:
|
| | s.close()
|
| | t1 = time.mktime(time.localtime())
|
| | ipup = False
|
| | for i in range(retry):
|
| | if isOpen(ip, port):
|
| | ipup = True
|
| | print(f"{ip} is online. Logging into device to perform post reload check")
|
| | # Capture four show commands result from each router
|
| | print("Performing post upgrade check.")
|
| | net_connect = ConnectHandler(**device)
|
| | net_connect.enable(cmd='enable 15')
|
| | config_commands1 = ['no boot system', 'boot system flash:/' + d_newios, 'do write memory']
|
| | output = net_connect.send_config_set(config_commands1)
|
| | print (output)
|
| | net_connect.send_command('terminal length 0\n')
|
| | with open(f'{ip}_showver_post.txt', 'w+') as f1:
|
| | print("Capturing post-reload 'show version'")
|
| | showver_post = net_connect.send_command("show version")
|
| | f1.write(showver_post)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showrun_post.txt', 'w+') as f2:
|
| | print("Capturing post-reload 'show running-config'")
|
| | showrun_post = net_connect.send_command("show running-config")
|
| | f2.write(showrun_post)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showint_post.txt', 'w+') as f3:
|
| | print("Capturing post-reload 'show ip interface brief'")
|
| | showint_post = net_connect.send_command("show ip interface brief")
|
| | f3.write(showint_post)
|
| | time.sleep(1)
|
| | with open(f'{ip}_showroute_post.txt', 'w+') as f4:
|
| | print("Capturing post-reload 'show ip route'")
|
| | showroute_post = net_connect.send_command("show ip route")
|
| | f4.write(showroute_post)
|
| | time.sleep(1)
|
| | # Compare pre vs post configurations
|
| | showver_pre = "showver_pre"
|
| | showver_post = "showver_post"
|
| | showver_pre_lines = open(f"{ip}_showver_pre.txt").readlines() #converts into strings first for comparison
|
| | #time.sleep(1)
|
| | showver_post_lines = open(f"{ip}_showver_post.txt").readlines() #converts into strings first for comparison
|
| | #time.sleep(1)
|
| | # Four arguments required in HtmlDiff function
|
| | difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showver_pre_lines, showver_post_lines, showver_pre, showver_post)
|
| | difference_report = open(f"{ip}_show_ver_compared.html", "w+")
|
| | difference_report.write(difference) # Writes the differences to html file
|
| | difference_report.close()
|
| | time.sleep(1)
|
| | print("-"*80)
|
| | showrun_pre = "showrun_pre"
|
| | showrun_post = "showrun_post"
|
| | showrun_pre_lines = open(f"{ip}_showrun_pre.txt").readlines()
|
| | showrun_post_lines = open(f"{ip}_showrun_post.txt").readlines()
|
| | difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showrun_pre_lines, showrun_post_lines, showrun_pre, showrun_post)
|
| | difference_report = open(f"{ip}_show_run_compared.html", "w+")
|
| | difference_report.write(difference)
|
| | difference_report.close()
|
| | time.sleep(1)
|
| | showint_pre = "showint_pre"
|
| | showint_post = "showint_post"
|
| | showint_pre_lines = open(f"{ip}_showint_pre.txt").readlines()
|
| | showint_post_lines = open(f"{ip}_showint_post.txt").readlines()
|
| | difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showint_pre_lines, showint_post_lines, showint_pre, showint_post)
|
| | difference_report = open(f"{ip}_show_int_compared.html", "w+")
|
| | difference_report.write(difference)
|
| | difference_report.close()
|
| | time.sleep(1)
|
| | showroute_pre = "showroute_pre"
|
| | showroute_post = "showroute_post"
|
| | showroute_pre_lines = open(f"{ip}_showroute_pre.txt").readlines()
|
| | showroute_post_lines = open(f"{ip}_showroute_post.txt").readlines()
|
| | difference = difflib.HtmlDiff(wrapcolumn=70).make_file(showroute_pre_lines, showroute_post_lines, showroute_pre, showroute_post)
|
| | difference_report = open(f"{ip}_show_route_compared.html", "w+")
|
| | difference_report.write(difference)
|
| | difference_report.close()
|
| | time.sleep(1)
|
| | print("-"*80)
|
| | break
|
| | else:
|
| | print("Device is still reloading. Please wait...")
|
| | time.sleep(delay)
|
| | t2 = time.mktime(time.localtime()) - t1
|
| | print("Total wait time : {0} seconds".format(t2))
|
| | print("="*80)
|
| | time.sleep(1)
|
| | post_check()
|
| | print("All tasks completed. Check pre and post configuration comparison html files.")
|
| 5 | 运行脚本;它应该在重新加载后创建备份文件,然后在比较每一行后创建 HTML 文件。该脚本不会触发路由器像前面的脚本一样重新加载。这里我们感兴趣的是创建文件,然后比较文件前后的内容。在最终的脚本中,我们将需要把这个脚本添加到reload_yesno2.py
中,并把它变成一个完全工作的应用。因此,如果 HTML 与您预期的不同,不要惊慌。 |
| | pynetauto@ubuntu20s1:~/my_tools/tool10_post_check$
python3 post_check2.py
|
| | 192.168.183.111 is online. Logging into device to perform post reload check
|
| | Performing post upgrade check.
|
| | configure terminal
|
| | Enter configuration commands, one per line. End with CNTL/Z.
|
| | csr1000v-1(config)#no boot system
|
| | csr1000v-1(config)#boot system flash:/csr1000v-universalk9.16.09.06.SPA.bin
|
| | csr1000v-1(config)#do write memory
|
| | Building configuration...
|
| | [OK]
|
| | csr1000v-1(config)#end
|
| | csr1000v-1#
|
| | Capturing post-reload 'show version'
|
| | Capturing post-reload 'show running-config'
|
| | Capturing post-reload 'show ip interface brief'
|
| | Capturing post-reload 'show ip route'
|
| | --------------------------------------------------------------------------------
|
| | --------------------------------------------------------------------------------
|
| | Total wait time : 19.0 seconds
|
| | ================================================================================
|
| | 192.168.183.222 is online. Logging into device to perform post reload check
|
| | Performing post upgrade check.
|
| | configure terminal
|
| | Enter configuration commands, one per line. End with CNTL/Z.
|
| | csr1000v-2(config)#no boot system
|
| | csr1000v-2(config)#boot system flash:/csr1000v-universalk9.16.09.06.SPA.bin
|
| | csr1000v-2(config)#do write memory
|
| | Building configuration...
|
| | [OK]
|
| | csr1000v-2(config)#end
|
| | csr1000v-2#
|
| | Capturing post-reload 'show version'
|
| | Capturing post-reload 'show running-config'
|
| | Capturing post-reload 'show ip interface brief'
|
| | Capturing post-reload 'show ip route'
|
| | --------------------------------------------------------------------------------
|
| | --------------------------------------------------------------------------------
|
| | Total wait time : 18.0 seconds
|
| | ================================================================================
|
| | All tasks completed. Check pre and post configuration comparison html files.
|
| | Connect to the ubuntu20s1
server using WinSCP or FileZilla and locate the backup and comparison files in the /home/yourname/my_tools/tool10_post_check/
directory. You can copy the HTML files and open them in a web browser. See Figure 18-8.图 18-8。WinSCP,检查和复制 HTML 比较文件 |
| 6 | Copy the files from the automation server onto your Windows host PC and open the HTML files in a web browser. If everything worked, you will see the headings with _pre
and _post
for each respective show
command. Since we have not integrated this script with the reload_yesno2.py
script, it will still display the currently running configurations. We will integrate all ten applications we have developed so far in the next chapter and turn them into an end-to-end working application. See Figure 18-9.图 18-9。复制和打开 HTML 文件检查示例 |
摘要
在本章中,您开发了十种迷你工具,为下一章的最终 Cisco IOS 升级应用做好了准备。本章中开发的每个工具都有其使用案例,也可以作为一个独立的工具,只需稍加修改或不需要修改就可以用于生产。正如您所看到的,我们使用了各种 Python 模块来开发我们自己的 Python 应用,以满足我们的需求。这些工具可以组合成一个应用;我们可以提出各种工作流,开发不同的 Python 网络应用(工具)。编写 Python 程序的一般过程在所有厂商的网络设备上都是一样的。只要 Python 模块支持特定的供应商产品,您就可以对任何供应商产品应用类似的自动化应用开发策略。现在,让我们结合所有十个应用,创建一个功能齐全的 Cisco IOS XE 升级工具。